mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
1273 lines
41 KiB
C++
1273 lines
41 KiB
C++
/// \file StackAnalysis.cpp
|
|
/// \brief Implementation of the stack analysis, which provides information
|
|
/// about function boundaries, basic block types, arguments and return
|
|
/// values.
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <fstream>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "llvm/ADT/DepthFirstIterator.h"
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/Analysis/ScopedNoAliasAA.h"
|
|
#include "llvm/CodeGen/UnreachableBlockElim.h"
|
|
#include "llvm/IR/Constants.h"
|
|
#include "llvm/IR/Dominators.h"
|
|
#include "llvm/IR/Function.h"
|
|
#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/IR/Instructions.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/ADT/Queue.h"
|
|
#include "revng/BasicAnalyses/CSVAliasAnalysis.h"
|
|
#include "revng/BasicAnalyses/PromoteGlobalToLocalVars.h"
|
|
#include "revng/BasicAnalyses/RemoveHelperCalls.h"
|
|
#include "revng/BasicAnalyses/RemoveNewPCCalls.h"
|
|
#include "revng/Dump/IndirectBranchInfoPrinterPass.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 "Cache.h"
|
|
#include "InterproceduralAnalysis.h"
|
|
#include "Intraprocedural.h"
|
|
|
|
using llvm::BasicBlock;
|
|
using llvm::Function;
|
|
using llvm::Module;
|
|
using llvm::RegisterPass;
|
|
|
|
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");
|
|
|
|
namespace StackAnalysis {
|
|
|
|
const std::set<llvm::GlobalVariable *> EmptyCSVSet;
|
|
|
|
char StackAnalysis::ID = 0;
|
|
|
|
using RegisterABI = RegisterPass<StackAnalysis>;
|
|
static RegisterABI Y("abi-analysis", "ABI Analysis Pass", true, true);
|
|
|
|
static opt<std::string> ABIAnalysisOutputPath("abi-analysis-output",
|
|
desc("Destination path for the "
|
|
"ABI Analysis Pass"),
|
|
value_desc("path"),
|
|
cat(MainCategory));
|
|
|
|
template<bool FunctionCall>
|
|
static model::RegisterState::Values
|
|
toRegisterState(RegisterArgument<FunctionCall> RA) {
|
|
switch (RA.value()) {
|
|
case RegisterArgument<FunctionCall>::NoOrDead:
|
|
return model::RegisterState::NoOrDead;
|
|
case RegisterArgument<FunctionCall>::Maybe:
|
|
return model::RegisterState::Maybe;
|
|
case RegisterArgument<FunctionCall>::Yes:
|
|
return model::RegisterState::Yes;
|
|
case RegisterArgument<FunctionCall>::Dead:
|
|
return model::RegisterState::Dead;
|
|
case RegisterArgument<FunctionCall>::Contradiction:
|
|
return model::RegisterState::Contradiction;
|
|
case RegisterArgument<FunctionCall>::No:
|
|
return model::RegisterState::No;
|
|
}
|
|
|
|
revng_abort();
|
|
}
|
|
|
|
static model::RegisterState::Values toRegisterState(FunctionReturnValue RV) {
|
|
switch (RV.value()) {
|
|
case FunctionReturnValue::No:
|
|
return model::RegisterState::No;
|
|
case FunctionReturnValue::NoOrDead:
|
|
return model::RegisterState::NoOrDead;
|
|
case FunctionReturnValue::YesOrDead:
|
|
return model::RegisterState::YesOrDead;
|
|
case FunctionReturnValue::Maybe:
|
|
return model::RegisterState::Maybe;
|
|
case FunctionReturnValue::Contradiction:
|
|
return model::RegisterState::Contradiction;
|
|
}
|
|
|
|
revng_abort();
|
|
}
|
|
|
|
static model::RegisterState::Values
|
|
toRegisterState(FunctionCallReturnValue RV) {
|
|
switch (RV.value()) {
|
|
case FunctionCallReturnValue::No:
|
|
return model::RegisterState::No;
|
|
case FunctionCallReturnValue::NoOrDead:
|
|
return model::RegisterState::NoOrDead;
|
|
case FunctionCallReturnValue::YesOrDead:
|
|
return model::RegisterState::YesOrDead;
|
|
case FunctionCallReturnValue::Yes:
|
|
return model::RegisterState::Yes;
|
|
case FunctionCallReturnValue::Dead:
|
|
return model::RegisterState::Dead;
|
|
case FunctionCallReturnValue::Maybe:
|
|
return model::RegisterState::Maybe;
|
|
case FunctionCallReturnValue::Contradiction:
|
|
return model::RegisterState::Contradiction;
|
|
}
|
|
|
|
revng_abort();
|
|
}
|
|
|
|
void commitToModel(GeneratedCodeBasicInfo &GCBI,
|
|
Function *F,
|
|
const FunctionsSummary &Summary,
|
|
model::Binary &TheBinary);
|
|
|
|
void commitToModel(GeneratedCodeBasicInfo &GCBI,
|
|
Function *F,
|
|
const FunctionsSummary &Summary,
|
|
model::Binary &TheBinary) {
|
|
using namespace model;
|
|
|
|
for (const auto &[Entry, FunctionSummary] : Summary.Functions) {
|
|
if (Entry == nullptr)
|
|
continue;
|
|
|
|
//
|
|
// Initialize model::Function
|
|
//
|
|
|
|
// Get the entry point address
|
|
MetaAddress EntryPC = getBasicBlockPC(Entry);
|
|
revng_assert(EntryPC.isValid());
|
|
|
|
// Create the function
|
|
revng_assert(TheBinary.Functions.count(EntryPC) == 0);
|
|
model::Function &Function = TheBinary.Functions[EntryPC];
|
|
|
|
// Assign a name
|
|
Function.Name = Entry->getName();
|
|
revng_assert(Function.Name.size() != 0);
|
|
|
|
using FT = model::FunctionType::Values;
|
|
Function.Type = static_cast<FT>(FunctionSummary.Type);
|
|
|
|
if (Function.Type == model::FunctionType::Fake)
|
|
continue;
|
|
|
|
// Populate arguments and return values
|
|
{
|
|
auto Inserter = Function.Registers.batch_insert();
|
|
for (auto &[CSV, FRD] : FunctionSummary.RegisterSlots) {
|
|
auto ID = ABIRegister::fromCSVName(CSV->getName(), GCBI.arch());
|
|
if (ID == model::Register::Invalid)
|
|
continue;
|
|
FunctionABIRegister TheRegister(ID);
|
|
TheRegister.Argument = toRegisterState(FRD.Argument);
|
|
TheRegister.ReturnValue = toRegisterState(FRD.ReturnValue);
|
|
Inserter.insert(TheRegister);
|
|
}
|
|
}
|
|
|
|
auto MakeEdge = [](MetaAddress Destination, FunctionEdgeType::Values Type) {
|
|
FunctionEdge *Result = nullptr;
|
|
if (FunctionEdgeType::isCall(Type))
|
|
Result = new CallEdge(Destination, Type);
|
|
else
|
|
Result = new FunctionEdge(Destination, Type);
|
|
return UpcastablePointer<FunctionEdge>(Result);
|
|
};
|
|
|
|
// Handle the situation in which we found no basic blocks at all
|
|
if (Function.Type == model::FunctionType::NoReturn
|
|
and FunctionSummary.BasicBlocks.size() == 0) {
|
|
auto &EntryNodeSuccessors = Function.CFG[EntryPC].Successors;
|
|
auto Edge = MakeEdge(MetaAddress::invalid(), FunctionEdgeType::LongJmp);
|
|
EntryNodeSuccessors.insert(Edge);
|
|
}
|
|
|
|
for (auto &[BB, Branch] : FunctionSummary.BasicBlocks) {
|
|
// Remap BranchType to FunctionEdgeType
|
|
namespace FET = FunctionEdgeType;
|
|
FET::Values EdgeType = FET::Invalid;
|
|
|
|
switch (Branch) {
|
|
case BranchType::Invalid:
|
|
case BranchType::FakeFunction:
|
|
case BranchType::RegularFunction:
|
|
case BranchType::NoReturnFunction:
|
|
case BranchType::UnhandledCall:
|
|
revng_abort();
|
|
break;
|
|
|
|
case BranchType::InstructionLocalCFG:
|
|
EdgeType = FET::Invalid;
|
|
break;
|
|
|
|
case BranchType::FunctionLocalCFG:
|
|
EdgeType = FET::DirectBranch;
|
|
break;
|
|
|
|
case BranchType::FakeFunctionCall:
|
|
EdgeType = FET::FakeFunctionCall;
|
|
break;
|
|
|
|
case BranchType::FakeFunctionReturn:
|
|
EdgeType = FET::FakeFunctionReturn;
|
|
break;
|
|
|
|
case BranchType::HandledCall:
|
|
EdgeType = FET::FunctionCall;
|
|
break;
|
|
|
|
case BranchType::IndirectCall:
|
|
EdgeType = FET::IndirectCall;
|
|
break;
|
|
|
|
case BranchType::Return:
|
|
EdgeType = FET::Return;
|
|
break;
|
|
|
|
case BranchType::BrokenReturn:
|
|
EdgeType = FET::BrokenReturn;
|
|
break;
|
|
|
|
case BranchType::IndirectTailCall:
|
|
EdgeType = FET::IndirectTailCall;
|
|
break;
|
|
|
|
case BranchType::LongJmp:
|
|
EdgeType = FET::LongJmp;
|
|
break;
|
|
|
|
case BranchType::Killer:
|
|
EdgeType = FET::Killer;
|
|
break;
|
|
|
|
case BranchType::Unreachable:
|
|
EdgeType = FET::Unreachable;
|
|
break;
|
|
}
|
|
|
|
if (EdgeType == FET::Invalid)
|
|
continue;
|
|
|
|
bool IsCall = FunctionEdgeType::isCall(EdgeType);
|
|
|
|
// Identify Source address
|
|
auto [Source, Size] = getPC(BB->getTerminator());
|
|
Source += Size;
|
|
revng_assert(Source.isValid());
|
|
|
|
// Identify Destination address
|
|
llvm::BasicBlock *JumpTargetBB = GCBI.getJumpTargetBlock(BB);
|
|
MetaAddress JumpTargetAddress = GCBI.getPCFromNewPC(JumpTargetBB);
|
|
model::BasicBlock &CurrentBlock = Function.CFG[JumpTargetAddress];
|
|
CurrentBlock.End = Source;
|
|
CurrentBlock.Name = JumpTargetBB->getName();
|
|
auto SuccessorsInserter = CurrentBlock.Successors.batch_insert();
|
|
|
|
if (EdgeType == FET::DirectBranch) {
|
|
// Handle direct branch
|
|
auto Successors = GCBI.getSuccessors(BB);
|
|
for (const MetaAddress &Destination : Successors.Addresses)
|
|
SuccessorsInserter.insert(MakeEdge(Destination, EdgeType));
|
|
|
|
} else if (EdgeType == FET::FakeFunctionReturn) {
|
|
// Handle fake function return
|
|
auto [First, Last] = FunctionSummary.FakeReturns.equal_range(BB);
|
|
revng_assert(First != Last);
|
|
for (const auto &[_, Destination] : make_range(First, Last))
|
|
SuccessorsInserter.insert(MakeEdge(Destination, EdgeType));
|
|
|
|
} else if (IsCall) {
|
|
// Handle call
|
|
llvm::BasicBlock *Successor = BB->getSingleSuccessor();
|
|
MetaAddress Destination = MetaAddress::invalid();
|
|
if (Successor != nullptr)
|
|
Destination = getBasicBlockPC(Successor);
|
|
|
|
// Record the edge in the CFG
|
|
auto TempEdge = MakeEdge(Destination, EdgeType);
|
|
const auto &Result = SuccessorsInserter.insert(TempEdge);
|
|
auto *Edge = llvm::cast<CallEdge>(Result.get());
|
|
|
|
bool Found = false;
|
|
for (const FunctionsSummary::CallSiteDescription &CSD :
|
|
FunctionSummary.CallSites) {
|
|
if (not CSD.Call->isTerminator() or CSD.Call->getParent() != BB)
|
|
continue;
|
|
|
|
revng_assert(not Found);
|
|
Found = true;
|
|
auto Inserter = Edge->Registers.batch_insert();
|
|
for (auto &[CSV, FCRD] : CSD.RegisterSlots) {
|
|
auto ID = ABIRegister::fromCSVName(CSV->getName(), GCBI.arch());
|
|
if (ID == model::Register::Invalid)
|
|
continue;
|
|
FunctionABIRegister TheRegister(ID);
|
|
TheRegister.Argument = toRegisterState(FCRD.Argument);
|
|
TheRegister.ReturnValue = toRegisterState(FCRD.ReturnValue);
|
|
Inserter.insert(TheRegister);
|
|
}
|
|
}
|
|
revng_assert(Found);
|
|
|
|
} else {
|
|
// Handle other successors
|
|
llvm::BasicBlock *Successor = BB->getSingleSuccessor();
|
|
MetaAddress Destination = MetaAddress::invalid();
|
|
if (Successor != nullptr)
|
|
Destination = getBasicBlockPC(Successor);
|
|
|
|
// Record the edge in the CFG
|
|
SuccessorsInserter.insert(MakeEdge(Destination, EdgeType));
|
|
}
|
|
}
|
|
}
|
|
|
|
revng_check(TheBinary.verify());
|
|
}
|
|
|
|
static std::optional<uint64_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;
|
|
}
|
|
|
|
template<class FunctionOracle>
|
|
FuncSummary CFEPAnalyzer<FunctionOracle>::analyze(
|
|
const std::vector<llvm::GlobalVariable *> &ABIRegs,
|
|
llvm::BasicBlock *Entry) {
|
|
using namespace llvm;
|
|
|
|
Value *RA = nullptr;
|
|
const auto *PCH = GCBI->programCounterHandler();
|
|
Function *OutFunc = createDisposableFunction(Entry);
|
|
|
|
// Identify the callee-saved registers and tell if a function is jumping
|
|
// to the return address. To achieve this, we craft the IR by loading the PC,
|
|
// the SP, as well as the ABI registers CSVs at function entry and end.
|
|
IRBuilder<> Builder(&OutFunc->getEntryBlock().front());
|
|
auto *IntPtrTy = GCBI->spReg()->getType();
|
|
auto *IntTy = GCBI->spReg()->getType()->getElementType();
|
|
|
|
auto *SP = Builder.CreateLoad(GCBI->spReg());
|
|
auto *SPPtr = Builder.CreateIntToPtr(SP, IntPtrTy);
|
|
auto *GEPI = Builder.CreateGEP(IntTy, SPPtr, ConstantInt::get(IntTy, 0));
|
|
|
|
switch (GCBI->arch()) {
|
|
case Triple::arm: {
|
|
constexpr auto *LinkRegister = "r14";
|
|
RA = Builder.CreateLoad(M.getGlobalVariable(LinkRegister, true));
|
|
break;
|
|
}
|
|
|
|
case Triple::aarch64: {
|
|
constexpr auto *LinkRegister = "lr";
|
|
RA = Builder.CreateLoad(M.getGlobalVariable(LinkRegister, true));
|
|
break;
|
|
};
|
|
|
|
case Triple::mips:
|
|
case Triple::mipsel: {
|
|
constexpr auto *ReturnRegister = "ra";
|
|
RA = Builder.CreateLoad(M.getGlobalVariable(ReturnRegister, true));
|
|
break;
|
|
}
|
|
|
|
case Triple::systemz: {
|
|
constexpr auto *ReturnRegister = "r14";
|
|
RA = Builder.CreateLoad(M.getGlobalVariable(ReturnRegister, true));
|
|
break;
|
|
}
|
|
|
|
case Triple::x86:
|
|
case Triple::x86_64: {
|
|
RA = Builder.CreateLoad(SPPtr);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
revng_abort("Unsupported architecture");
|
|
}
|
|
|
|
std::array<Value *, 4> DissectedPC = PCH->dissectJumpablePC(Builder,
|
|
RA,
|
|
GCBI->arch());
|
|
|
|
auto *PCI = MetaAddress::composeIntegerPC(Builder,
|
|
DissectedPC[0],
|
|
DissectedPC[1],
|
|
DissectedPC[2],
|
|
DissectedPC[3]);
|
|
|
|
SmallVector<Value *, 16> CSVI;
|
|
Type *IsRetTy = Type::getInt128Ty(Context);
|
|
SmallVector<Type *, 16> ArgTypes = { IsRetTy, IntTy, IntTy };
|
|
for (auto *CSR : ABIRegs) {
|
|
auto *V = Builder.CreateLoad(CSR);
|
|
CSVI.emplace_back(V);
|
|
ArgTypes.emplace_back(IntTy);
|
|
}
|
|
|
|
IndirectBranchInfoOpqF = Function::Create(llvm::FunctionType::get(IntTy,
|
|
ArgTypes,
|
|
false),
|
|
GlobalValue::ExternalLinkage,
|
|
"indirect_branch_info",
|
|
M);
|
|
IndirectBranchInfoOpqF->addFnAttr(Attribute::NoUnwind);
|
|
IndirectBranchInfoOpqF->addFnAttr(Attribute::NoReturn);
|
|
|
|
Instruction *Last = nullptr;
|
|
SmallVector<Instruction *, 4> Returns;
|
|
for (auto &BB : *OutFunc)
|
|
if (auto *Ret = dyn_cast<ReturnInst>(&BB.back()))
|
|
Last = Ret;
|
|
|
|
if (Last) {
|
|
for (auto *Pred : predecessors(Last->getParent())) {
|
|
auto *Term = Pred->getTerminator();
|
|
Builder.SetInsertPoint(Term);
|
|
|
|
auto *PCE = PCH->composeIntegerPC(Builder);
|
|
|
|
SmallVector<Value *, 16> CSVE;
|
|
for (auto *CSR : ABIRegs) {
|
|
auto *V = Builder.CreateLoad(CSR);
|
|
CSVE.emplace_back(V);
|
|
}
|
|
|
|
SP = Builder.CreateLoad(GCBI->spReg());
|
|
SPPtr = Builder.CreateIntToPtr(SP, IntPtrTy);
|
|
auto *GEPE = Builder.CreateGEP(IntTy, SPPtr, ConstantInt::get(IntTy, 0));
|
|
|
|
auto *SPI = Builder.CreatePtrToInt(GEPI, IntTy);
|
|
auto *SPE = Builder.CreatePtrToInt(GEPE, IntTy);
|
|
|
|
// Compute the difference between the return address and the program
|
|
// counter.
|
|
auto *IsRet = Builder.CreateSub(PCE, PCI);
|
|
|
|
// Compute the difference between the stack pointer values to evaluate the
|
|
// stack height, and the difference between the CSR values as well.
|
|
// Functions leaving the stack pointer higher than it was at function
|
|
// entry (i.e., in an irregular state) are marked as fake functions.
|
|
auto *SPDiff = Builder.CreateSub(SPE, SPI);
|
|
|
|
// Save the MetaAddress of the last call to newpc before the ret
|
|
// instruction.
|
|
auto *NewPCCall = reverseNewPCTraversal(Term);
|
|
revng_assert(NewPCCall != nullptr);
|
|
auto NewPCStruct = GCBI::getPCFromNewPC(NewPCCall);
|
|
auto *PC = ConstantInt::get(IntTy, NewPCStruct.address());
|
|
|
|
SmallVector<Value *, 16> ArgValues = { IsRet, SPDiff, PC };
|
|
for (unsigned I = 0; I < CSVI.size(); ++I) {
|
|
auto *V = Builder.CreateSub(CSVE[I], CSVI[I]);
|
|
ArgValues.emplace_back(V);
|
|
}
|
|
|
|
// Is jumping to the return address? What is the stack height?
|
|
// What are the callee-saved registers? Where do we come from?
|
|
auto *Call = Builder.CreateCall(IndirectBranchInfoOpqF, ArgValues);
|
|
Builder.CreateUnreachable();
|
|
|
|
Returns.emplace_back(Term);
|
|
}
|
|
}
|
|
|
|
for (auto *I : Returns)
|
|
I->eraseFromParent();
|
|
|
|
// Store the values of the 4 PCs in order to have them constant-folded away.
|
|
for (auto &BB : *OutFunc) {
|
|
for (auto &I : BB) {
|
|
if (auto *Call = getCallTo(&I, "newpc")) {
|
|
MetaAddress NewPC = GCBI::getPCFromNewPC(Call);
|
|
Builder.SetInsertPoint(Call);
|
|
PCH->setPC(Builder, NewPC);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run optimization passes over the disposable function
|
|
{
|
|
FunctionPassManager FPM;
|
|
|
|
// First stage: simplify the IR, 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(false));
|
|
FPM.addPass(EarlyCSEPass(true));
|
|
FPM.addPass(SimplifyCFGPass());
|
|
FPM.addPass(MergedLoadStoreMotionPass());
|
|
FPM.addPass(GVN());
|
|
|
|
// Second stage: add alias analysis info, perform the GEP
|
|
// transformation and promote CSVs to local variables.
|
|
// Since the IR may change remarkably, another round of passes
|
|
// is necessary to take more optimization opportunities.
|
|
FPM.addPass(CSVAliasAnalysisPass<true>());
|
|
FPM.addPass(InstCombinePass(false));
|
|
FPM.addPass(EarlyCSEPass(true));
|
|
FPM.addPass(InstCombinePass(false));
|
|
|
|
// Third stage: serialize the results on disk.
|
|
FPM.addPass(IndirectBranchInfoPrinterPass());
|
|
|
|
FunctionAnalysisManager FAM;
|
|
FAM.registerPass([] {
|
|
AAManager AA;
|
|
AA.registerFunctionAnalysis<BasicAA>();
|
|
AA.registerFunctionAnalysis<ScopedNoAliasAA>();
|
|
|
|
return AA;
|
|
});
|
|
|
|
ModuleAnalysisManager MAM;
|
|
MAM.registerPass([&] { return GeneratedCodeBasicInfoAnalysis(); });
|
|
FAM.registerPass([&] { return GeneratedCodeBasicInfoAnalysis(); });
|
|
FAM.registerPass([&] { return ModuleAnalysisManagerFunctionProxy(MAM); });
|
|
|
|
PassBuilder PB;
|
|
PB.registerFunctionAnalyses(FAM);
|
|
PB.registerModuleAnalyses(MAM);
|
|
|
|
FPM.run(*OutFunc, FAM);
|
|
}
|
|
|
|
FuncSummary FunctionInfo = milkResults(ABIRegs, OutFunc);
|
|
|
|
// Is the disposable function a FakeFunction? If so, return a
|
|
// copy of the unoptimized disposable function.
|
|
if (FunctionInfo.Type == FunctionTypeValue::Fake)
|
|
FunctionInfo.FakeFunction = createDisposableFunction(Entry);
|
|
|
|
// Discard it, no longer needed.
|
|
// TODO: fix bug. throwing the function, yet returning a pointer to dead callinst.
|
|
throwDisposableFunction(OutFunc);
|
|
return FunctionInfo;
|
|
}
|
|
|
|
template<class FunctionOracle>
|
|
FuncSummary CFEPAnalyzer<FunctionOracle>::milkResults(
|
|
const std::vector<llvm::GlobalVariable *> &ABIRegs,
|
|
llvm::Function *F) {
|
|
using namespace llvm;
|
|
|
|
SmallVector<std::pair<CallInst *, uint64_t>, 4> MaybeReturns;
|
|
SmallSet<std::pair<CallInst *, FunctionEdgeTypeValue>, 4> Result;
|
|
std::set<GlobalVariable *> CalleeSavedRegs;
|
|
std::set<GlobalVariable *> ClobberedRegs(ABIRegs.begin(), ABIRegs.end());
|
|
|
|
for (CallBase *Call : callers(IndirectBranchInfoOpqF)) {
|
|
auto *CI = cast<CallInst>(Call);
|
|
if (CI->getParent()->getParent() == F) {
|
|
if (isa<ConstantInt>(CI->getArgOperand(0))) {
|
|
if (isa<ConstantInt>(CI->getArgOperand(1))) {
|
|
int64_t FSO = cast<ConstantInt>(CI->getArgOperand(1))->getSExtValue();
|
|
if (FSO >= 0)
|
|
MaybeReturns.emplace_back(CI, FSO);
|
|
else
|
|
Result.insert({ CI, FunctionEdgeTypeValue::BrokenReturn });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Elect a final stack offset.
|
|
const auto WinFSO = electFSO(MaybeReturns);
|
|
|
|
// Did we find a valid return instruction?
|
|
for (const auto &E : MaybeReturns) {
|
|
auto *CI = E.first;
|
|
if (E.second == *WinFSO) {
|
|
Result.insert({ E.first, FunctionEdgeTypeValue::Return });
|
|
unsigned NumArgs = CI->getNumArgOperands();
|
|
if (NumArgs > 3) {
|
|
for (unsigned Idx = 3; Idx < NumArgs; ++Idx) {
|
|
if (isa<ConstantInt>(CI->getArgOperand(Idx))) {
|
|
if (cast<ConstantInt>(CI->getArgOperand(Idx))->getZExtValue() == 0)
|
|
CalleeSavedRegs.insert(ABIRegs[Idx - 3]);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Result.insert({ E.first, FunctionEdgeTypeValue::BrokenReturn });
|
|
}
|
|
}
|
|
|
|
// Neither a return nor a broken return?
|
|
for (CallBase *Call : callers(IndirectBranchInfoOpqF)) {
|
|
auto *CI = cast<CallInst>(Call);
|
|
if (CI->getParent()->getParent() == F) {
|
|
if (isa<ConstantInt>(CI->getArgOperand(0)))
|
|
continue;
|
|
|
|
if (WinFSO.has_value() && (isa<ConstantInt>(CI->getArgOperand(1)))
|
|
&& (cast<ConstantInt>(CI->getArgOperand(1))->getZExtValue()
|
|
== *WinFSO))
|
|
Result.insert({ CI, FunctionEdgeTypeValue::IndirectTailCall });
|
|
else
|
|
Result.insert({ CI, FunctionEdgeTypeValue::LongJmp });
|
|
}
|
|
}
|
|
|
|
bool FoundRet, FoundBrokenRet;
|
|
FoundRet = FoundBrokenRet = false;
|
|
for (const auto &E : Result) {
|
|
if (E.second == FunctionEdgeTypeValue::Return)
|
|
FoundRet = true;
|
|
if (E.second == FunctionEdgeTypeValue::BrokenReturn)
|
|
FoundBrokenRet = true;
|
|
}
|
|
|
|
FunctionTypeValue Type;
|
|
if (FoundRet) {
|
|
Type = FunctionTypeValue::Regular;
|
|
} else if (FoundBrokenRet) {
|
|
Type = FunctionTypeValue::Fake;
|
|
} else {
|
|
Type = FunctionTypeValue::NoReturn;
|
|
}
|
|
|
|
// Finally retrieve the clobbered registers and return them.
|
|
std::erase_if(ClobberedRegs, [&](const auto &E) {
|
|
return CalleeSavedRegs.find(E) != CalleeSavedRegs.end();
|
|
});
|
|
|
|
return FuncSummary(Type, ClobberedRegs, Result, WinFSO);
|
|
}
|
|
|
|
template<class FunctionOracle>
|
|
llvm::BasicBlock *
|
|
CFEPAnalyzer<FunctionOracle>::integrateFunctionCallee(llvm::BasicBlock *BB) {
|
|
using namespace llvm;
|
|
|
|
// In case the basic block has a call-site, the function call is
|
|
// replaced with i) hooks that facilitate the ABI analysis ii) summary
|
|
// of the registers clobbered by that function. The newly created basic block
|
|
// is returned so that it can be added to the ValueToValueMap.
|
|
BasicBlock *Split = nullptr;
|
|
auto *Term = BB->getTerminator();
|
|
auto *Call = getFunctionCall(Term);
|
|
|
|
// If the successor is null, we are dealing with an indirect call.
|
|
auto *Next = getFunctionCallCallee(Term);
|
|
|
|
// What is the function type of the callee?
|
|
FunctionTypeValue Ty = Oracle.getFunctionType(Next);
|
|
|
|
switch (Ty) {
|
|
case FunctionTypeValue::Regular: {
|
|
BranchInst *Br = BranchInst::Create(getFallthrough(Term));
|
|
ReplaceInstWithInst(Term, Br);
|
|
|
|
// What are the registers clobbered by the callee?
|
|
const auto &ClobberedRegs = Oracle.getRegistersClobbered(Next);
|
|
IRBuilder<> Builder(Br);
|
|
|
|
// Mark end of basic block with a pre-hook call.
|
|
auto *Last = Builder.CreateCall(PreHookOpqF);
|
|
|
|
// Prevent the store instructions from being optimized out by storing
|
|
// the rval of a call to an opaque function into the clobbered registers.
|
|
OFPRegistersClobbered.addFnAttribute(Attribute::ReadOnly);
|
|
OFPRegistersClobbered.addFnAttribute(Attribute::NoUnwind);
|
|
|
|
for (GlobalVariable *Reg : ClobberedRegs) {
|
|
auto *CSVTy = Reg->getType()->getPointerElementType();
|
|
auto *RegsClobbOpqF = OFPRegistersClobbered
|
|
.get(BB->getParent()->getName(),
|
|
CSVTy,
|
|
{},
|
|
"registers_clobbered");
|
|
Builder.CreateStore(Builder.CreateCall(RegsClobbOpqF), Reg);
|
|
}
|
|
|
|
// Adjust back the stack pointer.
|
|
switch (GCBI->arch()) {
|
|
case llvm::Triple::x86:
|
|
case llvm::Triple::x86_64: {
|
|
auto *SPCSV = M.getGlobalVariable(GCBI->spReg()->getName(), true);
|
|
auto *SP = Builder.CreateLoad(SPCSV);
|
|
|
|
// What is the elected final stack offset?
|
|
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, SPCSV);
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Mark end of basic block with a post-hook call.
|
|
Builder.CreateCall(PostHookOpqF);
|
|
|
|
Split = BB->splitBasicBlock(Last->getPrevNode(),
|
|
BB->getName() + Twine("__summary"));
|
|
break;
|
|
}
|
|
|
|
case FunctionTypeValue::NoReturn: {
|
|
CallInst *Abort = CallInst::Create(M.getFunction("abort"));
|
|
new UnreachableInst(BB->getContext(), BB);
|
|
ReplaceInstWithInst(Term, Abort);
|
|
break;
|
|
}
|
|
|
|
case FunctionTypeValue::Fake: {
|
|
BranchInst *Br = BranchInst::Create(getFallthrough(Term));
|
|
|
|
// 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(llvm::CallSite(CI), IFI, nullptr, true);
|
|
revng_log(StackAnalysisLog,
|
|
"Has callee " << FakeFunction->getName() << "been inlined? "
|
|
<< Status);
|
|
|
|
// Still need to replace the successor with the fall-through.
|
|
ReplaceInstWithInst(Term, Br);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
|
|
Call->eraseFromParent();
|
|
return Split;
|
|
}
|
|
|
|
template<class FunctionOracle>
|
|
llvm::Function *CFEPAnalyzer<FunctionOracle>::createDisposableFunction(
|
|
llvm::BasicBlock *Entry) {
|
|
using namespace llvm;
|
|
Function *Root = Entry->getParent();
|
|
|
|
OnceQueue<BasicBlock *> Queue;
|
|
std::vector<BasicBlock *> BlocksToClone;
|
|
Queue.insert(Entry);
|
|
|
|
while (!Queue.empty()) {
|
|
BasicBlock *Current = Queue.pop();
|
|
BlocksToClone.emplace_back(Current);
|
|
|
|
if (isFunctionCall(Current)) {
|
|
auto *Succ = getFallthrough(Current);
|
|
auto *Next = getFunctionCallCallee(Current);
|
|
|
|
if (Oracle.getFunctionType(Next) != FunctionTypeValue::NoReturn)
|
|
Queue.insert(Succ);
|
|
continue;
|
|
}
|
|
|
|
for (auto *Succ : successors(Current)) {
|
|
if (!GCBI::isPartOfRootDispatcher(Succ))
|
|
Queue.insert(Succ);
|
|
}
|
|
}
|
|
|
|
ValueToValueMapTy VMap;
|
|
SmallVector<BasicBlock *, 8> BlocksToExtract;
|
|
|
|
for (const auto &BB : BlocksToClone) {
|
|
auto *Cloned = CloneBasicBlock(BB, VMap, Twine("__cloned"), Root);
|
|
|
|
BasicBlock *New = nullptr;
|
|
if (isFunctionCall(Cloned)) {
|
|
New = integrateFunctionCallee(Cloned);
|
|
VMap[Cloned] = New;
|
|
}
|
|
|
|
VMap[BB] = Cloned;
|
|
BlocksToExtract.emplace_back(Cloned);
|
|
if (New)
|
|
BlocksToExtract.emplace_back(New);
|
|
}
|
|
|
|
remapInstructionsInBlocks(BlocksToExtract, VMap);
|
|
|
|
CodeExtractorAnalysisCache CEAC(*Root);
|
|
Function *OutlinedFunction = CodeExtractor(BlocksToExtract)
|
|
.extractCodeRegion(CEAC);
|
|
|
|
revng_assert(OutlinedFunction != nullptr);
|
|
revng_assert(OutlinedFunction->arg_size() == 0);
|
|
revng_assert(OutlinedFunction->getReturnType()->isVoidTy());
|
|
|
|
cast<Instruction>(*OutlinedFunction->user_begin())
|
|
->getParent()
|
|
->eraseFromParent();
|
|
|
|
return OutlinedFunction;
|
|
}
|
|
|
|
template<class FunctionOracle>
|
|
void CFEPAnalyzer<FunctionOracle>::throwDisposableFunction(llvm::Function *F) {
|
|
revng_assert(F->use_empty()
|
|
&& "Failed to remove all users of the outlined function.");
|
|
|
|
F->eraseFromParent();
|
|
IndirectBranchInfoOpqF->eraseFromParent();
|
|
F = nullptr;
|
|
IndirectBranchInfoOpqF = nullptr;
|
|
}
|
|
|
|
bool StackAnalysis::runOnModule(Module &M) {
|
|
Function &F = *M.getFunction("root");
|
|
|
|
revng_log(PassesLog, "Starting StackAnalysis");
|
|
|
|
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
|
|
|
|
auto &LMP = getAnalysis<LoadModelWrapperPass>().get();
|
|
|
|
// The stack analysis works function-wise. We consider two sets of functions:
|
|
// first (Force == true) those that are highly likely to be real functions
|
|
// (i.e., they have a direct call) and then (Force == false) all the remaining
|
|
// 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<CFEP> Functions;
|
|
|
|
// Register all the Candidate Function Entry Points
|
|
for (BasicBlock &BB : F) {
|
|
|
|
if (GCBI.getType(&BB) != BlockType::JumpTargetBlock)
|
|
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);
|
|
bool IsPCStore = hasReason(Reasons, JTReason::PCStore);
|
|
bool IsReturnAddress = hasReason(Reasons, JTReason::ReturnAddress);
|
|
bool IsLoadAddress = hasReason(Reasons, JTReason::LoadAddress);
|
|
|
|
if (IsFunctionSymbol or IsCallee) {
|
|
// Called addresses are a strong hint
|
|
Functions.emplace_back(&BB, true);
|
|
} else if (not IsLoadAddress
|
|
and (IsUnusedGlobalData
|
|
|| (IsMemoryStore and not IsPCStore
|
|
and not IsReturnAddress))) {
|
|
// TODO: keep IsReturnAddress?
|
|
// Consider addresses found in global data that have not been used or
|
|
// addresses that are not return addresses and do not end up in the PC
|
|
// directly.
|
|
Functions.emplace_back(&BB, false);
|
|
}
|
|
}
|
|
|
|
for (CFEP &Function : Functions) {
|
|
revng_log(CFEPLog,
|
|
getName(Function.Entry) << (Function.Force ? " (forced)" : ""));
|
|
}
|
|
|
|
// Initialize hook calls for subsequent ABI analysis.
|
|
auto *FTy = llvm::FunctionType::get(llvm::Type::getVoidTy(M.getContext()),
|
|
false);
|
|
auto *PreHookOpqF = Function::Create(FTy,
|
|
llvm::GlobalValue::ExternalLinkage,
|
|
"precall_hook",
|
|
M);
|
|
PreHookOpqF->addFnAttr(llvm::Attribute::ReadOnly);
|
|
PreHookOpqF->addFnAttr(llvm::Attribute::NoUnwind);
|
|
|
|
auto *PostHookOpqF = Function::Create(FTy,
|
|
llvm::GlobalValue::ExternalLinkage,
|
|
"postcall_hook",
|
|
M);
|
|
PostHookOpqF->addFnAttr(llvm::Attribute::ReadOnly);
|
|
PostHookOpqF->addFnAttr(llvm::Attribute::NoUnwind);
|
|
|
|
// Queue to be populated with the CFEP.
|
|
llvm::SmallVector<BasicBlock *, 8> Worklist;
|
|
llvm::SmallVector<BasicBlockNode *, 4> EntryBlocks;
|
|
UniquedQueue<BasicBlockNode *> CFEPQueue;
|
|
SmallCallGraph CG{};
|
|
|
|
using BasicBlockToNodeMapTy = llvm::DenseMap<BasicBlock *, BasicBlockNode *>;
|
|
BasicBlockToNodeMapTy BasicBlockNodeMap;
|
|
|
|
// Create over-approximated call graph.
|
|
for (CFEP &Function : Functions) {
|
|
BasicBlockNode Node{ Function.Entry };
|
|
auto *GraphNode = CG.addNode(Node);
|
|
BasicBlockNodeMap[Function.Entry] = GraphNode;
|
|
}
|
|
|
|
for (CFEP &Function : Functions) {
|
|
llvm::SmallSet<BasicBlock *, 8> Visited;
|
|
auto *StartNode = BasicBlockNodeMap[Function.Entry];
|
|
Worklist.emplace_back(Function.Entry);
|
|
|
|
do {
|
|
auto *Current = Worklist.pop_back_val();
|
|
Visited.insert(Current);
|
|
for (auto *Succ : successors(Current)) {
|
|
if (isFunctionCall(Current)) {
|
|
auto *Callee = getFunctionCallCallee(Current);
|
|
// If not an indirect call, add the node to the CG.
|
|
if (Callee) {
|
|
auto *Node = BasicBlockNodeMap[Callee];
|
|
StartNode->addSuccessor(Node);
|
|
}
|
|
auto *Next = getFallthrough(Current);
|
|
if (!Visited.count(Next))
|
|
Worklist.push_back(Next);
|
|
continue;
|
|
}
|
|
|
|
if (!GCBI::isPartOfRootDispatcher(Succ) && !Visited.count(Succ))
|
|
Worklist.push_back(Succ);
|
|
}
|
|
} while (!Worklist.empty());
|
|
}
|
|
|
|
for (const auto &[_, Node] : BasicBlockNodeMap) {
|
|
if (!Node->hasPredecessors())
|
|
EntryBlocks.emplace_back(Node);
|
|
|
|
// Recursive?
|
|
if (*Node->predecessors().begin() == Node)
|
|
EntryBlocks.emplace_back(Node);
|
|
}
|
|
|
|
BasicBlockNode FakeNode;
|
|
auto *RootNode = CG.addNode(FakeNode);
|
|
CG.setEntryNode(RootNode);
|
|
for (const auto &Node : EntryBlocks)
|
|
RootNode->addSuccessor(Node);
|
|
|
|
if (StackAnalysisLog.isEnabled()) {
|
|
std::error_code EC;
|
|
llvm::raw_fd_ostream Out(M.getModuleIdentifier() + "_CG.dot",
|
|
EC,
|
|
llvm::sys::fs::OF_None);
|
|
llvm::WriteGraph(Out, &CG);
|
|
revng_log(StackAnalysisLog, "Call graph written on disk");
|
|
Out.close();
|
|
}
|
|
|
|
for (auto *Node : llvm::post_order(&CG)) {
|
|
if (Node->hasPredecessors())
|
|
CFEPQueue.insert(Node);
|
|
}
|
|
|
|
// Collect all the ABI registers.
|
|
std::vector<llvm::GlobalVariable *> ABIRegisters;
|
|
for (auto *CSV : GCBI.abiRegisters())
|
|
if (CSV && !(GCBI.isSPReg(CSV)))
|
|
ABIRegisters.emplace_back(CSV);
|
|
|
|
std::optional<uint64_t> FSO;
|
|
switch (GCBI.arch()) {
|
|
case llvm::Triple::x86: {
|
|
FSO = 4;
|
|
break;
|
|
}
|
|
case llvm::Triple::x86_64: {
|
|
FSO = 8;
|
|
break;
|
|
}
|
|
default: {
|
|
FSO = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
using CFEPA = CFEPAnalyzer<FunctionProperties>;
|
|
|
|
// Cache summary default-constructed for indirect calls.
|
|
FuncSummary DefaultSummary(model::FunctionType::Values::Regular,
|
|
{ ABIRegisters.begin(), ABIRegisters.end() },
|
|
{},
|
|
FSO);
|
|
FunctionProperties Properties(std::move(DefaultSummary));
|
|
|
|
// Interprocedural analysis.
|
|
while (!CFEPQueue.empty()) {
|
|
auto *EntryNode = CFEPQueue.pop();
|
|
revng_log(StackAnalysisLog,
|
|
"Analyzing Entry: " << EntryNode->BB->getName());
|
|
|
|
// Intraprocedural analysis.
|
|
CFEPA Analyzer(M, &GCBI, Properties, PreHookOpqF, PostHookOpqF);
|
|
FuncSummary Res = Analyzer.analyze(ABIRegisters, EntryNode->BB);
|
|
bool Changed = Properties.registerFunc(EntryNode->BB, std::move(Res));
|
|
|
|
// Need to recompute the queue?
|
|
if (Changed) {
|
|
UniquedQueue<BasicBlockNode *> FakeFunctionWorklist;
|
|
FakeFunctionWorklist.insert(EntryNode);
|
|
|
|
while (!FakeFunctionWorklist.empty()) {
|
|
auto *Node = FakeFunctionWorklist.pop();
|
|
for (auto *Caller : Node->predecessors()) {
|
|
if (Caller == RootNode)
|
|
break;
|
|
|
|
if (!Properties.isFakeFunction(Caller->BB))
|
|
CFEPQueue.insert(Caller);
|
|
else
|
|
FakeFunctionWorklist.insert(Caller);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Still OK?
|
|
revng_assert(llvm::verifyModule(M, &llvm::dbgs()) == false);
|
|
|
|
// Initialize the cache where all the results will be accumulated
|
|
Cache TheCache(&F, &GCBI);
|
|
|
|
// Pool where the final results will be collected
|
|
ResultsPool Results;
|
|
|
|
// First analyze all the `Force`d functions (i.e., with an explicit direct
|
|
// call)
|
|
for (CFEP &Function : Functions) {
|
|
if (Function.Force) {
|
|
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
|
|
InterproceduralAnalysis SA(TheCache, GCBI);
|
|
SA.run(Function.Entry, Results);
|
|
}
|
|
}
|
|
|
|
// Now analyze all the remaining candidates which are not already part of
|
|
// another function
|
|
std::set<BasicBlock *> Visited = Results.visitedBlocks();
|
|
for (CFEP &Function : Functions) {
|
|
if (not Function.Force and Visited.count(Function.Entry) == 0) {
|
|
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
|
|
InterproceduralAnalysis SA(TheCache, GCBI);
|
|
SA.run(Function.Entry, Results);
|
|
}
|
|
}
|
|
|
|
for (CFEP &Function : Functions) {
|
|
using IFS = IntraproceduralFunctionSummary;
|
|
BasicBlock *Entry = Function.Entry;
|
|
llvm::Optional<const IFS *> Cached = TheCache.get(Entry);
|
|
revng_assert(Cached or TheCache.isFakeFunction(Entry));
|
|
|
|
// Has this function been analyzed already? If so, only now we register it
|
|
// in the ResultsPool.
|
|
FunctionType::Values Type;
|
|
if (TheCache.isFakeFunction(Entry))
|
|
Type = FunctionType::Fake;
|
|
else if (TheCache.isNoReturnFunction(Entry))
|
|
Type = FunctionType::NoReturn;
|
|
else
|
|
Type = FunctionType::Regular;
|
|
|
|
// Regular functions need to be composed by at least a basic block
|
|
if (Cached) {
|
|
const IFS *Summary = *Cached;
|
|
if (Type == FunctionType::Regular)
|
|
revng_assert(Summary->BranchesType.size() != 0);
|
|
|
|
Results.registerFunction(Entry, Type, Summary);
|
|
} else {
|
|
Results.registerFunction(Entry, Type, nullptr);
|
|
}
|
|
}
|
|
|
|
GrandResult = Results.finalize(&M, &TheCache);
|
|
|
|
if (ClobberedLog.isEnabled()) {
|
|
for (auto &P : GrandResult.Functions) {
|
|
ClobberedLog << getName(P.first) << ":";
|
|
for (const llvm::GlobalVariable *CSV : P.second.ClobberedRegisters)
|
|
ClobberedLog << " " << CSV->getName().data();
|
|
ClobberedLog << DoLog;
|
|
}
|
|
}
|
|
|
|
if (StackAnalysisLog.isEnabled()) {
|
|
std::stringstream Output;
|
|
GrandResult.dump(&M, Output);
|
|
TextRepresentation = Output.str();
|
|
revng_log(StackAnalysisLog, TextRepresentation);
|
|
}
|
|
|
|
revng_log(PassesLog, "Ending StackAnalysis");
|
|
|
|
if (ABIAnalysisOutputPath.getNumOccurrences() == 1) {
|
|
std::ofstream Output;
|
|
serialize(pathToStream(ABIAnalysisOutputPath, Output));
|
|
}
|
|
|
|
commitToModel(GCBI, &F, GrandResult, LMP.getWriteableModel());
|
|
|
|
return false;
|
|
}
|
|
|
|
void StackAnalysis::serializeMetadata(Function &F,
|
|
GeneratedCodeBasicInfo &GCBI) {
|
|
using namespace llvm;
|
|
|
|
const FunctionsSummary &Summary = GrandResult;
|
|
|
|
LLVMContext &Context = getContext(&F);
|
|
QuickMetadata QMD(Context);
|
|
|
|
// Temporary data structure so we can set all the `revng.func.member.of` in a
|
|
// single shot at the end
|
|
std::map<Instruction *, std::vector<Metadata *>> MemberOf;
|
|
|
|
// Loop over all the detected functions
|
|
for (const auto &P : Summary.Functions) {
|
|
BasicBlock *Entry = P.first;
|
|
const FunctionsSummary::FunctionDescription &Function = P.second;
|
|
|
|
if (Entry == nullptr or Function.BasicBlocks.size() == 0)
|
|
continue;
|
|
|
|
MetaAddress EntryPC = getBasicBlockPC(Entry);
|
|
|
|
//
|
|
// Add `revng.func.entry`:
|
|
// {
|
|
// name,
|
|
// address,
|
|
// type,
|
|
// { clobbered csv, ... },
|
|
// { { csv, argument, return value }, ... }
|
|
// }
|
|
//
|
|
auto TypeMD = QMD.get(FunctionType::getName(Function.Type));
|
|
|
|
// Clobbered registers metadata
|
|
std::vector<Metadata *> ClobberedMDs;
|
|
for (GlobalVariable *ClobberedCSV : Function.ClobberedRegisters) {
|
|
if (not GCBI.isServiceRegister(ClobberedCSV))
|
|
ClobberedMDs.push_back(QMD.get(ClobberedCSV));
|
|
}
|
|
|
|
// Register slots metadata
|
|
std::vector<Metadata *> SlotMDs;
|
|
for (auto &P : Function.RegisterSlots) {
|
|
if (GCBI.isServiceRegister(P.first))
|
|
continue;
|
|
|
|
auto *CSV = QMD.get(P.first);
|
|
auto *Argument = QMD.get(P.second.Argument.valueName());
|
|
auto *ReturnValue = QMD.get(P.second.ReturnValue.valueName());
|
|
SlotMDs.push_back(QMD.tuple({ CSV, Argument, ReturnValue }));
|
|
}
|
|
|
|
// Create revng.func.entry metadata
|
|
MDTuple *FunctionMD = QMD.tuple({ QMD.get(getName(Entry)),
|
|
QMD.get(GCBI.toConstant(EntryPC)),
|
|
TypeMD,
|
|
QMD.tuple(ClobberedMDs),
|
|
QMD.tuple(SlotMDs) });
|
|
Entry->getTerminator()->setMetadata("revng.func.entry", FunctionMD);
|
|
|
|
//
|
|
// Create func.call
|
|
//
|
|
for (const FunctionsSummary::CallSiteDescription &CallSite :
|
|
Function.CallSites) {
|
|
Instruction *Call = CallSite.Call;
|
|
|
|
// Register slots metadata
|
|
std::vector<Metadata *> SlotMDs;
|
|
for (auto &P : CallSite.RegisterSlots) {
|
|
if (GCBI.isServiceRegister(P.first))
|
|
continue;
|
|
|
|
auto *CSV = QMD.get(P.first);
|
|
auto *Argument = QMD.get(P.second.Argument.valueName());
|
|
auto *ReturnValue = QMD.get(P.second.ReturnValue.valueName());
|
|
SlotMDs.push_back(QMD.tuple({ CSV, Argument, ReturnValue }));
|
|
}
|
|
|
|
Call->setMetadata("func.call", QMD.tuple(QMD.tuple(SlotMDs)));
|
|
}
|
|
|
|
//
|
|
// Create revng.func.member.of
|
|
//
|
|
|
|
// Loop over all the basic blocks composing the function
|
|
for (const auto &P : Function.BasicBlocks) {
|
|
BasicBlock *BB = P.first;
|
|
BranchType::Values Type = P.second;
|
|
|
|
auto *Pair = QMD.tuple({ FunctionMD, QMD.get(getName(Type)) });
|
|
|
|
// Register that this block is associated to this function
|
|
MemberOf[BB->getTerminator()].push_back(Pair);
|
|
}
|
|
}
|
|
|
|
// Apply `revng.func.member.of`
|
|
for (auto &P : MemberOf)
|
|
P.first->setMetadata("revng.func.member.of", QMD.tuple(P.second));
|
|
}
|
|
|
|
} // namespace StackAnalysis
|