#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include #include "llvm/ADT/SmallVector.h" #include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h" #include "revng/Support/Debug.h" #include "revng/Support/IRHelpers.h" #include "revng/Support/MonotoneFramework.h" #include "ABIIR.h" #include "Cache.h" #include "Element.h" #include "FunctionABI.h" #include "IntraproceduralFunctionSummary.h" template inline bool compareOptional(llvm::Optional LHS, llvm::Optional RHS) { return LHS.hasValue() == RHS.hasValue() && (!LHS.hasValue() || *LHS == *RHS); } namespace StackAnalysis { class Cache; /// \brief Copy of \p I, i.e., a container of classes with no copy constructor, /// but having a .copy() method template inline T copyContainer(const T &I) { T Result; Result.reserve(I.size()); for (auto &V : I) Result.push_back(V.copy()); return Result; } namespace Intraprocedural { /// \brief Result of the transfer function /// /// This class represents the result of the transfer function, it might simply /// represent the result of the transfer functions starting from the initial /// state or more sophisticated situations, e.g., function calls that have to be /// handled by the intraprocedural part of the analysis. class Interrupt { public: using vector = llvm::SmallVector; using iterator = typename vector::iterator; using const_iterator = typename vector::const_iterator; using iterator_range = typename llvm::iterator_range; using const_iterator_range = typename llvm::iterator_range; private: bool ResultExtracted; BranchType::Values Type; Element Result; vector RelatedBasicBlocks; IntraproceduralFunctionSummary Summary; private: Interrupt() : ResultExtracted(false), Type(BranchType::Invalid), Result(Element::bottom()), Summary(IntraproceduralFunctionSummary::bottom()) {} Interrupt(BranchType::Values Type, IntraproceduralFunctionSummary Summary) : ResultExtracted(false), Type(Type), Result(Element::bottom()), Summary(std::move(Summary)) {} Interrupt(Element Result, BranchType::Values Type, vector Successors = {}) : ResultExtracted(false), Type(Type), Result(std::move(Result)), RelatedBasicBlocks(Successors), Summary(IntraproceduralFunctionSummary::bottom()) {} Interrupt(BranchType::Values Type, vector Successors) : ResultExtracted(false), Type(Type), Result(Element::bottom()), RelatedBasicBlocks(Successors), Summary(IntraproceduralFunctionSummary::bottom()) {} public: static Interrupt createInvalid() { return Interrupt(); }; static Interrupt createWithSuccessor(Element Result, BranchType::Values Type, llvm::BasicBlock *Successor) { revng_assert(Type == BranchType::FakeFunctionCall || Type == BranchType::HandledCall || Type == BranchType::IndirectCall || Type == BranchType::FakeFunctionReturn); return Interrupt(std::move(Result), Type, { Successor }); } static Interrupt createWithSuccessors(Element Result, BranchType::Values Type, vector Successors) { revng_assert(Type == BranchType::InstructionLocalCFG || Type == BranchType::FunctionLocalCFG); revng_assert(Successors.size() > 0); return Interrupt(std::move(Result), Type, Successors); } static Interrupt create(Element Result, BranchType::Values Type) { using namespace BranchType; revng_assert(Type == Return || Type == IndirectTailCall || Type == FakeFunction || Type == LongJmp || Type == Killer || Type == Unreachable || Type == NoReturnFunction); return Interrupt(std::move(Result), Type); } static Interrupt createUnhandledCall(llvm::BasicBlock *Callee) { return Interrupt(BranchType::UnhandledCall, { Callee }); } static Interrupt createSummary(IntraproceduralFunctionSummary Summary) { switch (Summary.Type) { case FunctionType::Regular: return Interrupt(BranchType::RegularFunction, std::move(Summary)); case FunctionType::NoReturn: return Interrupt(BranchType::NoReturnFunction, std::move(Summary)); case FunctionType::Fake: return Interrupt(BranchType::FakeFunction, std::move(Summary)); default: revng_abort(); } return Interrupt(BranchType::RegularFunction, std::move(Summary)); } public: /// \brief True if this result has successors bool hasSuccessors() const { switch (Type) { case BranchType::InstructionLocalCFG: case BranchType::FunctionLocalCFG: case BranchType::FakeFunctionCall: case BranchType::FakeFunctionReturn: case BranchType::HandledCall: case BranchType::IndirectCall: return true; case BranchType::UnhandledCall: case BranchType::Return: case BranchType::BrokenReturn: case BranchType::IndirectTailCall: case BranchType::FakeFunction: case BranchType::LongJmp: case BranchType::Killer: case BranchType::Unreachable: return false; case BranchType::Invalid: case BranchType::RegularFunction: case BranchType::NoReturnFunction: revng_abort(); } revng_abort(); } BranchType::Values type() const { return Type; } bool isPartOfFinalResults() const { // We bypass MonotoneFramework's collection of final results, since we're // already collecting them in `Analysis::Returns` and we need to // post-process them return false; } bool requiresInterproceduralHandling() const { switch (Type) { case BranchType::FakeFunction: case BranchType::UnhandledCall: case BranchType::RegularFunction: case BranchType::NoReturnFunction: return true; case BranchType::InstructionLocalCFG: case BranchType::FunctionLocalCFG: case BranchType::FakeFunctionCall: case BranchType::FakeFunctionReturn: case BranchType::HandledCall: case BranchType::IndirectCall: case BranchType::Return: case BranchType::BrokenReturn: case BranchType::IndirectTailCall: case BranchType::LongJmp: case BranchType::Killer: case BranchType::Unreachable: return false; case BranchType::Invalid: revng_abort(); } revng_abort(); } Element &&extractResult() { revng_assert(Type != BranchType::RegularFunction and Type != BranchType::UnhandledCall); revng_assert(not ResultExtracted); ResultExtracted = true; return std::move(Result); } llvm::BasicBlock *getCallee() const { revng_assert(Type == BranchType::UnhandledCall); revng_assert(RelatedBasicBlocks.size() == 1); return RelatedBasicBlocks[0]; } const IntraproceduralFunctionSummary &getFunctionSummary() { // TODO: is it OK for fake functions to have summaries? revng_assert(Type == BranchType::RegularFunction || Type == BranchType::FakeFunction || Type == BranchType::NoReturnFunction); return Summary; } const_iterator begin() { return RelatedBasicBlocks.begin(); } const_iterator end() { return RelatedBasicBlocks.end(); } size_t size() const { revng_assert(hasSuccessors()); return RelatedBasicBlocks.size(); } void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } template void dump(const llvm::Module *M, T &Output) const { Output << "Interrupt reason: " << BranchType::getName(Type) << "\n"; switch (Type) { case BranchType::InstructionLocalCFG: case BranchType::FunctionLocalCFG: case BranchType::FakeFunctionCall: case BranchType::FakeFunctionReturn: case BranchType::HandledCall: case BranchType::IndirectCall: Output << "Successors:"; for (llvm::BasicBlock *BB : RelatedBasicBlocks) Output << " " << getName(BB); Output << "\n"; Output << "Result:\n"; Result.dump(M); break; case BranchType::UnhandledCall: Output << "Unhandled call to " << getName(RelatedBasicBlocks[0]) << "\n"; break; case BranchType::RegularFunction: Output << "Summary:\n"; Summary.dump(M); break; case BranchType::Return: case BranchType::BrokenReturn: case BranchType::NoReturnFunction: case BranchType::IndirectTailCall: case BranchType::FakeFunction: case BranchType::LongJmp: case BranchType::Killer: Output << "Result:\n"; Result.dump(M); break; case BranchType::Invalid: case BranchType::Unreachable: revng_abort(); } } }; /// \brief Intraprocedural part of the stack analysis class Analysis : public MonotoneFramework { private: // Label: llvm::BasicBlock * // LatticeElement: Element // Interrupt: Interrupt // D (derived class): Analysis // SuccessorsRange: Interrupt::const_iterator_range // Visit: BreadthFirst // DynamicGraph: true using Base = MonotoneFramework; private: llvm::BasicBlock *Entry; ///< The entry point of the current function const llvm::Module *M; const Cache *TheCache; ///< Reference to the Cache (for query purposes) ASSlot ReturnAddressSlot; ///< Slot that contains the return address GeneratedCodeBasicInfo *GCBI; Element InitialState; ///< Empty Element with stack pointer initialized int32_t SPIndex; ///< Offset of the stack pointer CSV int32_t PCIndex; ///< Offset of the PC CSV ABIFunction TheABIIR; ///< The ABI IR /// \brief Set of return addresses from fake function calls std::set FakeReturnAddresses; /// \brief Branches list and classification std::map BranchesType; std::map VariableContent; ///< Content of allocas /// This flag is set if the last time we interrupted the analysis was due to /// an unhandled function call, which should then result in a cache hit bool CacheMustHit; /// \brief Set of functions currently being analyzed, for recursion detection /// purposes const std::set &InProgressFunctions; /// \brief Record all call sites and the associated stack size std::map> FrameSizeAtCallSite; /// \brief Called functions that have been found incoherent with the caller std::set IncoherentFunctions; std::map ReturnCandidates; std::multimap FakeReturns; public: Analysis(llvm::BasicBlock *Entry, const Cache &TheCache, GeneratedCodeBasicInfo *GCBI, const std::set &InProgressFunctions) : Base(Entry), Entry(Entry), M(getModule(Entry)), TheCache(&TheCache), ReturnAddressSlot(ASSlot::invalid()), GCBI(GCBI), InitialState(Element::bottom()), TheABIIR(Entry), InProgressFunctions(InProgressFunctions) { registerExtremal(Entry); initialize(); } bool isCSV(ASSlot Slot) const { return (Slot.addressSpace() == ASID::cpuID() and TheCache->isCSVIndex(Slot.offset())); } void assertLowerThanOrEqual(const Element &A, const Element &B) const { ::StackAnalysis::assertLowerThanOrEqual(A, B, getModule(Entry)); } llvm::Optional handleEdge(const Element &Original, llvm::BasicBlock *Source, llvm::BasicBlock *Destination) const { return llvm::Optional(); } llvm::BasicBlock *entry() const { return Entry; } void resetCacheMustHit() { CacheMustHit = false; } bool cacheMustHit() const { return CacheMustHit; } /// \brief Reset the analysis with a new intial state void initialize(); /// \brief Return the stack size of \p Result, if available llvm::Optional stackSize(Element &Result) const { Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex); ASID StackID = ASID::stackID(); // Save the value of the stack pointer for later Value OldStackPointer = Result.load(StackPointer); llvm::Optional CallerStackSize; if (const ASSlot *OldStackPointerSlot = OldStackPointer.directContent()) { if (OldStackPointerSlot->addressSpace() != StackID) return llvm::Optional(); return -OldStackPointerSlot->offset(); } else { return llvm::Optional(); } } /// \brief Register the stack size at call site \p TheFunctionCall /// /// \return false if the stack size is different from the one that was /// previously recorded, if any. bool registerStackSizeAtCallSite(FunctionCall TheFunctionCall, llvm::Optional StackSize) { auto It = FrameSizeAtCallSite.find(TheFunctionCall); if (It != FrameSizeAtCallSite.end()) { if (not compareOptional(It->second, StackSize)) { It->second = llvm::Optional(); return false; } } else { FrameSizeAtCallSite[TheFunctionCall] = StackSize; } return true; } /// \brief If available, return the registered size of the call site /// \p Location llvm::Optional frameSizeAt(FunctionCall Location) const { auto It = FrameSizeAtCallSite.find(Location); revng_assert(It != FrameSizeAtCallSite.end(), "Location has never been registered"); return It->second; } /// \brief The almighty transfer function Interrupt transfer(llvm::BasicBlock *BB); /// \brief The extremal value, i.e., the context of the analysis Element extremalValue(llvm::BasicBlock *) const { return InitialState.copy(); } void dumpFinalState() const { if (SaLog.isEnabled()) { SaLog << "FinalResult:\n"; FinalResult.dump(getModule(Entry), SaLog); SaLog << DoLog; } } Interrupt::const_iterator_range successors(llvm::BasicBlock *, Interrupt &I) const { return llvm::make_range(I.begin(), I.end()); } size_t successor_size(llvm::BasicBlock *, Interrupt &I) const { if (I.hasSuccessors()) return I.size(); else return 0; } Interrupt createSummaryInterrupt() { revng_abort(); } Interrupt createNoReturnInterrupt() { return Interrupt::createSummary(createSummary()); } /// \brief Return the set of functions called by this function in an /// incoherent way /// /// This function returns the set of basic blocks representing functions /// called by the current function for which the information obtained about a /// call site isn't compatible with the information obtained by analysing the /// callee. const std::set &incoherentFunctions() const { return IncoherentFunctions; } private: std::pair finalize(); /// \brief Creates a summary for the current analysis ready to be wrapped in /// an Interrupt IntraproceduralFunctionSummary createSummary(); /// \brief Check whether the ABI analysis results for a slot of the function /// and a call site are compatible bool isCoherent(const FunctionABI &CallerSummary, const FunctionABI &CalleeSummary, FunctionCall TheFunctionCall, IntraproceduralFunctionSummary::LocalSlot Slot) const; /// \brief Populate IncoherentFunctions void findIncoherentFunctions(const IntraproceduralFunctionSummary &ABISummary); /// \brief Part of the transfer function handling terminator instructions Interrupt handleTerminator(llvm::Instruction *T, Element &Result, ABIIRBasicBlock &ABIBB); /// \brief Part of the transfer function handling function calls Interrupt handleCall(llvm::Instruction *Caller, llvm::BasicBlock *Callee, MetaAddress ReturnAddress, llvm::BasicBlock *ReturnFromCall, Element &Result, ABIIRBasicBlock &ABIBB); /// \return true if at least a branch is an indirect tail call bool hasIndirectTailCall() const { for (auto &P : BranchesType) if (P.second == BranchType::IndirectTailCall) return true; return false; } ASSlot slotFromCSV(llvm::User *U) const; }; } // namespace Intraprocedural } // namespace StackAnalysis