Files
revng-revng/externaljumpshandler.cpp
T
Alessandro Di Federico 61cfbdfc56 Introduce support for dynamic binaries
This commit introduces support for dynamic programs. The current
implementation translate the main binary and uses native libraries. This
works only if the target architecture is the same as the source
one. Currently we only handle x86-64.

* The `ExternalJumpsHandler` class has been introduced. It basically
  takes care of extending the dispatcher handling the case in which the
  program counter is an address outside the range of executable
  addresses of the input program. In this case, a `setjmp` is perfomed,
  the CPU state is serialized to physical registers and jump to the
  value of the program counter is performed.

  Once the target code will try to return to the translated program, a
  segmentation fault will be triggered, a `longjmp` is performed and the
  CPU state is deserialized so that the execution can resume (from the
  dispatcher).

* `early-linked.c` has been introduced. Its purposes is to provide
  declarations of variables and functions defined in `support.c`. In the
  past, we had to manually create these definitions, a cumbersome and
  error prone we now avoid by letting `clang` compile `early-linked.c`
  and then linking it in.

* The old `support.h` is now known as `commonconstants.h`. `support.h`
  now contains declarations that have to be consumed by
  `early-linked.c`.

* Each architecture now provides additional information:

  1. Which registers are part of the ABI and have to be preserved. If
     necessary the QEMU name can be provided. For each register it's
     also possible to provide their position within the `mcontext_t`
     structure, provided by the signal handler.
  2. Three assembly snippets, one to write a register, one to read it
     and one perform an indirect jump.

  Some of this information is also exposed in the output module as
  metadata.

* `support.c` now installs a SIGSEGV signal handler. Since pages that
  were originally executable are no longer executable, jumping there
  (typically, from a library) will trigger a SIGSEGV that we will
  handle. This allows us to properly deserialize the CPU state and
  resume execution of the translate code.

* Now also a dynamic version of each test program is translated and
  tested.

* The `merge-dynamic.py` script has been introduced: it takes case of
  rewriting the translated binary so to tell the linker to performe both
  the relocations of the translate program and the relocations of the
  original program. It does so by rewriting a large portion of the
  sections employed by the dynamic linker such as `.dynamic`, `.dynsym`
  and so on.

* The `compile-time-constants.py` script has been introduced: it a
  user-specified compiler on a source file producing an object
  file. This object file is inspected and the value of global read-only
  variables is produced in a CSV.
2018-05-29 15:10:51 +02:00

268 lines
10 KiB
C++

/// \file externaljumpsHandler.cpp
/// \brief Inject code to support jumping in non-translated code and handling
/// the comeback.
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
// Standard includes
#include <string>
// LLVM includes
#include "llvm/ADT/Triple.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/Module.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
// Local includes
#include "binaryfile.h"
#include "debug.h"
#include "externaljumpshandler.h"
#include "jumptargetmanager.h"
using namespace llvm;
using std::string;
static string &replace(string &Target,
const StringRef Search,
const StringRef Replace) {
size_t Position = Target.find(Search.data());
assert(Position != string::npos);
Target.replace(Position, Search.size(), Replace);
return Target;
}
BasicBlock *ExternalJumpsHandler::createReturnFromExternal() {
// Create return_from_call BasicBlock
auto *ReturnFromExternal = BasicBlock::Create(Context,
"return_from_external",
&TheFunction);
IRBuilder<> Builder(ReturnFromExternal);
// Identify the global variables to be serialized
Constant *SavedRegistersPtr = TheModule.getGlobalVariable("saved_registers");
LoadInst *SavedRegisters = Builder.CreateLoad(SavedRegistersPtr);
{
// Deserialize the PC
Value *GEP = Builder.CreateGEP(SavedRegisters,
Builder.getInt32(Arch.pcMContextIndex()));
LoadInst *RegisterValue = Builder.CreateLoad(GEP);
Builder.CreateStore(RegisterValue, JumpTargets.pcReg());
}
// Deserialize the ABI registers
for (const ABIRegister &Register : Arch.abiRegisters()) {
GlobalVariable *CSV = TheModule.getGlobalVariable(Register.qemuName());
// Not all the registers have a corresponding CSV
if (CSV != nullptr) {
if (Register.inMContext()) {
Constant *RegisterIndex = Builder.getInt32(Register.mcontextIndex());
Value *GEP = Builder.CreateGEP(SavedRegisters, RegisterIndex);
LoadInst *RegisterValue = Builder.CreateLoad(GEP);
Builder.CreateStore(RegisterValue, CSV);
} else {
std::string AsmString = Arch.readRegisterAsm();
replace(AsmString, "REGISTER", Register.name());
std::stringstream ConstraintStringStream;
ConstraintStringStream << "*m,~{},~{dirflag},~{fpsr},~{flags}";
InlineAsm *Asm = InlineAsm::get(VoidFunctionType,
AsmString,
ConstraintStringStream.str(),
true,
InlineAsm::AsmDialect::AD_ATT);
Builder.CreateCall(Asm, CSV);
}
}
}
Builder.CreateBr(JumpTargets.dispatcher());
return ReturnFromExternal;
}
ExternalJumpsHandler::ExternalJumpsHandler(BinaryFile &TheBinary,
JumpTargetManager &JumpTargets,
Function &TheFunction) :
Context(getContext(&TheFunction)),
TheModule(*TheFunction.getParent()),
TheFunction(TheFunction),
TheBinary(TheBinary),
Arch(TheBinary.architecture()),
JumpTargets(JumpTargets),
RegisterType(JumpTargets.pcReg()->getType()->getPointerElementType()),
VoidFunctionType(FunctionType::get(Type::getVoidTy(Context), false)) { }
BasicBlock *ExternalJumpsHandler::createSerializeAndJumpOut() {
// Create the serialize and branch Basic Block
BasicBlock *Result = BasicBlock::Create(Context,
"serialize_and_jump_out",
&TheFunction);
IRBuilder<> Builder(Result);
// Serialize ABI CSVs
for (const ABIRegister &Register : Arch.abiRegisters()) {
GlobalVariable *CSV = TheModule.getGlobalVariable(Register.qemuName());
// Not all the registers have a corresponding CSV
if (CSV == nullptr)
continue;
string AsmString = Arch.writeRegisterAsm();
replace(AsmString, "REGISTER", Register.name());
std::stringstream ConstraintStringStream;
ConstraintStringStream << "*m,~{" << Register.name().data()
<< "},~{dirflag},~{fpsr},~{flags}";
InlineAsm *Asm = InlineAsm::get(VoidFunctionType,
AsmString,
ConstraintStringStream.str(),
true,
InlineAsm::AsmDialect::AD_ATT);
Builder.CreateCall(Asm, CSV);
}
// Branch to the Program Counter address
InlineAsm *Asm = InlineAsm::get(VoidFunctionType,
Arch.jumpAsm(),
"*m,~{dirflag},~{fpsr},~{flags}",
true,
InlineAsm::AsmDialect::AD_ATT);
Value *PCReg = JumpTargets.pcReg();
Builder.CreateCall(Asm, PCReg);
Builder.CreateUnreachable();
return Result;
}
llvm::BasicBlock *ExternalJumpsHandler::createSetjmp(BasicBlock *FirstReturn,
BasicBlock *SecondReturn) {
using CE = ConstantExpr;
using CI = ConstantInt;
BasicBlock *SetjmpBB = BasicBlock::Create(Context, "setjmp", &TheFunction);
IRBuilder<> Builder(SetjmpBB);
// Call setjmp
llvm::Constant *SetjmpFunction = TheModule.getFunction("setjmp");
auto *SetJmpTy = SetjmpFunction->getType()->getPointerElementType();
auto *JmpBuf = CE::getPointerCast(TheModule.getGlobalVariable("jmp_buffer"),
SetJmpTy->getFunctionParamType(0));
Value *SetjmpRes = Builder.CreateCall(SetjmpFunction, { JmpBuf });
// Check if it's the first or second return
auto *Zero = CI::get(cast<FunctionType>(SetJmpTy)->getReturnType(), 0);
Value *BrCond = Builder.CreateICmpNE(SetjmpRes, Zero);
Builder.CreateCondBr(BrCond, SecondReturn, FirstReturn);
return SetjmpBB;
}
void ExternalJumpsHandler::buildExecutableSegmentsList() {
SmallVector<Constant *, 10> ExecutableSegments;
auto Int = [this] (uint64_t V) { return ConstantInt::get(RegisterType, V); };
for (auto &Segment : TheBinary.segments()) {
if (Segment.IsExecutable) {
ExecutableSegments.push_back(Int(Segment.StartVirtualAddress));
ExecutableSegments.push_back(Int(Segment.EndVirtualAddress));
}
}
auto *SegmentsType = ArrayType::get(RegisterType, ExecutableSegments.size());
auto *SegmentsArray = ConstantArray::get(SegmentsType, ExecutableSegments);
// Create the array (unnamed)
auto *SegmentBoundaries = new GlobalVariable(TheModule,
SegmentsArray->getType(),
true,
GlobalValue::InternalLinkage,
SegmentsArray);
// Create a pointer to the array (segment_boundaries) for support.c
// consumption
new GlobalVariable(TheModule,
RegisterType->getPointerTo(),
true,
GlobalValue::ExternalLinkage,
ConstantExpr::getPointerCast(SegmentBoundaries,
RegisterType->getPointerTo()),
"segment_boundaries");
// Create a variable to hold the number of segments (segments_count)
new GlobalVariable(TheModule,
RegisterType,
true,
GlobalValue::ExternalLinkage,
Int(TheBinary.segments().size()),
"segments_count");
}
BasicBlock *
ExternalJumpsHandler::createExternalDispatcher(BasicBlock *IsExecutable,
BasicBlock *IsNotExecutable) {
buildExecutableSegmentsList();
Constant *IsExecutableFunction = TheModule.getFunction("is_executable");
BasicBlock *ExternalJumpHandler = BasicBlock::Create(Context,
"dispatcher.external",
&TheFunction);
IRBuilder<> Builder(ExternalJumpHandler);
Value *PC = Builder.CreateLoad(JumpTargets.pcReg());
Value *IsExecutableResult = Builder.CreateCall(IsExecutableFunction, { PC });
// If is_executable returns true go to default, otherwise setjmp
Builder.CreateCondBr(IsExecutableResult, IsNotExecutable, IsExecutable);
return ExternalJumpHandler;
}
void ExternalJumpsHandler::buildEmptyExecutableSegmentsList() {
new GlobalVariable(TheModule,
RegisterType->getPointerTo(),
true,
GlobalValue::ExternalLinkage,
Constant::getNullValue(RegisterType->getPointerTo()),
"segment_boundaries");
new GlobalVariable(TheModule,
RegisterType,
true,
GlobalValue::ExternalLinkage,
Constant::getNullValue(RegisterType),
"segments_count");
}
void ExternalJumpsHandler::createExternalJumpsHandler() {
if (not Arch.isJumpOutSupported()) {
buildEmptyExecutableSegmentsList();
return;
}
BasicBlock *SerializeAndBranch = createSerializeAndJumpOut();
BasicBlock *ReturnFromExternal = createReturnFromExternal();
BasicBlock *SetjmpBB = createSetjmp(SerializeAndBranch, ReturnFromExternal);
// Insert our BasicBlock as the default case of the dispatcher switch
BasicBlock *Dispatcher = JumpTargets.dispatcher();
auto *Switch = cast<SwitchInst>(Dispatcher->getTerminator());
BasicBlock *DispatcherFail = Switch->getDefaultDest();
// Replace the default case of the dispatcher with the external jump handler.
// In practice, perfrom a blind jump, unless the target is within the
// executable segment of the current module.
BasicBlock *ExternalJumpHandler = createExternalDispatcher(SetjmpBB,
DispatcherFail);
Switch->setDefaultDest(ExternalJumpHandler);
}