mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
1033 lines
33 KiB
C++
1033 lines
33 KiB
C++
/// \file IsolateFunctions.cpp
|
|
/// \brief Implements the IsolateFunctions pass which applies function isolation
|
|
/// using the informations provided by FunctionBoundariesDetectionPass.
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/IR/Verifier.h"
|
|
#include "llvm/Support/raw_os_ostream.h"
|
|
#include "llvm/Transforms/Utils/Cloning.h"
|
|
#include "llvm/Transforms/Utils/CodeExtractor.h"
|
|
#include "llvm/Transforms/Utils/Local.h"
|
|
|
|
#include "revng/ADT/KeyedObjectContainer.h"
|
|
#include "revng/ADT/KeyedObjectTraits.h"
|
|
#include "revng/ADT/ZipMapIterator.h"
|
|
#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h"
|
|
#include "revng/FunctionIsolation/IsolateFunctions.h"
|
|
#include "revng/Support/Debug.h"
|
|
#include "revng/Support/IRHelpers.h"
|
|
|
|
using namespace llvm;
|
|
|
|
class IsolateFunctionsImpl;
|
|
|
|
static Logger<> TheLogger("isolation");
|
|
|
|
// Define an alias for the data structure that will contain the LLVM functions
|
|
using FunctionsMap = std::map<MDString *, Function *>;
|
|
using ValueToValueMap = DenseMap<const Value *, Value *>;
|
|
|
|
using IF = IsolateFunctions;
|
|
using IFI = IsolateFunctionsImpl;
|
|
|
|
char IF::ID = 0;
|
|
static RegisterPass<IF> X("isolate", "Isolate Functions Pass", true, true);
|
|
|
|
static void
|
|
eraseBranch(Instruction *I, BasicBlock *ExpectedUniqueSuccessor = nullptr) {
|
|
auto *T = cast<BranchInst>(I);
|
|
revng_assert(T->isUnconditional());
|
|
if (ExpectedUniqueSuccessor != nullptr)
|
|
revng_assert(T->getSuccessor(0) == ExpectedUniqueSuccessor);
|
|
T->eraseFromParent();
|
|
}
|
|
|
|
class ConstantStringsPool {
|
|
private:
|
|
Module *M;
|
|
std::map<std::string, GlobalVariable *> StringsPool;
|
|
|
|
public:
|
|
ConstantStringsPool(Module *M) : M(M) {}
|
|
|
|
Constant *get(std::string String, const Twine &Name = "") {
|
|
auto It = StringsPool.find(String);
|
|
auto &C = M->getContext();
|
|
if (It == StringsPool.end()) {
|
|
auto *Initializer = ConstantDataArray::getString(C, String, true);
|
|
auto *NewVariable = new GlobalVariable(*M,
|
|
Initializer->getType(),
|
|
true,
|
|
GlobalValue::InternalLinkage,
|
|
Initializer);
|
|
It = StringsPool.insert(It, { String, NewVariable });
|
|
}
|
|
|
|
auto *U8PtrTy = Type::getInt8Ty(C)->getPointerTo();
|
|
return ConstantExpr::getPointerCast(It->second, U8PtrTy);
|
|
}
|
|
};
|
|
|
|
using SuccessorsList = GeneratedCodeBasicInfo::SuccessorsList;
|
|
struct Boundary {
|
|
BasicBlock *Block = nullptr;
|
|
BasicBlock *CalleeBlock = nullptr;
|
|
BasicBlock *ReturnBlock = nullptr;
|
|
SuccessorsList Successors;
|
|
|
|
bool isCall() const { return ReturnBlock != nullptr; }
|
|
|
|
void dump() const debug_function { dump(dbg); }
|
|
|
|
template<typename O>
|
|
void dump(O &Output) const {
|
|
Output << "Block: " << getName(Block) << "\n";
|
|
Output << "CalleeBlock: " << getName(CalleeBlock) << "\n";
|
|
Output << "ReturnBlock: " << getName(ReturnBlock) << "\n";
|
|
Output << "Successors: \n";
|
|
Successors.dump(Output);
|
|
}
|
|
};
|
|
|
|
class FunctionBlocks {
|
|
private:
|
|
enum FixedBlocks {
|
|
DummyEntryBlock,
|
|
ReturnBlock,
|
|
UnexpectedPCBlock,
|
|
FixedBlocksCount
|
|
};
|
|
|
|
public:
|
|
SmallVector<BasicBlock *, 16> Blocks;
|
|
|
|
public:
|
|
BasicBlock *&dummyEntryBlock() { return Blocks[DummyEntryBlock]; }
|
|
BasicBlock *&returnBlock() { return Blocks[ReturnBlock]; }
|
|
BasicBlock *&unexpectedPCBlock() { return Blocks[UnexpectedPCBlock]; }
|
|
|
|
public:
|
|
FunctionBlocks() : Blocks(FixedBlocksCount) {}
|
|
|
|
auto begin() { return Blocks.begin(); }
|
|
auto end() { return Blocks.end(); }
|
|
|
|
void push_back(BasicBlock *BB) { Blocks.push_back(BB); }
|
|
};
|
|
|
|
class IsolateFunctionsImpl {
|
|
private:
|
|
using BlockToFunctionsMap = std::map<BasicBlock *,
|
|
std::pair<unsigned, Function *>>;
|
|
using SuccessorsContainer = std::map<model::FunctionEdge, int>;
|
|
|
|
private:
|
|
Function *RootFunction = nullptr;
|
|
Module *TheModule = nullptr;
|
|
LLVMContext &Context;
|
|
GeneratedCodeBasicInfo &GCBI;
|
|
const model::Binary &Binary;
|
|
Function *RaiseException = nullptr;
|
|
Function *FunctionDispatcher = nullptr;
|
|
Function *CallMarker = nullptr;
|
|
BlockToFunctionsMap IsolatedFunctionsMap;
|
|
ConstantStringsPool Strings;
|
|
GlobalVariable *ExceptionSourcePC;
|
|
GlobalVariable *ExceptionDestinationPC;
|
|
|
|
public:
|
|
IsolateFunctionsImpl(Function *RootFunction,
|
|
GeneratedCodeBasicInfo &GCBI,
|
|
const model::Binary &Binary) :
|
|
RootFunction(RootFunction),
|
|
TheModule(RootFunction->getParent()),
|
|
Context(TheModule->getContext()),
|
|
GCBI(GCBI),
|
|
Binary(Binary),
|
|
Strings(TheModule) {}
|
|
|
|
void run();
|
|
|
|
private:
|
|
/// Isolate the function described by \p Function
|
|
///
|
|
/// \return a pair of the entry block in root and the newly create Function
|
|
std::pair<BasicBlock *, Function *> isolate(const model::Function &Function);
|
|
|
|
/// Process a basic block from the model
|
|
void handleBasicBlock(const model::BasicBlock &Block,
|
|
ValueToValueMapTy &OldToNew,
|
|
FunctionBlocks &ClonedBlocks);
|
|
|
|
/// Clone the basic blocks involved in \p Entry jump target
|
|
///
|
|
/// \return a vector of boundary basic blocks
|
|
std::vector<Boundary>
|
|
cloneAndIdentifyBoundaries(MetaAddress Entry,
|
|
ValueToValueMapTy &OldToNew,
|
|
FunctionBlocks &ClonedBlocks);
|
|
|
|
/// Create the code necessary to handle a direct branch in the IR
|
|
bool handleDirectBoundary(const Boundary &TheBoundary,
|
|
SuccessorsContainer &ExpectedSuccessors,
|
|
FunctionBlocks &ClonedBlocks);
|
|
|
|
/// Create the code necessary to handle an indirect branch in the IR
|
|
bool handleIndirectBoundary(const std::vector<Boundary> &Boundaries,
|
|
const model::BasicBlock &Block,
|
|
const SuccessorsContainer &ExpectedSuccessors,
|
|
bool CallConsumed,
|
|
FunctionBlocks &ClonedBlocks);
|
|
|
|
/// Emit a function call marker and a branch to the return address
|
|
void createFunctionCall(IRBuilder<> &Builder,
|
|
MetaAddress ExpectedCallee,
|
|
const Boundary &TheBoundary,
|
|
FunctionBlocks &ClonedBlocks);
|
|
|
|
void createFunctionCall(BasicBlock *BB,
|
|
MetaAddress Callee,
|
|
const Boundary &TheBoundary,
|
|
FunctionBlocks &ClonedBlocks) {
|
|
IRBuilder<> Builder(BB);
|
|
createFunctionCall(Builder, Callee, TheBoundary, ClonedBlocks);
|
|
}
|
|
|
|
/// Post process all the call markers, replacing them with actual calls
|
|
void replaceCallMarker() const;
|
|
|
|
/// Populate the function_dispatcher, needed to handle the indirect calls
|
|
void populateFunctionDispatcher();
|
|
|
|
/// Create the basic blocks that are hit on exit after an invoke instruction
|
|
BasicBlock *createInvokeReturnBlock(Function *Root);
|
|
|
|
/// Create the basic blocks that represent the catch of the invoke instruction
|
|
BasicBlock *createCatchBlock(Function *Root, BasicBlock *UnexpectedPC);
|
|
|
|
/// Create code to throw of an exception
|
|
void throwException(IRBuilder<> &Builder, StringRef Reason);
|
|
|
|
void throwException(BasicBlock *BB, StringRef Reason) {
|
|
IRBuilder<> Builder(BB);
|
|
throwException(Builder, Reason);
|
|
}
|
|
};
|
|
|
|
void IFI::throwException(IRBuilder<> &Builder, StringRef Reason) {
|
|
revng_assert(RaiseException != nullptr);
|
|
|
|
// Create the message string
|
|
Constant *ReasonString = Strings.get(Reason);
|
|
|
|
// Populate the source PC
|
|
MetaAddress SourcePC = MetaAddress::invalid();
|
|
|
|
if (Instruction *T = Builder.GetInsertBlock()->getTerminator())
|
|
SourcePC = getPC(T).first;
|
|
|
|
auto *Ty = ExceptionSourcePC->getType()->getPointerElementType();
|
|
Builder.CreateStore(SourcePC.toConstant(Ty), ExceptionSourcePC);
|
|
|
|
// Populate the destination PC
|
|
Builder.CreateStore(GCBI.programCounterHandler()->loadPC(Builder),
|
|
ExceptionDestinationPC);
|
|
|
|
Builder.CreateCall(RaiseException,
|
|
{ ReasonString,
|
|
ExceptionSourcePC,
|
|
ExceptionDestinationPC });
|
|
Builder.CreateUnreachable();
|
|
}
|
|
|
|
void IFI::populateFunctionDispatcher() {
|
|
|
|
BasicBlock *Dispatcher = BasicBlock::Create(Context,
|
|
"function_dispatcher",
|
|
FunctionDispatcher,
|
|
nullptr);
|
|
|
|
BasicBlock *Unexpected = BasicBlock::Create(Context,
|
|
"unexpectedpc",
|
|
FunctionDispatcher,
|
|
nullptr);
|
|
throwException(Unexpected, "An unexpected functions has been called");
|
|
setBlockType(Unexpected->getTerminator(), BlockType::UnexpectedPCBlock);
|
|
|
|
IRBuilder<> Builder(Context);
|
|
|
|
// Create all the entries of the dispatcher
|
|
ProgramCounterHandler::DispatcherTargets Targets;
|
|
for (auto &[Block, P] : IsolatedFunctionsMap) {
|
|
auto &[_, F] = P;
|
|
|
|
BasicBlock *Trampoline = BasicBlock::Create(Context,
|
|
F->getName() + "_trampoline",
|
|
FunctionDispatcher,
|
|
nullptr);
|
|
Targets.emplace_back(GCBI.getPCFromNewPC(&*Block->begin()), Trampoline);
|
|
|
|
Builder.SetInsertPoint(Trampoline);
|
|
Builder.CreateCall(F);
|
|
Builder.CreateRetVoid();
|
|
}
|
|
|
|
// Create switch
|
|
Builder.SetInsertPoint(Dispatcher);
|
|
GCBI.programCounterHandler()->buildDispatcher(Targets,
|
|
Builder,
|
|
Unexpected,
|
|
{});
|
|
}
|
|
|
|
BasicBlock *IFI::createInvokeReturnBlock(Function *Root) {
|
|
// Create the first block
|
|
BasicBlock *InvokeReturnBlock = BasicBlock::Create(Context,
|
|
"invoke_return",
|
|
Root,
|
|
nullptr);
|
|
|
|
BranchInst::Create(GCBI.dispatcher(), InvokeReturnBlock);
|
|
|
|
return InvokeReturnBlock;
|
|
}
|
|
|
|
BasicBlock *IFI::createCatchBlock(Function *Root, BasicBlock *UnexpectedPC) {
|
|
|
|
// Create a basic block that represents the catch part of the exception
|
|
BasicBlock *CatchBB = BasicBlock::Create(Context,
|
|
"catchblock",
|
|
Root,
|
|
nullptr);
|
|
|
|
// Create a builder object
|
|
IRBuilder<> Builder(Context);
|
|
Builder.SetInsertPoint(CatchBB);
|
|
|
|
// Create the StructType necessary for the landingpad
|
|
PointerType *RetTyPointerType = Type::getInt8PtrTy(Context);
|
|
IntegerType *RetTyIntegerType = Type::getInt32Ty(Context);
|
|
std::vector<Type *> InArgsType{ RetTyPointerType, RetTyIntegerType };
|
|
StructType *RetTyStruct = StructType::create(Context,
|
|
ArrayRef<Type *>(InArgsType),
|
|
"",
|
|
false);
|
|
|
|
// Create the landingpad instruction
|
|
LandingPadInst *LandingPad = Builder.CreateLandingPad(RetTyStruct, 0);
|
|
|
|
// Add a catch all (constructed with the null value as clause)
|
|
LandingPad->addClause(ConstantPointerNull::get(Type::getInt8PtrTy(Context)));
|
|
|
|
Builder.CreateBr(UnexpectedPC);
|
|
|
|
return CatchBB;
|
|
}
|
|
|
|
template<typename T, typename F>
|
|
static bool any(const T &Range, const F &Predicate) {
|
|
auto End = Range.end();
|
|
return End != std::find_if(Range.begin(), End, Predicate);
|
|
}
|
|
|
|
template<typename T, typename F>
|
|
static bool
|
|
allOrNone(const T &Range, const F &Predicate, bool Default = false) {
|
|
auto Start = Range.begin();
|
|
auto End = Range.end();
|
|
|
|
if (Start == End)
|
|
return Default;
|
|
|
|
bool First = Predicate(*Start);
|
|
++Start;
|
|
for (const auto &E : make_range(Start, End))
|
|
revng_assert(First == Predicate(E));
|
|
|
|
return First;
|
|
}
|
|
|
|
template<typename T, typename F>
|
|
static auto
|
|
zeroOrOne(const T &Range, const F &Predicate) -> decltype(&*Range.begin()) {
|
|
decltype(&*Range.begin()) Result = nullptr;
|
|
for (auto &E : Range) {
|
|
if (Predicate(E)) {
|
|
revng_assert(not Result);
|
|
Result = &E;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
struct SetAtMostOnce {
|
|
private:
|
|
bool State = false;
|
|
|
|
public:
|
|
bool get() const { return State; }
|
|
void set() {
|
|
revng_assert(not State);
|
|
State = true;
|
|
}
|
|
|
|
void setIf(bool Condition) {
|
|
if (Condition)
|
|
set();
|
|
}
|
|
|
|
operator bool() const { return State; }
|
|
};
|
|
|
|
static bool isDirectEdge(model::FunctionEdgeType::Values Type) {
|
|
using namespace model::FunctionEdgeType;
|
|
|
|
switch (Type) {
|
|
case IndirectCall:
|
|
case Return:
|
|
case BrokenReturn:
|
|
case IndirectTailCall:
|
|
case LongJmp:
|
|
case Killer:
|
|
case Unreachable:
|
|
return false;
|
|
|
|
case DirectBranch:
|
|
case FakeFunctionCall:
|
|
case FunctionCall:
|
|
case FakeFunctionReturn:
|
|
return true;
|
|
|
|
case Invalid:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
static bool isIndirectEdge(model::FunctionEdgeType::Values Type) {
|
|
return not isDirectEdge(Type);
|
|
}
|
|
|
|
template<typename LeftMap, typename RightMap>
|
|
void printAddressListComparison(const LeftMap &ExpectedAddresses,
|
|
const RightMap &ActualAddresses) {
|
|
// Compare expected and actual
|
|
if (TheLogger.isEnabled()) {
|
|
for (auto [ExpectedAddress, ActualAddress] :
|
|
zipmap_range(ExpectedAddresses, ActualAddresses)) {
|
|
if (ExpectedAddress == nullptr) {
|
|
TheLogger << "Warning: ";
|
|
ActualAddress->dump(TheLogger);
|
|
TheLogger << " detected as a jump target, but the model does not list "
|
|
"it"
|
|
<< DoLog;
|
|
} else if (ActualAddress == nullptr) {
|
|
TheLogger << "Warning: ";
|
|
ExpectedAddress->dump(TheLogger);
|
|
TheLogger << " not detected as a jump target, but the model lists it"
|
|
<< DoLog;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IFI::handleIndirectBoundary(const std::vector<Boundary> &Boundaries,
|
|
const model::BasicBlock &Block,
|
|
const SuccessorsContainer &ExpectedSuccessors,
|
|
bool CallConsumed,
|
|
FunctionBlocks &ClonedBlocks) {
|
|
std::vector<model::FunctionEdge> RemainingEdges;
|
|
for (auto &[Edge, EdgeUsageCount] : ExpectedSuccessors)
|
|
if (EdgeUsageCount == 0)
|
|
RemainingEdges.push_back(Edge);
|
|
|
|
// At this point RemainingEdges must either be a series of
|
|
// `DirectBranch` or a single one of the indirect ones
|
|
bool NoMore = RemainingEdges.size() == 0;
|
|
using namespace model::FunctionEdgeType;
|
|
|
|
auto IsDirectEdge = [](const model::FunctionEdge &E) {
|
|
return isDirectEdge(E.Type);
|
|
};
|
|
bool AllDirect = allOrNone(RemainingEdges, IsDirectEdge, false);
|
|
|
|
auto IndirectType = Invalid;
|
|
if (not NoMore and not AllDirect) {
|
|
// We're not out of expected successors, but they are not all direct.
|
|
// We expect a single indirect edge.
|
|
revng_assert(RemainingEdges.size() == 1);
|
|
IndirectType = RemainingEdges.begin()->Type;
|
|
revng_assert(isIndirectEdge(IndirectType));
|
|
}
|
|
|
|
// We now consumed all the direct jump/calls, let's proceed with the
|
|
// indirect jump/call
|
|
auto IsIndirectBoundary = [](const Boundary &B) {
|
|
return B.Successors.AnyPC or B.Successors.UnexpectedPC;
|
|
};
|
|
const Boundary *IndirectBoundary = zeroOrOne(Boundaries, IsIndirectBoundary);
|
|
|
|
if (IndirectBoundary == nullptr) {
|
|
revng_assert(NoMore);
|
|
return false;
|
|
}
|
|
|
|
BasicBlock *BB = IndirectBoundary->Block;
|
|
|
|
// Whatever the situation, we need to replace the terminator of this basic
|
|
// block at this point
|
|
Instruction *OldTerminator = BB->getTerminator();
|
|
IRBuilder<> Builder(OldTerminator);
|
|
|
|
if (AllDirect) {
|
|
if (IndirectBoundary->isCall()) {
|
|
if (TheLogger.isEnabled()) {
|
|
TheLogger << "The model expects a set of direct branches from ";
|
|
Block.End.dump(TheLogger);
|
|
TheLogger << ", but in the binary a function call has been identified."
|
|
<< DoLog;
|
|
}
|
|
}
|
|
|
|
SortedVector<MetaAddress> ExpectedAddresses;
|
|
{
|
|
auto Inserter = ExpectedAddresses.batch_insert();
|
|
for (const model::FunctionEdge &Edge : RemainingEdges)
|
|
Inserter.insert(Edge.Destination);
|
|
}
|
|
|
|
// Print comparison of targets mandated by the model and those identified in
|
|
// the IR
|
|
printAddressListComparison(ExpectedAddresses,
|
|
IndirectBoundary->Successors.Addresses);
|
|
|
|
// Create the dispatcher for the targets
|
|
auto Dispatcher = GCBI.buildDispatcher(ExpectedAddresses,
|
|
Builder,
|
|
ClonedBlocks.unexpectedPCBlock());
|
|
for (BasicBlock *BB : Dispatcher.NewBlocks)
|
|
ClonedBlocks.push_back(BB);
|
|
|
|
} else if (not NoMore) {
|
|
if (TheLogger.isEnabled()
|
|
and IndirectBoundary->isCall() != (IndirectType == IndirectCall)) {
|
|
TheLogger << "The model, at ";
|
|
Block.End.dump(TheLogger);
|
|
TheLogger << ", ";
|
|
if (IndirectType == IndirectCall)
|
|
TheLogger << "expects an indirect call, but it's not";
|
|
else
|
|
TheLogger << "does not expect an indirect call, but it's";
|
|
TheLogger << " in the binary." << DoLog;
|
|
}
|
|
|
|
switch (IndirectType) {
|
|
case IndirectCall:
|
|
case IndirectTailCall:
|
|
createFunctionCall(Builder,
|
|
MetaAddress::invalid(),
|
|
*IndirectBoundary,
|
|
ClonedBlocks);
|
|
break;
|
|
|
|
case Return:
|
|
Builder.CreateBr(ClonedBlocks.returnBlock());
|
|
break;
|
|
|
|
case BrokenReturn:
|
|
throwException(Builder, "A broken return was taken");
|
|
break;
|
|
case LongJmp:
|
|
throwException(Builder, "A longjmp was taken");
|
|
break;
|
|
case Killer:
|
|
throwException(Builder, "A killer block has been reached");
|
|
break;
|
|
case Unreachable:
|
|
throwException(Builder, "An unrechable instruction has been reached");
|
|
break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
} else {
|
|
// We have an indirect boundary but the model does not expect it
|
|
|
|
if (TheLogger.isEnabled()) {
|
|
TheLogger << "The model, at ";
|
|
Block.End.dump(TheLogger);
|
|
TheLogger << ", does not expect an indirect branch, ";
|
|
TheLogger << "but it's in the binary." << DoLog;
|
|
}
|
|
|
|
Builder.CreateBr(ClonedBlocks.unexpectedPCBlock());
|
|
}
|
|
|
|
OldTerminator->eraseFromParent();
|
|
|
|
return true;
|
|
}
|
|
|
|
/// \return true if this was a call
|
|
bool IFI::handleDirectBoundary(const Boundary &TheBoundary,
|
|
SuccessorsContainer &ExpectedSuccessors,
|
|
FunctionBlocks &ClonedBlocks) {
|
|
BasicBlock *BB = TheBoundary.Block;
|
|
SetAtMostOnce IsCall;
|
|
size_t Consumed = 0;
|
|
for (auto &[Edge, EdgeUsageCount] : ExpectedSuccessors) {
|
|
// Is this edge targeting who we expect?
|
|
if (TheBoundary.Successors.Addresses.count(Edge.Destination) == 0)
|
|
continue;
|
|
|
|
bool Match = false;
|
|
switch (Edge.Type) {
|
|
case model::FunctionEdgeType::DirectBranch:
|
|
if (not TheBoundary.isCall())
|
|
Match = true;
|
|
break;
|
|
|
|
case model::FunctionEdgeType::FunctionCall:
|
|
case model::FunctionEdgeType::FakeFunctionCall:
|
|
if (TheBoundary.isCall())
|
|
Match = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (not Match)
|
|
continue;
|
|
|
|
++Consumed;
|
|
++EdgeUsageCount;
|
|
|
|
if (TheBoundary.isCall()) {
|
|
IsCall.set();
|
|
|
|
if (Edge.Type == model::FunctionEdgeType::FunctionCall) {
|
|
eraseBranch(BB->getTerminator(), TheBoundary.CalleeBlock);
|
|
createFunctionCall(BB, Edge.Destination, TheBoundary, ClonedBlocks);
|
|
}
|
|
}
|
|
}
|
|
|
|
revng_assert(Consumed == TheBoundary.Successors.Addresses.size());
|
|
|
|
return IsCall;
|
|
}
|
|
|
|
std::vector<Boundary>
|
|
IFI::cloneAndIdentifyBoundaries(MetaAddress Entry,
|
|
ValueToValueMapTy &OldToNew,
|
|
FunctionBlocks &ClonedBlocks) {
|
|
std::set<BasicBlock *> Blocks;
|
|
for (BasicBlock *Block : GCBI.getBlocksGeneratedByPC(Entry))
|
|
Blocks.insert(Block);
|
|
revng_assert(Blocks.size() > 0);
|
|
|
|
std::vector<Boundary> Boundaries;
|
|
|
|
for (BasicBlock *BB : Blocks) {
|
|
// Clone basic block in root and register it
|
|
auto *NewBB = CloneBasicBlock(BB, OldToNew, "", RootFunction);
|
|
revng_assert(OldToNew.count(BB) == 0);
|
|
OldToNew.insert({ BB, NewBB });
|
|
ClonedBlocks.push_back(NewBB);
|
|
|
|
// Is this a boundary basic block?
|
|
auto HasNotBeenCloned = [&Blocks](BasicBlock *Successor) {
|
|
return Blocks.count(Successor) == 0;
|
|
};
|
|
if (any(successors(BB), HasNotBeenCloned)) {
|
|
BasicBlock *Callee = getFunctionCallCallee(BB);
|
|
BasicBlock *Fallthrough = getFallthrough(BB);
|
|
Boundary NewBoundary{
|
|
NewBB, Callee, Fallthrough, GCBI.getSuccessors(BB)
|
|
};
|
|
Boundaries.push_back(NewBoundary);
|
|
}
|
|
}
|
|
|
|
return Boundaries;
|
|
}
|
|
|
|
void IFI::handleBasicBlock(const model::BasicBlock &Block,
|
|
ValueToValueMapTy &OldToNew,
|
|
FunctionBlocks &ClonedBlocks) {
|
|
// Sentinel to ensure we don't have more than a call within a basic block
|
|
SetAtMostOnce CallConsumed;
|
|
|
|
// Identify boundary blocks
|
|
std::vector<Boundary> Boundaries = cloneAndIdentifyBoundaries(Block.Start,
|
|
OldToNew,
|
|
ClonedBlocks);
|
|
|
|
// At this point, we first need to handle all the boundary blocks that
|
|
// represent direct jumps, then we'll take care of the (only) indirect jump,
|
|
// if any
|
|
|
|
SuccessorsContainer ExpectedSuccessors;
|
|
for (const model::FunctionEdge &E : Block.Successors) {
|
|
// Ignore self-loops
|
|
if (E.Destination != Block.Start) {
|
|
ExpectedSuccessors[E] = 0;
|
|
}
|
|
}
|
|
|
|
int IndirectCount = 0;
|
|
|
|
if (TheLogger.isEnabled()) {
|
|
TheLogger << "Boundaries: \n";
|
|
for (const Boundary &B : Boundaries) {
|
|
B.dump(TheLogger);
|
|
TheLogger << "\n";
|
|
}
|
|
TheLogger << DoLog;
|
|
TheLogger << "Expected:\n";
|
|
std::string Buffer;
|
|
{
|
|
raw_string_ostream StringStream(Buffer);
|
|
yaml::Output YAMLOutput(StringStream);
|
|
for (model::FunctionEdge Edge : Block.Successors) {
|
|
YAMLOutput << Edge;
|
|
}
|
|
}
|
|
TheLogger << Buffer << DoLog;
|
|
}
|
|
|
|
// Consume direct jumps calls
|
|
for (const auto &Boundary : Boundaries) {
|
|
if (not(Boundary.Successors.AnyPC or Boundary.Successors.UnexpectedPC)) {
|
|
bool Result = handleDirectBoundary(Boundary,
|
|
ExpectedSuccessors,
|
|
ClonedBlocks);
|
|
CallConsumed.setIf(Result);
|
|
}
|
|
}
|
|
|
|
bool HasIndirectBoundary = handleIndirectBoundary(Boundaries,
|
|
Block,
|
|
ExpectedSuccessors,
|
|
CallConsumed,
|
|
ClonedBlocks);
|
|
|
|
// TODO: this is obscure and confusing
|
|
revng_assert(not(HasIndirectBoundary and CallConsumed));
|
|
}
|
|
|
|
std::pair<BasicBlock *, Function *>
|
|
IFI::isolate(const model::Function &Function) {
|
|
// Map from origina values to new ones
|
|
ValueToValueMapTy OldToNew;
|
|
|
|
// List of cloned basic blocks, dummy entry and return block are preallocated
|
|
FunctionBlocks ClonedBlocks;
|
|
|
|
auto CreateBB = [this](StringRef Name) {
|
|
return BasicBlock::Create(Context, Name, RootFunction, nullptr);
|
|
};
|
|
|
|
// Create return block
|
|
ClonedBlocks.returnBlock() = CreateBB("return");
|
|
ReturnInst::Create(Context, ClonedBlocks.returnBlock());
|
|
|
|
// Create unexpectedPC block
|
|
ClonedBlocks.unexpectedPCBlock() = CreateBB("unexpectedPC");
|
|
throwException(ClonedBlocks.unexpectedPCBlock(), "unexpectedPC");
|
|
OldToNew[GCBI.unexpectedPC()] = ClonedBlocks.unexpectedPCBlock();
|
|
|
|
// Get the entry basic block
|
|
BasicBlock *OriginalEntry = GCBI.getBlockAt(Function.Entry);
|
|
|
|
TheLogger << "Isolating ";
|
|
Function.Entry.dump(TheLogger);
|
|
TheLogger << DoLog;
|
|
LoggerIndent<> Indent(TheLogger);
|
|
|
|
for (const model::BasicBlock &Block : Function.CFG) {
|
|
|
|
if (TheLogger.isEnabled()) {
|
|
TheLogger << "Isolating ";
|
|
Block.Start.dump(TheLogger);
|
|
TheLogger << "-";
|
|
Block.End.dump(TheLogger);
|
|
TheLogger << DoLog;
|
|
}
|
|
|
|
LoggerIndent<> Indent2(TheLogger);
|
|
|
|
// Process the basic block
|
|
handleBasicBlock(Block, OldToNew, ClonedBlocks);
|
|
}
|
|
|
|
// Create a dummy entry branching to real entry
|
|
revng_assert(ClonedBlocks.dummyEntryBlock() == nullptr);
|
|
ClonedBlocks.dummyEntryBlock() = CreateBB("dummyentry");
|
|
BranchInst::Create(cast<BasicBlock>(&*OldToNew[OriginalEntry]),
|
|
ClonedBlocks.dummyEntryBlock());
|
|
|
|
// Drop all calls to `function_call`
|
|
std::vector<Instruction *> ToDrop;
|
|
for (BasicBlock *BB : ClonedBlocks) {
|
|
for (Instruction &I : *BB) {
|
|
if (isCallTo(&I, "function_call")) {
|
|
ToDrop.push_back(&I);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Instruction *I : ToDrop) {
|
|
I->eraseFromParent();
|
|
}
|
|
|
|
remapInstructionsInBlocks(ClonedBlocks.Blocks, OldToNew);
|
|
|
|
// Let CodeExtractor create the new function
|
|
// TODO: can we hoist CEAC?
|
|
CodeExtractorAnalysisCache CEAC(*RootFunction);
|
|
CodeExtractor CE(ClonedBlocks.Blocks,
|
|
nullptr,
|
|
false,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
false,
|
|
true,
|
|
"");
|
|
llvm::Function *NewFunction = CE.extractCodeRegion(CEAC);
|
|
|
|
revng_assert(NewFunction != nullptr);
|
|
NewFunction->setName(OriginalEntry->getName());
|
|
|
|
FunctionType *FT = NewFunction->getFunctionType();
|
|
revng_assert(FT->getReturnType()->isVoidTy());
|
|
revng_assert(FT->getNumParams() == 0);
|
|
|
|
return { OriginalEntry, NewFunction };
|
|
}
|
|
|
|
void IFI::createFunctionCall(IRBuilder<> &Builder,
|
|
MetaAddress ExpectedCallee,
|
|
const Boundary &TheBoundary,
|
|
FunctionBlocks &ClonedBlocks) {
|
|
BasicBlock *ExpectedCalleeBB = nullptr;
|
|
unsigned CalleeIndex = 0;
|
|
if (ExpectedCallee.isValid()) {
|
|
ExpectedCalleeBB = GCBI.getBlockAt(ExpectedCallee);
|
|
CalleeIndex = IsolatedFunctionsMap.at(ExpectedCalleeBB).first;
|
|
}
|
|
|
|
if (TheBoundary.CalleeBlock != nullptr
|
|
and TheBoundary.CalleeBlock != ExpectedCalleeBB) {
|
|
revng_log(TheLogger,
|
|
"Warning: The callee in the binary ("
|
|
<< getName(TheBoundary.CalleeBlock)
|
|
<< ") is different from the one provided by the model ("
|
|
<< getName(ExpectedCalleeBB) << ")");
|
|
}
|
|
|
|
Builder.CreateCall(CallMarker, Builder.getInt32(CalleeIndex));
|
|
|
|
if (TheBoundary.ReturnBlock != nullptr) {
|
|
// Emit jump to fallthrough
|
|
Builder.CreateBr(TheBoundary.ReturnBlock);
|
|
} else {
|
|
if (TheLogger.isEnabled()) {
|
|
TheLogger << "Call to ";
|
|
ExpectedCallee.dump(TheLogger);
|
|
TheLogger << " in " << getName(TheBoundary.Block)
|
|
<< " has not been detected as a function call in the binary."
|
|
<< DoLog;
|
|
}
|
|
|
|
throwException(Builder,
|
|
"An instruction marked as a call has not been "
|
|
"identified as such in the binary");
|
|
}
|
|
}
|
|
|
|
void IFI::replaceCallMarker() const {
|
|
std::vector<Function *> Functions;
|
|
Functions.resize(IsolatedFunctionsMap.size() + 1);
|
|
|
|
Functions[0] = FunctionDispatcher;
|
|
for (auto [_, P] : IsolatedFunctionsMap) {
|
|
auto [Index, Function] = P;
|
|
revng_assert(Index != 0);
|
|
revng_assert(Function != nullptr);
|
|
Functions[Index] = Function;
|
|
}
|
|
|
|
for (auto It = CallMarker->user_begin(); It != CallMarker->user_end();) {
|
|
User *U = *It;
|
|
++It;
|
|
|
|
auto *Call = cast<CallInst>(U);
|
|
Value *CallMarkerArgument = Call->getArgOperand(0);
|
|
unsigned Index = cast<ConstantInt>(CallMarkerArgument)->getLimitedValue();
|
|
Function *Callee = Functions[Index];
|
|
CallInst::Create(FunctionCallee{ Callee }, "", Call);
|
|
Call->eraseFromParent();
|
|
}
|
|
}
|
|
|
|
void IFI::run() {
|
|
ExceptionSourcePC = MetaAddress::createStructVariable(TheModule,
|
|
"exception_source_pc");
|
|
ExceptionDestinationPC = MetaAddress::createStructVariable(TheModule,
|
|
"exception_"
|
|
"destination_pc");
|
|
|
|
// Declare the raise_exception_helper function that we will use as a throw
|
|
std::vector<Type *> ArgsType{ Type::getInt8Ty(Context)->getPointerTo(),
|
|
ExceptionSourcePC->getType(),
|
|
ExceptionDestinationPC->getType() };
|
|
auto *RaiseExceptionTy = FunctionType::get(Type::getVoidTy(Context),
|
|
ArgsType,
|
|
false);
|
|
RaiseException = Function::Create(RaiseExceptionTy,
|
|
Function::ExternalLinkage,
|
|
"raise_exception_helper",
|
|
TheModule);
|
|
|
|
FunctionDispatcher = Function::Create(createFunctionType<void>(Context),
|
|
GlobalValue::ExternalLinkage,
|
|
"function_dispatcher",
|
|
TheModule);
|
|
|
|
auto *CallMarkerFTy = createFunctionType<void, uint32_t>(Context);
|
|
CallMarker = Function::Create(CallMarkerFTy,
|
|
GlobalValue::ExternalLinkage,
|
|
"call_marker",
|
|
TheModule);
|
|
|
|
unsigned I = 1;
|
|
for (const model::Function &Function : Binary.Functions) {
|
|
if (Function.Type == model::FunctionType::Fake)
|
|
continue;
|
|
IsolatedFunctionsMap[GCBI.getBlockAt(Function.Entry)].first = I;
|
|
++I;
|
|
}
|
|
|
|
std::set<Function *> IsolatedFunctions;
|
|
for (const model::Function &Function : Binary.Functions) {
|
|
// Do not isolate fake functions
|
|
if (Function.Type == model::FunctionType::Fake)
|
|
continue;
|
|
|
|
// Perform isolation
|
|
auto [EntryBlock, IsolatedFunction] = isolate(Function);
|
|
|
|
// Record new isolated function
|
|
BasicBlock *OriginalEntry = GCBI.getBlockAt(Function.Entry);
|
|
IsolatedFunctions.insert(IsolatedFunction);
|
|
IsolatedFunctionsMap.at(OriginalEntry).second = IsolatedFunction;
|
|
}
|
|
|
|
replaceCallMarker();
|
|
|
|
this->CallMarker->eraseFromParent();
|
|
this->CallMarker = nullptr;
|
|
|
|
revng_check(not verifyModule(*TheModule, &dbgs()));
|
|
|
|
// Create the functions and basic blocks needed for the correct execution of
|
|
// the exception handling mechanism
|
|
|
|
// Populate the function_dispatcher
|
|
populateFunctionDispatcher();
|
|
|
|
// Retrieve the root function, we use it a lot.
|
|
Function *Root = TheModule->getFunction("root");
|
|
|
|
// Get the unexpectedpc block of the root function
|
|
BasicBlock *UnexpectedPC = GCBI.unexpectedPC();
|
|
|
|
// Instantiate the basic block structure that handles the control flow after
|
|
// an invoke
|
|
BasicBlock *InvokeReturnBlock = createInvokeReturnBlock(Root);
|
|
|
|
// Instantiate the basic block structure that represents the catch of the
|
|
// invoke, please remember that this is not used at the moment (exceptions
|
|
// are handled in a customary way from the standard exit control flow path)
|
|
BasicBlock *CatchBB = createCatchBlock(Root, UnexpectedPC);
|
|
|
|
// Declaration of an ad-hoc personality function that is implemented in the
|
|
// support.c source file
|
|
auto *PersonalityFT = FunctionType::get(Type::getInt32Ty(Context), true);
|
|
|
|
Function *PersonalityFunction = Function::Create(PersonalityFT,
|
|
Function::ExternalLinkage,
|
|
"__gxx_personality_v0",
|
|
TheModule);
|
|
|
|
// Add the personality to the root function
|
|
Root->setPersonalityFn(PersonalityFunction);
|
|
|
|
for (auto [BB, P] : IsolatedFunctionsMap) {
|
|
auto [_, F] = P;
|
|
|
|
// Create a new trampoline entry block and substitute it to the old entry
|
|
// block
|
|
BasicBlock *NewBB = BasicBlock::Create(Context, "", BB->getParent(), BB);
|
|
BB->replaceAllUsesWith(NewBB);
|
|
NewBB->takeName(BB);
|
|
|
|
// Emit the invoke instruction
|
|
InvokeInst *Invoke = InvokeInst::Create(F,
|
|
InvokeReturnBlock,
|
|
CatchBB,
|
|
ArrayRef<Value *>(),
|
|
"",
|
|
NewBB);
|
|
|
|
// Mark the invoke as non eligible for inlining that could break our
|
|
// exception mechanism
|
|
Invoke->setIsNoInline();
|
|
}
|
|
|
|
// Remove all the orphan basic blocks from the root function (e.g., the blocks
|
|
// that have been substitued by the trampoline)
|
|
{
|
|
ReversePostOrderTraversal<BasicBlock *> RPOT(&Root->getEntryBlock());
|
|
std::set<BasicBlock *> Reachable;
|
|
for (BasicBlock *BB : RPOT)
|
|
Reachable.insert(BB);
|
|
|
|
std::vector<BasicBlock *> ToDelete;
|
|
for (BasicBlock &BB : *Root)
|
|
if (Reachable.count(&BB) == 0)
|
|
ToDelete.push_back(&BB);
|
|
|
|
for (BasicBlock *BB : ToDelete)
|
|
BB->dropAllReferences();
|
|
|
|
for (BasicBlock *BB : ToDelete)
|
|
BB->eraseFromParent();
|
|
}
|
|
|
|
// Before emitting it in output we check that the module in passes the
|
|
// verifyModule pass
|
|
if (VerifyLog.isEnabled())
|
|
revng_assert(not verifyModule(*TheModule, &dbgs()));
|
|
}
|
|
|
|
bool IF::runOnModule(Module &TheModule) {
|
|
// Retrieve analyses
|
|
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
|
|
const model::Binary &Binary = getAnalysis<LoadModelPass>().getReadOnlyModel();
|
|
|
|
// Create an object of type IsolateFunctionsImpl and run the pass
|
|
IFI Impl(TheModule.getFunction("root"), GCBI, Binary);
|
|
Impl.run();
|
|
|
|
return false;
|
|
}
|