diff --git a/include/revng/StackAnalysis/StackAnalysis.h b/include/revng/StackAnalysis/StackAnalysis.h index 6e728e450..0cfb9fc41 100644 --- a/include/revng/StackAnalysis/StackAnalysis.h +++ b/include/revng/StackAnalysis/StackAnalysis.h @@ -4,14 +4,30 @@ // This file is distributed under the MIT License. See LICENSE.md for details. // +#include +#include #include +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/IR/BasicBlock.h" #include "llvm/Pass.h" +#include "llvm/Support/DOTGraphTraits.h" +#include "llvm/Support/GraphWriter.h" +#include "revng/ADT/GenericGraph.h" #include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h" #include "revng/FunctionCallIdentification/FunctionCallIdentification.h" +#include "revng/Model/Binary.h" #include "revng/Model/LoadModelPass.h" +#include "revng/StackAnalysis/AAWriterPass.h" #include "revng/StackAnalysis/FunctionsSummary.h" +#include "revng/StackAnalysis/IndirectBranchInfoPrinterPass.h" +#include "revng/StackAnalysis/PromoteGlobalToLocalVars.h" +#include "revng/StackAnalysis/SegregateDirectStackAccesses.h" +#include "revng/Support/Assert.h" +#include "revng/Support/MetaAddress.h" +#include "revng/Support/OpaqueFunctionsPool.h" namespace StackAnalysis { diff --git a/lib/StackAnalysis/StackAnalysis.cpp b/lib/StackAnalysis/StackAnalysis.cpp index 5c3a0fe60..e596dcfec 100644 --- a/lib/StackAnalysis/StackAnalysis.cpp +++ b/lib/StackAnalysis/StackAnalysis.cpp @@ -7,37 +7,109 @@ // This file is distributed under the MIT License. See LICENSE.md for details. // +#include +#include #include #include #include #include #include "llvm/ADT/DepthFirstIterator.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/SCCIterator.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Analysis/BasicAliasAnalysis.h" +#include "llvm/Analysis/ScopedNoAliasAA.h" +#include "llvm/CodeGen/UnreachableBlockElim.h" +#include "llvm/IR/Attributes.h" +#include "llvm/IR/Constants.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/Function.h" -#include "llvm/Pass.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/InstCombine/InstCombine.h" +#include "llvm/Transforms/Scalar/EarlyCSE.h" +#include "llvm/Transforms/Scalar/GVN.h" +#include "llvm/Transforms/Scalar/JumpThreading.h" +#include "llvm/Transforms/Scalar/MergedLoadStoreMotion.h" +#include "llvm/Transforms/Scalar/SROA.h" +#include "llvm/Transforms/Scalar/SimplifyCFG.h" +#include "llvm/Transforms/Utils.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/Transforms/Utils/CodeExtractor.h" +#include "llvm/Transforms/Utils/Mem2Reg.h" -#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h" +#include "revng/ADT/KeyedObjectTraits.h" +#include "revng/ADT/Queue.h" +#include "revng/ADT/SortedVector.h" +#include "revng/ADT/ZipMapIterator.h" +#include "revng/BasicAnalyses/RemoveHelperCalls.h" +#include "revng/BasicAnalyses/RemoveNewPCCalls.h" #include "revng/Model/Binary.h" #include "revng/StackAnalysis/StackAnalysis.h" +#include "revng/Support/Assert.h" #include "revng/Support/CommandLine.h" #include "revng/Support/IRHelpers.h" +#include "revng/Support/MetaAddress.h" #include "Cache.h" #include "InterproceduralAnalysis.h" #include "Intraprocedural.h" +using llvm::ArrayRef; using llvm::BasicBlock; using llvm::Function; +using llvm::GlobalVariable; +using llvm::Instruction; using llvm::Module; +using llvm::raw_fd_ostream; using llvm::RegisterPass; +using llvm::SmallVectorImpl; +using llvm::Type; -static Logger<> ClobberedLog("clobbered"); -static Logger<> StackAnalysisLog("stackanalysis"); -static Logger<> CFEPLog("cfep"); +using FunctionEdgeTypeValue = model::FunctionEdgeType::Values; +using FunctionTypeValue = model::FunctionType::Values; +using GCBI = GeneratedCodeBasicInfo; using namespace llvm::cl; +static Logger<> CFEPLog("cfep"); +static Logger<> ClobberedLog("clobbered"); +static Logger<> StackAnalysisLog("stackanalysis"); + +struct BasicBlockNodeData { + BasicBlockNodeData(llvm::BasicBlock *BB) : BB(BB){}; + llvm::BasicBlock *BB; +}; +using BasicBlockNode = BidirectionalNode; +using SmallCallGraph = GenericGraph; + +template<> +struct llvm::DOTGraphTraits + : public llvm::DefaultDOTGraphTraits { + using EdgeIterator = llvm::GraphTraits::ChildIteratorType; + DOTGraphTraits(bool IsSimple = false) : DefaultDOTGraphTraits(IsSimple) {} + + static std::string + getNodeLabel(const BasicBlockNode *Node, const SmallCallGraph *Graph) { + if (Node->BB == nullptr) + return "null"; + return Node->BB->getName().str(); + } + + static std::string getEdgeAttributes(const BasicBlockNode *Node, + const EdgeIterator EI, + const SmallCallGraph *Graph) { + return "color=black,style=dashed"; + } +}; + namespace StackAnalysis { const std::set EmptyCSVSet; @@ -53,6 +125,342 @@ static opt ABIAnalysisOutputPath("abi-analysis-output", value_desc("path"), cat(MainCategory)); +static opt CallGraphOutputPath("cg-output", + desc("Dump to disk the recovered " + "call graph."), + value_desc("filename")); + +static opt IndirectBranchInfoSummaryPath("indirect-branch-info-" + "summary", + desc("Write the results " + "of SA2 on disk."), + value_desc("filename")); + +static opt AAWriterPath("aa-writer", + desc("Dump to disk the outlined functions " + "with annotated alias info."), + value_desc("filename")); + +/// Candidate Function Entry Points structure. +struct CFEP { + CFEP(llvm::BasicBlock *Entry, bool Force) : Entry(Entry), Force(Force) {} + + llvm::BasicBlock *Entry; + bool Force; +}; + +/// A summary of the analysis of a function. +/// +/// For each function detected, the following information are included: +/// its type ("regular", "noreturn" or "fake"), which ABI registers are +/// overwritten, its control-flow graph, and an elected stack offset (to +/// tell if the stack pointer is restored at its original position). +struct FunctionSummary { +public: + model::FunctionType::Values Type; + std::set ClobberedRegisters; + SortedVector CFG; + std::optional ElectedFSO; + llvm::Function *FakeFunction; + +public: + FunctionSummary(model::FunctionType::Values Type, + std::set ClobberedRegisters, + SortedVector CFG, + std::optional ElectedFSO, + llvm::Function *FakeFunction) : + Type(Type), + ClobberedRegisters(std::move(ClobberedRegisters)), + CFG(std::move(CFG)), + ElectedFSO(ElectedFSO), + FakeFunction(FakeFunction) { + if (FakeFunction != nullptr) + revng_assert(CFG.empty()); + } + + FunctionSummary() = delete; + FunctionSummary(const FunctionSummary &) = delete; + FunctionSummary(FunctionSummary &&) = default; + FunctionSummary &operator=(const FunctionSummary &) = delete; + FunctionSummary &operator=(FunctionSummary &&) = default; + +public: + static bool compare(const FunctionSummary &Old, const FunctionSummary &New) { + if (New.Type == Old.Type) + return std::includes(Old.ClobberedRegisters.begin(), + Old.ClobberedRegisters.end(), + New.ClobberedRegisters.begin(), + New.ClobberedRegisters.end()); + + return New.Type <= Old.Type; + } + + void dump() const debug_function { dump(dbg); } + + template + void dump(T &Output) const { + Output << "Dumping summary \n" + << " Type: " << Type << "\n" + << " ElectedFSO: " << (ElectedFSO.has_value() ? *ElectedFSO : -1) + << "\n" + << " Clobbered registers: \n"; + + for (auto *Reg : ClobberedRegisters) + Output << " " << Reg->getName().str() << "\n"; + } +}; + +/// A cache holding the results of the analyses of functions. +/// +/// Leaf subroutines are analyzed first; non-leaf afterwards. The whole +/// process is repeated until a fixed point is reached, and no further +/// refinement can be achieved. The cache can be queried through specific +/// methods to retrieve the stored information, acting as a oracle. +class FunctionAnalysisResults { +private: + /// For each function, the result of the intraprocedural analysis + std::map FunctionsBucket; + FunctionSummary DefaultSummary; + +public: + FunctionAnalysisResults(FunctionSummary DefaultSummary) : + DefaultSummary(std::move(DefaultSummary)) {} + + FunctionSummary &at(MetaAddress PC) { return FunctionsBucket.at(PC); } + + const FunctionSummary &at(MetaAddress PC) const { + return FunctionsBucket.at(PC); + } + + model::FunctionType::Values getFunctionType(MetaAddress PC) const { + return get(PC).Type; + } + + bool isFakeFunction(MetaAddress PC) const { + return getFunctionType(PC) == model::FunctionType::Values::Fake; + } + + llvm::Function *getFakeFunction(MetaAddress PC) const { + return get(PC).FakeFunction; + } + + const auto &getRegistersClobbered(MetaAddress PC) const { + return get(PC).ClobberedRegisters; + } + + std::optional getElectedFSO(MetaAddress PC) const { + return get(PC).ElectedFSO; + } + + bool registerFunction(MetaAddress PC, FunctionSummary &&F) { + revng_assert(PC.isValid()); + auto It = FunctionsBucket.find(PC); + if (It != FunctionsBucket.end()) { + bool Changed = FunctionSummary::compare(It->second, F); + It->second = std::move(F); + return not Changed; + } else { + FunctionsBucket.emplace(PC, std::move(F)); + return true; + } + } + +private: + const FunctionSummary &get(MetaAddress PC) const { + auto It = FunctionsBucket.find(PC); + if (It != FunctionsBucket.end()) + return It->second; + return DefaultSummary; + } +}; + +/// An outlined function helper object. +struct OutlinedFunction { + /// The actual LLVM outlined function + llvm::Function *F = nullptr; + /// The marker that detects returns and regular jumps + llvm::Function *IndirectBranchInfoMarker = nullptr; + llvm::BasicBlock *AnyPCCloned = nullptr; + llvm::BasicBlock *UnexpectedPCCloned = nullptr; + + OutlinedFunction() = default; + OutlinedFunction(const OutlinedFunction &Other) = delete; + OutlinedFunction(OutlinedFunction &&Other) : F(Other.F) { Other.F = nullptr; } + OutlinedFunction &operator=(OutlinedFunction &&) = delete; + OutlinedFunction &operator=(const OutlinedFunction &) = delete; + + llvm::Function *extractFunction() { + auto *ToReturn = F; + F = nullptr; + return ToReturn; + } + + ~OutlinedFunction() { + if (F != nullptr) { + revng_assert(F->use_empty() + && "Failed to remove all users of the outlined function."); + F->eraseFromParent(); + } + + if (IndirectBranchInfoMarker != nullptr) + IndirectBranchInfoMarker->eraseFromParent(); + } +}; + +struct TemporaryOpaqueFunction { + llvm::Function *F = nullptr; + llvm::FunctionType *FTy; + llvm::StringRef Name; + llvm::Module *M; + + TemporaryOpaqueFunction(llvm::FunctionType *FTy, + llvm::StringRef Name, + llvm::Module *M) : + FTy(FTy), Name(Name), M(M) { + F = Function::Create(FTy, llvm::GlobalValue::ExternalLinkage, Name, M); + + revng_assert(F != nullptr); + F->addFnAttr(llvm::Attribute::ReadOnly); + F->addFnAttr(llvm::Attribute::NoUnwind); + F->addFnAttr(llvm::Attribute::WillReturn); + } + + ~TemporaryOpaqueFunction() { + if (F != nullptr) { + revng_assert(F->use_empty() + && "Failed to remove all users of the temporary opaque " + "function."); + F->eraseFromParent(); + } + } +}; + +/// An intraprocedural analysis storage. +/// +/// Implementation of the intraprocedural stack analysis. It holds the +/// necessary information to detect the boundaries of a function, track +/// how the stack evolves within those functions, and detect the +/// callee-saved registers. +template +class CFEPAnalyzer { +private: + llvm::Module &M; + llvm::LLVMContext &Context; + GeneratedCodeBasicInfo *GCBI; + FunctionOracle &Oracle; + ArrayRef ABIRegisters; + /// PreHookMarker and PostHookMarker mark the presence of an original + /// function call, and surround a basic block containing the registers + /// clobbered by the function called. They take the MetaAddress of the + /// callee and the call-site. + TemporaryOpaqueFunction PreHookMarker; + TemporaryOpaqueFunction PostHookMarker; + /// UnexpectedPCMarker is used to indicate that `unexpectedpc` basic + /// block of fake functions need to be adjusted to jump to + /// `unexpectedpc` of their caller. + TemporaryOpaqueFunction UnexpectedPCMarker; + std::unique_ptr OutputIBI; + std::unique_ptr OutputAAWriter; + OpaqueFunctionsPool RegistersClobberedPool; + OpaqueFunctionsPool OpaqueBranchConditionsPool; + const llvm::CodeExtractorAnalysisCache CEAC; + const ProgramCounterHandler *PCH; + +public: + CFEPAnalyzer(llvm::Module &, + GeneratedCodeBasicInfo *GCBI, + FunctionOracle &, + ArrayRef); + +public: + /// The `analyze` method is the entry point of the intraprocedural analysis, + /// and it is called on each CFEP until a fixed point is reached. It is + /// responsible for performing the whole computation. + FunctionSummary analyze(llvm::BasicBlock *BB); + +private: + OutlinedFunction outlineFunction(llvm::BasicBlock *BB); + void integrateFunctionCallee(llvm::BasicBlock *BB, MetaAddress); + SortedVector collectDirectCFG(OutlinedFunction *F); + void createIBIMarker(OutlinedFunction *F, 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 &); + llvm::Function *createFakeFunction(llvm::BasicBlock *BB); + +private: + static auto *markerType(llvm::Module &M) { + return llvm::FunctionType::get(Type::getVoidTy(M.getContext()), + { MetaAddress::getStruct(&M), + MetaAddress::getStruct(&M) }, + false); + } + + static auto *unexpectedPCMarkerType(llvm::Module &M) { + return llvm::FunctionType::get(Type::getVoidTy(M.getContext()), false); + } +}; + +using TOF = TemporaryOpaqueFunction; + +template +CFEPAnalyzer::CFEPAnalyzer(llvm::Module &M, + GeneratedCodeBasicInfo *GCBI, + FunctionOracle &Oracle, + ArrayRef ABIRegs) : + M(M), + Context(M.getContext()), + GCBI(GCBI), + Oracle(Oracle), + ABIRegisters(ABIRegs), + // Initialize hook markers for subsequent ABI analyses on function calls + PreHookMarker(TOF(markerType(M), "precall_hook", &M)), + PostHookMarker(TOF(markerType(M), "postcall_hook", &M)), + // Initialize marker to adjust `unexpectedpc` basic block for fake functions + UnexpectedPCMarker(TOF(unexpectedPCMarkerType(M), "unexpectedpc_hook", &M)), + RegistersClobberedPool(&M, false), + OpaqueBranchConditionsPool(&M, false), + // Initialize the cache for the `CodeExtractor` analysis on `root` + CEAC(llvm::CodeExtractorAnalysisCache(*M.getFunction("root"))), + PCH(GCBI->programCounterHandler()) { + + // Open streams for dumping results + if (IndirectBranchInfoSummaryPath.getNumOccurrences() == 1) { + std::ifstream File(IndirectBranchInfoSummaryPath.c_str()); + if (File.is_open()) { + int Status = std::remove(IndirectBranchInfoSummaryPath.c_str()); + revng_assert(Status == 0); + } + + std::error_code EC; + OutputIBI = std::make_unique(IndirectBranchInfoSummaryPath, + EC, + llvm::sys::fs::OF_Append); + revng_assert(!EC); + + *OutputIBI << "name,ra,fso,address"; + for (const auto &Reg : ABIRegisters) + *OutputIBI << "," << Reg->getName(); + *OutputIBI << "\n"; + } + + if (AAWriterPath.getNumOccurrences() == 1) { + std::ifstream File(AAWriterPath.c_str()); + if (File.is_open()) { + int Status = std::remove(AAWriterPath.c_str()); + revng_assert(Status == 0); + } + + std::error_code EC; + OutputAAWriter = std::make_unique(AAWriterPath, + EC, + llvm::sys::fs::OF_Append); + revng_assert(!EC); + } +} + template static model::RegisterState::Values toRegisterState(RegisterArgument RA) { @@ -135,9 +543,7 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, MetaAddress EntryPC = getBasicBlockPC(Entry); revng_assert(EntryPC.isValid()); - // Create the function - revng_assert(TheBinary.Functions.count(EntryPC) == 0); - model::Function &Function = TheBinary.Functions[EntryPC]; + model::Function &Function = Binary.Functions[EntryPC]; // Assign a name @@ -420,6 +826,869 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, revng_check(TheBinary.verify(true)); } +/// Elect a final stack offset to tell whether the function is leaving +/// the stack pointer higher than it was at the function entry. +static std::optional electFSO(const auto &MaybeReturns) { + auto It = std::min_element(MaybeReturns.begin(), + MaybeReturns.end(), + [](const auto &LHS, const auto &RHS) { + return LHS.second < RHS.second; + }); + if (It == MaybeReturns.end()) + return {}; + return It->second; +} + +static UpcastablePointer +makeEdge(MetaAddress Destination, model::FunctionEdgeType::Values Type) { + model::FunctionEdge *Result = nullptr; + using ReturnType = UpcastablePointer; + + if (model::FunctionEdgeType::isCall(Type)) + return ReturnType::make(Destination, Type); + else + return ReturnType::make(Destination, Type); +}; + +static MetaAddress getFinalAddressOfBasicBlock(llvm::BasicBlock *BB) { + auto [End, Size] = getPC(BB->getTerminator()); + return End + Size; +} + +template +SortedVector +CFEPAnalyzer::collectDirectCFG(OutlinedFunction *F) { + using namespace llvm; + + SortedVector CFG; + + for (BasicBlock &BB : *F->F) { + if (GCBI::isJumpTarget(&BB)) { + MetaAddress Start = getBasicBlockPC(&BB); + model::BasicBlock Block{ Start }; + Block.End = getFinalAddressOfBasicBlock(&BB); + + OnceQueue Queue; + Queue.insert(&BB); + + // A JT with no successors? + if (isa(BB.getTerminator())) { + auto Type = model::FunctionEdgeType::Unreachable; + Block.Successors.insert(makeEdge(MetaAddress::invalid(), Type)); + } + + while (!Queue.empty()) { + BasicBlock *Current = Queue.pop(); + + MetaAddress CurrentBlockEnd = getFinalAddressOfBasicBlock(Current); + if (CurrentBlockEnd > Block.End) + Block.End = CurrentBlockEnd; + + for (BasicBlock *Succ : successors(Current)) { + if (GCBI::isJumpTarget(Succ)) { + MetaAddress Destination = getBasicBlockPC(Succ); + auto Edge = makeEdge(Destination, + model::FunctionEdgeType::DirectBranch); + Block.Successors.insert(Edge); + } else if (F->UnexpectedPCCloned == Succ && succ_size(Current) == 1) { + // Need to create an edge only when `unexpectedpc` is the unique + // successor of the current basic block. + Block.Successors.insert(makeEdge(MetaAddress::invalid(), + model::FunctionEdgeType::LongJmp)); + } else { + Instruction *I = &(*Succ->begin()); + + if (auto *Call = getCallTo(I, PreHookMarker.F)) { + MetaAddress Destination; + model::FunctionEdgeType::Values Type; + auto *CalleePC = Call->getArgOperand(1); + + // Direct or indirect call? + if (isa(CalleePC)) { + Destination = MetaAddress::fromConstant(CalleePC); + Type = model::FunctionEdgeType::FunctionCall; + } else { + Destination = MetaAddress::invalid(); + Type = model::FunctionEdgeType::IndirectCall; + } + + auto Edge = makeEdge(Destination, Type); + auto DestTy = Oracle.getFunctionType(Destination); + if (DestTy == FunctionTypeValue::NoReturn) { + auto *CE = cast(Edge.get()); + CE->Attributes.insert(model::FunctionAttribute::NoReturn); + } + + Block.Successors.insert(Edge); + } else if (auto *Call = getCallTo(I, "function_call")) { + // At this stage, `function_call` marker has only been left to + // signal the presence of fake functions. We can safely erase it + // and add an edge of type FakeFunctionCall (still used in + // IsolateFunction). + Call->eraseFromParent(); + + auto Destination = getBasicBlockPC(Succ); + auto Edge = makeEdge(Destination, + model::FunctionEdgeType::FakeFunctionCall); + Block.Successors.insert(Edge); + } else { + Queue.insert(Succ); + } + } + } + } + + CFG.insert(Block); + } + } + + return CFG; +} + +template +void CFEPAnalyzer::createIBIMarker(OutlinedFunction *OutlinedFunction, + llvm::IRBuilder<> &IRB) { + using namespace llvm; + + StructType *MetaAddressTy = MetaAddress::getStruct(&M); + IRB.SetInsertPoint(&OutlinedFunction->F->getEntryBlock().front()); + auto *IntPtrTy = GCBI->spReg()->getType(); + auto *IntTy = GCBI->spReg()->getType()->getElementType(); + + // At the entry of the function, load the initial value of stack pointer, + // program counter and ABI registers used within this function. + auto *SP = IRB.CreateLoad(GCBI->spReg()); + auto *SPPtr = IRB.CreateIntToPtr(SP, IntPtrTy); + auto *GEPI = IRB.CreateGEP(IntTy, SPPtr, ConstantInt::get(IntTy, 0)); + + Value *RA = IRB.CreateLoad(GCBI->raReg() ? GCBI->raReg() : SPPtr); + + std::array DissectedPC = PCH->dissectJumpablePC(IRB, + RA, + GCBI->arch()); + + auto *PCI = MetaAddress::composeIntegerPC(IRB, + DissectedPC[0], + DissectedPC[1], + DissectedPC[2], + DissectedPC[3]); + + SmallVector CSVI; + Type *IsRetTy = Type::getInt128Ty(Context); + SmallVector ArgTypes = { IsRetTy, IntTy, MetaAddressTy }; + for (auto *CSR : ABIRegisters) { + auto *V = IRB.CreateLoad(CSR); + CSVI.emplace_back(V); + ArgTypes.emplace_back(IntTy); + } + + auto *FTy = llvm::FunctionType::get(IntTy, ArgTypes, false); + OutlinedFunction + ->IndirectBranchInfoMarker = Function::Create(FTy, + GlobalValue::ExternalLinkage, + "indirect_branch_info", + M); + 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) { + auto *IBIBlock = BasicBlock::Create(Context, + Term->getParent()->getName() + + Twine("_indirect_branch_info"), + OutlinedFunction->F, + nullptr); + + Term->replaceUsesOfWith(OutlinedFunction->AnyPCCloned, IBIBlock); + + IRB.SetInsertPoint(IBIBlock); + auto *PCE = PCH->composeIntegerPC(IRB); + + SmallVector CSVE; + for (auto *CSR : ABIRegisters) { + auto *V = IRB.CreateLoad(CSR); + CSVE.emplace_back(V); + } + + SP = IRB.CreateLoad(GCBI->spReg()); + SPPtr = IRB.CreateIntToPtr(SP, IntPtrTy); + auto *GEPE = IRB.CreateGEP(IntTy, SPPtr, ConstantInt::get(IntTy, 0)); + + auto *SPI = IRB.CreatePtrToInt(GEPI, IntTy); + auto *SPE = IRB.CreatePtrToInt(GEPE, IntTy); + + // Compute the difference between the program counter at entry and exit + // function. Should it turn out to be zero, the function jumps to its return + // address. + auto *JumpsToReturnAddress = IRB.CreateSub(PCE, PCI); + + // Compute the difference between the stack pointer values to evaluate the + // stack height. Functions leaving the stack pointer higher than it was at + // function entry (i.e., in an irregular state) will be marked as fake + // functions. + auto *StackPointerDifference = IRB.CreateSub(SPE, SPI); + + // Save the MetaAddress of the final jump target + auto NewPCJT = GCBI->getJumpTarget(Term->getParent()); + revng_assert(NewPCJT.isValid()); + + SmallVector ArgValues = { JumpsToReturnAddress, + StackPointerDifference, + NewPCJT.toConstant(MetaAddressTy) }; + + // Compute the difference between the initial and final values of the CSV + // ABI registers. Should it turn out to be zero, the CSV is preserved across + // the function call (callee-saved). + for (const auto &[Initial, End] : zip(CSVI, CSVE)) { + auto *ABIRegistersDifference = IRB.CreateSub(Initial, End); + ArgValues.emplace_back(ABIRegistersDifference); + } + + // Install the `indirect_branch_info` call + IRB.CreateCall(OutlinedFunction->IndirectBranchInfoMarker, ArgValues); + IRB.CreateUnreachable(); + } +} + +template +void CFEPAnalyzer::opaqueBranchConditions(llvm::Function *F, + llvm::IRBuilder<> &IRB) { + using namespace llvm; + + for (auto &BB : *F) { + auto *Term = BB.getTerminator(); + if ((isa(Term) && cast(Term)->isConditional()) + || isa(Term)) { + Value *Condition = isa(Term) ? + cast(Term)->getCondition() : + cast(Term)->getCondition(); + + OpaqueBranchConditionsPool.addFnAttribute(Attribute::NoUnwind); + OpaqueBranchConditionsPool.addFnAttribute(Attribute::ReadOnly); + OpaqueBranchConditionsPool.addFnAttribute(Attribute::WillReturn); + + auto *FTy = llvm::FunctionType::get(Condition->getType(), + { Condition->getType() }, + false); + + auto *OpaqueTrueCallee = OpaqueBranchConditionsPool.get(FTy, + FTy, + "opaque_true"); + + IRB.SetInsertPoint(Term); + auto *RetVal = IRB.CreateCall(OpaqueTrueCallee, { Condition }); + + if (isa(Term)) + cast(Term)->setCondition(RetVal); + else + cast(Term)->setCondition(RetVal); + } + } +} + +template +void CFEPAnalyzer::materializePCValues(llvm::Function *F, + llvm::IRBuilder<> &IRB) { + using namespace llvm; + + for (auto &BB : *F) { + for (auto &I : BB) { + if (auto *Call = getCallTo(&I, "newpc")) { + MetaAddress NewPC = GCBI::getPCFromNewPC(Call); + IRB.SetInsertPoint(Call); + PCH->setPC(IRB, NewPC); + } + } + } +} + +template +struct TemporaryOption { +public: + TemporaryOption(const char *Name, const T &Value) : + Name(Name), Options(llvm::cl::getRegisteredOptions()) { + OldValue = Opt(Options, Name)->getValue(); + Opt(Options, Name)->setInitialValue(Value); + } + + ~TemporaryOption() { Opt(Options, Name)->setInitialValue(OldValue); } + +private: + T OldValue; + const char *Name; + llvm::StringMap &Options; + static constexpr const auto &Opt = getOption; +}; + +template +void CFEPAnalyzer::runOptimizationPipeline(llvm::Function *F) { + using namespace llvm; + + // Some LLVM passes used later in the pipeline scan for cut-offs, meaning that + // further computation may not be done when they are reached; making some + // optimizations opportunities missed. Hence, we set the involved thresholds + // (e.g., the maximum value that MemorySSA uses to take into account + // stores/phis) to have initial unbounded value. + static constexpr const char *MemSSALimit = "memssa-check-limit"; + static constexpr const char *MemDepBlockLimit = "memdep-block-scan-limit"; + + using TemporaryUOption = TemporaryOption; + TemporaryUOption MemSSALimitOption(MemSSALimit, UINT_MAX); + TemporaryUOption MemDepBlockLimitOption(MemDepBlockLimit, UINT_MAX); + + // TODO: break it down in the future, and check if some passes can be dropped + { + FunctionPassManager FPM; + + // First stage: simplify the IR, promote the CSVs to local variables, + // compute subexpressions elimination and resolve redundant expressions in + // order to compute the stack height. + FPM.addPass(RemoveNewPCCallsPass()); + FPM.addPass(RemoveHelperCallsPass()); + FPM.addPass(PromoteGlobalToLocalPass()); + FPM.addPass(SimplifyCFGPass()); + FPM.addPass(SROA()); + FPM.addPass(EarlyCSEPass(true)); + FPM.addPass(JumpThreadingPass()); + FPM.addPass(UnreachableBlockElimPass()); + FPM.addPass(InstCombinePass(true)); + FPM.addPass(EarlyCSEPass(true)); + FPM.addPass(SimplifyCFGPass()); + FPM.addPass(MergedLoadStoreMotionPass()); + FPM.addPass(GVN()); + + // Second stage: add alias analysis info and canonicalize `i2p` + `add` into + // `getelementptr` instructions. Since the IR may change remarkably, another + // round of passes is necessary to take more optimization opportunities. + FPM.addPass(SegregateDirectStackAccessesPass()); + FPM.addPass(EarlyCSEPass(true)); + FPM.addPass(InstCombinePass(true)); + FPM.addPass(GVN()); + + // Third stage: if enabled, serialize the results and dump the functions on + // disk with the alias information included as comments. + if (IndirectBranchInfoSummaryPath.getNumOccurrences() == 1) + FPM.addPass(IndirectBranchInfoPrinterPass(*OutputIBI)); + + if (AAWriterPath.getNumOccurrences() == 1) + FPM.addPass(AAWriterPass(*OutputAAWriter)); + + ModuleAnalysisManager MAM; + + FunctionAnalysisManager FAM; + FAM.registerPass([] { + AAManager AA; + AA.registerFunctionAnalysis(); + AA.registerFunctionAnalysis(); + + return AA; + }); + FAM.registerPass([&] { return GeneratedCodeBasicInfoAnalysis(); }); + FAM.registerPass([&] { return ModuleAnalysisManagerFunctionProxy(MAM); }); + + PassBuilder PB; + PB.registerFunctionAnalyses(FAM); + PB.registerModuleAnalyses(MAM); + + FPM.run(*F, FAM); + } +} + +template +llvm::Function *CFEPAnalyzer::createFakeFunction(llvm::BasicBlock *Entry) { + using namespace llvm; + + // Recreate outlined function + OutlinedFunction FakeFunction = outlineFunction(Entry); + + // Adjust `anypc` and `unexpectedpc` BBs of the fake function + revng_assert(FakeFunction.AnyPCCloned != nullptr); + + // Fake functions must have one and only one broken return + revng_assert(FakeFunction.AnyPCCloned->hasNPredecessors(1)); + + // Replace the broken return with a `ret` + auto *Br = FakeFunction.AnyPCCloned->getUniquePredecessor()->getTerminator(); + auto *Ret = ReturnInst::Create(Context); + ReplaceInstWithInst(Br, Ret); + + if (FakeFunction.UnexpectedPCCloned != nullptr) { + CallInst::Create(UnexpectedPCMarker.F, + "", + FakeFunction.UnexpectedPCCloned->getTerminator()); + } + + return FakeFunction.extractFunction(); +} + +template +FunctionSummary CFEPAnalyzer::analyze(BasicBlock *Entry) { + using namespace llvm; + + IRBuilder<> Builder(M.getContext()); + + // Detect function boundaries + struct OutlinedFunction OutlinedFunction = outlineFunction(Entry); + + // Recover the control-flow graph of the function + auto CFG = collectDirectCFG(&OutlinedFunction); + + // 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 + // return address (regular function). In order to achieve this, the IR is + // crafted by loading the program counter, the stack pointer, as well as the + // ABI registers CSVs respectively at function prologue / epilogue. When the + // subtraction between their entry and end values is found to be zero (after + // running an LLVM optimization pipeline), we may infer if the function + // returns correctly, the stack is left unanaltered, etc. Hence, upon every + // original indirect jump (candidate exit point), a marker of this kind is + // installed: + // + // jumps to RA, SP, rax, rbx, rbp + // call i64 @indirect_branch_info(i128 0, i64 8, i64 %8, i64 0, i64 0) + // + // Here, subsequently the opt pipeline computation, we may tell that the + // 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); + + // Prevent DCE by making branch conditions opaque + opaqueBranchConditions(OutlinedFunction.F, Builder); + + // Store the values that build up the program counter in order to have them + // constant-folded away by the optimization pipeline. + materializePCValues(OutlinedFunction.F, Builder); + + // Execute the optimization pipeline over the outlined function + runOptimizationPipeline(OutlinedFunction.F); + + // Squeeze out the results obtained from the optimization passes + auto FunctionInfo = milkInfo(&OutlinedFunction, CFG); + + // 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 + // function is returned. When analyzing the caller, this function will be + // inlined in its call-site. + if (FunctionInfo.Type == FunctionTypeValue::Fake) + FunctionInfo.FakeFunction = createFakeFunction(Entry); + + // Reset the DomTree for the current outlined function + GCBI->purgeDomTree(OutlinedFunction.F); + + return FunctionInfo; +} + +template +FunctionSummary +CFEPAnalyzer::milkInfo(OutlinedFunction *OutlinedFunction, + SortedVector &CFG) { + using namespace llvm; + + SmallVector, 4> MaybeReturns; + SmallVector, 4> NotReturns; + SmallVector, 4> IBIResult; + std::set CalleeSavedRegs; + std::set ClobberedRegs(ABIRegisters.begin(), + ABIRegisters.end()); + + for (CallBase *CI : callers(OutlinedFunction->IndirectBranchInfoMarker)) { + if (CI->getParent()->getParent() == OutlinedFunction->F) { + bool JumpsToReturnAddress = false; + auto MayJumpToReturnAddress = dyn_cast(CI->getArgOperand(0)); + if (MayJumpToReturnAddress) + JumpsToReturnAddress = MayJumpToReturnAddress->getSExtValue() == 0; + + auto *StackPointerOffset = dyn_cast(CI->getArgOperand(1)); + if (StackPointerOffset) { + int64_t FSO = StackPointerOffset->getSExtValue(); + if (JumpsToReturnAddress) { + if (FSO >= 0) + MaybeReturns.emplace_back(CI, FSO); + else + IBIResult.emplace_back(CI, FunctionEdgeTypeValue::BrokenReturn); + } else { + NotReturns.emplace_back(CI, FSO); + } + } else { + if (JumpsToReturnAddress) + IBIResult.emplace_back(CI, FunctionEdgeTypeValue::LongJmp); + } + } + } + + // Elect a final stack offset + auto WinFSO = electFSO(MaybeReturns); + + // Did we find at least a valid return instruction? + for (const auto &[CI, FSO] : MaybeReturns) { + if (FSO == *WinFSO) { + IBIResult.emplace_back(CI, FunctionEdgeTypeValue::Return); + unsigned ArgumentsCount = CI->getNumArgOperands(); + if (ArgumentsCount > 3) { + for (unsigned Idx = 3; Idx < ArgumentsCount; ++Idx) { + auto *Register = dyn_cast(CI->getArgOperand(Idx)); + if (Register && Register->getZExtValue() == 0) + CalleeSavedRegs.insert(ABIRegisters[Idx - 3]); + } + } + } else { + IBIResult.emplace_back(CI, FunctionEdgeTypeValue::BrokenReturn); + } + } + + // Neither a return nor a broken return? Re-elect a FSO taking into account no + // returns indirect jumps only. + if (!WinFSO.has_value()) + WinFSO = electFSO(NotReturns); + + for (CallBase *CI : callers(OutlinedFunction->IndirectBranchInfoMarker)) { + if (CI->getParent()->getParent() == OutlinedFunction->F) { + auto MayJumpToReturnAddress = dyn_cast(CI->getArgOperand(0)); + if (MayJumpToReturnAddress && MayJumpToReturnAddress->getSExtValue() == 0) + continue; + + // We have an indirect jump and we classify it depending on the status of + // the stack pointer. + auto *StackOffset = dyn_cast(CI->getArgOperand(1)); + if (WinFSO.has_value() && StackOffset != nullptr + && StackOffset->getSExtValue() == *WinFSO) + IBIResult.emplace_back(CI, FunctionEdgeTypeValue::IndirectTailCall); + else + IBIResult.emplace_back(CI, FunctionEdgeTypeValue::LongJmp); + } + } + + bool FoundReturn = false; + bool FoundBrokenReturn = false; + int BrokenReturnCount = 0, NoReturnCount = 0; + for (const auto &[CI, EdgeType] : IBIResult) { + if (EdgeType == FunctionEdgeTypeValue::Return) { + FoundReturn = true; + } else if (EdgeType == FunctionEdgeTypeValue::BrokenReturn) { + FoundBrokenReturn = true; + BrokenReturnCount++; + } else { + NoReturnCount++; + } + } + + // Function is elected fake if there is one and only one broken return + FunctionTypeValue Type; + if (FoundReturn) { + Type = FunctionTypeValue::Regular; + } else if (FoundBrokenReturn && BrokenReturnCount == 1 + && NoReturnCount == 0) { + Type = FunctionTypeValue::Fake; + } else { + Type = FunctionTypeValue::NoReturn; + } + + // Retrieve the clobbered registers + std::erase_if(ClobberedRegs, + [&](const auto &E) { return CalleeSavedRegs.count(E) != 0; }); + + // Finalize CFG for the model + for (const auto &[CI, EdgeType] : IBIResult) { + auto PC = MetaAddress::fromConstant(CI->getArgOperand(2)); + model::BasicBlock &Block = CFG.at(PC); + Block.Successors.insert(makeEdge(MetaAddress::invalid(), EdgeType)); + } + + // Empty CFG if function is fake + if (Type == FunctionTypeValue::Fake) + CFG.clear(); + + return FunctionSummary(Type, + std::move(ClobberedRegs), + std::move(CFG), + WinFSO, + nullptr); +} + +template +void CFEPAnalyzer::integrateFunctionCallee(llvm::BasicBlock *BB, + MetaAddress Next) { + using namespace llvm; + + // If the basic block originally had a call-site, the function call is + // replaced with 1) hooks that delimit the space of the ABI analyses' + // traversals and 2) a summary of the registers clobbered by that function. + auto *Term = BB->getTerminator(); + auto *Call = getFunctionCall(Term); + + // What is the function type of the callee? + FunctionTypeValue Type = Oracle.getFunctionType(Next); + + switch (Type) { + case FunctionTypeValue::Regular: + case FunctionTypeValue::NoReturn: { + // Extract MetaAddress of JT of the call-site + auto CallSiteJT = GCBI->getJumpTarget(BB); + revng_assert(CallSiteJT.isValid()); + + // What are the registers clobbered by the callee? + const auto &ClobberedRegisters = Oracle.getRegistersClobbered(Next); + + // Different insert point depending on the callee type + IRBuilder<> Builder(M.getContext()); + if (Type == FunctionTypeValue::Regular) { + Builder.SetInsertPoint(Term); + } else { + auto *AbortCall = dyn_cast(Term->getPrevNode()); + revng_assert(AbortCall != nullptr + && AbortCall->getCalledFunction() == M.getFunction("abort")); + Builder.SetInsertPoint(AbortCall); + } + + // Mark end of basic block with a pre-hook call + StructType *MetaAddressTy = MetaAddress::getStruct(&M); + SmallVector Args = { CallSiteJT.toConstant(MetaAddressTy), + Next.toConstant(MetaAddressTy) }; + auto *Last = Builder.CreateCall(PreHookMarker.F, Args); + + // Prevent the store instructions from being optimized out by storing + // the rval of a call to an opaque function into the clobbered registers. + RegistersClobberedPool.addFnAttribute(Attribute::ReadOnly); + RegistersClobberedPool.addFnAttribute(Attribute::NoUnwind); + RegistersClobberedPool.addFnAttribute(Attribute::WillReturn); + + for (GlobalVariable *Register : ClobberedRegisters) { + auto *CSVTy = Register->getType()->getPointerElementType(); + auto Name = ("registers_clobbered_" + Twine(Register->getName())).str(); + auto *OpaqueRegistersClobberedCallee = RegistersClobberedPool + .get(Register->getName(), + CSVTy, + {}, + Name); + + Builder.CreateStore(Builder.CreateCall(OpaqueRegistersClobberedCallee), + Register); + } + + // Adjust back the stack pointer + switch (GCBI->arch()) { + case llvm::Triple::x86: + case llvm::Triple::x86_64: { + auto *SP = Builder.CreateLoad(GCBI->spReg()); + + const auto &FSO = Oracle.getElectedFSO(Next); + Value *Offset = GCBI->arch() == llvm::Triple::x86 ? + Builder.getInt32(*FSO) : + Builder.getInt64(*FSO); + auto *Inc = Builder.CreateAdd(SP, Offset); + Builder.CreateStore(Inc, GCBI->spReg()); + break; + } + default: { + break; + } + } + + // Mark end of basic block with a post-hook call + Builder.CreateCall(PostHookMarker.F, Args); + + BB->splitBasicBlock(Last->getPrevNode(), + BB->getName() + Twine("__summary")); + + // Erase the `function_call` marker unless fake + Call->eraseFromParent(); + break; + } + + case FunctionTypeValue::Fake: { + // Get the fake function by its entry basic block + Function *FakeFunction = Oracle.getFakeFunction(Next); + + // If fake, it must have been already analyzed + revng_assert(FakeFunction != nullptr); + + // If possible, inline the fake function + auto *CI = CallInst::Create(FakeFunction, "", Term); + InlineFunctionInfo IFI; + bool Status = InlineFunction(*CI, IFI, nullptr, true).isSuccess(); + revng_log(StackAnalysisLog, + "Has callee " << FakeFunction->getName() << "been inlined? " + << Status); + break; + } + + default: + revng_abort(); + } +} + +template +OutlinedFunction CFEPAnalyzer::outlineFunction(llvm::BasicBlock *Entry) { + using namespace llvm; + + Function *Root = Entry->getParent(); + + OutlinedFunction OutlinedFunction; + OnceQueue Queue; + std::vector BlocksToClone; + Queue.insert(Entry); + + // Collect list of blocks to clone + while (!Queue.empty()) { + BasicBlock *Current = Queue.pop(); + BlocksToClone.emplace_back(Current); + + if (isFunctionCall(Current)) { + auto *Successor = getFallthrough(Current); + MetaAddress PCCallee = MetaAddress::invalid(); + if (auto *Next = getFunctionCallCallee(Current)) + PCCallee = getBasicBlockPC(Next); + + if (Oracle.getFunctionType(PCCallee) != FunctionTypeValue::NoReturn) + Queue.insert(Successor); + } else { + for (auto *Successor : successors(Current)) { + if (!GCBI::isPartOfRootDispatcher(Successor)) + Queue.insert(Successor); + } + } + } + + // Create a copy of all the basic blocks to outline in `root` + ValueToValueMapTy VMap; + SmallVector BlocksToExtract; + + for (const auto &BB : BlocksToClone) { + BasicBlock *Cloned = CloneBasicBlock(BB, VMap, Twine("_cloned"), Root); + + VMap[BB] = Cloned; + BlocksToExtract.emplace_back(Cloned); + } + + auto AnyPCIt = VMap.find(GCBI->anyPC()); + if (AnyPCIt != VMap.end()) + OutlinedFunction.AnyPCCloned = cast(AnyPCIt->second); + + auto UnexpPCIt = VMap.find(GCBI->unexpectedPC()); + if (UnexpPCIt != VMap.end()) + OutlinedFunction.UnexpectedPCCloned = cast(UnexpPCIt->second); + + remapInstructionsInBlocks(BlocksToExtract, VMap); + + // Fix successor when encountering a call-site and fix fall-through in + // presence of a noreturn function. + std::map CallMap; + for (const auto &BB : BlocksToExtract) { + if (isFunctionCall(BB)) { + auto *Term = BB->getTerminator(); + CallInst *CI = getFunctionCall(Term); + + // If the function callee is null, we are dealing with an indirect call + MetaAddress PCCallee = MetaAddress::invalid(); + if (BasicBlock *Next = getFunctionCallCallee(Term)) + PCCallee = getBasicBlockPC(Next); + + auto CalleeType = Oracle.getFunctionType(PCCallee); + + if (CalleeType != FunctionTypeValue::NoReturn) { + auto *Br = BranchInst::Create(getFallthrough(Term)); + ReplaceInstWithInst(Term, Br); + } else if (CalleeType == FunctionTypeValue::NoReturn) { + auto *Abort = CallInst::Create(M.getFunction("abort")); + new UnreachableInst(Term->getContext(), BB); + ReplaceInstWithInst(Term, Abort); + } + + // To allow correct function extraction, there must not exist users of BBs + // to be extracted, so we destroy the blockaddress of the fall-through BB + // in the `function_call` marker. + unsigned ArgNo = 0; + PointerType *I8PtrTy = Type::getInt8PtrTy(M.getContext()); + Constant *I8NullPtr = ConstantPointerNull::get(I8PtrTy); + for (Value *Arg : CI->args()) { + if (isa(Arg)) { + CI->setArgOperand(ArgNo, I8NullPtr); + if (Arg->use_empty()) + cast(Arg)->destroyConstant(); + } + ++ArgNo; + } + + CallMap.insert({ CI, PCCallee }); + } + } + + // Extract outlined function + OutlinedFunction.F = CodeExtractor(BlocksToExtract).extractCodeRegion(CEAC); + + revng_assert(OutlinedFunction.F != nullptr); + revng_assert(OutlinedFunction.F->arg_size() == 0); + revng_assert(OutlinedFunction.F->getReturnType()->isVoidTy()); + revng_assert(OutlinedFunction.F->hasOneUser()); + + // Remove the only user (call to the outlined function) in `root` + auto It = OutlinedFunction.F->user_begin(); + cast(*It)->getParent()->eraseFromParent(); + + // Integrate function callee + for (auto &BB : *OutlinedFunction.F) { + if (isFunctionCall(&BB)) { + auto *Term = BB.getTerminator(); + MetaAddress CalleePC = CallMap.at(getFunctionCall(Term)); + integrateFunctionCallee(&BB, CalleePC); + } + } + + // TODO: fix `unexpectedpc` of the fake callee + for (auto &I : instructions(OutlinedFunction.F)) { + if (CallInst *Call = getCallTo(&I, UnexpectedPCMarker.F)) { + // TODO: can `unexpectedpc` not exist in the caller? + revng_assert(OutlinedFunction.UnexpectedPCCloned != nullptr); + + auto *Br = BranchInst::Create(OutlinedFunction.UnexpectedPCCloned); + ReplaceInstWithInst(I.getParent()->getTerminator(), Br); + Call->eraseFromParent(); + break; + } + } + + // Make sure `newpc` is still the first instruction when we have a jump target + // (if not, create a new dedicated basic block); that otherwise would break + // further assumptions when using `getBasicBlockPC` for model population. + BasicBlock *Split = nullptr; + Instruction *SplitPoint = nullptr; + for (auto &BB : *OutlinedFunction.F) { + if (Split == &BB) + continue; + + Split = nullptr; + for (auto &I : BB) { + if (CallInst *Call = getCallTo(&I, "newpc")) { + Value *IsJT = Call->getArgOperand(2); + if (BB.getFirstNonPHI() != Call && getLimitedValue(IsJT) == 1) { + + if (isCallTo(Call->getPrevNode(), "function_call")) + SplitPoint = Call->getPrevNode(); + else + SplitPoint = Call; + + Split = BB.splitBasicBlock(SplitPoint, BB.getName() + Twine("_jt")); + break; + } + } + } + } + + return OutlinedFunction; +} + bool StackAnalysis::runOnModule(Module &M) { Function &F = *M.getFunction("root"); @@ -435,22 +1704,24 @@ bool StackAnalysis::runOnModule(Module &M) { // candidates whose entry point is not included in any function of the first // set. - struct CFEP { - CFEP(BasicBlock *Entry, bool Force) : Entry(Entry), Force(Force) {} - - BasicBlock *Entry; - bool Force; - }; std::vector Functions; + model::Binary &Binary = *LMP.getWriteableModel(); - // Register all the Candidate Function Entry Points + // Register all the static symbols + for (const auto &F : Binary.Functions) + Functions.emplace_back(GCBI.getBlockAt(F.Entry), true); + + // Register all the other candidate entry points for (BasicBlock &BB : F) { - if (GCBI.getType(&BB) != BlockType::JumpTargetBlock) continue; + auto It = llvm::find_if(Functions, + [&BB](const auto &E) { return E.Entry == &BB; }); + if (It != Functions.end()) + continue; + uint32_t Reasons = GCBI.getJTReasons(&BB); - bool IsFunctionSymbol = hasReason(Reasons, JTReason::FunctionSymbol); bool IsCallee = hasReason(Reasons, JTReason::Callee); bool IsUnusedGlobalData = hasReason(Reasons, JTReason::UnusedGlobalData); bool IsMemoryStore = hasReason(Reasons, JTReason::MemoryStore); @@ -458,7 +1729,7 @@ bool StackAnalysis::runOnModule(Module &M) { bool IsReturnAddress = hasReason(Reasons, JTReason::ReturnAddress); bool IsLoadAddress = hasReason(Reasons, JTReason::LoadAddress); - if (IsFunctionSymbol or IsCallee) { + if (IsCallee) { // Called addresses are a strong hint Functions.emplace_back(&BB, true); } else if (not IsLoadAddress @@ -478,6 +1749,140 @@ bool StackAnalysis::runOnModule(Module &M) { getName(Function.Entry) << (Function.Force ? " (forced)" : "")); } + using BasicBlockToNodeMapTy = llvm::DenseMap; + BasicBlockToNodeMapTy BasicBlockNodeMap; + + // Queue to be populated with the CFEP + llvm::SmallVector Worklist; + SmallCallGraph CG; + + // Create an over-approximated call graph + for (CFEP &Function : Functions) { + BasicBlockNode Node{ Function.Entry }; + BasicBlockNode *GraphNode = CG.addNode(Node); + BasicBlockNodeMap[Function.Entry] = GraphNode; + } + + for (CFEP &Function : Functions) { + llvm::SmallSet Visited; + BasicBlockNode *StartNode = BasicBlockNodeMap[Function.Entry]; + revng_assert(StartNode != nullptr); + Worklist.emplace_back(Function.Entry); + + while (!Worklist.empty()) { + BasicBlock *Current = Worklist.pop_back_val(); + Visited.insert(Current); + + if (isFunctionCall(Current)) { + // If not an indirect call, add the node to the CG + if (BasicBlock *Callee = getFunctionCallCallee(Current)) { + auto *Node = BasicBlockNodeMap[Callee]; + StartNode->addSuccessor(Node); + } + BasicBlock *Next = getFallthrough(Current); + if (!Visited.count(Next)) + Worklist.push_back(Next); + } + + for (BasicBlock *Successor : successors(Current)) { + if (!GCBI::isPartOfRootDispatcher(Successor) + && !Visited.count(Successor)) + 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 FakeNode{ nullptr }; + BasicBlockNode *RootNode = CG.addNode(FakeNode); + CG.setEntryNode(RootNode); + + for (const auto &[_, Node] : BasicBlockNodeMap) + RootNode->addSuccessor(Node); + + UniquedQueue CFEPQueue; + for (auto *Node : llvm::post_order(&CG)) { + if (Node != RootNode) + CFEPQueue.insert(Node); + } + + // Dump the call-graph, if requested + std::unique_ptr OutputCG; + if (CallGraphOutputPath.getNumOccurrences() == 1) { + std::ifstream File(CallGraphOutputPath.c_str()); + if (File.is_open()) { + int Status = std::remove(CallGraphOutputPath.c_str()); + revng_assert(Status == 0); + } + + std::error_code EC; + OutputCG = std::make_unique(CallGraphOutputPath, + EC, + llvm::sys::fs::OF_Append); + revng_assert(!EC); + llvm::WriteGraph(*OutputCG, &CG); + } + + // Collect all the ABI registers, leave out the stack pointer for the moment. + // We will include it back later when refining ABI results. + std::vector ABIRegisters; + for (GlobalVariable *CSV : GCBI.abiRegisters()) + if (CSV != nullptr && !(GCBI.isSPReg(CSV))) + ABIRegisters.emplace_back(CSV); + + // Default-constructed cache summary for indirect calls + FunctionSummary DefaultSummary(model::FunctionType::Values::Regular, + { ABIRegisters.begin(), ABIRegisters.end() }, + {}, + GCBI.minimalFSO(), + nullptr); + FunctionAnalysisResults Properties(std::move(DefaultSummary)); + + // Instantiate a CFEPAnalyzer object + using CFEPA = CFEPAnalyzer; + CFEPA Analyzer(M, &GCBI, Properties, ABIRegisters); + + // Interprocedural analysis over the collected functions in post-order + // traversal (leafs first). + while (!CFEPQueue.empty()) { + BasicBlockNode *EntryNode = CFEPQueue.pop(); + revng_log(StackAnalysisLog, + "Analyzing Entry: " << EntryNode->BB->getName()); + + // Intraprocedural analysis + FunctionSummary AnalysisResult = Analyzer.analyze(EntryNode->BB); + bool Changed = Properties.registerFunction(getBasicBlockPC(EntryNode->BB), + std::move(AnalysisResult)); + + // If we got improved results for a function, we need to recompute its + // callers, and if a caller turns out to be fake, the callers of the fake + // function too. + if (Changed) { + UniquedQueue FakeFunctionWorklist; + FakeFunctionWorklist.insert(EntryNode); + + while (!FakeFunctionWorklist.empty()) { + BasicBlockNode *Node = FakeFunctionWorklist.pop(); + for (auto *Caller : Node->predecessors()) { + if (Caller == RootNode) + break; + + if (!Properties.isFakeFunction(getBasicBlockPC(Caller->BB))) + CFEPQueue.insert(Caller); + else + FakeFunctionWorklist.insert(Caller); + } + } + } + } + + // Still OK? + if (VerifyLog.isEnabled()) + revng_assert(llvm::verifyModule(M, &llvm::dbgs()) == false); + // Initialize the cache where all the results will be accumulated Cache TheCache(&F, &GCBI); @@ -558,7 +1963,7 @@ bool StackAnalysis::runOnModule(Module &M) { serialize(pathToStream(ABIAnalysisOutputPath, Output)); } - commitToModel(GCBI, &F, GrandResult, *LMP.getWriteableModel()); + commitToModel(GCBI, &F, GrandResult, Binary); return false; }