diff --git a/include/revng/StackAnalysis/StackAnalysis.h b/include/revng/StackAnalysis/StackAnalysis.h index 0cfb9fc41..e07ea3766 100644 --- a/include/revng/StackAnalysis/StackAnalysis.h +++ b/include/revng/StackAnalysis/StackAnalysis.h @@ -15,6 +15,7 @@ #include "llvm/Support/DOTGraphTraits.h" #include "llvm/Support/GraphWriter.h" +#include "revng/ABIAnalyses/ABIAnalysis.h" #include "revng/ADT/GenericGraph.h" #include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h" #include "revng/FunctionCallIdentification/FunctionCallIdentification.h" diff --git a/lib/StackAnalysis/ABIAnalyses/ABIAnalysis.cpp b/lib/StackAnalysis/ABIAnalyses/ABIAnalysis.cpp index b0289af24..d529c8aa4 100644 --- a/lib/StackAnalysis/ABIAnalyses/ABIAnalysis.cpp +++ b/lib/StackAnalysis/ABIAnalyses/ABIAnalysis.cpp @@ -227,7 +227,8 @@ void finalizeReturnValues(ABIAnalysesResults &ABIResults) { ABIAnalysesResults analyzeOutlinedFunction(Function *F, const GeneratedCodeBasicInfo &GCBI, Function *PreCallSiteHook, - Function *PostCallSiteHook) { + Function *PostCallSiteHook, + Function *RetHook) { namespace UAOF = UsedArgumentsOfFunction; namespace DRAOF = DeadRegisterArgumentsOfFunction; namespace RAOFC = RegisterArgumentsOfFunctionCall; @@ -246,7 +247,8 @@ ABIAnalysesResults analyzeOutlinedFunction(Function *F, if (auto *Call = dyn_cast(&I)) { MetaAddress PC; - if (isCallTo(Call, PreCallSiteHook) || isCallTo(Call, PostCallSiteHook)) + if (isCallTo(Call, PreCallSiteHook) || isCallTo(Call, PostCallSiteHook) + || isCallTo(Call, RetHook)) PC = MetaAddress::fromConstant(Call->getArgOperand(0)); if (isCallTo(Call, PreCallSiteHook)) { @@ -254,7 +256,7 @@ ABIAnalysesResults analyzeOutlinedFunction(Function *F, } else if (isCallTo(Call, PostCallSiteHook)) { Results.URVOFC[{ PC, BB }] = URVOFC::analyze(BB, GCBI); Results.DRVOFC[{ PC, BB }] = DRVOFC::analyze(BB, GCBI); - } else if (auto *R = dyn_cast(&I)) { + } else if (isCallTo(Call, RetHook)) { Results.URVOF[{ PC, BB }] = URVOF::analyze(BB, GCBI); } } diff --git a/lib/StackAnalysis/ABIAnalyses/ABIAnalysis.h b/lib/StackAnalysis/ABIAnalyses/ABIAnalysis.h index 81c98d2fa..0eef4aa6c 100644 --- a/lib/StackAnalysis/ABIAnalyses/ABIAnalysis.h +++ b/lib/StackAnalysis/ABIAnalyses/ABIAnalysis.h @@ -53,6 +53,7 @@ model::RegisterState::Values ABIAnalysesResults analyzeOutlinedFunction(llvm::Function *F, const GeneratedCodeBasicInfo &, llvm::Function *, + llvm::Function *, llvm::Function *); void finalizeReturnValues(ABIAnalysesResults &); diff --git a/lib/StackAnalysis/StackAnalysis.cpp b/lib/StackAnalysis/StackAnalysis.cpp index a584cddf1..83a7df21f 100644 --- a/lib/StackAnalysis/StackAnalysis.cpp +++ b/lib/StackAnalysis/StackAnalysis.cpp @@ -160,6 +160,7 @@ struct FunctionSummary { public: model::FunctionType::Values Type; std::set ClobberedRegisters; + ABIAnalyses::ABIAnalysesResults ABIResults; SortedVector CFG; std::optional ElectedFSO; llvm::Function *FakeFunction; @@ -167,11 +168,13 @@ public: public: FunctionSummary(model::FunctionType::Values Type, std::set ClobberedRegisters, + ABIAnalyses::ABIAnalysesResults ABIResults, SortedVector CFG, std::optional ElectedFSO, llvm::Function *FakeFunction) : Type(Type), ClobberedRegisters(std::move(ClobberedRegisters)), + ABIResults(std::move(ABIResults)), CFG(std::move(CFG)), ElectedFSO(ElectedFSO), FakeFunction(FakeFunction) { @@ -208,6 +211,9 @@ public: for (auto *Reg : ClobberedRegisters) Output << " " << Reg->getName().str() << "\n"; + + Output << " ABI info: \n"; + ABIResults.dump(); } }; @@ -356,6 +362,7 @@ private: /// callee and the call-site. TemporaryOpaqueFunction PreHookMarker; TemporaryOpaqueFunction PostHookMarker; + TemporaryOpaqueFunction RetHookMarker; /// UnexpectedPCMarker is used to indicate that `unexpectedpc` basic /// block of fake functions need to be adjusted to jump to /// `unexpectedpc` of their caller. @@ -383,12 +390,20 @@ private: OutlinedFunction outlineFunction(llvm::BasicBlock *BB); void integrateFunctionCallee(llvm::BasicBlock *BB, MetaAddress); SortedVector collectDirectCFG(OutlinedFunction *F); - void createIBIMarker(OutlinedFunction *F, llvm::IRBuilder<> &); + void initMarkersForABI(OutlinedFunction *F, + llvm::SmallVectorImpl &, + llvm::IRBuilder<> &); + std::set findWrittenRegisters(llvm::Function *F); + void createIBIMarker(OutlinedFunction *F, + llvm::SmallVectorImpl &, + llvm::IRBuilder<> &); void opaqueBranchConditions(llvm::Function *F, llvm::IRBuilder<> &); void materializePCValues(llvm::Function *F, llvm::IRBuilder<> &); void runOptimizationPipeline(llvm::Function *F); - FunctionSummary - milkInfo(OutlinedFunction *F, SortedVector &); + FunctionSummary milkInfo(OutlinedFunction *F, + SortedVector &, + ABIAnalyses::ABIAnalysesResults &, + const std::set &); llvm::Function *createFakeFunction(llvm::BasicBlock *BB); private: @@ -419,6 +434,7 @@ CFEPAnalyzer::CFEPAnalyzer(llvm::Module &M, // Initialize hook markers for subsequent ABI analyses on function calls PreHookMarker(TOF(markerType(M), "precall_hook", &M)), PostHookMarker(TOF(markerType(M), "postcall_hook", &M)), + RetHookMarker(TOF(markerType(M), "retcall_hook", &M)), // Initialize marker to adjust `unexpectedpc` basic block for fake functions UnexpectedPCMarker(TOF(unexpectedPCMarkerType(M), "unexpectedpc_hook", &M)), RegistersClobberedPool(&M, false), @@ -946,8 +962,72 @@ CFEPAnalyzer::collectDirectCFG(OutlinedFunction *F) { return CFG; } +template +void CFEPAnalyzer::initMarkersForABI(OutlinedFunction *OutlinedFunction, + SmallVectorImpl &SV, + llvm::IRBuilder<> &IRB) { + using namespace llvm; + + StructType *MetaAddressTy = MetaAddress::getStruct(&M); + SmallVector IndirectBranchPredecessors; + if (OutlinedFunction->AnyPCCloned) { + for (auto *Pred : predecessors(OutlinedFunction->AnyPCCloned)) { + auto *Term = Pred->getTerminator(); + IndirectBranchPredecessors.emplace_back(Term); + } + } + + // Initialize ret-hook marker (needed for the ABI analyses on return values) + // and fix pre-hook marker upon encountering a jump to `anypc`. Since we don't + // know in advance whether it will be classified as a return or indirect tail + // call, ABIAnalyses (e.g., RAOFC) need to run on this potential call-site as + // well. The results will not be merged eventually, if the indirect jump turns + // out to be a proper return. + for (auto *Term : IndirectBranchPredecessors) { + auto *BB = Term->getParent(); + + MetaAddress IndirectRetBBAddress = GCBI->getJumpTarget(BB); + revng_assert(IndirectRetBBAddress.isValid()); + + auto *Split = BB->splitBasicBlock(Term, BB->getName() + Twine("_anypc")); + auto *JumpToAnyPC = Split->getTerminator(); + revng_assert(isa(JumpToAnyPC)); + IRB.SetInsertPoint(JumpToAnyPC); + + IRB.CreateCall(PreHookMarker.F, + { IndirectRetBBAddress.toConstant(MetaAddressTy), + MetaAddress::invalid().toConstant(MetaAddressTy) }); + + IRB.CreateCall(RetHookMarker.F, + { IndirectRetBBAddress.toConstant(MetaAddressTy), + MetaAddress::invalid().toConstant(MetaAddressTy) }); + + SV.emplace_back(JumpToAnyPC); + } +} + +template +std::set +CFEPAnalyzer::findWrittenRegisters(llvm::Function *F) { + using namespace llvm; + + std::set WrittenRegisters; + for (auto &BB : *F) { + for (auto &I : BB) { + if (auto *SI = dyn_cast(&I)) { + Value *Ptr = skipCasts(SI->getPointerOperand()); + if (auto *GV = dyn_cast(Ptr)) + WrittenRegisters.insert(GV); + } + } + } + + return WrittenRegisters; +} + template void CFEPAnalyzer::createIBIMarker(OutlinedFunction *OutlinedFunction, + SmallVectorImpl &SV, llvm::IRBuilder<> &IRB) { using namespace llvm; @@ -992,19 +1072,10 @@ void CFEPAnalyzer::createIBIMarker(OutlinedFunction *OutlinedFunction, OutlinedFunction->IndirectBranchInfoMarker->addFnAttr(Attribute::NoUnwind); OutlinedFunction->IndirectBranchInfoMarker->addFnAttr(Attribute::NoReturn); - SmallVector BranchesForIBI; - if (OutlinedFunction->AnyPCCloned) { - for (auto *Pred : predecessors(OutlinedFunction->AnyPCCloned)) { - auto *Term = Pred->getTerminator(); - revng_assert(isa(Term)); - BranchesForIBI.emplace_back(Term); - } - } - // When an indirect jump is encountered (possible exit point), a dedicated // basic block is created, and the values of the stack pointer, program // counter and ABI registers are loaded. - for (auto *Term : BranchesForIBI) { + for (auto *Term : SV) { auto *IBIBlock = BasicBlock::Create(Context, Term->getParent()->getName() + Twine("_indirect_branch_info"), @@ -1239,6 +1310,7 @@ FunctionSummary CFEPAnalyzer::analyze(BasicBlock *Entry) { using namespace ABIAnalyses; IRBuilder<> Builder(M.getContext()); + SmallVector BranchesForIBI; // Detect function boundaries struct OutlinedFunction OutlinedFunction = outlineFunction(Entry); @@ -1246,12 +1318,24 @@ FunctionSummary CFEPAnalyzer::analyze(BasicBlock *Entry) { // Recover the control-flow graph of the function auto CFG = collectDirectCFG(&OutlinedFunction); + // Initalize markers for ABI analyses and set up the branches on which + // `indirect_branch_info` will be installed. + initMarkersForABI(&OutlinedFunction, BranchesForIBI, Builder); + + // Find registers that may be target of at least one store. This helps + // refine the final results. + auto WrittenRegisters = findWrittenRegisters(OutlinedFunction.F); + // Run ABI-independent data-flow analyses - ABIAnalysesResults - ABIResults = ABIAnalyses::analyzeOutlinedFunction(OutFunc, - *GCBI, - PreHookMarker, - PostHookMarker); + ABIAnalysesResults ABIResults = analyzeOutlinedFunction(OutlinedFunction.F, + *GCBI, + PreHookMarker.F, + PostHookMarker.F, + RetHookMarker.F); + + // Recompute the DomTree for the current outlined function due to split basic + // blocks. + GCBI->purgeDomTree(OutlinedFunction.F); // The analysis aims at identifying the callee-saved registers of a function // and establishing if a function returns properly, i.e., it jumps to the @@ -1271,7 +1355,7 @@ FunctionSummary CFEPAnalyzer::analyze(BasicBlock *Entry) { // function jumps to its return address (thus, it is not a longjmp / tail // call), `rax` register has been clobbered by the callee, whereas `rbx` and // `rbp` are callee-saved registers. - createIBIMarker(&OutlinedFunction, Builder); + createIBIMarker(&OutlinedFunction, BranchesForIBI, Builder); // Prevent DCE by making branch conditions opaque opaqueBranchConditions(OutlinedFunction.F, Builder); @@ -1284,7 +1368,10 @@ FunctionSummary CFEPAnalyzer::analyze(BasicBlock *Entry) { runOptimizationPipeline(OutlinedFunction.F); // Squeeze out the results obtained from the optimization passes - auto FunctionInfo = milkInfo(&OutlinedFunction, CFG); + auto FunctionInfo = milkInfo(&OutlinedFunction, + CFG, + ABIResults, + WrittenRegisters); // Does the outlined function basically represent a function prologue? If so, // the function is said to be fake, and a copy of the unoptimized outlined @@ -1299,10 +1386,71 @@ FunctionSummary CFEPAnalyzer::analyze(BasicBlock *Entry) { return FunctionInfo; } +static void +suppressCSAndSPRegisters(ABIAnalyses::ABIAnalysesResults &ABIResults, + const std::set &CalleeSavedRegs) { + using RegisterState = model::RegisterState::Values; + + // Suppress from arguments + for (const auto &Reg : CalleeSavedRegs) { + auto It = ABIResults.ArgumentsRegisters.find(Reg); + if (It != ABIResults.ArgumentsRegisters.end()) + It->second = RegisterState::No; + } + + // Suppress from return values + for (const auto &[K, _] : ABIResults.ReturnValuesRegisters) { + for (const auto &Reg : CalleeSavedRegs) { + auto It = ABIResults.ReturnValuesRegisters[K].find(Reg); + if (It != ABIResults.ReturnValuesRegisters[K].end()) + It->second = RegisterState::No; + } + } + + // Suppress from call-sites + for (const auto &[K, _] : ABIResults.CallSites) { + for (const auto &Reg : CalleeSavedRegs) { + if (ABIResults.CallSites[K].ArgumentsRegisters.count(Reg) != 0) + ABIResults.CallSites[K].ArgumentsRegisters[Reg] = RegisterState::No; + + if (ABIResults.CallSites[K].ReturnValuesRegisters.count(Reg) != 0) + ABIResults.CallSites[K].ReturnValuesRegisters[Reg] = RegisterState::No; + } + } +} + +static void discardBrokenReturns(ABIAnalyses::ABIAnalysesResults &ABIResults, + const auto &IBIResult) { + for (const auto &[CI, EdgeType] : IBIResult) { + if (EdgeType != FunctionEdgeTypeValue::Return + && EdgeType != FunctionEdgeTypeValue::IndirectTailCall) { + auto PC = MetaAddress::fromConstant(CI->getOperand(2)); + + auto It = ABIResults.ReturnValuesRegisters.find(PC); + if (It != ABIResults.ReturnValuesRegisters.end()) + ABIResults.ReturnValuesRegisters.erase(PC); + } + } +} + +static std::set +intersect(const std::set &First, + const std::set &Last) { + std::set Output; + std::set_intersection(First.begin(), + First.end(), + Last.begin(), + Last.end(), + std::inserter(Output, Output.begin())); + return Output; +} + template FunctionSummary CFEPAnalyzer::milkInfo(OutlinedFunction *OutlinedFunction, - SortedVector &CFG) { + SortedVector &CFG, + ABIAnalyses::ABIAnalysesResults &ABIResults, + const std::set &WrittenRegisters) { using namespace llvm; SmallVector, 4> MaybeReturns; @@ -1419,8 +1567,30 @@ CFEPAnalyzer::milkInfo(OutlinedFunction *OutlinedFunction, if (Type == FunctionTypeValue::Fake) CFG.clear(); + // We say that a register is callee-saved when, besides being preserved by the + // callee, there is at least a write onto this register. + auto ActualCalleeSavedRegs = intersect(CalleeSavedRegs, WrittenRegisters); + + // Union between effective callee-saved registers and SP + ActualCalleeSavedRegs.insert(GCBI->spReg()); + + // Refine ABI analyses results by suppressing callee-saved and stack pointer + // registers. + suppressCSAndSPRegisters(ABIResults, ActualCalleeSavedRegs); + + // ABI analyses run from all the `indirect_branch_info` (i.e., all candidate + // returns). We are going to merge the results only from those return points + // that have been classified as proper return (i.e., no broken return). + discardBrokenReturns(ABIResults, IBIResult); + + // Merge return values registers + ABIAnalyses::finalizeReturnValues(ABIResults); + + ABIResults.dump(StackAnalysisLog); + return FunctionSummary(Type, std::move(ClobberedRegs), + std::move(ABIResults), std::move(CFG), WinFSO, nullptr); @@ -1845,6 +2015,7 @@ bool StackAnalysis::runOnModule(Module &M) { // Default-constructed cache summary for indirect calls FunctionSummary DefaultSummary(model::FunctionType::Values::Regular, { ABIRegisters.begin(), ABIRegisters.end() }, + ABIAnalyses::ABIAnalysesResults(), {}, GCBI.minimalFSO(), nullptr);