mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
6a56557177
Inline `revng_inline` helper calls into each outlined stub which is consumed by `analyzeABI`, so the analysis can observe the reads and writes the helper performs on the floating point registers. The helper module is linked only once at the beginning of the pass, to avoid double linking issues.
1157 lines
40 KiB
C++
1157 lines
40 KiB
C++
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <memory>
|
|
|
|
#include "llvm/ADT/DenseSet.h"
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SetOperations.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/IR/GlobalVariable.h"
|
|
#include "llvm/IR/Instruction.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/Support/GraphWriter.h"
|
|
|
|
#include "revng/ABI/Definition.h"
|
|
#include "revng/ABI/FunctionType/Layout.h"
|
|
#include "revng/ADT/Queue.h"
|
|
#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h"
|
|
#include "revng/EarlyFunctionAnalysis/CFGAnalyzer.h"
|
|
#include "revng/EarlyFunctionAnalysis/CallEdge.h"
|
|
#include "revng/EarlyFunctionAnalysis/CallGraph.h"
|
|
#include "revng/EarlyFunctionAnalysis/CollectFunctionsFromCalleesPass.h"
|
|
#include "revng/EarlyFunctionAnalysis/CollectFunctionsFromUnusedAddressesPass.h"
|
|
#include "revng/EarlyFunctionAnalysis/ControlFlowGraph.h"
|
|
#include "revng/EarlyFunctionAnalysis/ControlFlowGraphCache.h"
|
|
#include "revng/EarlyFunctionAnalysis/DetectABI.h"
|
|
#include "revng/EarlyFunctionAnalysis/FunctionEdgeBase.h"
|
|
#include "revng/EarlyFunctionAnalysis/FunctionSummaryOracle.h"
|
|
#include "revng/InlineHelpers/InlineHelpers.h"
|
|
#include "revng/InlineHelpers/LinkHelpersToInline.h"
|
|
#include "revng/Model/Binary.h"
|
|
#include "revng/Model/NameBuilder.h"
|
|
#include "revng/Model/Pass/DeduplicateCollidingNames.h"
|
|
#include "revng/Model/Register.h"
|
|
#include "revng/Pipeline/Pipe.h"
|
|
#include "revng/Pipeline/RegisterAnalysis.h"
|
|
#include "revng/Pipes/Kinds.h"
|
|
#include "revng/Pipes/ModelGlobal.h"
|
|
#include "revng/Support/BasicBlockID.h"
|
|
#include "revng/Support/Debug.h"
|
|
#include "revng/Support/IRBuilder.h"
|
|
#include "revng/Support/IRHelpers.h"
|
|
#include "revng/Support/MetaAddress.h"
|
|
#include "revng/Support/OpaqueRegisterUser.h"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::cl;
|
|
|
|
static opt<std::string> CallGraphOutputPath("cg-output",
|
|
desc("Dump to disk the recovered "
|
|
"call graph."),
|
|
value_desc("filename"));
|
|
|
|
enum ABIEnforcementOption {
|
|
NoABIEnforcement = 0,
|
|
SoftABIEnforcement,
|
|
FullABIEnforcement,
|
|
};
|
|
|
|
using ABIOpt = ABIEnforcementOption;
|
|
static opt<ABIOpt> ABIEnforcement("abi-enforcement-level",
|
|
desc("ABI refinement preferences."),
|
|
values(clEnumValN(NoABIEnforcement,
|
|
"no",
|
|
"Do not enforce "
|
|
"ABI-specific "
|
|
"information."),
|
|
clEnumValN(SoftABIEnforcement,
|
|
"soft",
|
|
"Enforce ABI-specific "
|
|
"information, but allows "
|
|
"for incompatibility, if "
|
|
"unsure about the ABI."),
|
|
clEnumValN(FullABIEnforcement,
|
|
"full",
|
|
"Enforce ABI-specific "
|
|
"information, and do not "
|
|
"allow for any possible "
|
|
"violations of the ABI "
|
|
"found.")),
|
|
init(ABIOpt::FullABIEnforcement));
|
|
|
|
static Logger Log("detect-abi");
|
|
|
|
struct Changes {
|
|
bool Function = false;
|
|
std::set<MetaAddress> Callees;
|
|
};
|
|
|
|
class DetectABIAnalysis {
|
|
public:
|
|
static constexpr auto Name = "detect-abi";
|
|
|
|
std::vector<std::vector<pipeline::Kind *>> AcceptedKinds = {
|
|
{ &revng::kinds::Root }
|
|
};
|
|
|
|
void run(const pipeline::ExecutionContext &EC,
|
|
pipeline::LLVMContainer &ModuleContainer) {
|
|
revng::pipes::CFGMap CFGs("");
|
|
llvm::legacy::PassManager Manager;
|
|
using namespace revng;
|
|
auto Global = cantFail(EC.getContext()
|
|
.getGlobal<ModelGlobal>(ModelGlobalName));
|
|
Manager.add(new LoadModelWrapperPass(ModelWrapper(Global->get())));
|
|
Manager.add(new CollectFunctionsFromCalleesWrapperPass());
|
|
Manager.add(new ControlFlowGraphCachePass(CFGs));
|
|
Manager.add(new LinkHelpersToInlinePass());
|
|
Manager.add(new efa::DetectABIPass());
|
|
Manager.add(new CollectFunctionsFromUnusedAddressesWrapperPass());
|
|
Manager.add(new efa::DetectABIPass());
|
|
Manager.run(ModuleContainer.getModule());
|
|
}
|
|
};
|
|
|
|
static pipeline::RegisterAnalysis<DetectABIAnalysis> A1;
|
|
|
|
namespace efa {
|
|
|
|
static model::Architecture::Values getCodeArchitecture(const MetaAddress &MA) {
|
|
const auto Architecture = MetaAddressType::arch(MA.type());
|
|
revng_assert(Architecture != model::Architecture::Invalid,
|
|
"The architecture is available for code addresses");
|
|
return Architecture;
|
|
}
|
|
|
|
static bool isWritingToMemory(llvm::Instruction &I) {
|
|
if (auto *StoreInst = dyn_cast<llvm::StoreInst>(&I)) {
|
|
auto *Destination = StoreInst->getPointerOperand();
|
|
|
|
return isMemory(Destination);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
using BasicBlockQueue = UniquedQueue<const BasicBlockNode *>;
|
|
|
|
class DetectABI {
|
|
private:
|
|
using BasicBlockToNodeMap = llvm::DenseMap<llvm::BasicBlock *,
|
|
BasicBlockNode *>;
|
|
|
|
private:
|
|
llvm::Module &M;
|
|
llvm::LLVMContext &Context;
|
|
GeneratedCodeBasicInfo &GCBI;
|
|
ControlFlowGraphCache &FMC;
|
|
TupleTree<model::Binary> &Binary;
|
|
FunctionSummaryOracle &Oracle;
|
|
CFGAnalyzer &Analyzer;
|
|
|
|
CallGraph ApproximateCallGraph;
|
|
BasicBlockToNodeMap BasicBlockNodeMap;
|
|
|
|
public:
|
|
DetectABI(llvm::Module &M,
|
|
GeneratedCodeBasicInfo &GCBI,
|
|
ControlFlowGraphCache &FMC,
|
|
TupleTree<model::Binary> &Binary,
|
|
FunctionSummaryOracle &Oracle,
|
|
CFGAnalyzer &Analyzer) :
|
|
M(M),
|
|
Context(M.getContext()),
|
|
GCBI(GCBI),
|
|
FMC(FMC),
|
|
Binary(Binary),
|
|
Oracle(Oracle),
|
|
Analyzer(Analyzer) {}
|
|
|
|
public:
|
|
void run() {
|
|
llvm::Task Task(6, "DetectABI");
|
|
Task.advance("computeApproximateCallGraph");
|
|
computeApproximateCallGraph();
|
|
|
|
// Perform a preliminary analysis of the function
|
|
//
|
|
// We're interested:
|
|
//
|
|
// 1. the function being noreturn or not;
|
|
// 2. the list of callee-saved registers;
|
|
// 3. the final stack offset;
|
|
// 4. the CFG (specifically, which indirect jumps are returns);
|
|
Task.advance("preliminaryFunctionAnalysis");
|
|
preliminaryFunctionAnalysis();
|
|
|
|
// Run the (fixed-point) analysis of the ABI of each function
|
|
Task.advance("analyzeABI");
|
|
analyzeABI();
|
|
|
|
// Refine results with ABI-specific information
|
|
Task.advance("applyABIDeductions");
|
|
applyABIDeductions();
|
|
|
|
// Commit the results onto the model. A non-const model is taken as
|
|
// argument to be written.
|
|
Task.advance("finalizeModel");
|
|
finalizeModel();
|
|
|
|
// Propagate prototypes
|
|
Task.advance("propagatePrototypes");
|
|
propagatePrototypes();
|
|
}
|
|
|
|
private:
|
|
void computeApproximateCallGraph();
|
|
void preliminaryFunctionAnalysis();
|
|
void analyzeABI();
|
|
Changes analyzeFunctionABI(const model::Function &Function,
|
|
OutlinedFunction &OutlinedFunction,
|
|
OpaqueRegisterUser &Clobberer);
|
|
void applyABIDeductions();
|
|
|
|
/// Finish the population of the model by building the prototype
|
|
void finalizeModel();
|
|
|
|
// Calls propagatePrototypesInFunction for all functions in Binary
|
|
void propagatePrototypes();
|
|
|
|
/// Propagate prototypes to callers
|
|
void propagatePrototypesInFunction(model::Function &Function);
|
|
|
|
private:
|
|
void recordRegisters(const efa::CSVSet &CSVs, auto Inserter);
|
|
|
|
CSVSet computePreservedCSVs(const CSVSet &ClobberedRegisters) const;
|
|
|
|
TrackingSortedVector<model::Register::Values>
|
|
computePreservedRegisters(const CSVSet &ClobberedRegisters) const;
|
|
|
|
Changes runAnalyses(MetaAddress EntryAddress,
|
|
OutlinedFunction &OutlinedFunction);
|
|
|
|
CSVSet findWrittenRegisters(llvm::Function *F);
|
|
|
|
model::UpcastableType
|
|
buildPrototypeForIndirectCall(const FunctionSummary &CallerSummary,
|
|
const efa::BasicBlock &CallerBlock);
|
|
|
|
bool getRegisterState(model::Register::Values, const CSVSet &);
|
|
};
|
|
|
|
void DetectABI::computeApproximateCallGraph() {
|
|
using llvm::BasicBlock;
|
|
|
|
// Temporary worklist to collect the function entrypoints
|
|
llvm::SmallVector<BasicBlock *, 8> Worklist;
|
|
|
|
// Create an over-approximated call graph
|
|
for (const auto &Function : Binary->Functions()) {
|
|
auto *Entry = GCBI.getBlockAt(Function.Entry());
|
|
BasicBlockNode Node{ Function.Entry() };
|
|
BasicBlockNode *GraphNode = ApproximateCallGraph.addNode(Node);
|
|
BasicBlockNodeMap[Entry] = GraphNode;
|
|
}
|
|
|
|
for (const auto &Function : Binary->Functions()) {
|
|
llvm::SmallSet<BasicBlock *, 8> Visited;
|
|
auto *Entry = GCBI.getBlockAt(Function.Entry());
|
|
revng_assert(Entry != nullptr);
|
|
|
|
BasicBlockNode *StartNode = BasicBlockNodeMap[Entry];
|
|
revng_assert(StartNode != nullptr);
|
|
Worklist.emplace_back(Entry);
|
|
|
|
while (!Worklist.empty()) {
|
|
BasicBlock *Current = Worklist.pop_back_val();
|
|
Visited.insert(Current);
|
|
|
|
if (hasMarker(Current, "function_call")) {
|
|
// If not an indirect call, add the node to the CG
|
|
if (BasicBlock *Callee = getFunctionCallCallee(Current)) {
|
|
BasicBlockNode *GraphNode = nullptr;
|
|
auto It = BasicBlockNodeMap.find(Callee);
|
|
if (It != BasicBlockNodeMap.end()) {
|
|
auto IsCurrentSuccessor = [&It](auto &Successor) {
|
|
return Successor == It->second;
|
|
};
|
|
if (not any_of(StartNode->successors(), IsCurrentSuccessor))
|
|
StartNode->addSuccessor(It->second);
|
|
}
|
|
}
|
|
BasicBlock *Next = getFallthrough(Current);
|
|
revng_assert(Next != nullptr);
|
|
|
|
if (!Visited.contains(Next))
|
|
Worklist.push_back(Next);
|
|
|
|
} else {
|
|
|
|
for (BasicBlock *Successor : successors(Current)) {
|
|
if (not isPartOfRootDispatcher(Successor)
|
|
&& !Visited.contains(Successor)) {
|
|
revng_assert(Successor != nullptr);
|
|
Worklist.push_back(Successor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a root entry node for the call-graph, connect all the nodes to it,
|
|
// and perform a post-order traversal. Keep in mind that adding a root node
|
|
// as a predecessor to all nodes does not affect POT of any node, except the
|
|
// root node itself.
|
|
BasicBlockNode *RootNode = ApproximateCallGraph.addNode(MetaAddress());
|
|
ApproximateCallGraph.setEntryNode(RootNode);
|
|
|
|
for (const auto &[_, Node] : BasicBlockNodeMap)
|
|
RootNode->addSuccessor(Node);
|
|
|
|
// Dump the call-graph, if requested
|
|
if (CallGraphOutputPath.getNumOccurrences() == 1) {
|
|
std::error_code EC;
|
|
raw_fd_ostream OutputCG(CallGraphOutputPath, EC);
|
|
revng_assert(!EC);
|
|
llvm::WriteGraph(OutputCG, &ApproximateCallGraph);
|
|
}
|
|
}
|
|
|
|
void DetectABI::preliminaryFunctionAnalysis() {
|
|
revng_log(Log, "Running the preliminary function analysis");
|
|
LoggerIndent LodIndent(Log);
|
|
|
|
BasicBlockQueue EntrypointsQueue;
|
|
|
|
//
|
|
// Populate queue of entry points
|
|
//
|
|
|
|
// Create an over-approximated call graph of the program. A queue of all the
|
|
// function entrypoints is maintained.
|
|
for (auto *Node : llvm::post_order(&ApproximateCallGraph)) {
|
|
// Ignore entry node
|
|
if (Node == ApproximateCallGraph.getEntryNode())
|
|
continue;
|
|
|
|
// The intraprocedural analysis will be scheduled only for those functions
|
|
// which have `Invalid` as type.
|
|
auto &Function = Binary->Functions().at(Node->Address);
|
|
EntrypointsQueue.insert(Node);
|
|
}
|
|
|
|
revng_assert(Binary->Functions().size() == EntrypointsQueue.size());
|
|
|
|
//
|
|
// Process the queue
|
|
//
|
|
|
|
while (!EntrypointsQueue.empty()) {
|
|
const BasicBlockNode *EntryNode = EntrypointsQueue.pop();
|
|
MetaAddress EntryPointAddress = EntryNode->Address;
|
|
revng_log(Log, "Analyzing " << EntryPointAddress.toString());
|
|
LoggerIndent Indent(Log);
|
|
|
|
FunctionSummary AnalysisResult = Analyzer.analyze(EntryNode->Address);
|
|
|
|
if (Log.isEnabled()) {
|
|
AnalysisResult.dump(Log);
|
|
Log << DoLog;
|
|
}
|
|
|
|
// Serialize CFG in the ControlFlowGraphCache
|
|
{
|
|
TupleTree<efa::ControlFlowGraph> New;
|
|
New->Entry() = EntryNode->Address;
|
|
New->Blocks() = AnalysisResult.CFG;
|
|
New->simplify(*Binary);
|
|
FMC.set(std::move(New));
|
|
}
|
|
|
|
// Perform some early sanity checks once the CFG is ready
|
|
revng_assert(AnalysisResult.CFG.size() > 0);
|
|
|
|
if (not Binary->Functions()[EntryPointAddress].Prototype().isEmpty()) {
|
|
Oracle.getLocalFunction(EntryPointAddress)
|
|
.CFG = std::move(AnalysisResult.CFG);
|
|
continue;
|
|
}
|
|
|
|
bool Changed = Oracle.registerLocalFunction(EntryPointAddress,
|
|
std::move(AnalysisResult));
|
|
|
|
revng_assert(Oracle.getLocalFunction(EntryPointAddress).CFG.size() > 0);
|
|
|
|
// If we got improved results for a function, we need to recompute its
|
|
// callers, and if a caller turns out to be an inline function, the
|
|
// callers of the inline function too.
|
|
if (Changed) {
|
|
revng_log(Log,
|
|
"Entry " << EntryPointAddress.toString() << " has changed");
|
|
LoggerIndent Indent(Log);
|
|
OnceQueue<const BasicBlockNode *> InlineFunctionWorklist;
|
|
InlineFunctionWorklist.insert(EntryNode);
|
|
|
|
while (!InlineFunctionWorklist.empty()) {
|
|
const BasicBlockNode *Node = InlineFunctionWorklist.pop();
|
|
MetaAddress NodeAddress = Node->Address;
|
|
revng_log(Log,
|
|
"Re-enqueuing callers of " << NodeAddress.toString() << ":");
|
|
LoggerIndent Indent(Log);
|
|
for (auto *Caller : Node->predecessors()) {
|
|
// Root node?
|
|
if (Caller->Address.isInvalid())
|
|
continue;
|
|
|
|
// If it's inline, re-enqueue its callers too
|
|
MetaAddress CallerPC = Caller->Address;
|
|
const auto &CallerSummary = Oracle.getLocalFunction(CallerPC);
|
|
using namespace model::FunctionAttribute;
|
|
if (CallerSummary.Attributes.contains(AlwaysInline))
|
|
InlineFunctionWorklist.insert(Caller);
|
|
|
|
if (Binary->Functions().at(CallerPC).Prototype().isEmpty()) {
|
|
revng_log(Log, CallerPC.toString());
|
|
EntrypointsQueue.insert(Caller);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Register all indirect call sites in all functions, we'll need to fill in
|
|
// their prototypes
|
|
for (auto &Function : Binary->Functions()) {
|
|
auto &CFG = Oracle.getLocalFunction(Function.Entry()).CFG;
|
|
revng_log(Log,
|
|
"Registering indirect call sites of "
|
|
<< Function.Entry().toString());
|
|
for (efa::BasicBlock &Block : CFG) {
|
|
for (auto &Edge : Block.Successors()) {
|
|
if (auto *Call = dyn_cast<CallEdge>(Edge.get())) {
|
|
if (Call->isIndirect()) {
|
|
revng_log(Log,
|
|
"Registering " << Function.Entry().toString() << " "
|
|
<< Block.ID().toString());
|
|
Oracle.registerCallSite(Function.Entry(),
|
|
Block.ID(),
|
|
FunctionSummary(),
|
|
Call->IsTailCall());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DetectABI::analyzeABI() {
|
|
revng_log(Log, "Running ABI analyses");
|
|
LoggerIndent Indent(Log);
|
|
|
|
llvm::Task Task(3, "analyzeABI");
|
|
std::map<MetaAddress, std::unique_ptr<OutlinedFunction>> Functions;
|
|
|
|
// Create all temporary functions
|
|
Task.advance("Create temporary functions");
|
|
for (model::Function &Function : Binary->Functions()) {
|
|
const MetaAddress &Entry = Function.Entry();
|
|
auto NewFunction = std::make_unique<OutlinedFunction>(Analyzer
|
|
.outline(Entry));
|
|
Functions[Function.Entry()] = std::move(NewFunction);
|
|
}
|
|
|
|
// Inline `revng_inline`-tagged helper calls so the subsequent ABI dataflow
|
|
// analysis can observe the helpers' register reads and writes directly.
|
|
// `InlineHelpersPass` only inlines into `Isolated` functions, so we tag the
|
|
// outlined stubs as such. These stubs are temporary and are discarded with
|
|
// the cloned module once the analysis ends, so the tag never escapes.
|
|
// Helpers whose per-call critical arguments are not LLVM constants here
|
|
// survive un-inlined and are consumed as opaque calls by the analysis.
|
|
for (auto &[Entry, OutlinedFn] : Functions)
|
|
FunctionTags::Isolated.addTo(OutlinedFn->Function.get());
|
|
|
|
Task.advance("Inline helpers");
|
|
{
|
|
llvm::legacy::PassManager PM;
|
|
PM.add(new InlineHelpersPass());
|
|
PM.run(M);
|
|
}
|
|
|
|
// Push this into analyzeFunction
|
|
OpaqueRegisterUser RegisterUser(&M);
|
|
|
|
// TODO: this really needs to become a monotone framework
|
|
Task.advance("Run fixed-point analyses");
|
|
llvm::Task FixedPointTask({}, "Fixed-point analysis");
|
|
UniquedQueue<model::Function *> ToAnalyze;
|
|
for (model::Function &Function : Binary->Functions())
|
|
ToAnalyze.insert(&Function);
|
|
|
|
// Change the oracle default prototype to have no arguments nor return values
|
|
{
|
|
auto NewDefault = Oracle.getDefault().clone();
|
|
NewDefault.ABIResults.ArgumentsRegisters = {};
|
|
NewDefault.ABIResults.ReturnValuesRegisters = {};
|
|
Oracle.setDefault(std::move(NewDefault));
|
|
}
|
|
|
|
unsigned Runs = 0;
|
|
model::CNameBuilder NameBuilder = *Binary;
|
|
while (not ToAnalyze.empty()) {
|
|
model::Function &Function = *ToAnalyze.pop();
|
|
revng_log(Log, "Analyzing " << Function.Entry().toString());
|
|
FixedPointTask.advance(NameBuilder.name(Function));
|
|
OutlinedFunction &OutlinedFunction = *Functions.at(Function.Entry());
|
|
Changes Changes = analyzeFunctionABI(Function,
|
|
OutlinedFunction,
|
|
RegisterUser);
|
|
|
|
if (Changes.Function) {
|
|
revng_log(Log, "The function has changed, re-enqueing all callers:");
|
|
LoggerIndent Indent(Log);
|
|
// The prototype of the function we analyzed has changed, reanalyze
|
|
// callers
|
|
auto &FunctionNode = BasicBlockNodeMap[GCBI.getBlockAt(Function.Entry())];
|
|
for (auto &CallerNode : FunctionNode->predecessors()) {
|
|
if (CallerNode->Address.isValid()) {
|
|
revng_log(Log, CallerNode->Address.toString());
|
|
ToAnalyze.insert(&Binary->Functions().at(CallerNode->Address));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Register for re-analysis all the callees for which we have new
|
|
// information
|
|
for (const MetaAddress &ToReanalyze : Changes.Callees) {
|
|
revng_assert(ToReanalyze.isValid());
|
|
revng_log(Log, "Re-enqueing callee " << ToReanalyze.toString());
|
|
ToAnalyze.insert(&Binary->Functions().at(ToReanalyze));
|
|
}
|
|
}
|
|
}
|
|
|
|
Changes DetectABI::analyzeFunctionABI(const model::Function &Function,
|
|
OutlinedFunction &OutlinedFunction,
|
|
OpaqueRegisterUser &RegisterReader) {
|
|
revng_log(Log, "Analyzing the ABI of " << Function.Entry().toString());
|
|
|
|
//
|
|
// Enrich all the call sites
|
|
//
|
|
|
|
// Collect all calls to precall_hook and postcall_hook
|
|
SmallVector<std::pair<CallInst *, bool>> Hooks;
|
|
for (Instruction &I : instructions(OutlinedFunction.Function.get())) {
|
|
if (CallInst *CallToPreHook = getCallTo(&I, Analyzer.preCallHook())) {
|
|
Hooks.emplace_back(CallToPreHook, true);
|
|
} else if (CallInst *CallToPostHook = getCallTo(&I,
|
|
Analyzer.postCallHook())) {
|
|
Hooks.emplace_back(CallToPostHook, false);
|
|
}
|
|
}
|
|
|
|
// TODO: the checks should be enabled conditionally based on the user.
|
|
revng::NonDebugInfoCheckingIRBuilder Builder(M.getContext());
|
|
for (auto &[Call, IsPreHook] : Hooks) {
|
|
auto CallerBlock = BasicBlockID::fromValue(Call->getArgOperand(0));
|
|
auto CalleeAddress = MetaAddress::fromValue(Call->getArgOperand(1));
|
|
auto ExtractString = extractFromConstantStringPtr;
|
|
StringRef CalleeSymbol = ExtractString(Call->getArgOperand(2));
|
|
auto &&[Summary, _] = Oracle.getCallSite(Function.Entry(),
|
|
CallerBlock,
|
|
CalleeAddress,
|
|
CalleeSymbol);
|
|
auto &ABIResults = Summary->ABIResults;
|
|
|
|
auto *BB = Call->getParent();
|
|
if (IsPreHook) {
|
|
Builder.SetInsertPoint(Call);
|
|
for (auto *CSV : ABIResults.ArgumentsRegisters) {
|
|
// Inject a virtual read of the arguments of the callee
|
|
// TODO: drop const_cast. Unfortunately it requires a significant
|
|
// refactoring.
|
|
RegisterReader.read(Builder, const_cast<GlobalVariable *>(CSV));
|
|
}
|
|
|
|
// Ensure the precall_hook is the first instruction of the block
|
|
BB->splitBasicBlockBefore(Call);
|
|
} else {
|
|
// Ensure the postcall_hook is the last instruction of the block
|
|
BB->splitBasicBlockBefore(Call->getNextNode());
|
|
|
|
Builder.SetInsertPoint(BB, BB->getFirstInsertionPt());
|
|
for (auto *CSV : ABIResults.ReturnValuesRegisters) {
|
|
// Inject a virtual write to the return values of the callee
|
|
// TODO: drop const_cast. Unfortunately it requires a significant
|
|
// refactoring.
|
|
RegisterReader.write(Builder, const_cast<GlobalVariable *>(CSV));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Perform the analysis
|
|
Changes Changes = runAnalyses(Function.Entry(), OutlinedFunction);
|
|
|
|
RegisterReader.purgeCreated();
|
|
|
|
return Changes;
|
|
}
|
|
|
|
// TODO: drop this.
|
|
// Since function type conversion is capable of handling the holes
|
|
// internally, there's not much reason to push such invasive changes
|
|
// this early in the pipeline.
|
|
void DetectABI::applyABIDeductions() {
|
|
if (ABIEnforcement == NoABIEnforcement)
|
|
return;
|
|
|
|
auto ABI = abi::Definition::get(Binary->DefaultABI());
|
|
for (const model::Function &Function : Binary->Functions()) {
|
|
auto &Summary = Oracle.getLocalFunction(Function.Entry());
|
|
|
|
abi::Definition::RegisterSet Arguments;
|
|
abi::Definition::RegisterSet RValues;
|
|
model::Architecture::Values Architecture = Binary->Architecture();
|
|
for (const auto &Register : model::Architecture::registers(Architecture)) {
|
|
auto Name = model::Register::getCSVName(Register);
|
|
if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) {
|
|
if (Summary.ABIResults.ArgumentsRegisters.contains(CSV))
|
|
Arguments.emplace(Register);
|
|
|
|
if (Summary.ABIResults.ReturnValuesRegisters.contains(CSV))
|
|
RValues.emplace(Register);
|
|
}
|
|
}
|
|
|
|
if (ABIEnforcement == FullABIEnforcement) {
|
|
Arguments = ABI.enforceArgumentRegisterState(std::move(Arguments));
|
|
RValues = ABI.enforceReturnValueRegisterState(std::move(RValues));
|
|
} else {
|
|
if (auto R = ABI.tryDeducingArgumentRegisterState(std::move(Arguments)))
|
|
Arguments = *R;
|
|
else
|
|
continue; // Register deduction failed.
|
|
|
|
if (auto R = ABI.tryDeducingReturnValueRegisterState(std::move(RValues)))
|
|
RValues = *R;
|
|
else
|
|
continue; // Register deduction failed.
|
|
}
|
|
|
|
efa::CSVSet ResultingArguments;
|
|
efa::CSVSet ResultingReturnValues;
|
|
for (const auto &Register : model::Architecture::registers(Architecture)) {
|
|
auto Name = model::Register::getCSVName(Register);
|
|
if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) {
|
|
if (Arguments.contains(Register))
|
|
ResultingArguments.insert(CSV);
|
|
if (RValues.contains(Register))
|
|
ResultingReturnValues.insert(CSV);
|
|
}
|
|
}
|
|
|
|
for (auto &Block : Summary.CFG) {
|
|
for (auto &Edge : Block.Successors()) {
|
|
if (efa::FunctionEdgeType::isCall(Edge->Type())
|
|
&& Edge->Type() != efa::FunctionEdgeType::FunctionCall) {
|
|
revng_assert(Block.ID().isValid());
|
|
auto &CSSummary = Summary.ABIResults.CallSites.at(Block.ID());
|
|
CSSummary.ArgumentsRegisters = ResultingArguments;
|
|
CSSummary.ReturnValuesRegisters = ResultingReturnValues;
|
|
}
|
|
}
|
|
}
|
|
Summary.ABIResults.ArgumentsRegisters = std::move(ResultingArguments);
|
|
Summary.ABIResults.ReturnValuesRegisters = std::move(ResultingReturnValues);
|
|
|
|
if (Log.isEnabled()) {
|
|
Log << "Summary for " << Function.Name() << ":\n";
|
|
Summary.dump(Log);
|
|
Log << DoLog;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DetectABI::recordRegisters(const efa::CSVSet &CSVs, auto Inserter) {
|
|
for (auto *CSV : CSVs) {
|
|
auto Reg = model::Register::fromCSVName(CSV->getName(),
|
|
Binary->Architecture());
|
|
Inserter.emplace(Reg).Type() = model::PrimitiveType::makeGeneric(Reg);
|
|
}
|
|
}
|
|
|
|
void DetectABI::finalizeModel() {
|
|
using namespace model;
|
|
|
|
// Fill up the model and build its prototype for each function
|
|
std::set<model::Function *> Functions;
|
|
for (model::Function &Function : Binary->Functions()) {
|
|
// Ignore if we already have a prototype
|
|
if (not Function.Prototype().isEmpty())
|
|
continue;
|
|
|
|
MetaAddress EntryPC = Function.Entry();
|
|
revng_assert(EntryPC.isValid());
|
|
auto &Summary = Oracle.getLocalFunction(EntryPC);
|
|
|
|
// Replace function attributes
|
|
Function.Attributes() = Summary.Attributes;
|
|
|
|
auto &&[Prototype, NewType] = Binary->makeRawFunctionDefinition();
|
|
Prototype.Architecture() = getCodeArchitecture(EntryPC);
|
|
|
|
// Record arguments and return values
|
|
recordRegisters(Summary.ABIResults.ArgumentsRegisters,
|
|
Prototype.Arguments().batch_insert());
|
|
recordRegisters(Summary.ABIResults.ReturnValuesRegisters,
|
|
Prototype.ReturnValues().batch_insert());
|
|
|
|
// Preserved registers
|
|
const auto &ClobberedRegisters = Summary.ClobberedRegisters;
|
|
auto PreservedRegisters = computePreservedRegisters(ClobberedRegisters);
|
|
Prototype.PreservedRegisters() = std::move(PreservedRegisters);
|
|
|
|
// Final stack offset
|
|
Prototype.FinalStackOffset() = Summary.ElectedFSO.value_or(0);
|
|
|
|
Function.Prototype() = std::move(NewType);
|
|
Functions.insert(&Function);
|
|
}
|
|
|
|
// Build prototype for indirect function calls
|
|
for (auto &Function : Functions) {
|
|
auto &Summary = Oracle.getLocalFunction(Function->Entry());
|
|
for (auto &Block : Summary.CFG) {
|
|
|
|
// TODO: we do not detect prototypes for inlined call sites
|
|
if (Block.ID().isInlined())
|
|
continue;
|
|
MetaAddress BlockAddress = Block.ID().notInlinedAddress();
|
|
|
|
for (auto &Edge : Block.Successors()) {
|
|
if (auto *CE = llvm::dyn_cast<efa::CallEdge>(Edge.get())) {
|
|
auto &CallSitePrototypes = Function->CallSitePrototypes();
|
|
bool IsDirect = CE->Destination().isValid();
|
|
bool IsDynamic = not CE->DynamicFunction().empty();
|
|
bool HasInfoOnEdge = CallSitePrototypes.contains(BlockAddress);
|
|
if (not IsDynamic and not IsDirect and not HasInfoOnEdge) {
|
|
// It's an indirect call for which we have now call site information
|
|
auto Prototype = buildPrototypeForIndirectCall(Summary, Block);
|
|
|
|
// This analysis does not have the power to detect whether an
|
|
// indirect call site is a tail call, noreturn or inline
|
|
bool IsTailCall = false;
|
|
|
|
// Register new prototype
|
|
Function->CallSitePrototypes().emplace(BlockAddress,
|
|
std::move(Prototype),
|
|
IsTailCall);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto &Function : Functions) {
|
|
auto &Summary = Oracle.getLocalFunction(Function->Entry());
|
|
efa::ControlFlowGraph FM(Function->Entry(), "", Summary.CFG);
|
|
FM.verify(*Binary, true);
|
|
}
|
|
|
|
revng_check(Binary->verify(true));
|
|
}
|
|
|
|
void DetectABI::propagatePrototypes() {
|
|
for (model::Function &Function : Binary->Functions()) {
|
|
propagatePrototypesInFunction(Function);
|
|
}
|
|
}
|
|
|
|
// TODO: is this still necessary after the new EFA?
|
|
void DetectABI::propagatePrototypesInFunction(model::Function &Function) {
|
|
const MetaAddress &Entry = Function.Entry();
|
|
|
|
revng_log(Log, "Trying to propagate prototypes for " << Entry.toString());
|
|
LoggerIndent Indent(Log);
|
|
|
|
FunctionSummary &Summary = Oracle.getLocalFunction(Entry);
|
|
RUAResults &ABI = Summary.ABIResults;
|
|
SortedVector<efa::BasicBlock> &CFG = Summary.CFG;
|
|
CSVSet &WrittenRegisters = Summary.WrittenRegisters;
|
|
|
|
bool IsSingleNode = CFG.size() == 1;
|
|
|
|
revng_log(Log, "IsSingleNode: " << IsSingleNode);
|
|
|
|
if (not IsSingleNode)
|
|
return;
|
|
|
|
efa::BasicBlock &Block = *CFG.begin();
|
|
bool HasSingleSuccessor = Block.Successors().size() == 1;
|
|
revng_log(Log, "HasSingleSuccessor: " << HasSingleSuccessor);
|
|
|
|
if (not HasSingleSuccessor)
|
|
return;
|
|
|
|
auto &Successor = *Block.Successors().begin();
|
|
const auto *Call = dyn_cast<efa::CallEdge>(Successor.get());
|
|
bool SuccessorIsCall = Call != nullptr;
|
|
revng_log(Log, "SuccessorIsCall: " << SuccessorIsCall);
|
|
|
|
// Select new prototype for wrapper function
|
|
if (SuccessorIsCall) {
|
|
auto Prototype = getPrototype(*Binary, Entry, Block, *Call);
|
|
|
|
using abi::FunctionType::Layout;
|
|
// Get layout of wrapped function
|
|
Layout CalleeLayout = Layout::make(*Prototype);
|
|
|
|
// Verify that wrapper function:
|
|
// - don't write stack pointer
|
|
// - don't write to callee arguments
|
|
// - every store instruction writes to registers (not memory)
|
|
llvm::BasicBlock *BB = GCBI.getBlockAt(Block.ID().start());
|
|
|
|
GlobalVariable *StackPointer = GCBI.spReg();
|
|
|
|
// Check if wrapper writes to Stack Pointer
|
|
const bool WritesSP = WrittenRegisters.contains(StackPointer);
|
|
|
|
using std::ranges::count_if;
|
|
auto IsWrittenByCaller = [this, &WrittenRegisters](auto &Argument) {
|
|
auto *CSV = M.getGlobalVariable(model::Register::getName(Argument));
|
|
return WrittenRegisters.count(CSV);
|
|
};
|
|
const auto &Arguments = CalleeLayout.argumentRegisters();
|
|
bool WritesCalleeArgs = any_of(Arguments, IsWrittenByCaller);
|
|
const auto &ReturnValues = CalleeLayout.returnValueRegisters();
|
|
bool WritesCalleeReturnValues = any_of(ReturnValues, IsWrittenByCaller);
|
|
|
|
bool WritesToMemory = count_if(*BB, isWritingToMemory) > 0;
|
|
bool WritesOnlyRegisters = WritesToMemory == 0;
|
|
|
|
revng_log(Log, "WritesSP: " << WritesSP);
|
|
revng_log(Log, "WritesCalleeArgs: " << WritesCalleeArgs);
|
|
revng_log(Log, "WritesOnlyRegisters: " << WritesOnlyRegisters);
|
|
|
|
// When above conditions are met, overwrite wrappers prototype with
|
|
// wrapped function prototype (`CABIFunctionDefinition` or
|
|
// `RawFunctionDefinition`)
|
|
if (not WritesSP and not WritesCalleeArgs and not WritesCalleeReturnValues
|
|
and WritesOnlyRegisters) {
|
|
|
|
if (Log.isEnabled()) {
|
|
Log << "Overwriting " << Entry.toString() << " prototype ";
|
|
if (!Function.Prototype().isEmpty())
|
|
Log << "(" << toString(Function.Prototype()) << ") ";
|
|
Log << "with wrapped function's prototype: "
|
|
<< toString(model::copyTypeDefinition(*Prototype)) << DoLog;
|
|
}
|
|
|
|
Function.Prototype() = Binary->makeType(Prototype->key());
|
|
|
|
if (Function.Name().empty()) {
|
|
if (not Call->DynamicFunction().empty()) {
|
|
Function.Name() = Call->DynamicFunction();
|
|
|
|
} else if (Call->Destination().isValid()) {
|
|
const model::Function &Callee = Binary->Functions()
|
|
.at(Call->Destination()
|
|
.notInlinedAddress());
|
|
if (not Callee.Name().empty())
|
|
Function.Name() = Callee.Name();
|
|
else if (not Callee.Name().empty())
|
|
Function.Name() = Callee.Name();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: should this be done at a higher abstraction level?
|
|
model::deduplicateCollidingNames(Binary);
|
|
}
|
|
|
|
model::UpcastableType
|
|
DetectABI::buildPrototypeForIndirectCall(const FunctionSummary &CallerSummary,
|
|
const efa::BasicBlock &CallerBlock) {
|
|
using namespace model;
|
|
|
|
auto &&[Prototype, NewType] = Binary->makeRawFunctionDefinition();
|
|
Prototype.Architecture() = getCodeArchitecture(CallerBlock.ID().start());
|
|
|
|
bool Found = false;
|
|
for (const auto &[PC, CallSites] : CallerSummary.ABIResults.CallSites) {
|
|
if (PC != CallerBlock.ID())
|
|
continue;
|
|
|
|
revng_assert(!Found);
|
|
Found = true;
|
|
|
|
recordRegisters(CallSites.ArgumentsRegisters,
|
|
Prototype.Arguments().batch_insert());
|
|
recordRegisters(CallSites.ReturnValuesRegisters,
|
|
Prototype.ReturnValues().batch_insert());
|
|
}
|
|
revng_assert(Found);
|
|
|
|
// Import FinalStackOffset and CalleeSavedRegisters from the default
|
|
// prototype
|
|
const FunctionSummary &DefaultSummary = Oracle.getDefault();
|
|
const auto &Clobbered = DefaultSummary.ClobberedRegisters;
|
|
Prototype.PreservedRegisters() = computePreservedRegisters(Clobbered);
|
|
|
|
Prototype.FinalStackOffset() = DefaultSummary.ElectedFSO.value_or(0);
|
|
|
|
return std::move(NewType);
|
|
}
|
|
|
|
static void combineCrossCallSites(auto &CallSite, auto &Callee) {
|
|
// TODO: why not return values?
|
|
for (auto *CSV : CallSite.ArgumentsRegisters) {
|
|
Callee.ArgumentsRegisters.insert(CSV);
|
|
}
|
|
}
|
|
|
|
bool DetectABI::getRegisterState(model::Register::Values RegisterValue,
|
|
const CSVSet &ABIRegisterMap) {
|
|
|
|
auto Name = model::Register::getCSVName(RegisterValue);
|
|
if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) {
|
|
return ABIRegisterMap.contains(CSV);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CSVSet DetectABI::findWrittenRegisters(llvm::Function *F) {
|
|
using namespace llvm;
|
|
|
|
CSVSet WrittenRegisters;
|
|
for (auto &BB : *F) {
|
|
for (auto &I : BB) {
|
|
if (auto *SI = dyn_cast<StoreInst>(&I)) {
|
|
Value *Ptr = skipCasts(SI->getPointerOperand());
|
|
if (auto *GV = dyn_cast<GlobalVariable>(Ptr))
|
|
WrittenRegisters.insert(GV);
|
|
}
|
|
}
|
|
}
|
|
|
|
return WrittenRegisters;
|
|
}
|
|
|
|
CSVSet DetectABI::computePreservedCSVs(const CSVSet &ClobberedRegisters) const {
|
|
using llvm::GlobalVariable;
|
|
using std::set;
|
|
CSVSet PreservedRegisters(Analyzer.abiCSVs().begin(),
|
|
Analyzer.abiCSVs().end());
|
|
|
|
for (GlobalVariable *ClobberedRegister : ClobberedRegisters)
|
|
PreservedRegisters.erase(ClobberedRegister);
|
|
|
|
return PreservedRegisters;
|
|
}
|
|
|
|
TrackingSortedVector<model::Register::Values>
|
|
DetectABI::computePreservedRegisters(const CSVSet &ClobberedRegisters) const {
|
|
using namespace model;
|
|
|
|
TrackingSortedVector<model::Register::Values> Result;
|
|
CSVSet PreservedRegisters = computePreservedCSVs(ClobberedRegisters);
|
|
|
|
auto PreservedRegistersInserter = Result.batch_insert();
|
|
for (auto *CSV : PreservedRegisters) {
|
|
auto RegisterID = model::Register::fromCSVName(CSV->getName(),
|
|
Binary->Architecture());
|
|
if (RegisterID == Register::Invalid)
|
|
continue;
|
|
|
|
PreservedRegistersInserter.insert(RegisterID);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static void suppressCalleeSaved(RUAResults &ABIResults,
|
|
const CSVSet &CalleeSavedRegs) {
|
|
|
|
// Suppress from arguments
|
|
for (const auto &Reg : CalleeSavedRegs)
|
|
ABIResults.ArgumentsRegisters.erase(Reg);
|
|
|
|
// Suppress from return values
|
|
for (const auto &Reg : CalleeSavedRegs)
|
|
ABIResults.ReturnValuesRegisters.erase(Reg);
|
|
|
|
// Suppress from call-sites
|
|
for (const auto &[K, _] : ABIResults.CallSites) {
|
|
for (const auto &Reg : CalleeSavedRegs) {
|
|
ABIResults.CallSites[K].ArgumentsRegisters.erase(Reg);
|
|
ABIResults.CallSites[K].ReturnValuesRegisters.erase(Reg);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename T, typename F>
|
|
static auto zeroOrOne(const T &Range, const F &Predicate)
|
|
-> decltype(&*Range.begin()) {
|
|
decltype(&*Range.begin()) Result = nullptr;
|
|
for (auto &E : Range) {
|
|
if (Predicate(E)) {
|
|
revng_assert(not Result);
|
|
Result = &E;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
Changes DetectABI::runAnalyses(MetaAddress EntryAddress,
|
|
OutlinedFunction &OutlinedFunction) {
|
|
using namespace llvm;
|
|
using llvm::BasicBlock;
|
|
|
|
revng::IRBuilder Builder(M.getContext());
|
|
RUAResults ABIResults;
|
|
|
|
// Find registers that may be target of at least one store. This helps
|
|
// refine the final results.
|
|
auto WrittenRegisters = findWrittenRegisters(OutlinedFunction.Function.get());
|
|
|
|
// Run ABI-independent data-flow analyses
|
|
ABIResults = analyzeRegisterUsage(OutlinedFunction.Function.get(),
|
|
GCBI,
|
|
Binary->Architecture(),
|
|
Analyzer.preCallHook(),
|
|
Analyzer.postCallHook(),
|
|
Analyzer.retHook());
|
|
|
|
// We say that a register is callee-saved when, besides being preserved by
|
|
// the callee, there is at least a write onto this register.
|
|
FunctionSummary &Summary = Oracle.getLocalFunction(EntryAddress);
|
|
auto CalleeSavedRegs = computePreservedCSVs(Summary.ClobberedRegisters);
|
|
auto ActualCalleeSavedRegs = llvm::set_intersection(CalleeSavedRegs,
|
|
WrittenRegisters);
|
|
|
|
// Refine ABI analyses results by suppressing callee-saved and stack
|
|
// pointer registers.
|
|
suppressCalleeSaved(ABIResults, ActualCalleeSavedRegs);
|
|
|
|
// Commit ABI analysis results to the oracle
|
|
auto Old = Summary.ABIResults;
|
|
Summary.ABIResults.combine(ABIResults);
|
|
Summary.WrittenRegisters = WrittenRegisters;
|
|
|
|
Changes Changes;
|
|
Changes.Function = Old != Summary.ABIResults;
|
|
|
|
for (auto &[BlockID, CallSite] : ABIResults.CallSites) {
|
|
// TODO: why are we ignoring inlined call sites?
|
|
if (BlockID.isInlined())
|
|
continue;
|
|
|
|
MetaAddress Callee = CallSite.CalleeAddress;
|
|
|
|
// TODO: eventually we'll want to add arguments/return values to dynamic
|
|
// functions too
|
|
FunctionSummary *Summary = nullptr;
|
|
if (Callee.isValid()) {
|
|
Summary = &Oracle.getLocalFunction(Callee);
|
|
revng_assert(Summary != nullptr);
|
|
} else {
|
|
Summary = Oracle.getExactCallSite(EntryAddress, BlockID).first;
|
|
}
|
|
|
|
// TODO: are longjmps calls? Do we want to collect prototype for them?
|
|
// Right now, they are in the IR, but we do not register them as call
|
|
// sites in the Oracle.
|
|
if (Summary != nullptr) {
|
|
bool Changed = false;
|
|
RUAResults &ToAdjust = Summary->ABIResults;
|
|
|
|
for (auto *CSV : CallSite.ArgumentsRegisters)
|
|
Changed = Changed or ToAdjust.ArgumentsRegisters.insert(CSV).second;
|
|
|
|
for (auto *CSV : CallSite.ReturnValuesRegisters)
|
|
Changed = Changed or ToAdjust.ReturnValuesRegisters.insert(CSV).second;
|
|
|
|
if (Changed and Callee.isValid())
|
|
Changes.Callees.insert(Callee);
|
|
}
|
|
}
|
|
|
|
return Changes;
|
|
}
|
|
|
|
static void runDetectABI(Module &M,
|
|
GeneratedCodeBasicInfo &GCBI,
|
|
ControlFlowGraphCache &FMC,
|
|
TupleTree<model::Binary> &Binary) {
|
|
using FSOracle = FunctionSummaryOracle;
|
|
FSOracle Oracle = FSOracle::importFullPrototypes(M, GCBI, *Binary);
|
|
CFGAnalyzer Analyzer(M, GCBI, Binary, Oracle);
|
|
DetectABI ABIDetector(M, GCBI, FMC, Binary, Oracle, Analyzer);
|
|
ABIDetector.run();
|
|
}
|
|
|
|
bool DetectABIPass::runOnModule(Module &M) {
|
|
revng_log(PassesLog, "Starting EarlyFunctionAnalysis");
|
|
|
|
if (not M.getFunction("root") or M.getFunction("root")->isDeclaration())
|
|
return false;
|
|
|
|
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
|
|
auto &FMC = getAnalysis<ControlFlowGraphCachePass>().get();
|
|
auto &LMP = getAnalysis<LoadModelWrapperPass>().get();
|
|
|
|
TupleTree<model::Binary> &Binary = LMP.getWriteableModel();
|
|
runDetectABI(M, GCBI, FMC, Binary);
|
|
return false;
|
|
}
|
|
|
|
char DetectABIPass::ID = 0;
|
|
|
|
using ABIDetectionPass = RegisterPass<DetectABIPass>;
|
|
static ABIDetectionPass X("detect-abi", "ABI Detection Pass", true, false);
|
|
|
|
} // namespace efa
|
|
|
|
namespace revng::pypeline::analyses {
|
|
|
|
llvm::Error DetectABI::run(Model &Model,
|
|
const Request &Incoming,
|
|
llvm::StringRef Configuration,
|
|
LLVMRootContainer &ModuleContainer) {
|
|
llvm::Module &Module = ModuleContainer.getModule();
|
|
TupleTree<model::Binary> &TupleModel = Model.get();
|
|
model::Binary &Binary = *TupleModel;
|
|
|
|
GeneratedCodeBasicInfo GCBI(Binary, Module);
|
|
|
|
revng::pipes::CFGMap CFGs("");
|
|
ControlFlowGraphCache FMC(CFGs);
|
|
|
|
// Link helper bodies once at the start of the analysis, to avoid relinking
|
|
// for every `inline-helpers` call
|
|
{
|
|
llvm::legacy::PassManager PM;
|
|
PM.add(new LinkHelpersToInlinePass());
|
|
PM.run(Module);
|
|
}
|
|
|
|
collectFunctionsFromCallees(Module, GCBI, Binary);
|
|
efa::runDetectABI(Module, GCBI, FMC, TupleModel);
|
|
collectFunctionsFromUnusedAddresses(Module, GCBI, Binary, FMC);
|
|
efa::runDetectABI(Module, GCBI, FMC, TupleModel);
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
} // namespace revng::pypeline::analyses
|