Files
revng-revng/lib/EarlyFunctionAnalysis/EarlyFunctionAnalysis.cpp
Antonio Frighetto 9980fc8c67 EFA: support dynamic function calls
EarlyFunctionAnalysis now can handle calls to
dynamically linked functions.
2022-04-05 15:37:57 +02:00

2112 lines
77 KiB
C++

/// \file EarlyFunctionAnalysis.cpp
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <climits>
#include <cstdio>
#include <fstream>
#include <map>
#include <sstream>
#include <vector>
#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/IR/GlobalVariable.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Value.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/ABI/FunctionType.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/EarlyFunctionAnalysis/EarlyFunctionAnalysis.h"
#include "revng/EarlyFunctionAnalysis/FunctionMetadata.h"
#include "revng/Model/Architecture.h"
#include "revng/Model/Binary.h"
#include "revng/Model/NamedTypedRegister.h"
#include "revng/Model/Register.h"
#include "revng/Model/TypedRegister.h"
#include "revng/Support/Assert.h"
#include "revng/Support/CommandLine.h"
#include "revng/Support/IRHelpers.h"
#include "revng/Support/MetaAddress.h"
#include "ABIAnalyses/ABIAnalysis.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;
using FunctionEdgeTypeValue = efa::FunctionEdgeType::Values;
using FunctionTypeValue = model::FunctionType::Values;
using GCBI = GeneratedCodeBasicInfo;
using namespace llvm::cl;
static Logger<> EarlyFunctionAnalysisLog("earlyfunctionanalysis");
struct BasicBlockNodeData {
BasicBlockNodeData(llvm::BasicBlock *BB) : BB(BB){};
llvm::BasicBlock *BB;
};
using BasicBlockNode = BidirectionalNode<BasicBlockNodeData>;
using SmallCallGraph = GenericGraph<BasicBlockNode>;
template<>
struct llvm::DOTGraphTraits<SmallCallGraph *>
: public llvm::DefaultDOTGraphTraits {
using EdgeIterator = llvm::GraphTraits<SmallCallGraph *>::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 EarlyFunctionAnalysis {
template<>
char EarlyFunctionAnalysis<true>::ID = 0;
template<>
char EarlyFunctionAnalysis<false>::ID = 0;
using ABIDetectionPass = RegisterPass<EarlyFunctionAnalysis<true>>;
static ABIDetectionPass X("detect-abi", "ABI Detection Pass", true, false);
using CFGCollectionPass = RegisterPass<EarlyFunctionAnalysis<false>>;
static CFGCollectionPass Y("collect-cfg", "CFG Collection Pass", true, true);
static opt<std::string> CallGraphOutputPath("cg-output",
desc("Dump to disk the recovered "
"call graph."),
value_desc("filename"));
static opt<std::string> IndirectBranchInfoSummaryPath("indirect-branch-info-"
"summary",
desc("Write the results "
"of SA2 on disk."),
value_desc("filename"));
static opt<std::string> AAWriterPath("aa-writer",
desc("Dump to disk the outlined functions "
"with annotated alias info."),
value_desc("filename"));
/// 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<llvm::GlobalVariable *> ClobberedRegisters;
ABIAnalyses::ABIAnalysesResults ABIResults;
SortedVector<efa::BasicBlock> CFG;
std::optional<int64_t> ElectedFSO;
llvm::Function *FakeFunction;
public:
FunctionSummary(model::FunctionType::Values Type,
std::set<llvm::GlobalVariable *> ClobberedRegisters,
ABIAnalyses::ABIAnalysesResults ABIResults,
SortedVector<efa::BasicBlock> CFG,
std::optional<int64_t> ElectedFSO,
llvm::Function *FakeFunction) :
Type(Type),
ClobberedRegisters(std::move(ClobberedRegisters)),
ABIResults(std::move(ABIResults)),
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<typename T>
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";
Output << " ABI info: \n";
ABIResults.dump();
}
};
/// 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<MetaAddress, FunctionSummary> FunctionsBucket;
/// A default summary for indirect function calls is maintained. It has
/// `Regular` type, it clobbers all the registers and it has default FSO.
FunctionSummary DefaultSummary;
public:
FunctionAnalysisResults(FunctionSummary DefaultSummary) :
DefaultSummary(std::move(DefaultSummary)) {}
FunctionSummary &insert(const MetaAddress &PC, FunctionSummary &&Summary) {
auto It = FunctionsBucket.find(PC);
revng_assert(It == FunctionsBucket.end());
return FunctionsBucket.emplace(PC, std::move(Summary)).first->second;
}
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<uint64_t> 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();
}
}
};
using BasicBlockQueue = UniquedQueue<BasicBlockNode *>;
/// 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.
class FunctionEntrypointAnalyzer {
private:
llvm::Module &M;
llvm::LLVMContext &Context;
GeneratedCodeBasicInfo *GCBI;
ArrayRef<GlobalVariable *> ABICSVs;
BasicBlockQueue *EntrypointsQueue;
FunctionAnalysisResults &Oracle;
const TupleTree<model::Binary> &Binary;
/// 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;
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.
TemporaryOpaqueFunction UnexpectedPCMarker;
std::unique_ptr<raw_fd_ostream> OutputIBI;
std::unique_ptr<raw_fd_ostream> OutputAAWriter;
OpaqueFunctionsPool<llvm::StringRef> RegistersClobberedPool;
OpaqueFunctionsPool<llvm::Type *> OpaqueBranchConditionsPool;
const llvm::CodeExtractorAnalysisCache CEAC;
const ProgramCounterHandler *PCH;
public:
FunctionEntrypointAnalyzer(llvm::Module &,
GeneratedCodeBasicInfo *GCBI,
ArrayRef<GlobalVariable *>,
BasicBlockQueue *,
FunctionAnalysisResults &,
const TupleTree<model::Binary> &);
public:
void importModel();
void runInterproceduralAnalysis();
void interproceduralPropagation();
void finalizeModel(TupleTree<model::Binary> &);
void recoverCFG();
void serializeFunctionMetadata();
private:
/// The `analyze` method is the entry point of the intraprocedural analysis,
/// and it is called on each function entrypoint until a fixed point is
/// reached. It is responsible for performing the whole computation.
FunctionSummary analyze(llvm::BasicBlock *BB, bool ShouldAnalyzeABI);
private:
OutlinedFunction outlineFunction(llvm::BasicBlock *BB);
void integrateFunctionCallee(llvm::BasicBlock *BB, MetaAddress);
UpcastablePointer<efa::FunctionEdgeBase>
handleFunctionCallForCFG(llvm::CallInst *);
SortedVector<efa::BasicBlock> collectDirectCFG(OutlinedFunction *F);
void initMarkersForABI(OutlinedFunction *F,
llvm::SmallVectorImpl<Instruction *> &,
llvm::IRBuilder<> &);
std::set<llvm::GlobalVariable *> findWrittenRegisters(llvm::Function *F);
void createIBIMarker(OutlinedFunction *F,
llvm::SmallVectorImpl<Instruction *> &,
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<efa::BasicBlock> &,
ABIAnalyses::ABIAnalysesResults &,
const std::set<llvm::GlobalVariable *> &,
bool ShouldAnalyzeABI);
llvm::Function *createFakeFunction(llvm::BasicBlock *BB);
UpcastablePointer<model::Type> buildPrototype(TupleTree<model::Binary> &,
const FunctionSummary &,
const efa::BasicBlock &);
FunctionSummary importPrototype(model::FunctionType::Values, model::TypePath);
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;
using FEA = FunctionEntrypointAnalyzer;
FEA::FunctionEntrypointAnalyzer(llvm::Module &M,
GeneratedCodeBasicInfo *GCBI,
ArrayRef<GlobalVariable *> ABICSVs,
BasicBlockQueue *EntrypointsQueue,
FunctionAnalysisResults &Oracle,
const TupleTree<model::Binary> &Binary) :
M(M),
Context(M.getContext()),
GCBI(GCBI),
ABICSVs(ABICSVs),
EntrypointsQueue(EntrypointsQueue),
Oracle(Oracle),
Binary(Binary),
// 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),
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<raw_fd_ostream>(IndirectBranchInfoSummaryPath,
EC,
llvm::sys::fs::OF_Append);
revng_assert(!EC);
*OutputIBI << "name,ra,fso,address";
for (const auto &Reg : ABICSVs)
*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<raw_fd_ostream>(AAWriterPath,
EC,
llvm::sys::fs::OF_Append);
revng_assert(!EC);
}
}
void FunctionEntrypointAnalyzer::serializeFunctionMetadata() {
using namespace llvm;
for (const auto &Function : Binary->Functions) {
if (Function.Type == FunctionTypeValue::Invalid
|| Function.Type == FunctionTypeValue::Fake)
continue;
auto &CFG = Oracle.at(Function.Entry).CFG;
BasicBlock *BB = GCBI->getBlockAt(Function.Entry);
std::string Buffer;
{
efa::FunctionMetadata FM(Function.Entry);
raw_string_ostream Stream(Buffer);
for (efa::BasicBlock Edge : CFG)
FM.ControlFlowGraph.insert(Edge);
FM.verify(*Binary, true);
serialize(Stream, FM);
}
Instruction *Term = BB->getTerminator();
MDNode *Node = MDNode::get(Context, MDString::get(Context, Buffer));
Term->setMetadata(FunctionMetadataMDName, Node);
}
}
FunctionSummary
FunctionEntrypointAnalyzer::importPrototype(model::FunctionType::Values Type,
model::TypePath Prototype) {
using namespace llvm;
using namespace model;
using Register = model::Register::Values;
using State = abi::RegisterState::Values;
FunctionSummary Summary(Type,
{ ABICSVs.begin(), ABICSVs.end() },
ABIAnalyses::ABIAnalysesResults(),
{},
0,
nullptr);
for (GlobalVariable *CSV : ABICSVs) {
Summary.ABIResults.ArgumentsRegisters[CSV] = State::No;
Summary.ABIResults.FinalReturnValuesRegisters[CSV] = State::No;
}
auto Layout = abi::FunctionType::Layout::make(Prototype);
for (const auto &ArgumentLayout : Layout.Arguments) {
for (Register ArgumentRegister : ArgumentLayout.Registers) {
StringRef Name = model::Register::getCSVName(ArgumentRegister);
if (GlobalVariable *CSV = M.getGlobalVariable(Name, true))
Summary.ABIResults.ArgumentsRegisters.at(CSV) = State::Yes;
}
}
for (Register ReturnValueRegister : Layout.ReturnValue.Registers) {
StringRef Name = model::Register::getCSVName(ReturnValueRegister);
if (GlobalVariable *CSV = M.getGlobalVariable(Name, true))
Summary.ABIResults.FinalReturnValuesRegisters.at(CSV) = State::Yes;
}
std::set<llvm::GlobalVariable *> PreservedRegisters;
for (Register CalleeSavedRegister : Layout.CalleeSavedRegisters) {
StringRef Name = model::Register::getCSVName(CalleeSavedRegister);
if (GlobalVariable *CSV = M.getGlobalVariable(Name, true))
PreservedRegisters.insert(CSV);
}
std::erase_if(Summary.ClobberedRegisters, [&](const auto &E) {
return PreservedRegisters.count(E) != 0;
});
Summary.ElectedFSO = Layout.FinalStackOffset;
return Summary;
}
void FunctionEntrypointAnalyzer::importModel() {
// Import existing functions from model
for (const model::Function &Function : Binary->Functions) {
if (Function.Type == model::FunctionType::Invalid)
continue;
Oracle.insert(Function.Entry,
importPrototype(Function.Type, Function.Prototype));
}
// Re-create fake functions, should they exist
for (const model::Function &Function : Binary->Functions) {
if (Function.Type != FunctionTypeValue::Fake)
continue;
auto &Summary = Oracle.at(Function.Entry);
revng_assert(Summary.Type == FunctionTypeValue::Fake);
Summary.FakeFunction = createFakeFunction(GCBI->getBlockAt(Function.Entry));
}
// Import dynamic functions from model. Dynamic functions will take the
// precedence over local functions.
for (BasicBlock &BB : *M.getFunction("root")) {
if (getType(&BB) != BlockType::JumpTargetBlock)
continue;
llvm::StringRef SymbolName = getDynamicSymbol(&BB);
if (SymbolName.empty())
continue;
auto [Entry, _] = getPC(&*BB.begin());
// TODO: should we assert that all dynamic function have been imported?
auto It = Binary->ImportedDynamicFunctions.find(SymbolName.str());
if (It == Binary->ImportedDynamicFunctions.end())
continue;
model::FunctionType::Values FunctionType;
if (It->Attributes.count(model::FunctionAttribute::NoReturn) != 0)
FunctionType = model::FunctionType::NoReturn;
else
FunctionType = model::FunctionType::Regular;
Oracle.insert(Entry,
importPrototype(FunctionType, getPrototype(*Binary, *It)));
}
}
UpcastablePointer<model::Type>
FEA::buildPrototype(TupleTree<model::Binary> &OutputBinary,
const FunctionSummary &Summary,
const efa::BasicBlock &Block) {
using namespace model;
using RegisterState = abi::RegisterState::Values;
auto NewType = makeType<RawFunctionType>();
auto &CallType = *llvm::cast<RawFunctionType>(NewType.get());
{
auto ArgumentsInserter = CallType.Arguments.batch_insert();
auto ReturnValuesInserter = CallType.ReturnValues.batch_insert();
bool Found = false;
for (const auto &[PC, CallSites] : Summary.ABIResults.CallSites) {
if (PC != Block.Start)
continue;
revng_assert(!Found);
Found = true;
for (const auto &[Arg, RV] :
zipmap_range(CallSites.ArgumentsRegisters,
CallSites.ReturnValuesRegisters)) {
auto *CSV = Arg == nullptr ? RV->first : Arg->first;
RegisterState RSArg = Arg == nullptr ? RegisterState::Maybe :
Arg->second;
RegisterState RSRV = RV == nullptr ? RegisterState::Maybe : RV->second;
auto RegisterID = model::Register::fromCSVName(CSV->getName(),
Binary->Architecture);
if (RegisterID == Register::Invalid || CSV == GCBI->spReg())
continue;
auto *CSVType = CSV->getType()->getPointerElementType();
auto CSVSize = CSVType->getIntegerBitWidth() / 8;
if (abi::RegisterState::shouldEmit(RSArg)) {
NamedTypedRegister TR(RegisterID);
TR.Type = { OutputBinary->getPrimitiveType(PrimitiveTypeKind::Generic,
CSVSize),
{} };
ArgumentsInserter.insert(TR);
}
if (abi::RegisterState::shouldEmit(RSRV)) {
TypedRegister TR(RegisterID);
TR.Type = { OutputBinary->getPrimitiveType(PrimitiveTypeKind::Generic,
CSVSize),
{} };
ReturnValuesInserter.insert(TR);
}
}
}
revng_assert(Found);
CallType.PreservedRegisters = {};
CallType.FinalStackOffset = 0;
}
return NewType;
}
/// Finish the population of the model by building the prototype
void FEA::finalizeModel(TupleTree<model::Binary> &OutputBinary) {
using namespace model;
using RegisterState = abi::RegisterState::Values;
// Fill up the model and build its prototype for each function
std::set<model::Function *> Functions;
for (model::Function &Function : OutputBinary->Functions) {
if (Function.Type != model::FunctionType::Invalid)
continue;
MetaAddress EntryPC = Function.Entry;
revng_assert(EntryPC.isValid());
auto &Summary = Oracle.at(EntryPC);
Function.Type = Summary.Type;
auto NewType = makeType<RawFunctionType>();
auto &FunctionType = *llvm::cast<RawFunctionType>(NewType.get());
{
auto ArgumentsInserter = FunctionType.Arguments.batch_insert();
auto ReturnValuesInserter = FunctionType.ReturnValues.batch_insert();
auto PreservedRegistersInserter = FunctionType.PreservedRegisters
.batch_insert();
// Argument and return values
for (const auto &[Arg, RV] :
zipmap_range(Summary.ABIResults.ArgumentsRegisters,
Summary.ABIResults.FinalReturnValuesRegisters)) {
auto *CSV = Arg == nullptr ? RV->first : Arg->first;
RegisterState RSArg = Arg == nullptr ? RegisterState::Maybe :
Arg->second;
RegisterState RSRV = RV == nullptr ? RegisterState::Maybe : RV->second;
auto RegisterID = model::Register::fromCSVName(CSV->getName(),
Binary->Architecture);
if (RegisterID == Register::Invalid || CSV == GCBI->spReg())
continue;
auto *CSVType = CSV->getType()->getPointerElementType();
auto CSVSize = CSVType->getIntegerBitWidth() / 8;
if (abi::RegisterState::shouldEmit(RSArg)) {
NamedTypedRegister TR(RegisterID);
TR.Type = { OutputBinary->getPrimitiveType(PrimitiveTypeKind::Generic,
CSVSize),
{} };
ArgumentsInserter.insert(TR);
}
if (abi::RegisterState::shouldEmit(RSRV)) {
TypedRegister TR(RegisterID);
TR.Type = { OutputBinary->getPrimitiveType(PrimitiveTypeKind::Generic,
CSVSize),
{} };
ReturnValuesInserter.insert(TR);
}
}
// Preserved registers
std::set<llvm::GlobalVariable *> PreservedRegisters(ABICSVs.begin(),
ABICSVs.end());
std::erase_if(PreservedRegisters, [&](const auto &E) {
auto End = Summary.ClobberedRegisters.end();
return Summary.ClobberedRegisters.find(E) != End;
});
for (auto *CSV : PreservedRegisters) {
auto RegisterID = model::Register::fromCSVName(CSV->getName(),
Binary->Architecture);
if (RegisterID == Register::Invalid)
continue;
PreservedRegistersInserter.insert(RegisterID);
}
// Final stack offset
FunctionType.FinalStackOffset = Summary.ElectedFSO.has_value() ?
*Summary.ElectedFSO :
0;
}
Function.Prototype = OutputBinary->recordNewType(std::move(NewType));
Functions.insert(&Function);
}
// Build prototype for indirect function calls
for (auto &Function : Functions) {
if (Function->Type == FunctionTypeValue::Fake)
continue;
auto &Summary = Oracle.at(Function->Entry);
for (auto &Block : Summary.CFG) {
for (auto &Edge : Block.Successors) {
if (efa::FunctionEdgeType::isCall(Edge->Type)) {
auto *CE = llvm::cast<efa::CallEdge>(Edge.get());
if (not CE->DynamicFunction.empty()) {
// It's a dynamic function call
revng_assert(CE->Type == efa::FunctionEdgeType::FunctionCall);
// The prototype must not exist among the ones of the call sites of
// the function, it is implicitly the one of the callee.
revng_assert(not Function->CallSitePrototypes.count(Block.Start));
} else if (CE->Destination.isValid()) {
// It's a simple direct function call
revng_assert(CE->Type == efa::FunctionEdgeType::FunctionCall);
// The prototype must not exist among the ones of the call sites of
// the function, it is implicitly the one of the callee.
revng_assert(not Function->CallSitePrototypes.count(Block.Start));
} else {
// It's an indirect call: forge a new prototype
auto Prototype = buildPrototype(OutputBinary, Summary, Block);
auto Path = OutputBinary->recordNewType(std::move(Prototype));
auto PrototypeInserter = Function->CallSitePrototypes
.batch_insert();
PrototypeInserter.insert({ Block.Start, Path });
}
}
}
}
efa::FunctionMetadata FM(Function->Entry, Summary.CFG);
FM.verify(*Binary, true);
}
revng_check(Binary->verify(true));
}
static void combineCrossCallSites(auto &CallSite, auto &Callee) {
using namespace ABIAnalyses;
using RegisterState = abi::RegisterState::Values;
for (auto &[FuncArg, CSArg] :
zipmap_range(Callee.ArgumentsRegisters, CallSite.ArgumentsRegisters)) {
auto *CSV = FuncArg == nullptr ? CSArg->first : FuncArg->first;
auto RSFArg = FuncArg == nullptr ? RegisterState::Maybe : FuncArg->second;
auto RSCSArg = CSArg == nullptr ? RegisterState::Maybe : CSArg->second;
Callee.ArgumentsRegisters[CSV] = combine(RSFArg, RSCSArg);
}
}
/// Perform cross-call site propagation
void FunctionEntrypointAnalyzer::interproceduralPropagation() {
for (const model::Function &Function : Binary->Functions) {
auto &Summary = Oracle.at(Function.Entry);
for (auto &[PC, CallSite] : Summary.ABIResults.CallSites) {
if (PC == Function.Entry)
combineCrossCallSites(CallSite, Summary.ABIResults);
}
}
}
/// 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<int64_t> 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<efa::FunctionEdgeBase>
makeEdge(MetaAddress Destination, efa::FunctionEdgeType::Values Type) {
efa::FunctionEdge *Result = nullptr;
using ReturnType = UpcastablePointer<efa::FunctionEdgeBase>;
if (efa::FunctionEdgeType::isCall(Type))
return ReturnType::make<efa::CallEdge>(Destination, Type);
else
return ReturnType::make<efa::FunctionEdge>(Destination, Type);
};
static MetaAddress getFinalAddressOfBasicBlock(llvm::BasicBlock *BB) {
auto [End, Size] = getPC(BB->getTerminator());
return End + Size;
}
UpcastablePointer<efa::FunctionEdgeBase>
FunctionEntrypointAnalyzer::handleFunctionCallForCFG(llvm::CallInst *Call) {
using namespace llvm;
auto *CalleePC = Call->getArgOperand(1);
MetaAddress AddressPC = MetaAddress::invalid();
auto It = Binary->Functions.end();
StringRef SymbolName;
// Direct or indirect call?
if (isa<ConstantStruct>(CalleePC)) {
AddressPC = MetaAddress::fromConstant(CalleePC);
It = Binary->Functions.find(AddressPC);
BasicBlock *OriginalEntry = GCBI->getBlockAt(AddressPC);
SymbolName = getDynamicSymbol(OriginalEntry);
}
MetaAddress Destination;
efa::FunctionEdgeType::Values Type;
const auto IDF = Binary->ImportedDynamicFunctions;
bool IsDynamicCall = (not SymbolName.empty()
and IDF.count(SymbolName.str()) != 0);
if (It != Binary->Functions.end()) {
// The function exist within the model
Destination = AddressPC;
Type = efa::FunctionEdgeType::FunctionCall;
} else if (IsDynamicCall) {
// The function exist within the imported dynamic functions
Destination = MetaAddress::invalid();
Type = efa::FunctionEdgeType::FunctionCall;
} else {
// The function is an indirect call, we will forge a new prototype later in
// `finalizeModel`.
Destination = MetaAddress::invalid();
Type = efa::FunctionEdgeType::IndirectCall;
}
auto Edge = makeEdge(Destination, Type);
auto DestTy = Oracle.getFunctionType(Destination);
if (DestTy == FunctionTypeValue::NoReturn) {
auto *CE = cast<efa::CallEdge>(Edge.get());
CE->Attributes.insert(model::FunctionAttribute::NoReturn);
}
if (IsDynamicCall) {
auto *CE = cast<efa::CallEdge>(Edge.get());
CE->DynamicFunction = SymbolName.str();
}
return Edge;
}
SortedVector<efa::BasicBlock>
FunctionEntrypointAnalyzer::collectDirectCFG(OutlinedFunction *F) {
using namespace llvm;
SortedVector<efa::BasicBlock> CFG;
for (BasicBlock &BB : *F->F) {
if (GCBI::isJumpTarget(&BB)) {
MetaAddress Start = getBasicBlockPC(&BB);
efa::BasicBlock Block{ Start };
Block.End = getFinalAddressOfBasicBlock(&BB);
OnceQueue<BasicBlock *> Queue;
Queue.insert(&BB);
// A JT with no successors?
if (isa<UnreachableInst>(BB.getTerminator())) {
auto Type = efa::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,
efa::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.
auto Edge = makeEdge(MetaAddress::invalid(),
efa::FunctionEdgeType::LongJmp);
Block.Successors.insert(Edge);
} else {
Instruction *I = &(*Succ->begin());
if (isa<ReturnInst>(I)) {
// Did we meet the end of the cloned function? Do nothing
revng_assert(Succ->getInstList().size() == 1);
} else if (auto *Call = getCallTo(I, PreHookMarker.F)) {
// Handle edge for direct function calls, dynamic function calls
// as well as indirect ones.
auto Edge = handleFunctionCallForCFG(Call);
Block.Successors.insert(Edge);
} else if (auto *Call = getCallTo(I, "function_call")) {
// Handle edge for fake function calls. The marker `function_call`
// has been left to signal the presence of an edge of type
// FakeFunctionCall (still used in IsolateFunction). It can be
// safely erased now.
Call->eraseFromParent();
auto Destination = getBasicBlockPC(Succ);
auto Edge = makeEdge(Destination,
efa::FunctionEdgeType::FakeFunctionCall);
Block.Successors.insert(Edge);
} else {
// Not one of the cases above? Enqueue the successor basic block.
Queue.insert(Succ);
}
}
}
}
CFG.insert(Block);
}
}
return CFG;
}
void FEA::initMarkersForABI(OutlinedFunction *OutlinedFunction,
SmallVectorImpl<Instruction *> &SV,
llvm::IRBuilder<> &IRB) {
using namespace llvm;
StructType *MetaAddressTy = MetaAddress::getStruct(&M);
SmallVector<Instruction *, 4> 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<BranchInst>(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);
}
}
std::set<llvm::GlobalVariable *>
FunctionEntrypointAnalyzer::findWrittenRegisters(llvm::Function *F) {
using namespace llvm;
std::set<GlobalVariable *> 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;
}
void FEA::createIBIMarker(OutlinedFunction *OutlinedFunction,
SmallVectorImpl<Instruction *> &SV,
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 *SPI = IRB.CreateLoad(GCBI->spReg());
auto *SPPtr = IRB.CreateIntToPtr(SPI, IntPtrTy);
Value *RA = IRB.CreateLoad(GCBI->raReg() ? GCBI->raReg() : SPPtr);
auto ToLLVMArchitecture = model::Architecture::toLLVMArchitecture;
auto LLVMArchitecture = ToLLVMArchitecture(Binary->Architecture);
std::array<Value *, 4> DissectedPC = PCH->dissectJumpablePC(IRB,
RA,
LLVMArchitecture);
auto *PCI = MetaAddress::composeIntegerPC(IRB,
DissectedPC[0],
DissectedPC[1],
DissectedPC[2],
DissectedPC[3]);
SmallVector<Value *, 16> CSVI;
Type *IsRetTy = Type::getInt128Ty(Context);
SmallVector<Type *, 16> ArgTypes = { IsRetTy, IntTy, MetaAddressTy };
for (auto *CSR : ABICSVs) {
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);
// 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 : SV) {
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<Value *, 16> CSVE;
for (auto *CSR : ABICSVs) {
auto *V = IRB.CreateLoad(CSR);
CSVE.emplace_back(V);
}
auto *SPE = IRB.CreateLoad(GCBI->spReg());
// 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<Value *, 16> 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();
}
}
void FEA::opaqueBranchConditions(llvm::Function *F, llvm::IRBuilder<> &IRB) {
using namespace llvm;
for (auto &BB : *F) {
auto *Term = BB.getTerminator();
if ((isa<BranchInst>(Term) && cast<BranchInst>(Term)->isConditional())
|| isa<SwitchInst>(Term)) {
Value *Condition = isa<BranchInst>(Term) ?
cast<BranchInst>(Term)->getCondition() :
cast<SwitchInst>(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<BranchInst>(Term))
cast<BranchInst>(Term)->setCondition(RetVal);
else
cast<SwitchInst>(Term)->setCondition(RetVal);
}
}
}
void FEA::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<typename T>
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<llvm::cl::Option *> &Options;
static constexpr const auto &Opt = getOption<T>;
};
void FunctionEntrypointAnalyzer::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<unsigned>;
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<BasicAA>();
AA.registerFunctionAnalysis<ScopedNoAliasAA>();
return AA;
});
FAM.registerPass([this] {
using LMA = LoadModelAnalysis;
return LMA::fromModelWrapper(Binary);
});
FAM.registerPass([&] { return GeneratedCodeBasicInfoAnalysis(); });
FAM.registerPass([&] { return ModuleAnalysisManagerFunctionProxy(MAM); });
PassBuilder PB;
PB.registerFunctionAnalyses(FAM);
PB.registerModuleAnalyses(MAM);
FPM.run(*F, FAM);
}
}
llvm::Function *
FunctionEntrypointAnalyzer::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();
}
void FunctionEntrypointAnalyzer::recoverCFG() {
for (const auto &Function : Binary->Functions) {
// No CFG will be recovered for `Fake` or `Invalid` functions
if (Function.Type == FunctionTypeValue::Invalid
|| Function.Type == FunctionTypeValue::Fake)
continue;
auto *Entry = GCBI->getBlockAt(Function.Entry);
// Recover the control-flow graph of the function
auto &Summary = Oracle.at(Function.Entry);
Summary.CFG = std::move(analyze(Entry, false).CFG);
}
}
FunctionSummary
FunctionEntrypointAnalyzer::analyze(BasicBlock *Entry, bool ShouldAnalyzeABI) {
using namespace llvm;
using namespace ABIAnalyses;
IRBuilder<> Builder(M.getContext());
ABIAnalysesResults ABIResults;
SmallVector<Instruction *, 4> BranchesForIBI;
// Detect function boundaries
struct OutlinedFunction OutlinedFunction = outlineFunction(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);
if (ShouldAnalyzeABI) {
// Run ABI-independent data-flow analyses
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
// 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, BranchesForIBI, 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,
ABIResults,
WrittenRegisters,
ShouldAnalyzeABI);
// 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;
}
static void
suppressCSAndSPRegisters(ABIAnalyses::ABIAnalysesResults &ABIResults,
const std::set<GlobalVariable *> &CalleeSavedRegs) {
using RegisterState = abi::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<GlobalVariable *>
intersect(const std::set<GlobalVariable *> &First,
const std::set<GlobalVariable *> &Last) {
std::set<GlobalVariable *> Output;
std::set_intersection(First.begin(),
First.end(),
Last.begin(),
Last.end(),
std::inserter(Output, Output.begin()));
return Output;
}
FunctionSummary
FEA::milkInfo(OutlinedFunction *OutlinedFunction,
SortedVector<efa::BasicBlock> &CFG,
ABIAnalyses::ABIAnalysesResults &ABIResults,
const std::set<GlobalVariable *> &WrittenRegisters,
bool ShouldAnalyzeABI) {
using namespace llvm;
SmallVector<std::pair<CallBase *, int64_t>, 4> MaybeReturns;
SmallVector<std::pair<CallBase *, int64_t>, 4> NotReturns;
SmallVector<std::pair<CallBase *, FunctionEdgeTypeValue>, 4> IBIResult;
std::set<GlobalVariable *> CalleeSavedRegs;
std::set<GlobalVariable *> ClobberedRegs(ABICSVs.begin(), ABICSVs.end());
for (CallBase *CI : callers(OutlinedFunction->IndirectBranchInfoMarker)) {
if (CI->getParent()->getParent() == OutlinedFunction->F) {
bool JumpsToReturnAddress = false;
auto MayJumpToReturnAddress = dyn_cast<ConstantInt>(CI->getArgOperand(0));
if (MayJumpToReturnAddress)
JumpsToReturnAddress = MayJumpToReturnAddress->getSExtValue() == 0;
auto *StackPointerOffset = dyn_cast<ConstantInt>(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<ConstantInt>(CI->getArgOperand(Idx));
if (Register && Register->getZExtValue() == 0)
CalleeSavedRegs.insert(ABICSVs[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<ConstantInt>(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<ConstantInt>(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));
efa::BasicBlock &Block = CFG.at(PC);
Block.Successors.insert(makeEdge(MetaAddress::invalid(), EdgeType));
}
// Empty CFG if function is fake
if (Type == FunctionTypeValue::Fake)
CFG.clear();
if (ShouldAnalyzeABI) {
// 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(EarlyFunctionAnalysisLog);
}
return FunctionSummary(Type,
std::move(ClobberedRegs),
std::move(ABIResults),
std::move(CFG),
WinFSO,
nullptr);
}
void FunctionEntrypointAnalyzer::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<CallInst>(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<Value *, 2> 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
if (auto MaybeFSO = Oracle.getElectedFSO(Next); MaybeFSO) {
GlobalVariable *SPCSV = GCBI->spReg();
auto *StackPointer = Builder.CreateLoad(SPCSV);
Value *Offset = ConstantInt::get(StackPointer->getPointerOperandType()
->getPointerElementType(),
*MaybeFSO);
auto *AdjustedStackPointer = Builder.CreateAdd(StackPointer, Offset);
Builder.CreateStore(AdjustedStackPointer, SPCSV);
}
// 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(EarlyFunctionAnalysisLog,
"Has callee " << FakeFunction->getName() << "been inlined? "
<< Status);
break;
}
default:
revng_abort();
}
}
OutlinedFunction
FunctionEntrypointAnalyzer::outlineFunction(llvm::BasicBlock *Entry) {
using namespace llvm;
Function *Root = Entry->getParent();
OutlinedFunction OutlinedFunction;
OnceQueue<BasicBlock *> Queue;
std::vector<BasicBlock *> 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 (not isPartOfRootDispatcher(Successor))
Queue.insert(Successor);
}
}
}
// Create a copy of all the basic blocks to outline in `root`
ValueToValueMapTy VMap;
SmallVector<BasicBlock *, 8> BlocksToExtract;
auto *AnyPCBB = GCBI->anyPC();
auto *UnexpectedPCBB = GCBI->unexpectedPC();
for (const auto &BB : BlocksToClone) {
BasicBlock *Cloned = CloneBasicBlock(BB, VMap, Twine("_cloned"), Root);
VMap[BB] = Cloned;
BlocksToExtract.emplace_back(Cloned);
}
auto AnyPCIt = VMap.find(AnyPCBB);
if (AnyPCIt != VMap.end())
OutlinedFunction.AnyPCCloned = cast<BasicBlock>(AnyPCIt->second);
auto UnexpPCIt = VMap.find(UnexpectedPCBB);
if (UnexpPCIt != VMap.end())
OutlinedFunction.UnexpectedPCCloned = cast<BasicBlock>(UnexpPCIt->second);
remapInstructionsInBlocks(BlocksToExtract, VMap);
// Fix successor when encountering a call-site and fix fall-through in
// presence of a noreturn function.
std::map<llvm::CallInst *, MetaAddress> 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<BlockAddress>(Arg)) {
CI->setArgOperand(ArgNo, I8NullPtr);
if (Arg->use_empty())
cast<BlockAddress>(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<Instruction>(*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;
}
void FunctionEntrypointAnalyzer::runInterproceduralAnalysis() {
while (!EntrypointsQueue->empty()) {
BasicBlockNode *EntryNode = EntrypointsQueue->pop();
revng_log(EarlyFunctionAnalysisLog,
"Analyzing Entry: " << EntryNode->BB->getName());
// Intraprocedural analysis
FunctionSummary AnalysisResult = analyze(EntryNode->BB, true);
bool Changed = Oracle.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) {
BasicBlockQueue FakeFunctionWorklist;
FakeFunctionWorklist.insert(EntryNode);
while (!FakeFunctionWorklist.empty()) {
BasicBlockNode *Node = FakeFunctionWorklist.pop();
for (auto *Caller : Node->predecessors()) {
// Root node?
if (Caller->BB == nullptr)
break;
if (!Oracle.isFakeFunction(getBasicBlockPC(Caller->BB)))
EntrypointsQueue->insert(Caller);
else
FakeFunctionWorklist.insert(Caller);
}
}
}
}
}
template<bool ShouldAnalyzeABI>
bool EarlyFunctionAnalysis<ShouldAnalyzeABI>::runOnModule(Module &M) {
revng_log(PassesLog, "Starting EarlyFunctionAnalysis");
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
auto &LMP = getAnalysis<LoadModelWrapperPass>().get();
TupleTree<model::Binary> &Binary = LMP.getWriteableModel();
using BasicBlockToNodeMap = llvm::DenseMap<BasicBlock *, BasicBlockNode *>;
BasicBlockToNodeMap BasicBlockNodeMap;
// Temporary worklist to collect the function entrypoints
llvm::SmallVector<BasicBlock *, 8> Worklist;
SmallCallGraph CG;
// Create an over-approximated call graph
for (const auto &Function : Binary->Functions) {
auto *Entry = GCBI.getBlockAt(Function.Entry);
BasicBlockNode Node{ Entry };
BasicBlockNode *GraphNode = CG.addNode(Node);
BasicBlockNodeMap[Entry] = GraphNode;
}
for (const auto &Function : Binary->Functions) {
llvm::SmallSet<BasicBlock *, 8> Visited;
auto *Entry = GCBI.getBlockAt(Function.Entry);
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 (isFunctionCall(Current)) {
// 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())
StartNode->addSuccessor(It->second);
}
BasicBlock *Next = getFallthrough(Current);
if (!Visited.count(Next))
Worklist.push_back(Next);
}
for (BasicBlock *Successor : successors(Current)) {
if (not 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);
// Create an over-approximated call graph of the program. A queue of all the
// function entrypoints is maintained.
BasicBlockQueue EntrypointsQueue;
for (auto *Node : llvm::post_order(&CG)) {
if (Node == RootNode)
continue;
// The intraprocedural analysis will be scheduled only for those functions
// which have `Invalid` as type.
auto &Function = Binary->Functions.at(getBasicBlockPC(Node->BB));
if (Function.Type == model::FunctionType::Invalid)
EntrypointsQueue.insert(Node);
}
// Dump the call-graph, if requested
std::unique_ptr<raw_fd_ostream> 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<raw_fd_ostream>(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<llvm::GlobalVariable *> ABICSVs;
for (GlobalVariable *CSV : GCBI.abiRegisters())
if (CSV != nullptr && !(GCBI.isSPReg(CSV)))
ABICSVs.emplace_back(CSV);
// Default-constructed cache summary for indirect calls
unsigned MinimalFSO;
{
using namespace model::Architecture;
MinimalFSO = getMinimalFinalStackOffset(Binary->Architecture);
}
FunctionSummary DefaultSummary(model::FunctionType::Values::Regular,
{ ABICSVs.begin(), ABICSVs.end() },
ABIAnalyses::ABIAnalysesResults(),
{},
MinimalFSO,
nullptr);
using FAR = FunctionAnalysisResults;
FAR Properties(std::move(DefaultSummary));
// Instantiate a FunctionEntrypointAnalyzer object
FEA Analyzer(M, &GCBI, ABICSVs, &EntrypointsQueue, Properties, Binary);
// Prepopulate the cache with existing functions and dynamic functions from
// model, and recreate fake functions
Analyzer.importModel();
// EarlyFunctionAnalysis can be invoked with two option: via `--detect-abi` in
// order to schedule a full ABI analysis for each function entry-point
// detected, or via `--collect-cfg`, for control-flow graph recovery. In doing
// so, the following property is obtained: `--detect-abi` writes the model but
// not the IR, whereas `--collect-cfg` writes the IR but not the model.
if (!ShouldAnalyzeABI) {
// Recover the control-flow graph
Analyzer.recoverCFG();
// Serialize function metadata, CFG included, to IR
Analyzer.serializeFunctionMetadata();
} else {
// Interprocedural analysis over the collected functions in post-order
// traversal (leafs first).
Analyzer.runInterproceduralAnalysis();
// Propagate results between call-sites and functions
Analyzer.interproceduralPropagation();
// Commit the results onto the model. A non-const model is taken as argument
// to be written.
Analyzer.finalizeModel(Binary);
}
// Still OK?
if (VerifyLog.isEnabled())
revng_assert(llvm::verifyModule(M, &llvm::dbgs()) == false);
return false;
}
} // namespace EarlyFunctionAnalysis