From 2dff880270e59391a380acd3d6c5f3055e5d7d21 Mon Sep 17 00:00:00 2001 From: Ryan Zmuda Date: Tue, 18 Aug 2026 12:03:08 -0400 Subject: [PATCH] libreach: consume frozen binary facts --- resolve-facts/CMakeLists.txt | 15 +- resolve-facts/include/reach/distmap.hpp | 6 +- resolve-facts/include/reach/facts.hpp | 9 + resolve-facts/include/reach/graph.hpp | 14 +- resolve-facts/libs/reach/distmap.cpp | 86 ++++++ resolve-facts/libs/reach/facts.cpp | 20 ++ resolve-facts/libs/reach/facts_view.hpp | 169 +++++++++++ resolve-facts/libs/reach/graph.cpp | 357 +++++++++++++++++++++--- 8 files changed, 634 insertions(+), 42 deletions(-) create mode 100644 resolve-facts/libs/reach/facts_view.hpp diff --git a/resolve-facts/CMakeLists.txt b/resolve-facts/CMakeLists.txt index bba8bbc86..b8102da84 100644 --- a/resolve-facts/CMakeLists.txt +++ b/resolve-facts/CMakeLists.txt @@ -152,6 +152,7 @@ file(GLOB_RECURSE SRC file(GLOB_RECURSE LIB "${CMAKE_CURRENT_SOURCE_DIR}/include/reach/*.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/libs/reach/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/reach/*.hpp" ) # reach lib @@ -166,10 +167,20 @@ add_library(libreach set_target_properties(libreach PROPERTIES OUTPUT_NAME "reach") target_include_directories(libreach PUBLIC - "$/include" + "$" "$" ) -target_link_libraries(libreach PUBLIC resolve_facts json) +target_include_directories(libreach PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/libs" +) +target_link_libraries(libreach PUBLIC + resolve_facts + facts_rs + json + Threads::Threads + ${CMAKE_DL_LIBS} + m +) target_compile_features(libreach PUBLIC cxx_std_23) diff --git a/resolve-facts/include/reach/distmap.hpp b/resolve-facts/include/reach/distmap.hpp index 109a58552..7e21a7448 100644 --- a/resolve-facts/include/reach/distmap.hpp +++ b/resolve-facts/include/reach/distmap.hpp @@ -25,4 +25,8 @@ namespace distmap { distmap_blacklist gen(const reach_facts::database &db, const NNodeId &dst, bool dynlink = false, const std::optional> &loaded_syms = {}); -} + +distmap_blacklist +gen(const facts_rs::FactsBuf *facts, const NNodeId &dst, bool dynlink = false, + const std::optional> &loaded_syms = {}); +} // namespace distmap diff --git a/resolve-facts/include/reach/facts.hpp b/resolve-facts/include/reach/facts.hpp index cab2b7e61..1a64108dc 100644 --- a/resolve-facts/include/reach/facts.hpp +++ b/resolve-facts/include/reach/facts.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,10 @@ using NodeType = resolve_facts::NodeType; using Linkage = resolve_facts::Linkage; using CallType = resolve_facts::CallType; +namespace facts_rs { +struct FactsBuf; +} + namespace reach_facts { enum class LoadOptions : int { @@ -71,6 +76,10 @@ struct database { database load(std::istream &facts, LoadOptions options); database load(const std::filesystem::path &facts_dir, LoadOptions options); +std::vector +find_functions_by_name_suffix(const facts_rs::FactsBuf *facts, + std::string_view suffix); + bool validate(const database &db); } // namespace reach_facts diff --git a/resolve-facts/include/reach/graph.hpp b/resolve-facts/include/reach/graph.hpp index 64ebb9db5..e39606203 100644 --- a/resolve-facts/include/reach/graph.hpp +++ b/resolve-facts/include/reach/graph.hpp @@ -13,6 +13,10 @@ #include "reach/facts.hpp" +namespace facts_rs { +struct FactsBuf; +} + using NNodeId = resolve_facts::NamespacedNodeId; namespace graph { @@ -58,7 +62,11 @@ struct T { bool wf(const E &g); T build_from_program_facts( - const resolve_facts::ProgramFacts &pf, bool dynlink, + const resolve_facts::ProgramFacts &facts, bool dynlink, + const std::optional> &loaded_syms); + +T build_from_program_facts( + const facts_rs::FactsBuf *facts, bool dynlink, const std::optional> &loaded_syms); constexpr reach_facts::LoadOptions SIMPLE_LOAD_OPTIONS = @@ -104,6 +112,10 @@ T build_cfg( T build_instr_cfg( const reach_facts::database &db, bool dynlink = false, const std::optional> &loaded_syms = {}); + +T build_instr_cfg( + const facts_rs::FactsBuf *facts, bool dynlink = false, + const std::optional> &loaded_syms = {}); } // namespace graph namespace std { diff --git a/resolve-facts/libs/reach/distmap.cpp b/resolve-facts/libs/reach/distmap.cpp index 744940617..25a96b859 100644 --- a/resolve-facts/libs/reach/distmap.cpp +++ b/resolve-facts/libs/reach/distmap.cpp @@ -9,11 +9,97 @@ #include #include "reach/distmap.hpp" +#include "reach/facts_view.hpp" #include "reach/search.hpp" #include "reach/util.hpp" using namespace std; +namespace { + +template +void for_each_function_instruction(const reach_facts::ProgramFactsView &pf, + const NNodeId function, Function callback) { + const auto [module_id, function_id] = function; + const auto module = pf.module(module_id); + for (const auto &contains_block : module.out_edges(function_id)) { + if (!reach_facts::edge_has_kind(contains_block, + facts_rs::EdgeKind::Contains) || + module.node(contains_block.dst).type() != + facts_rs::NodeType::BasicBlock) { + continue; + } + for (const auto &contains_instruction : + module.out_edges(contains_block.dst)) { + if (reach_facts::edge_has_kind(contains_instruction, + facts_rs::EdgeKind::Contains) && + module.node(contains_instruction.dst).type() == + facts_rs::NodeType::Instruction) { + callback(make_pair(module_id, contains_instruction.dst)); + } + } + } +} + +} // namespace + +distmap_blacklist +distmap::gen(const facts_rs::FactsBuf *facts, const NNodeId &dst, bool dynlink, + const optional> &loaded_syms) { + const reach_facts::ProgramFactsView pf{facts}; + if (!pf.contains_node(dst)) { + throw runtime_error("distmap::gen: node not found"); + } + const auto target = pf.node(dst); + if (target.type() != facts_rs::NodeType::Function) { + throw runtime_error("distmap::gen: node is not a function"); + } + const auto target_name = target.name(); + if (!target_name) { + throw runtime_error("distmap::gen: target function has no name"); + } + + const auto graph = graph::build_instr_cfg(facts, dynlink, loaded_syms); + auto distances = search::min_distances(graph.edges, dst); + + for_each_function_instruction( + pf, dst, [&](const NNodeId instruction) { distances[instruction] = 0; }); + + for (uint32_t module_id = 0; module_id < pf.module_count(); ++module_id) { + const auto module = pf.module(module_id); + for (uint32_t node_id = 0; node_id < module.nodes().size(); ++node_id) { + const auto node = module.node(node_id); + if (node.linkage() == facts_rs::Linkage::ExternalLinkage && + node.name() == target_name) { + for_each_function_instruction( + pf, make_pair(module_id, node_id), + [&](const NNodeId instruction) { distances[instruction] = 0; }); + } + } + } + + resolve_facts::NodeMap instruction_distances; + for (const auto &[id, distance] : distances) { + if (pf.node(id).type() == facts_rs::NodeType::Instruction) { + instruction_distances.emplace(id, distance); + } + } + + unordered_set blacklist; + for (uint32_t module_id = 0; module_id < pf.module_count(); ++module_id) { + const auto module = pf.module(module_id); + for (uint32_t node_id = 0; node_id < module.nodes().size(); ++node_id) { + const auto id = make_pair(module_id, node_id); + if (module.node(node_id).type() == facts_rs::NodeType::Instruction && + !instruction_distances.contains(id)) { + blacklist.insert(id); + } + } + } + + return {move(instruction_distances), move(blacklist)}; +} + distmap_blacklist distmap::gen(const reach_facts::database &db, const NNodeId &dst, bool dynlink, const optional> &loaded_syms) { diff --git a/resolve-facts/libs/reach/facts.cpp b/resolve-facts/libs/reach/facts.cpp index 956f75b12..1790f391c 100644 --- a/resolve-facts/libs/reach/facts.cpp +++ b/resolve-facts/libs/reach/facts.cpp @@ -10,6 +10,7 @@ #include #include "reach/facts.hpp" +#include "reach/facts_view.hpp" #include "reach/util.hpp" using namespace resolve_facts; @@ -101,6 +102,25 @@ database reach_facts::load(const fs::path &facts_dir, LoadOptions options) { return load(facts, options); } +vector +reach_facts::find_functions_by_name_suffix(const facts_rs::FactsBuf *facts, + const string_view suffix) { + const ProgramFactsView pf{facts}; + vector matches; + for (uint32_t mid = 0; mid < pf.module_count(); ++mid) { + const auto module = pf.module(mid); + for (uint32_t nid = 0; nid < module.nodes().size(); ++nid) { + const auto node = module.node(nid); + const auto name = node.name(); + if (node.type() == facts_rs::NodeType::Function && name && + name->ends_with(suffix)) { + matches.emplace_back(mid, nid); + } + } + } + return matches; +} + // These checks ensure that the hashmap lookups in // graph::build_call_graph and graph::build_cfg will succeed. bool reach_facts::validate(const database &db) { diff --git a/resolve-facts/libs/reach/facts_view.hpp b/resolve-facts/libs/reach/facts_view.hpp new file mode 100644 index 000000000..b1f2f282f --- /dev/null +++ b/resolve-facts/libs/reach/facts_view.hpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2025 Riverside Research. + * LGPL-3; See LICENSE.txt in the repo root for details. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "facts_rs.hpp" + +namespace reach_facts { + +static_assert(std::endian::native == std::endian::little); +static_assert(sizeof(facts_rs::Node) == 32); +static_assert(alignof(facts_rs::Node) == 4); +static_assert(sizeof(facts_rs::Edge) == 12); +static_assert(alignof(facts_rs::Edge) == 4); + +class NodeView { + const facts_rs::Node *node_; + std::span strings_; + + std::string_view string_at(const facts_rs::Interned id) const { + const auto offset = static_cast(id); + assert(offset + sizeof(uint32_t) <= strings_.size()); + + uint32_t length; + std::memcpy(&length, strings_.data() + offset, sizeof(length)); + const auto start = offset + sizeof(length); + assert(start + length <= strings_.size()); + return {reinterpret_cast(strings_.data() + start), length}; + } + + std::optional string(const uint32_t property, + const facts_rs::Interned id) const { + if ((node_->meta & property) == 0) { + return {}; + } + return string_at(id); + } + +public: + NodeView(const facts_rs::Node &node, const std::span strings) + : node_(&node), strings_(strings) {} + + facts_rs::NodeType type() const { + return static_cast( + (node_->meta & facts_rs::NODE_TYPE_MASK) >> facts_rs::NODE_TYPE_SHIFT); + } + + std::optional name() const { + return string(facts_rs::P_NAME, node_->name); + } + + std::optional linkage() const { + if ((node_->meta & facts_rs::P_LINKAGE) == 0) { + return {}; + } + return static_cast( + (node_->meta & facts_rs::LINKAGE_MASK) >> facts_rs::LINKAGE_SHIFT); + } + + std::optional call_type() const { + if ((node_->meta & facts_rs::P_CALL_TYPE) == 0) { + return {}; + } + return static_cast( + (node_->meta & facts_rs::CALL_TYPE_MASK) >> facts_rs::CALL_TYPE_SHIFT); + } + + std::optional source_file() const { + return string(facts_rs::P_SOURCE_FILE, node_->source_file); + } + + std::optional function_type() const { + return string(facts_rs::P_FUNCTION_TYPE, node_->function_type); + } + + bool address_taken() const { + return (node_->meta & facts_rs::P_ADDRESS_TAKEN) != 0; + } +}; + +inline bool edge_has_kind(const facts_rs::Edge &edge, + const facts_rs::EdgeKind kind) { + return (edge.kinds & (1u << static_cast(kind))) != 0; +} + +class ModuleView { + facts_rs::FactsModuleView module_; + +public: + explicit ModuleView(const facts_rs::FactsModuleView module) + : module_(module) {} + + std::span nodes() const { + return {module_.nodes, module_.node_count}; + } + + std::span edges() const { + return {module_.edges, module_.edge_count}; + } + + std::span out_edges(const facts_rs::NodeID id) const { + const auto all = edges(); + const auto begin = std::lower_bound( + all.begin(), all.end(), id, + [](const facts_rs::Edge &edge, const facts_rs::NodeID value) { + return edge.src < value; + }); + const auto end = std::upper_bound( + begin, all.end(), id, + [](const facts_rs::NodeID value, const facts_rs::Edge &edge) { + return value < edge.src; + }); + return all.subspan(begin - all.begin(), end - begin); + } + + bool contains(const facts_rs::NodeID id) const { return id < nodes().size(); } + + NodeView node(const facts_rs::NodeID id) const { + assert(contains(id)); + return {nodes()[id], {module_.string_pool, module_.string_pool_len}}; + } +}; + +class ProgramFactsView { + std::vector modules_; + +public: + explicit ProgramFactsView(const facts_rs::FactsBuf *facts) { + if (!facts) { + throw std::invalid_argument("null FactsBuf"); + } + facts_rs::FactsModuleCursor cursor{}; + facts_rs::FactsModuleView module{}; + while (facts_rs::facts_module_next(facts, &cursor, &module)) { + modules_.push_back(module); + } + } + + size_t module_count() const { return modules_.size(); } + + ModuleView module(const uint32_t index) const { + assert(index < modules_.size()); + return ModuleView{modules_[index]}; + } + + bool contains_node(const std::pair id) const { + return id.first < modules_.size() && module(id.first).contains(id.second); + } + + NodeView node(const std::pair id) const { + return module(id.first).node(id.second); + } +}; + +} // namespace reach_facts diff --git a/resolve-facts/libs/reach/graph.cpp b/resolve-facts/libs/reach/graph.cpp index efc7b8b88..80d6122c6 100644 --- a/resolve-facts/libs/reach/graph.cpp +++ b/resolve-facts/libs/reach/graph.cpp @@ -11,6 +11,7 @@ #include #include "reach/facts.hpp" +#include "reach/facts_view.hpp" #include "reach/graph.hpp" #include "reach/util.hpp" @@ -89,6 +90,119 @@ map_loaded_symbols_to_ids(const database &db, T graph::build_from_program_facts(const ProgramFacts &pf, bool dynlink, const optional> &loaded_syms) { + T g; + + NodeMap calls; + NodeMap> bb_calls; + unordered_map> address_taken_by_sig; + unordered_map> externs_by_name; + unordered_set loaded_ids; + vector syms; + if (loaded_syms.has_value()) { + syms = *loaded_syms; + } + + for (const auto &[mid, module] : pf.modules) { + for (const auto &[eid, edge] : module.edges) { + const auto &[src, dst] = eid; + const auto sid = make_pair(mid, src); + const auto did = make_pair(mid, dst); + + for (const auto kind : edge.kinds) { + if (kind == EdgeKind::EntryPoint) { + g.addEdge(did, sid, EdgeType::Contains); + } else if (kind == EdgeKind::ControlFlowTo) { + g.addEdge(did, sid, EdgeType::Succ); + } else if (kind == EdgeKind::Calls) { + calls.emplace(sid, did); + } + + if (kind == EdgeKind::Contains && + module.nodes.at(src).type == NodeType::BasicBlock && + module.nodes.at(dst).call_type.has_value()) { + bb_calls[sid].push_back(did); + } + } + } + + for (const auto &[nid, node] : module.nodes) { + const auto id = make_pair(mid, nid); + if (node.linkage == Linkage::ExternalLinkage) { + externs_by_name[*node.name].push_back(id); + } + + if (node.address_taken) { + address_taken_by_sig[*node.function_type].push_back(id); + } + + if (node.type == NodeType::Function && dynlink) { + for (const auto &sym : syms) { + if (sym.symbol == node.name) { + loaded_ids.emplace(id); + break; + } + } + } + } + } + + for (const auto &[bb, instrs] : bb_calls) { + const auto [mid, bbid] = bb; + const auto &module = pf.modules.at(mid); + for (const auto &instr : instrs) { + const auto [_, iid] = instr; + const auto &node = module.nodes.at(iid); + if (node.call_type == CallType::Direct) { + const auto &call_id = calls.at(instr); + g.addEdge(call_id, bb, EdgeType::DirectCall); + + const auto &[_, cid] = call_id; + const auto &fn_name = module.nodes.at(cid).name; + if (fn_name == "pthread_create") { + for (const auto &fn : address_taken_by_sig.at("ptr (ptr)")) { + g.addEdge(fn, bb, EdgeType::IndirectCall, INDIRECT_WEIGHT); + } + } + continue; + } + + if (address_taken_by_sig.contains(*node.function_type)) { + for (const auto &fn : address_taken_by_sig.at(*node.function_type)) { + g.addEdge(fn, bb, EdgeType::IndirectCall, INDIRECT_WEIGHT); + } + } + + if (dynlink) { + for (const auto &[_, handles] : externs_by_name) { + for (const auto &handle : handles) { + const auto &candidate = pf.getNode(handle); + if (candidate.type == NodeType::Function && + candidate.function_type == node.function_type && + (!loaded_syms.has_value() || loaded_ids.contains(handle))) { + g.addEdge(handle, bb, EdgeType::ExternIndirectCall, + INDIRECT_WEIGHT); + } + } + } + } + } + } + + for (const auto &[_, handles] : externs_by_name) { + for (size_t i = 0; i < handles.size(); ++i) { + for (size_t j = i + 1; j < handles.size(); ++j) { + g.addEdge(handles[i], handles[j], EdgeType::Extern, INDIRECT_WEIGHT); + g.addEdge(handles[j], handles[i], EdgeType::Extern, INDIRECT_WEIGHT); + } + } + } + + return g; +} + +T graph::build_from_program_facts(const facts_rs::FactsBuf *facts, bool dynlink, + const optional> &loaded_syms) { + const reach_facts::ProgramFactsView pf{facts}; T g; @@ -98,11 +212,11 @@ T graph::build_from_program_facts(const ProgramFacts &pf, bool dynlink, NodeMap> bb_calls; // For indirect calls we want to get all function that match a signature - std::unordered_map> address_taken_by_sig; + std::unordered_map> address_taken_by_sig; // We want to be able to link all externs of the same name together // and also externs to dynamic symbols if applicable. - unordered_map> externs_by_name; + unordered_map> externs_by_name; std::unordered_set loaded_ids; std::vector syms; @@ -110,46 +224,49 @@ T graph::build_from_program_facts(const ProgramFacts &pf, bool dynlink, syms = *loaded_syms; } - for (const auto &[mid, m] : pf.modules) { + for (uint32_t mid = 0; mid < pf.module_count(); ++mid) { + const auto m = pf.module(mid); - for (const auto &[eid, e] : m.edges) { - const auto &[s, d] = eid; + for (const auto &e : m.edges()) { + const auto s = e.src; + const auto d = e.dst; auto sid = std::make_pair(mid, s); auto did = std::make_pair(mid, d); - for (const auto &k : e.kinds) { - // fn to first block - if (k == EdgeKind::EntryPoint) { - g.addEdge(did, sid, EdgeType::Contains); - // BB control flow - } else if (k == EdgeKind::ControlFlowTo) { - g.addEdge(did, sid, EdgeType::Succ); - } else if (k == EdgeKind::Calls) { - calls.emplace(sid, did); - } + // fn to first block + if (edge_has_kind(e, facts_rs::EdgeKind::EntryPoint)) { + g.addEdge(did, sid, EdgeType::Contains); + } + // BB control flow + if (edge_has_kind(e, facts_rs::EdgeKind::ControlFlowTo)) { + g.addEdge(did, sid, EdgeType::Succ); + } + if (edge_has_kind(e, facts_rs::EdgeKind::Calls)) { + calls.emplace(sid, did); + } - if (k == EdgeKind::Contains && - m.nodes.at(s).type == NodeType::BasicBlock && - m.nodes.at(d).call_type.has_value()) { - bb_calls[sid].push_back(did); - } + if (edge_has_kind(e, facts_rs::EdgeKind::Contains) && + m.node(s).type() == facts_rs::NodeType::BasicBlock && + m.node(d).call_type().has_value()) { + bb_calls[sid].push_back(did); } } - for (const auto &[nid, n] : m.nodes) { + for (uint32_t nid = 0; nid < m.nodes().size(); ++nid) { + const auto n = m.node(nid); auto id = std::make_pair(mid, nid); - if (n.linkage == Linkage::ExternalLinkage) { - externs_by_name[*n.name].push_back(id); + if (n.linkage() == facts_rs::Linkage::ExternalLinkage) { + externs_by_name[*n.name()].push_back(id); } - if (n.address_taken == true) { - auto sig = *n.function_type; + if (n.address_taken()) { + auto sig = *n.function_type(); address_taken_by_sig[sig].push_back(id); } - if (n.type == NodeType::Function && dynlink) { + if (n.type() == facts_rs::NodeType::Function && dynlink) { for (const auto &sym : syms) { - if (sym.symbol == n.name) { + if (n.name() && sym.symbol == *n.name()) { loaded_ids.emplace(id); break; } @@ -161,13 +278,13 @@ T graph::build_from_program_facts(const ProgramFacts &pf, bool dynlink, // Calls for (const auto &[bb, instrs] : bb_calls) { const auto [mid, bbid] = bb; - const auto &module = pf.modules.at(mid); + const auto module = pf.module(mid); for (const auto &instr : instrs) { const auto [_, iid] = instr; - const auto &n = module.nodes.at(iid); - const auto &call_ty = n.call_type; + const auto n = module.node(iid); + const auto call_ty = n.call_type(); // If direct, add one edge. - if (call_ty == CallType::Direct) { + if (call_ty == facts_rs::CallType::Direct) { const auto &call_id = calls.at(instr); g.addEdge(call_id, bb, EdgeType::DirectCall); @@ -176,8 +293,8 @@ T graph::build_from_program_facts(const ProgramFacts &pf, bool dynlink, // "ptr (ptr)". const auto &[_, cid] = call_id; - const auto &fn_name = module.nodes.at(cid).name; - if (fn_name == "pthread_create") { + const auto fn_name = module.node(cid).name(); + if (fn_name && *fn_name == "pthread_create") { for (const auto &fn : address_taken_by_sig.at("ptr (ptr)")) { g.addEdge(fn, bb, EdgeType::IndirectCall, INDIRECT_WEIGHT); } @@ -186,9 +303,9 @@ T graph::build_from_program_facts(const ProgramFacts &pf, bool dynlink, continue; } - if (address_taken_by_sig.contains(*n.function_type)) { + if (address_taken_by_sig.contains(*n.function_type())) { // Else indirect. Add edges for all compatible address-taken functions. - for (const auto &fn : address_taken_by_sig.at(*n.function_type)) { + for (const auto &fn : address_taken_by_sig.at(*n.function_type())) { g.addEdge(fn, bb, EdgeType::IndirectCall, INDIRECT_WEIGHT); } } @@ -199,9 +316,9 @@ T graph::build_from_program_facts(const ProgramFacts &pf, bool dynlink, if (dynlink) { for (const auto &[_, handles] : externs_by_name) { for (const auto &h : handles) { - const auto &n2 = pf.getNode(h); - if (n2.type == NodeType::Function && - n2.function_type == n.function_type && + const auto n2 = pf.node(h); + if (n2.type() == facts_rs::NodeType::Function && + n2.function_type() == n.function_type() && (!loaded_syms.has_value() || loaded_ids.contains(h))) { g.addEdge(h, bb, EdgeType::ExternIndirectCall, INDIRECT_WEIGHT); } @@ -252,6 +369,170 @@ T graph::build_from_program_facts(const ProgramFacts &pf, bool dynlink, // Same thing here as [build_cfg] (see above) wrt. Call edges going // through intermediate function nodes. +T graph::build_instr_cfg(const facts_rs::FactsBuf *facts, bool dynlink, + const optional> &loaded_syms) { + const reach_facts::ProgramFactsView pf{facts}; + T g; + + unordered_map> address_taken_by_sig; + unordered_map> externs_by_name; + unordered_set loaded_ids; + + for (uint32_t mid = 0; mid < pf.module_count(); ++mid) { + const auto module = pf.module(mid); + for (uint32_t nid = 0; nid < module.nodes().size(); ++nid) { + const auto node = module.node(nid); + const auto id = make_pair(mid, nid); + + if (node.linkage() == facts_rs::Linkage::ExternalLinkage) { + externs_by_name[*node.name()].push_back(id); + } + if (node.address_taken()) { + address_taken_by_sig[*node.function_type()].push_back(id); + } + if (dynlink && loaded_syms && + node.type() == facts_rs::NodeType::Function) { + for (const auto &loaded : *loaded_syms) { + if (node.name() && loaded.symbol == *node.name()) { + loaded_ids.insert(id); + break; + } + } + } + } + } + + auto instruction_bounds = [](const reach_facts::ModuleView module, + const facts_rs::NodeID bb) { + pair, optional> result; + for (const auto &edge : module.out_edges(bb)) { + if (edge_has_kind(edge, facts_rs::EdgeKind::Contains) && + module.node(edge.dst).type() == facts_rs::NodeType::Instruction) { + if (!result.first) { + result.first = edge.dst; + } + result.second = edge.dst; + } + } + return result; + }; + + for (uint32_t mid = 0; mid < pf.module_count(); ++mid) { + const auto module = pf.module(mid); + + for (uint32_t bb = 0; bb < module.nodes().size(); ++bb) { + if (module.node(bb).type() != facts_rs::NodeType::BasicBlock) { + continue; + } + + optional previous; + for (const auto &edge : module.out_edges(bb)) { + if (!edge_has_kind(edge, facts_rs::EdgeKind::Contains) || + module.node(edge.dst).type() != facts_rs::NodeType::Instruction) { + continue; + } + + const auto instruction = edge.dst; + if (previous) { + g.addEdge(make_pair(mid, instruction), make_pair(mid, *previous), + EdgeType::Succ); + } + previous = instruction; + + const auto node = module.node(instruction); + const auto call_type = node.call_type(); + if (!call_type) { + continue; + } + + const auto instruction_id = make_pair(mid, instruction); + if (*call_type == facts_rs::CallType::Direct) { + optional target; + for (const auto &call : module.out_edges(instruction)) { + if (edge_has_kind(call, facts_rs::EdgeKind::Calls)) { + target = make_pair(mid, call.dst); + break; + } + } + if (!target) { + throw runtime_error("direct call has no call edge"); + } + + g.addEdge(*target, instruction_id, EdgeType::DirectCall); + const auto [_, target_node] = *target; + if (module.node(target_node).name() == "pthread_create") { + if (const auto it = address_taken_by_sig.find("ptr (ptr)"); + it != address_taken_by_sig.end()) { + for (const auto &function : it->second) { + g.addEdge(function, instruction_id, EdgeType::IndirectCall, + INDIRECT_WEIGHT); + } + } + } + continue; + } + + const auto signature = node.function_type(); + if (!signature) { + throw runtime_error("indirect call has no function type"); + } + if (const auto it = address_taken_by_sig.find(*signature); + it != address_taken_by_sig.end()) { + for (const auto &function : it->second) { + g.addEdge(function, instruction_id, EdgeType::IndirectCall, + INDIRECT_WEIGHT); + } + } + + if (dynlink) { + for (const auto &[_, handles] : externs_by_name) { + for (const auto &handle : handles) { + const auto function = pf.node(handle); + if (function.type() == facts_rs::NodeType::Function && + function.function_type() == signature && + (!loaded_syms || loaded_ids.contains(handle))) { + g.addEdge(handle, instruction_id, EdgeType::ExternIndirectCall, + INDIRECT_WEIGHT); + } + } + } + } + } + } + + for (const auto &edge : module.edges()) { + if (edge_has_kind(edge, facts_rs::EdgeKind::EntryPoint)) { + const auto bounds = instruction_bounds(module, edge.dst); + if (!bounds.first) { + throw runtime_error("entry block has no instruction"); + } + g.addEdge(make_pair(mid, *bounds.first), make_pair(mid, edge.src), + EdgeType::Contains); + } + if (edge_has_kind(edge, facts_rs::EdgeKind::ControlFlowTo)) { + const auto source = instruction_bounds(module, edge.src); + const auto destination = instruction_bounds(module, edge.dst); + if (!source.second || !destination.first) { + throw runtime_error("control-flow block has no instruction"); + } + g.addEdge(make_pair(mid, *destination.first), + make_pair(mid, *source.second), EdgeType::Succ); + } + } + } + + for (const auto &[_, handles] : externs_by_name) { + for (size_t i = 0; i < handles.size(); ++i) { + for (size_t j = i + 1; j < handles.size(); ++j) { + g.addEdge(handles[i], handles[j], EdgeType::Extern, INDIRECT_WEIGHT); + g.addEdge(handles[j], handles[i], EdgeType::Extern, INDIRECT_WEIGHT); + } + } + } + + return g; +} + T graph::build_instr_cfg(const database &db, bool dynlink, const optional> &loaded_syms) { const auto loaded_ids = map_loaded_symbols_to_ids(db, loaded_syms);