Files
revng-revng/lib/FunctionIsolation/EnforceABI.cpp
Alessandro Di Federico fd30d3de42 Import the model's type system
This commit introduces the type system of the model along with several
various other improvements to the model and its users.

* Introduce the type system.
* Introduce possibility to tag certain fields in the model as to be
  optional during YAML serialization.
* All the `Name` fields have been replaced in favor of `CustomName` plus
  a `name` method that will use `CustomName` if available, or an
  automatically generated name otherwise.
* Make TupleTreeReferences behavior more robust: now you either need to
  have a valid pointer to `Root` and a `Path` or be default constructed
  (`nullptr` for `Root` and an empty `Path`). Any other configuration is
  invalid.
* The type system introduces `RawFunctionType`: this superseds the
  previous way in which we were specifying arguments and return
  values. Users of such information have been updated accordingly.
2021-07-21 18:22:58 +02:00

393 lines
14 KiB
C++

/// \file EnforceABI.cpp
/// \brief Promotes global variables CSV to function arguments or local
/// variables, according to the ABI analysis.
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "revng/ADT/LazySmallBitVector.h"
#include "revng/ADT/SmallMap.h"
#include "revng/FunctionIsolation/EnforceABI.h"
#include "revng/FunctionIsolation/StructInitializers.h"
#include "revng/Support/FunctionTags.h"
#include "revng/Support/IRHelpers.h"
#include "revng/Support/OpaqueFunctionsPool.h"
using namespace llvm;
using StackAnalysis::FunctionCallRegisterArgument;
using StackAnalysis::FunctionCallReturnValue;
using StackAnalysis::FunctionRegisterArgument;
using StackAnalysis::FunctionReturnValue;
using StackAnalysis::FunctionsSummary;
using CallSiteDescription = FunctionsSummary::CallSiteDescription;
using FunctionDescription = FunctionsSummary::FunctionDescription;
using FCRD = FunctionsSummary::FunctionCallRegisterDescription;
using FunctionCallRegisterDescription = FCRD;
using FRD = FunctionsSummary::FunctionRegisterDescription;
using FunctionRegisterDescription = FRD;
char EnforceABI::ID = 0;
using Register = RegisterPass<EnforceABI>;
static Register X("enforce-abi", "Enforce ABI Pass", true, true);
static Logger<> EnforceABILog("enforce-abi");
static cl::opt<bool> DisableSafetyChecks("disable-enforce-abi-safety-checks",
cl::desc("Disable safety checks in "
" ABI enforcing"),
cl::cat(MainCategory),
cl::init(false));
class EnforceABIImpl {
public:
EnforceABIImpl(Module &M,
GeneratedCodeBasicInfo &GCBI,
const model::Binary &Binary) :
M(M),
GCBI(GCBI),
FunctionDispatcher(M.getFunction("function_dispatcher")),
Context(M.getContext()),
Initializers(&M),
IndirectPlaceholderPool(&M, false),
Binary(Binary) {}
void run();
private:
Function *handleFunction(Function &F, const model::Function &FunctionModel);
void handleRegularFunctionCall(CallInst *Call);
void generateCall(IRBuilder<> &Builder,
Function *Callee,
const model::CallEdge &CallSite);
private:
Module &M;
GeneratedCodeBasicInfo &GCBI;
std::map<Function *, const model::Function *> FunctionsMap;
std::map<Function *, Function *> OldToNew;
Function *FunctionDispatcher;
Function *OpaquePC;
LLVMContext &Context;
StructInitializers Initializers;
OpaqueFunctionsPool<FunctionType *> IndirectPlaceholderPool;
const model::Binary &Binary;
};
bool EnforceABI::runOnModule(Module &M) {
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
const auto &ModelWrapper = getAnalysis<LoadModelWrapperPass>().get();
const model::Binary &Binary = ModelWrapper.getReadOnlyModel();
EnforceABIImpl Impl(M, GCBI, Binary);
Impl.run();
return false;
}
void EnforceABIImpl::run() {
// Declare an opaque function used later to obtain a value to store in the
// local %pc alloca, so that we don't incur in error when removing the bad
// return pc checks.
Type *PCType = GCBI.pcReg()->getType()->getPointerElementType();
auto *OpaqueFT = FunctionType::get(PCType, {}, false);
OpaquePC = Function::Create(OpaqueFT,
Function::ExternalLinkage,
"opaque_pc",
M);
OpaquePC->addFnAttr(Attribute::NoUnwind);
OpaquePC->addFnAttr(Attribute::ReadOnly);
FunctionTags::OpaqueCSVValue.addTo(OpaquePC);
std::vector<Function *> OldFunctions;
for (const model::Function &FunctionModel : Binary.Functions) {
if (FunctionModel.Type == model::FunctionType::Fake)
continue;
revng_assert(not FunctionModel.name().empty());
Function *OldFunction = M.getFunction(FunctionModel.name());
revng_assert(OldFunction != nullptr);
OldFunctions.push_back(OldFunction);
Function *NewFunction = handleFunction(*OldFunction, FunctionModel);
FunctionsMap[NewFunction] = &FunctionModel;
OldToNew[OldFunction] = NewFunction;
}
auto IsInIsolatedFunction = [this](Instruction *I) -> bool {
return FunctionsMap.count(I->getParent()->getParent()) != 0;
};
// Handle function calls in isolated functions
std::vector<CallInst *> RegularCalls;
for (auto *F : OldFunctions)
for (User *U : F->users())
if (auto *Call = dyn_cast<CallInst>(skipCasts(U)))
if (IsInIsolatedFunction(Call))
RegularCalls.push_back(Call);
for (CallInst *Call : RegularCalls)
handleRegularFunctionCall(Call);
// Drop function_dispatcher
if (FunctionDispatcher != nullptr) {
FunctionDispatcher->deleteBody();
ReturnInst::Create(Context,
BasicBlock::Create(Context, "", FunctionDispatcher));
}
// Drop all the old functions, after we stole all of its blocks
for (Function *OldFunction : OldFunctions) {
for (User *U : OldFunction->users())
cast<Instruction>(U)->getParent()->dump();
OldFunction->eraseFromParent();
}
// Quick and dirty DCE
for (auto [F, _] : FunctionsMap)
EliminateUnreachableBlocks(*F, nullptr, false);
if (VerifyLog.isEnabled()) {
raw_os_ostream Stream(dbg);
revng_assert(not verifyModule(M, &Stream));
}
}
static FunctionType *
toLLVMType(llvm::Module *M, const model::RawFunctionType &Prototype) {
using model::NamedTypedRegister;
using model::RawFunctionType;
using model::TypedRegister;
LLVMContext &Context = M->getContext();
SmallVector<llvm::Type *, 8> ArgumentsTypes;
SmallVector<llvm::Type *, 8> ReturnTypes;
for (const NamedTypedRegister &TR : Prototype.Arguments) {
auto Name = ABIRegister::toCSVName(TR.Location);
auto *CSV = cast<GlobalVariable>(M->getGlobalVariable(Name, true));
ArgumentsTypes.push_back(CSV->getType()->getPointerElementType());
}
for (const TypedRegister &TR : Prototype.ReturnValues) {
auto Name = ABIRegister::toCSVName(TR.Location);
auto *CSV = cast<GlobalVariable>(M->getGlobalVariable(Name, true));
ReturnTypes.push_back(CSV->getType()->getPointerElementType());
}
// Create the return type
Type *ReturnType = Type::getVoidTy(Context);
if (ReturnTypes.size() == 0)
ReturnType = Type::getVoidTy(Context);
else if (ReturnTypes.size() == 1)
ReturnType = ReturnTypes[0];
else
ReturnType = StructType::create(ReturnTypes);
// Create new function
return FunctionType::get(ReturnType, ArgumentsTypes, false);
}
Function *EnforceABIImpl::handleFunction(Function &OldFunction,
const model::Function &FunctionModel) {
using model::NamedTypedRegister;
using model::RawFunctionType;
using model::TypedRegister;
SmallVector<GlobalVariable *, 8> ArgumentCSVs;
SmallVector<GlobalVariable *, 8> ReturnCSVs;
const auto &Prototype = *cast<RawFunctionType>(FunctionModel.Prototype.get());
// We sort arguments by their CSV name
for (const NamedTypedRegister &TR : Prototype.Arguments) {
auto Name = ABIRegister::toCSVName(TR.Location);
auto *CSV = cast<GlobalVariable>(M.getGlobalVariable(Name, true));
ArgumentCSVs.push_back(CSV);
}
for (const TypedRegister &TR : Prototype.ReturnValues) {
auto Name = ABIRegister::toCSVName(TR.Location);
auto *CSV = cast<GlobalVariable>(M.getGlobalVariable(Name, true));
ReturnCSVs.push_back(CSV);
}
// Create new function
auto *NewType = toLLVMType(&M, Prototype);
auto *NewFunction = Function::Create(NewType,
GlobalValue::ExternalLinkage,
"",
OldFunction.getParent());
NewFunction->takeName(&OldFunction);
NewFunction->copyAttributesFrom(&OldFunction);
FunctionTags::Lifted.addTo(NewFunction);
// Set argument names
for (const auto &[LLVMArgument, ModelArgument] :
zip(NewFunction->args(), Prototype.Arguments))
LLVMArgument.setName(ModelArgument.name());
// Steal body from the old function
std::vector<BasicBlock *> Body;
for (BasicBlock &BB : OldFunction)
Body.push_back(&BB);
auto &NewBody = NewFunction->getBasicBlockList();
for (BasicBlock *BB : Body) {
BB->removeFromParent();
revng_assert(BB->getParent() == nullptr);
NewBody.push_back(BB);
revng_assert(BB->getParent() == NewFunction);
}
// Store arguments to CSVs
BasicBlock &Entry = NewFunction->getEntryBlock();
IRBuilder<> StoreBuilder(Entry.getTerminator());
for (const auto &[TheArgument, CSV] : zip(NewFunction->args(), ArgumentCSVs))
StoreBuilder.CreateStore(&TheArgument, CSV);
// Build the return value
if (ReturnCSVs.size() != 0) {
for (BasicBlock &BB : *NewFunction) {
if (auto *Return = dyn_cast<ReturnInst>(BB.getTerminator())) {
IRBuilder<> Builder(Return);
std::vector<Value *> ReturnValues;
for (GlobalVariable *ReturnCSV : ReturnCSVs)
ReturnValues.push_back(Builder.CreateLoad(ReturnCSV));
if (ReturnValues.size() == 1)
Builder.CreateRet(ReturnValues[0]);
else
Initializers.createReturn(Builder, ReturnValues);
Return->eraseFromParent();
}
}
}
return NewFunction;
}
void EnforceABIImpl::handleRegularFunctionCall(CallInst *Call) {
Function *Caller = Call->getParent()->getParent();
const model::Function &FunctionModel = *FunctionsMap.at(Caller);
Function *CallerFunction = Call->getParent()->getParent();
revng_assert(CallerFunction->getName() == FunctionModel.name());
Function *Callee = cast<Function>(skipCasts(Call->getCalledOperand()));
bool IsDirect = (Callee != FunctionDispatcher);
if (IsDirect)
Callee = OldToNew.at(Callee);
// Identify the corresponding call site in the model
MetaAddress BasicBlockAddress = GCBI.getJumpTarget(Call->getParent());
const model::BasicBlock &Block = FunctionModel.CFG.at(BasicBlockAddress);
const model::CallEdge *CallSite = nullptr;
for (const auto &Edge : Block.Successors) {
using namespace model::FunctionEdgeType;
CallSite = dyn_cast<model::CallEdge>(Edge.get());
if (CallSite != nullptr)
break;
}
// Note that currently, in case of indirect call, we emit a call to a
// placeholder function that will throw an exception. If exceptions are
// correctly supported post enforce-abi, and the ABI data is correct, this
// should work. However this is not very efficient.
//
// Alternatives:
//
// 1. Emit an inline dispatcher that calls all the compatible functions (i.e.,
// they take a subset of the call site's arguments and return a superset of
// the call site's return values).
// 2. We have a dedicated outlined dispatcher that takes all the arguments of
// the call site, plus all the registers of the return values. Under the
// assumption that each return value of the call site is either a return
// value of the callee or is preserved by the callee, we can fill each
// return value using the callee's return value or the argument
// representing the value of that register before the call.
// In case the call site expects a return value that is neither a return
// value nor a preserved register or the callee, we exclude it from the
/// switch.
// Generate the call
IRBuilder<> Builder(Call);
generateCall(Builder, Callee, *CallSite);
// Create an additional store to the local %pc, so that the optimizer cannot
// do stuff with llvm.assume.
revng_assert(OpaquePC != nullptr);
Builder.CreateStore(Builder.CreateCall(OpaquePC), GCBI.pcReg());
// Drop the original call
Call->eraseFromParent();
}
void EnforceABIImpl::generateCall(IRBuilder<> &Builder,
Function *Callee,
const model::CallEdge &CallSite) {
using model::NamedTypedRegister;
using model::RawFunctionType;
using model::TypedRegister;
revng_assert(Callee != nullptr);
llvm::SmallVector<Value *, 8> Arguments;
llvm::SmallVector<GlobalVariable *, 8> ReturnCSVs;
const auto &Prototype = *cast<RawFunctionType>(CallSite.Prototype.get());
bool IsIndirect = (Callee != FunctionDispatcher);
if (IsIndirect) {
// Create a new `indirect_placeholder` function with the specific function
// type we need
auto *NewType = toLLVMType(&M, Prototype);
Callee = IndirectPlaceholderPool.get(NewType,
NewType,
"indirect_placeholder");
} else {
BasicBlock *InsertBlock = Builder.GetInsertPoint()->getParent();
revng_log(EnforceABILog,
"Emitting call to " << getName(Callee) << " from "
<< getName(InsertBlock));
}
//
// Collect arguments and returns
//
for (const NamedTypedRegister &TR : Prototype.Arguments) {
auto Name = ABIRegister::toCSVName(TR.Location);
GlobalVariable *CSV = M.getGlobalVariable(Name, true);
Arguments.push_back(Builder.CreateLoad(CSV));
}
for (const TypedRegister &TR : Prototype.ReturnValues) {
auto Name = ABIRegister::toCSVName(TR.Location);
GlobalVariable *CSV = M.getGlobalVariable(Name, true);
ReturnCSVs.push_back(CSV);
}
//
// Produce the call
//
auto *Result = Builder.CreateCall(Callee, Arguments);
if (ReturnCSVs.size() != 1) {
unsigned I = 0;
for (GlobalVariable *ReturnCSV : ReturnCSVs) {
Builder.CreateStore(Builder.CreateExtractValue(Result, { I }), ReturnCSV);
I++;
}
} else {
Builder.CreateStore(Result, ReturnCSVs[0]);
}
}