Files
revng-revng/jumptargetmanager.cpp
T
Alessandro Di Federico e407fc11a1 Various minor cleanups
2018-05-30 12:45:45 +02:00

1408 lines
46 KiB
C++

/// \file jumptargetmanager.cpp
/// \brief This file handles the possible jump targets encountered during
/// translation and the creation and management of the respective
/// BasicBlock.
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
// Standard includes
#include <cassert>
#include <cstdint>
#include <fstream>
#include <queue>
#include <sstream>
// Boost includes
#include <boost/icl/interval_set.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/icl/right_open_interval.hpp>
// LLVM includes
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/Endian.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Utils/Cloning.h"
// Local includes
#include "datastructures.h"
#include "debug.h"
#include "generatedcodebasicinfo.h"
#include "ir-helpers.h"
#include "jumptargetmanager.h"
#include "revamb.h"
#include "set.h"
#include "simplifycomparisons.h"
#include "subgraph.h"
using namespace llvm;
static bool isSumJump(StoreInst *PCWrite);
char TranslateDirectBranchesPass::ID = 0;
static RegisterPass<TranslateDirectBranchesPass> X("translate-db",
"Translate Direct Branches"
" Pass",
false,
false);
void TranslateDirectBranchesPass::getAnalysisUsage(AnalysisUsage &AU) const {
AU.addRequired<DominatorTreeWrapperPass>();
AU.addUsedIfAvailable<SETPass>();
AU.setPreservesAll();
}
/// \brief Purges everything is after a call to exitTB (except the call itself)
static void exitTBCleanup(Instruction *ExitTBCall) {
BasicBlock *BB = ExitTBCall->getParent();
// Cleanup everything it's aftewards starting from the end
Instruction *ToDelete = &*(--BB->end());
while (ToDelete != ExitTBCall) {
if (auto DeadBranch = dyn_cast<BranchInst>(ToDelete))
purgeBranch(BasicBlock::iterator(DeadBranch));
else
ToDelete->eraseFromParent();
ToDelete = &*(--BB->end());
}
}
bool TranslateDirectBranchesPass::pinJTs(Function &F) {
const auto *SET = getAnalysisIfAvailable<SETPass>();
if (SET == nullptr || SET->jumps().size() == 0)
return false;
LLVMContext &Context = getContext(&F);
Value *PCReg = JTM->pcReg();
auto *RegType = cast<IntegerType>(PCReg->getType()->getPointerElementType());
auto C = [RegType] (uint64_t A) { return ConstantInt::get(RegType, A); };
BasicBlock *AnyPC = JTM->anyPC();
BasicBlock *UnexpectedPC = JTM->unexpectedPC();
// TODO: enforce CFG
for (const auto &Jump : SET->jumps()) {
StoreInst *PCWrite = Jump.Instruction;
bool Approximate = Jump.Approximate;
const std::vector<uint64_t> &Destinations = Jump.Destinations;
// We don't care if we already handled this call too exitTB in the past,
// information should become progressively more precise, so let's just
// remove everything after this call and put a new handler
CallInst *CallExitTB = JTM->findNextExitTB(PCWrite);
assert(CallExitTB != nullptr);
assert(PCWrite->getParent()->getParent() == &F);
assert(JTM->isPCReg(PCWrite->getPointerOperand()));
assert(Destinations.size() != 0);
auto *ExitTBArg = ConstantInt::get(Type::getInt32Ty(Context),
Destinations.size());
uint64_t OldTargetsCount = getLimitedValue(CallExitTB->getArgOperand(0));
// TODO: we should check Destinations.size() >= OldTargetsCount
// TODO: we should also check the destinations are actually the same
BasicBlock *FailBB = Approximate ? AnyPC : UnexpectedPC;
BasicBlock *BB = CallExitTB->getParent();
// Kill everything is after the call to exitTB
exitTBCleanup(CallExitTB);
// Mark this call to exitTB as handled
CallExitTB->setArgOperand(0, ExitTBArg);
IRBuilder<> Builder(BB);
auto PCLoad = Builder.CreateLoad(PCReg);
if (Destinations.size() == 1) {
auto *Comparison = Builder.CreateICmpEQ(C(Destinations[0]), PCLoad);
Builder.CreateCondBr(Comparison,
JTM->getBlockAt(Destinations[0]),
FailBB);
} else {
auto *Switch = Builder.CreateSwitch(PCLoad, FailBB, Destinations.size());
for (uint64_t Destination : Destinations)
Switch->addCase(C(Destination), JTM->getBlockAt(Destination));
}
// Move all the markers right before the branch instruction
Instruction *Last = BB->getTerminator();
auto It = CallExitTB->getIterator();
while (isMarker(&*It)) {
// Get the marker instructions
Instruction *I = &*It;
// Move the iterator back
It--;
// Move the last moved instruction (initially the terminator)
I->moveBefore(Last);
Last = I;
}
// Notify new branches only if the amount of possible targets actually
// increased
if (Destinations.size() > OldTargetsCount)
JTM->newBranch();
}
return true;
}
bool TranslateDirectBranchesPass::pinConstantStore(Function &F) {
auto &Context = F.getParent()->getContext();
Function *ExitTB = JTM->exitTB();
auto ExitTBIt = ExitTB->use_begin();
while (ExitTBIt != ExitTB->use_end()) {
// Take note of the use and increment the iterator immediately: this allows
// us to erase the call to exit_tb without unexpected behaviors
Use &ExitTBUse = *ExitTBIt++;
if (auto Call = dyn_cast<CallInst>(ExitTBUse.getUser())) {
if (Call->getCalledFunction() == ExitTB) {
// Look for the last write to the PC
StoreInst *PCWrite = JTM->getPrevPCWrite(Call);
// Is destination a constant?
if (PCWrite == nullptr) {
forceFallthroughAfterHelper(Call);
} else {
uint64_t NextPC = JTM->getNextPC(PCWrite);
if (NextPC != 0 && JTM->isOSRAEnabled() && isSumJump(PCWrite))
JTM->registerJT(NextPC, JumpTargetManager::SumJump);
auto *Address = dyn_cast<ConstantInt>(PCWrite->getValueOperand());
if (Address != nullptr) {
// Compute the actual PC and get the associated BasicBlock
uint64_t TargetPC = Address->getSExtValue();
auto *TargetBlock = JTM->registerJT(TargetPC,
JumpTargetManager::DirectJump);
// Remove unreachable right after the exit_tb
BasicBlock::iterator CallIt(Call);
BasicBlock::iterator BlockEnd = Call->getParent()->end();
CallIt++;
assert(CallIt != BlockEnd && isa<UnreachableInst>(&*CallIt));
CallIt->eraseFromParent();
// Cleanup of what's afterwards (only a unconditional jump is
// allowed)
CallIt = BasicBlock::iterator(Call);
BlockEnd = Call->getParent()->end();
if (++CallIt != BlockEnd)
purgeBranch(CallIt);
if (TargetBlock != nullptr) {
// A target was found, jump there
BranchInst::Create(TargetBlock, Call);
JTM->newBranch();
} else {
// We're jumping to an invalid location, abort everything
// TODO: emit a warning
CallInst::Create(F.getParent()->getFunction("abort"), { }, Call);
new UnreachableInst(Context, Call);
}
Call->eraseFromParent();
}
}
} else
llvm_unreachable("Unexpected instruction using the PC");
} else
llvm_unreachable("Unhandled usage of the PC");
}
return true;
}
bool TranslateDirectBranchesPass::forceFallthroughAfterHelper(CallInst *Call) {
// If someone else already took care of the situation, quit
if (getLimitedValue(Call->getArgOperand(0)) > 0)
return false;
auto *PCReg = JTM->pcReg();
auto PCRegTy = PCReg->getType()->getPointerElementType();
bool ForceFallthrough = false;
BasicBlock::reverse_iterator It(make_reverse_iterator(Call));
auto *BB = Call->getParent();
auto EndIt = BB->rend();
while (!ForceFallthrough) {
while (It != EndIt) {
Instruction *I = &*It;
if (auto *Store = dyn_cast<StoreInst>(I)) {
if (Store->getPointerOperand() == PCReg) {
// We found a PC-store, give up
return false;
}
} else if (auto *Call = dyn_cast<CallInst>(I)) {
if (Function *Callee = Call->getCalledFunction()) {
if (Callee->getName().startswith("helper_")) {
// We found a call to an helper
ForceFallthrough = true;
break;
}
}
}
It++;
}
if (!ForceFallthrough) {
// Proceed only to unique predecessor, if present
if (auto *Pred = BB->getUniquePredecessor()) {
BB = Pred;
It = BB->rbegin();
EndIt = BB->rend();
} else {
// We have multiple predecessors, give up
return false;
}
}
}
exitTBCleanup(Call);
JTM->newBranch();
IRBuilder<> Builder(Call->getParent());
Call->setArgOperand(0, Builder.getInt32(1));
// Create the fallthrough jump
uint64_t NextPC = JTM->getNextPC(Call);
Value *NextPCConst = Builder.getIntN(PCRegTy->getIntegerBitWidth(), NextPC);
Builder.CreateCondBr(Builder.CreateICmpEQ(Builder.CreateLoad(PCReg),
NextPCConst),
JTM->registerJT(NextPC, JumpTargetManager::PostHelper),
JTM->anyPC());
return true;
}
bool TranslateDirectBranchesPass::runOnFunction(Function &F) {
pinConstantStore(F);
pinJTs(F);
return true;
}
uint64_t TranslateDirectBranchesPass::getNextPC(Instruction *TheInstruction) {
DominatorTree& DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
BasicBlock *Block = TheInstruction->getParent();
BasicBlock::reverse_iterator It(make_reverse_iterator(TheInstruction));
while (true) {
BasicBlock::reverse_iterator Begin(Block->rend());
// Go back towards the beginning of the basic block looking for a call to
// newpc
CallInst *Marker = nullptr;
for (; It != Begin; It++) {
if ((Marker = dyn_cast<CallInst>(&*It))) {
// TODO: comparing strings is not very elegant
if (Marker->getCalledFunction()->getName() == "newpc") {
uint64_t PC = getLimitedValue(Marker->getArgOperand(0));
uint64_t Size = getLimitedValue(Marker->getArgOperand(1));
assert(Size != 0);
return PC + Size;
}
}
}
auto *Node = DT.getNode(Block);
assert(Node != nullptr &&
"BasicBlock not in the dominator tree, is it reachable?" );
Block = Node->getIDom()->getBlock();
It = Block->rbegin();
}
llvm_unreachable("Can't find the PC marker");
}
Optional<uint64_t>
JumpTargetManager::readRawValue(uint64_t Address,
unsigned Size,
Endianess ReadEndianess) const {
bool IsLittleEndian;
if (ReadEndianess == OriginalEndianess) {
IsLittleEndian = Binary.architecture().isLittleEndian();
} else if (ReadEndianess == DestinationEndianess) {
IsLittleEndian = TheModule.getDataLayout().isLittleEndian();
} else {
abort();
}
for (auto &Segment : Binary.segments()) {
// Note: we also consider writeable memory areas because, despite being
// modifiable, can contain useful information
if (Segment.contains(Address, Size) && Segment.IsReadable) {
// TODO: we ignore .bss here, it might be beneficial to take it into
// account in certain situations
const Constant *Initializer = Segment.Variable->getInitializer();
if (isa<ConstantAggregateZero>(Initializer))
continue;
auto *Array = cast<ConstantDataArray>(Segment.Variable->getInitializer());
StringRef RawData = Array->getRawDataValues();
const unsigned char *RawDataPtr = RawData.bytes_begin();
uint64_t Offset = Address - Segment.StartVirtualAddress;
const unsigned char *Start = RawDataPtr + Offset;
using support::endian::read;
using support::endianness;
switch (Size) {
case 1:
return read<uint8_t, endianness::little, 1>(Start);
case 2:
if (IsLittleEndian)
return read<uint16_t, endianness::little, 1>(Start);
else
return read<uint16_t, endianness::big, 1>(Start);
case 4:
if (IsLittleEndian)
return read<uint32_t, endianness::little, 1>(Start);
else
return read<uint32_t, endianness::big, 1>(Start);
case 8:
if (IsLittleEndian)
return read<uint64_t, endianness::little, 1>(Start);
else
return read<uint64_t, endianness::big, 1>(Start);
default:
assert(false && "Unexpected read size");
}
}
}
return Optional<uint64_t>();
}
Constant *JumpTargetManager::readConstantPointer(Constant *Address,
Type *PointerTy,
Endianess ReadEndianess) {
auto *Value = readConstantInt(Address,
Binary.architecture().pointerSize() / 8,
ReadEndianess);
if (Value != nullptr) {
return ConstantExpr::getIntToPtr(Value, PointerTy);
} else {
return nullptr;
}
}
ConstantInt *JumpTargetManager::readConstantInt(Constant *ConstantAddress,
unsigned Size,
Endianess ReadEndianess) {
const DataLayout &DL = TheModule.getDataLayout();
if (ConstantAddress->getType()->isPointerTy()) {
using CE = ConstantExpr;
auto IntPtrTy = Type::getIntNTy(Context,
Binary.architecture().pointerSize());
ConstantAddress = CE::getPtrToInt(ConstantAddress, IntPtrTy);
}
uint64_t Address = getZExtValue(ConstantAddress, DL);
UnusedCodePointers.erase(Address);
registerReadRange(Address, Size);
auto Result = readRawValue(Address, Size, ReadEndianess);
if (Result.hasValue())
return ConstantInt::get(IntegerType::get(Context, Size * 8),
Result.getValue());
else
return nullptr;
}
template<typename T>
static cl::opt<T> *getOption(StringMap<cl::Option *>& Options,
const char *Name) {
return static_cast<cl::opt<T> *>(Options[Name]);
}
JumpTargetManager::JumpTargetManager(Function *TheFunction,
Value *PCReg,
const BinaryFile &Binary,
bool EnableOSRA) :
TheModule(*TheFunction->getParent()),
Context(TheModule.getContext()),
TheFunction(TheFunction),
OriginalInstructionAddresses(),
JumpTargets(),
PCReg(PCReg),
ExitTB(nullptr),
Dispatcher(nullptr),
DispatcherSwitch(nullptr),
Binary(Binary),
EnableOSRA(EnableOSRA),
NoReturn(Binary.architecture()),
CurrentCFGForm(UnknownFormCFG) {
FunctionType *ExitTBTy = FunctionType::get(Type::getVoidTy(Context),
{ Type::getInt32Ty(Context) },
false);
ExitTB = cast<Function>(TheModule.getOrInsertFunction("exitTB", ExitTBTy));
createDispatcher(TheFunction, PCReg);
for (auto &Segment : Binary.segments())
Segment.insertExecutableRanges(std::back_inserter(ExecutableRanges));
initializeSymbolMap();
// Configure GlobalValueNumbering
StringMap<cl::Option *>& Options(cl::getRegisteredOptions());
getOption<bool>(Options, "enable-load-pre")->setInitialValue(false);
getOption<unsigned>(Options, "memdep-block-scan-limit")->setInitialValue(100);
// getOption<bool>(Options, "enable-pre")->setInitialValue(false);
// getOption<uint32_t>(Options, "max-recurse-depth")->setInitialValue(10);
}
void JumpTargetManager::initializeSymbolMap() {
// Collect how many times each name is used
std::map<std::string, unsigned> SeenCount;
for (const SymbolInfo &Symbol : Binary.symbols())
SeenCount[std::string(Symbol.Name)]++;
for (const SymbolInfo &Symbol : Binary.symbols()) {
// Discard symbols pointing to 0, with zero-sized names or present multiple
// times. Note that we keep zero-size symbols.
if (Symbol.Address == 0
|| Symbol.Name.size() == 0
|| SeenCount[std::string(Symbol.Name)] > 1)
continue;
// Associate to this interval the symbol
unsigned Size = std::max(1UL, Symbol.Size);
auto NewInterval = interval::right_open(Symbol.Address,
Symbol.Address + Size);
SymbolMap += make_pair(NewInterval, SymbolInfoSet { &Symbol });
}
}
// TODO: move this in BinaryFile?
std::string JumpTargetManager::nameForAddress(uint64_t Address) const {
std::stringstream Result;
// Take the interval greater than [Address, Address + 1[
auto It = SymbolMap.upper_bound(interval::right_open(Address, Address + 1));
if (It != SymbolMap.begin()) {
// Go back one position
It--;
// In case we have multiple matching symbols, take the closest one
const SymbolInfoSet &Matching = It->second;
auto MaxIt = std::max_element(Matching.begin(), Matching.end());
const SymbolInfo *const BestMatch = *MaxIt;
// Use the symbol name
Result << BestMatch->Name.str();
// And, if necessary, an offset
if (Address != BestMatch->Address)
Result << ".0x" << std::hex << (Address - BestMatch->Address);
} else {
// We don't have a symbol to use, just return the address
Result << "0x" << std::hex << Address;
}
return Result.str();
}
void JumpTargetManager::harvestGlobalData() {
// Register landing pads, if available
// TODO: should register them in UnusedCodePointers?
for (uint64_t LandingPad : Binary.landingPads())
registerJT(LandingPad, GlobalData);
for (auto& Segment : Binary.segments()) {
const Constant *Initializer = Segment.Variable->getInitializer();
if (isa<ConstantAggregateZero>(Initializer))
continue;
auto *Data = cast<ConstantDataArray>(Initializer);
uint64_t StartVirtualAddress = Segment.StartVirtualAddress;
const unsigned char *DataStart = Data->getRawDataValues().bytes_begin();
const unsigned char *DataEnd = Data->getRawDataValues().bytes_end();
using endianness = support::endianness;
if (Binary.architecture().pointerSize() == 64) {
if (Binary.architecture().isLittleEndian())
findCodePointers<uint64_t, endianness::little>(StartVirtualAddress,
DataStart,
DataEnd);
else
findCodePointers<uint64_t, endianness::big>(StartVirtualAddress,
DataStart,
DataEnd);
} else if (Binary.architecture().pointerSize() == 32) {
if (Binary.architecture().isLittleEndian())
findCodePointers<uint32_t, endianness::little>(StartVirtualAddress,
DataStart,
DataEnd);
else
findCodePointers<uint32_t, endianness::big>(StartVirtualAddress,
DataStart,
DataEnd);
}
}
DBG("jtcount", dbg
<< "JumpTargets found in global data: " << std::dec
<< Unexplored.size() << "\n");
}
template<typename value_type, unsigned endian>
void JumpTargetManager::findCodePointers(uint64_t StartVirtualAddress,
const unsigned char *Start,
const unsigned char *End) {
using support::endian::read;
using support::endianness;
for (auto Pos = Start; Pos < End - sizeof(value_type); Pos++) {
uint64_t Value = read<value_type,
static_cast<endianness>(endian),
1>(Pos);
BasicBlock *Result = registerJT(Value, GlobalData);
if (Result != nullptr)
UnusedCodePointers.insert(StartVirtualAddress + (Pos - Start));
}
}
/// Handle a new program counter. We might already have a basic block for that
/// program counter, or we could even have a translation for it. Return one of
/// these, if appropriate.
///
/// \param PC the new program counter.
/// \param ShouldContinue an out parameter indicating whether the returned
/// basic block was just a placeholder or actually contains a
/// translation.
///
/// \return the basic block to use from now on, or null if the program counter
/// is not associated to a basic block.
// TODO: make this return a pair
BasicBlock *JumpTargetManager::newPC(uint64_t PC, bool& ShouldContinue) {
// Did we already meet this PC?
auto JTIt = JumpTargets.find(PC);
if (JTIt != JumpTargets.end()) {
// If it was planned to explore it in the future, just to do it now
for (auto UnexploredIt = Unexplored.begin();
UnexploredIt != Unexplored.end();
UnexploredIt++) {
if (UnexploredIt->first == PC) {
auto Result = UnexploredIt->second;
Unexplored.erase(UnexploredIt);
ShouldContinue = true;
assert(Result->empty());
return Result;
}
}
// It wasn't planned to visit it, so we've already been there, just jump
// there
BasicBlock *BB = JTIt->second.head();
assert(!BB->empty());
ShouldContinue = false;
return BB;
}
// Check if we already translated this PC even if it's not associated to a
// basic block (i.e., we have to split its basic block). This typically
// happens with variable-length instruction encodings.
if (OriginalInstructionAddresses.count(PC) != 0) {
ShouldContinue = false;
return registerJT(PC, AmbigousInstruction);
}
// We don't know anything about this PC
return nullptr;
}
/// Save the PC-Instruction association for future use (jump target)
void JumpTargetManager::registerInstruction(uint64_t PC,
Instruction *Instruction) {
// Never save twice a PC
assert(!OriginalInstructionAddresses.count(PC));
OriginalInstructionAddresses[PC] = Instruction;
}
CallInst *JumpTargetManager::findNextExitTB(Instruction *Start) {
CallInst *Result = nullptr;
visitSuccessors(Start,
make_blacklist(*this),
[this,&Result] (BasicBlockRange Range) {
for (Instruction &I : Range) {
if (auto *Call = dyn_cast<CallInst>(&I)) {
assert(!(Call->getCalledFunction()->getName() == "newpc"));
if (Call->getCalledFunction() == ExitTB) {
assert(Result == nullptr);
Result = Call;
return ExhaustQueueAndStop;
}
}
}
return Continue;
});
return Result;
}
StoreInst *JumpTargetManager::getPrevPCWrite(Instruction *TheInstruction) {
// Look for the last write to the PC
BasicBlock::iterator I(TheInstruction);
BasicBlock::iterator Begin(TheInstruction->getParent()->begin());
while (I != Begin) {
I--;
Instruction *Current = &*I;
auto *Store = dyn_cast<StoreInst>(Current);
if (Store != nullptr && Store->getPointerOperand() == PCReg)
return Store;
// If we meet a call to an helper, return nullptr
// TODO: for now we just make calls to helpers, is this is OK even if we
// split the translated function in multiple functions?
if (isa<CallInst>(Current))
return nullptr;
}
// TODO: handle the following case:
// pc = x
// brcond ?, a, b
// a:
// pc = y
// br b
// b:
// exitTB
// TODO: emit warning
return nullptr;
}
// TODO: this is outdated and we should drop it, we now have OSRA and friends
/// \brief Tries to detect pc += register In general, we assume what we're
/// translating is code emitted by a compiler. This means that usually all the
/// possible jump targets are explicit jump to a constant or are stored
/// somewhere in memory (e.g. jump tables and vtables). However, in certain
/// cases, mainly due to handcrafted assembly we can have a situation like the
/// following:
///
/// addne pc, pc, \\curbit, lsl #2
///
/// (taken from libgcc ARM's lib1funcs.S, specifically line 592 of
/// `libgcc/config/arm/lib1funcs.S` at commit
/// `f1717362de1e56fe1ffab540289d7d0c6ed48b20`)
///
/// This code basically jumps forward a number of instructions depending on a
/// run-time value. Therefore, without further analysis, potentially, all the
/// coming instructions are jump targets.
///
/// To workaround this issue we use a simple heuristics, which basically
/// consists in making all the coming instructions possible jump targets until
/// the next write to the PC. In the future, we could extend this until the end
/// of the function.
static bool isSumJump(StoreInst *PCWrite) {
// * Follow the written value recursively
// * Is it a `load` or a `constant`? Fine. Don't proceed.
// * Is it an `and`? Enqueue the operands in the worklist.
// * Is it an `add`? Make all the coming instructions jump targets.
//
// This approach has a series of problems:
//
// * It doesn't work with delay slots. Delay slots are handled by libtinycode
// as follows:
//
// jump lr
// store btarget, lr
// store 3, r0
// store 3, r0
// store btarget, pc
//
// Clearly, if we don't follow the loads we miss the situation we're trying
// to handle.
// * It is unclear how this would perform without EarlyCSE and SROA.
std::queue<Value *> WorkList;
WorkList.push(PCWrite->getValueOperand());
while (!WorkList.empty()) {
Value *V = WorkList.front();
WorkList.pop();
if (isa<Constant>(V) || isa<LoadInst>(V)) {
// Fine
} else if (auto *BinOp = dyn_cast<BinaryOperator>(V)) {
switch (BinOp->getOpcode()) {
case Instruction::Add:
case Instruction::Or:
return true;
case Instruction::Shl:
case Instruction::LShr:
case Instruction::AShr:
case Instruction::And:
for (auto& Operand : BinOp->operands())
if (!isa<Constant>(Operand.get()))
WorkList.push(Operand.get());
break;
default:
// TODO: emit warning
return false;
}
} else {
// TODO: emit warning
return false;
}
}
return false;
}
std::pair<uint64_t, uint64_t>
JumpTargetManager::getPC(Instruction *TheInstruction) const {
CallInst *NewPCCall = nullptr;
std::set<BasicBlock *> Visited;
std::queue<BasicBlock::reverse_iterator> WorkList;
if (TheInstruction->getIterator() == TheInstruction->getParent()->begin())
WorkList.push(--TheInstruction->getParent()->rend());
else
WorkList.push(make_reverse_iterator(TheInstruction));
while (!WorkList.empty()) {
auto I = WorkList.front();
WorkList.pop();
auto *BB = I->getParent();
auto End = BB->rend();
// Go through the instructions looking for calls to newpc
for (; I != End; I++) {
if (auto Marker = dyn_cast<CallInst>(&*I)) {
// TODO: comparing strings is not very elegant
auto *Callee = Marker->getCalledFunction();
if (Callee != nullptr && Callee->getName() == "newpc") {
// We found two distinct newpc leading to the requested instruction
if (NewPCCall != nullptr)
return { 0, 0 };
NewPCCall = Marker;
break;
}
}
}
// If we haven't find a newpc call yet, continue exploration backward
if (NewPCCall == nullptr) {
// If one of the predecessors is the dispatcher, don't explore any further
for (BasicBlock *Predecessor : predecessors(BB)) {
// Assert we didn't reach the almighty dispatcher
assert(!(NewPCCall == nullptr && Predecessor == Dispatcher));
if (Predecessor == Dispatcher)
continue;
}
for (BasicBlock *Predecessor : predecessors(BB)) {
// Ignore already visited or empty BBs
if (!Predecessor->empty()
&& Visited.find(Predecessor) == Visited.end()) {
WorkList.push(Predecessor->rbegin());
Visited.insert(Predecessor);
}
}
}
}
// Couldn't find the current PC
if (NewPCCall == nullptr)
return { 0, 0 };
uint64_t PC = getLimitedValue(NewPCCall->getArgOperand(0));
uint64_t Size = getLimitedValue(NewPCCall->getArgOperand(1));
assert(Size != 0);
return { PC, Size };
}
void JumpTargetManager::handleSumJump(Instruction *SumJump) {
// Take the next PC
uint64_t NextPC = getNextPC(SumJump);
assert(NextPC != 0);
BasicBlock *BB = registerJT(NextPC, JumpTargetManager::SumJump);
assert(BB && !BB->empty());
std::set<BasicBlock *> Visited;
Visited.insert(Dispatcher);
std::queue<BasicBlock *> WorkList;
WorkList.push(BB);
while (!WorkList.empty()) {
BB = WorkList.front();
Visited.insert(BB);
WorkList.pop();
BasicBlock::iterator I(BB->begin());
BasicBlock::iterator End(BB->end());
while (I != End) {
// Is it a new PC marker?
if (auto *Call = dyn_cast<CallInst>(&*I)) {
Function *Callee = Call->getCalledFunction();
// TODO: comparing strings is not very elegant
if (Callee != nullptr && Callee->getName() == "newpc") {
uint64_t PC = getLimitedValue(Call->getArgOperand(0));
// If we've found a (direct or indirect) jump, stop
if (PC != NextPC)
return;
// Split and update iterators to proceed
BB = registerJT(PC, JumpTargetManager::SumJump);
// Do we have a block?
if (BB == nullptr)
return;
I = BB->begin();
End = BB->end();
// Updated the expectation for the next PC
NextPC = PC + getLimitedValue(Call->getArgOperand(1));
} else if (Call->getCalledFunction() == ExitTB) {
// We've found an unparsed indirect jump
return;
}
}
// Proceed to next instruction
I++;
}
// Inspect and enqueue successors
for (BasicBlock *Successor : successors(BB))
if (Visited.find(Successor) == Visited.end())
WorkList.push(Successor);
}
}
/// \brief Class to iterate over all the BBs associated to a translated PC
class BasicBlockVisitor {
public:
BasicBlockVisitor(const SwitchInst *Dispatcher) :
Dispatcher(Dispatcher),
JumpTargetIndex(0),
JumpTargetsCount(Dispatcher->getNumSuccessors()),
DL(Dispatcher->getParent()->getParent()->getParent()->getDataLayout()) { }
void enqueue(BasicBlock *BB) {
if (Visited.count(BB))
return;
Visited.insert(BB);
uint64_t PC = getPC(BB);
if (PC == 0)
SamePC.push(BB);
else
NewPC.push({ BB, PC });
}
// TODO: this function assumes 0 is not a valid PC
std::pair<BasicBlock *, uint64_t> pop() {
if (!SamePC.empty()) {
auto Result = SamePC.front();
SamePC.pop();
return { Result, 0 };
} else if (!NewPC.empty()) {
auto Result = NewPC.front();
NewPC.pop();
return Result;
} else if (JumpTargetIndex < JumpTargetsCount) {
BasicBlock *BB = Dispatcher->getSuccessor(JumpTargetIndex);
JumpTargetIndex++;
return { BB, getPC(BB) };
} else {
return { nullptr, 0 };
}
}
private:
// TODO: this function assumes 0 is not a valid PC
uint64_t getPC(BasicBlock *BB) {
if (!BB->empty()) {
if (auto *Call = dyn_cast<CallInst>(&*BB->begin())) {
Function *Callee = Call->getCalledFunction();
// TODO: comparing with "newpc" string is sad
if (Callee != nullptr && Callee->getName() == "newpc") {
Constant *PCOperand = cast<Constant>(Call->getArgOperand(0));
return getZExtValue(PCOperand, DL);
}
}
}
return 0;
}
private:
const SwitchInst *Dispatcher;
unsigned JumpTargetIndex;
unsigned JumpTargetsCount;
const DataLayout &DL;
std::set<BasicBlock *> Visited;
std::queue<BasicBlock *> SamePC;
std::queue<std::pair<BasicBlock *, uint64_t>> NewPC;
};
void JumpTargetManager::translateIndirectJumps() {
if (ExitTB->use_empty())
return;
auto I = ExitTB->use_begin();
while (I != ExitTB->use_end()) {
Use& ExitTBUse = *I++;
if (auto *Call = dyn_cast<CallInst>(ExitTBUse.getUser())) {
if (Call->getCalledFunction() == ExitTB) {
// Look for the last write to the PC
StoreInst *PCWrite = getPrevPCWrite(Call);
assert((PCWrite == nullptr
|| !isa<ConstantInt>(PCWrite->getValueOperand()))
&& "Direct jumps should not be handled here");
if (PCWrite != nullptr && EnableOSRA && isSumJump(PCWrite))
handleSumJump(PCWrite);
if (getLimitedValue(Call->getArgOperand(0)) == 0) {
exitTBCleanup(Call);
BranchInst::Create(Dispatcher, Call);
}
Call->eraseFromParent();
}
}
}
assert(ExitTB->use_empty());
ExitTB->eraseFromParent();
ExitTB = nullptr;
}
JumpTargetManager::BlockWithAddress JumpTargetManager::peek() {
harvest();
// Purge all the partial translations we know might be wrong
for (BasicBlock *BB : ToPurge)
purgeTranslation(BB);
ToPurge.clear();
if (Unexplored.empty())
return NoMoreTargets;
else {
BlockWithAddress Result = Unexplored.back();
Unexplored.pop_back();
return Result;
}
}
void JumpTargetManager::unvisit(BasicBlock *BB) {
if (Visited.find(BB) != Visited.end()) {
std::vector<BasicBlock *> WorkList;
WorkList.push_back(BB);
while (!WorkList.empty()) {
BasicBlock *Current = WorkList.back();
WorkList.pop_back();
Visited.erase(Current);
for (BasicBlock *Successor : successors(BB)) {
if (Visited.find(Successor) != Visited.end()
&& !Successor->empty()) {
auto *Call = dyn_cast<CallInst>(&*Successor->begin());
if (Call == nullptr
|| Call->getCalledFunction()->getName() != "newpc") {
WorkList.push_back(Successor);
}
}
}
}
}
}
BasicBlock *JumpTargetManager::getBlockAt(uint64_t PC) {
auto TargetIt = JumpTargets.find(PC);
assert(TargetIt != JumpTargets.end());
return TargetIt->second.head();
}
void JumpTargetManager::purgeTranslation(BasicBlock *Start) {
OnceQueue<BasicBlock *> Queue;
Queue.insert(Start);
// Collect all the descendats, except if we meet a jump target
while (!Queue.empty()) {
BasicBlock *BB = Queue.pop();
for (BasicBlock *Successor : successors(BB)) {
if (isTranslatedBB(Successor)
&& !isJumpTarget(Successor)
&& !hasPredecessor(Successor, Dispatcher)) {
Queue.insert(Successor);
}
}
}
// Erase all the visited basic blocks
std::set<BasicBlock *> Visited = Queue.visited();
// Build a subgraph, so that we can visit it in post order, and purge the
// content of each basic block
SubGraph<BasicBlock *> TranslatedBBs(Start, Visited);
for (auto *Node : post_order(TranslatedBBs)) {
BasicBlock *BB = Node->get();
while (!BB->empty())
eraseInstruction(&*(--BB->end()));
}
// Remove Start, since we want to keep it (even if empty)
Visited.erase(Start);
for (BasicBlock *BB : Visited) {
// We might have some predecessorless basic blocks jumping to us, purge them
// TODO: why this?
while (pred_begin(BB) != pred_end(BB)) {
BasicBlock *Predecessor = *pred_begin(BB);
assert(pred_empty(Predecessor));
Predecessor->eraseFromParent();
}
assert(BB->use_empty());
BB->eraseFromParent();
}
}
// TODO: register Reason
BasicBlock *JumpTargetManager::registerJT(uint64_t PC, JTReason Reason) {
if (!isExecutableAddress(PC) || !isInstructionAligned(PC))
return nullptr;
// Do we already have a BasicBlock for this PC?
BlockMap::iterator TargetIt = JumpTargets.find(PC);
if (TargetIt != JumpTargets.end()) {
// Case 1: there's already a BasicBlock for that address, return it
BasicBlock *BB = TargetIt->second.head();
TargetIt->second.setReason(Reason);
unvisit(BB);
return BB;
}
// Did we already meet this PC (i.e. do we know what's the associated
// instruction)?
BasicBlock *NewBlock = nullptr;
InstructionMap::iterator InstrIt = OriginalInstructionAddresses.find(PC);
if (InstrIt != OriginalInstructionAddresses.end()) {
// Case 2: the address has already been met, but needs to be promoted to
// BasicBlock level.
Instruction *I = InstrIt->second;
BasicBlock *ContainingBlock = I->getParent();
if (isFirst(I)) {
NewBlock = ContainingBlock;
} else {
assert(I != nullptr && I != ContainingBlock->end());
NewBlock = ContainingBlock->splitBasicBlock(I);
}
// Register the basic block and all of its descendants to be purged so that
// we can retranslate this PC
// TODO: this might create a problem if QEMU generates control flow that
// crosses an instruction boundary
ToPurge.insert(NewBlock);
unvisit(NewBlock);
} else {
// Case 3: the address has never been met, create a temporary one, register
// it for future exploration and return it
NewBlock = BasicBlock::Create(Context, "", TheFunction);
}
Unexplored.push_back(BlockWithAddress(PC, NewBlock));
if (NewBlock->getName().empty()) {
std::stringstream Name;
Name << "bb." << nameForAddress(PC);
NewBlock->setName(Name.str());
}
// Create a case for the address associated to the new block
auto *PCRegType = PCReg->getType();
auto *SwitchType = cast<IntegerType>(PCRegType->getPointerElementType());
DispatcherSwitch->addCase(ConstantInt::get(SwitchType, PC), NewBlock);
// Associate the PC with the chosen basic block
JumpTargets[PC] = JumpTarget(NewBlock, Reason);
return NewBlock;
}
void JumpTargetManager::registerReadRange(uint64_t Address, uint64_t Size) {
using interval = boost::icl::interval<uint64_t>;
ReadIntervalSet += interval::right_open(Address, Address + Size);
}
// TODO: instead of a gigantic switch case we could map the original memory area
// and write the address of the translated basic block at the jump target
// If this function looks weird it's because it has been designed to be able
// to create the dispatcher in the "root" function or in a standalone function
void JumpTargetManager::createDispatcher(Function *OutputFunction,
Value *SwitchOnPtr) {
IRBuilder<> Builder(Context);
QuickMetadata QMD(Context);
// Create the first block of the dispatcher
BasicBlock *Entry = BasicBlock::Create(Context,
"dispatcher.entry",
OutputFunction);
// The default case of the switch statement it's an unhandled cases
DispatcherFail = BasicBlock::Create(Context,
"dispatcher.default",
OutputFunction);
Builder.SetInsertPoint(DispatcherFail);
Module *TheModule = TheFunction->getParent();
auto *UnknownPCTy = FunctionType::get(Type::getVoidTy(Context), { }, false);
Constant *UnknownPC = TheModule->getOrInsertFunction("unknownPC",
UnknownPCTy);
Builder.CreateCall(cast<Function>(UnknownPC));
auto *FailUnreachable = Builder.CreateUnreachable();
FailUnreachable->setMetadata("revamb.block.type",
QMD.tuple(DispatcherFailure));
// Switch on the first argument of the function
Builder.SetInsertPoint(Entry);
Value *SwitchOn = Builder.CreateLoad(SwitchOnPtr);
SwitchInst *Switch = Builder.CreateSwitch(SwitchOn, DispatcherFail);
// The switch is the terminator of the dispatcher basic block
Switch->setMetadata("revamb.block.type", QMD.tuple(DispatcherBlock));
Dispatcher = Entry;
DispatcherSwitch = Switch;
NoReturn.setDispatcher(Dispatcher);
// Create basic blocks to handle jumps to any PC and to a PC we didn't expect
AnyPC = BasicBlock::Create(Context, "anypc", OutputFunction);
UnexpectedPC = BasicBlock::Create(Context, "unexpectedpc", OutputFunction);
setCFGForm(SemanticPreservingCFG);
}
static void purge(BasicBlock *BB) {
// Allow up to a single instruction in the basic block
if (!BB->empty())
BB->begin()->eraseFromParent();
assert(BB->empty());
}
void JumpTargetManager::setCFGForm(CFGForm NewForm) {
assert(CurrentCFGForm != NewForm);
assert(NewForm != UnknownFormCFG);
CFGForm OldForm = CurrentCFGForm;
CurrentCFGForm = NewForm;
switch (NewForm) {
case SemanticPreservingCFG:
purge(AnyPC);
BranchInst::Create(dispatcher(), AnyPC);
// TODO: Here we should have an hard fail, since it's the situation in
// which we expected to know where execution could go but we made a
// mistake.
purge(UnexpectedPC);
BranchInst::Create(dispatcher(), UnexpectedPC);
break;
case RecoveredOnlyCFG:
case NoFunctionCallsCFG:
purge(AnyPC);
new UnreachableInst(Context, AnyPC);
purge(UnexpectedPC);
new UnreachableInst(Context, UnexpectedPC);
break;
default:
assert(false && "Not implemented yet");
break;
}
QuickMetadata QMD(Context);
AnyPC->getTerminator()->setMetadata("revamb.block.type",
QMD.tuple(AnyPCBlock));
UnexpectedPC->getTerminator()->setMetadata("revamb.block.type",
QMD.tuple(UnexpectedPCBlock));
// If we're entering or leaving the NoFunctionCallsCFG form, update all the
// branch instruction forming a function call
if (NewForm == NoFunctionCallsCFG || OldForm == NoFunctionCallsCFG) {
if (auto *FunctionCall = TheModule.getFunction("function_call")) {
for (User *U : FunctionCall->users()) {
auto *Call = cast<CallInst>(U);
// Ignore indirect calls
// TODO: why this is needed is unclear
if (isa<ConstantPointerNull>(Call->getArgOperand(0)))
continue;
auto *Terminator = cast<TerminatorInst>(nextNonMarker(Call));
assert(Terminator->getNumSuccessors() == 1);
// Get the correct argument, the first is the callee, the second the
// return basic block
Value *Op = Call->getArgOperand(NewForm == NoFunctionCallsCFG ? 1 : 0);
BasicBlock *NewSuccessor = cast<BlockAddress>(Op)->getBasicBlock();
Terminator->setSuccessor(0, NewSuccessor);
}
}
}
rebuildDispatcher();
}
void JumpTargetManager::rebuildDispatcher() {
// Remove all cases
unsigned NumCases = DispatcherSwitch->getNumCases();
while (NumCases --> 0)
DispatcherSwitch->removeCase(DispatcherSwitch->case_begin());
auto *PCRegType = PCReg->getType()->getPointerElementType();
auto *SwitchType = cast<IntegerType>(PCRegType);
// Add all the jump targets if we're using the SemanticPreservingCFG, or
// only those with no predecessors otherwise
for (auto &P : JumpTargets) {
uint64_t PC = P.first;
BasicBlock *BB = P.second.head();
if (CurrentCFGForm == SemanticPreservingCFG || !hasPredecessors(BB))
DispatcherSwitch->addCase(ConstantInt::get(SwitchType, PC), BB);
}
}
bool JumpTargetManager::hasPredecessors(BasicBlock *BB) const {
for (BasicBlock *Pred : predecessors(BB))
if (isTranslatedBB(Pred))
return true;
return false;
}
// Harvesting proceeds trying to avoid to run expensive analyses if not strictly
// necessary, OSRA in particular. To do this we keep in mind two aspects: do we
// have new basic blocks to visit? If so, we avoid any further anyalysis and
// give back control to the translator. If not, we proceed with other analyses
// until we either find a new basic block to translate. If we can't find a new
// block to translate we proceed as long as we are able to create new edges on
// the CFG (not considering the dispatcher).
void JumpTargetManager::harvest() {
if (empty()) {
// TODO: move me to a commit function
// Update the third argument of newpc calls (isJT, i.e., is this instruction
// a jump target?)
IRBuilder<> Builder(Context);
Function *NewPCFunction = TheModule.getFunction("newpc");
if (NewPCFunction != nullptr) {
for (User *U : NewPCFunction->users()) {
auto *Call = cast<CallInst>(U);
if (Call->getParent() != nullptr) {
// Report the instruction on the coverage CSV
using CI = ConstantInt;
uint64_t PC = (cast<CI>(Call->getArgOperand(0)))->getLimitedValue();
bool IsJT = isJumpTarget(PC);
Call->setArgOperand(2, Builder.getInt32(static_cast<uint32_t>(IsJT)));
}
}
}
DBG("verify", if (verifyModule(TheModule, &dbgs())) { abort(); });
DBG("jtcount", dbg << "Harvesting: SROA, ConstProp, EarlyCSE and SET\n");
legacy::PassManager OptimizingPM;
OptimizingPM.add(createSROAPass());
OptimizingPM.add(createConstantPropagationPass());
OptimizingPM.add(createEarlyCSEPass());
OptimizingPM.run(TheModule);
// To improve the quality of our analysis, keep in the CFG only the edges we
// where able to recover (e.g., no jumps to the dispatcher)
setCFGForm(RecoveredOnlyCFG);
NewBranches = 0;
legacy::PassManager AnalysisPM;
AnalysisPM.add(new SETPass(this, false, &Visited));
AnalysisPM.add(new TranslateDirectBranchesPass(this));
AnalysisPM.run(TheModule);
// Restore the CFG
setCFGForm(SemanticPreservingCFG);
DBG("jtcount", dbg << std::dec
<< Unexplored.size() << " new jump targets and "
<< NewBranches << " new branches were found\n");
}
if (EnableOSRA && empty()) {
DBG("verify", if (verifyModule(TheModule, &dbgs())) { abort(); });
NoReturn.registerSyscalls(TheFunction);
do {
DBG("jtcount",
dbg << "Harvesting: reset Visited, "
<< (NewBranches > 0 ? "SROA, ConstProp, EarlyCSE, " : "")
<< "SET + OSRA\n");
// TODO: decide what to do with Visited
Visited.clear();
legacy::PassManager PM;
if (NewBranches > 0) {
legacy::PassManager OptimizingPM;
OptimizingPM.add(createSROAPass());
OptimizingPM.add(createConstantPropagationPass());
OptimizingPM.add(createEarlyCSEPass());
OptimizingPM.run(TheModule);
}
setCFGForm(RecoveredOnlyCFG);
NewBranches = 0;
legacy::PassManager AnalysisPM;
AnalysisPM.add(new SETPass(this, true, &Visited));
AnalysisPM.add(new TranslateDirectBranchesPass(this));
AnalysisPM.run(TheModule);
// Restore the CFG
setCFGForm(SemanticPreservingCFG);
DBG("jtcount", dbg << std::dec
<< Unexplored.size() << " new jump targets and "
<< NewBranches << " new branches were found\n");
} while (empty() && NewBranches > 0);
}
if (empty()) {
DBG("jtcount", dbg<< "We're done looking for jump targets\n");
}
}
const JumpTargetManager::BlockWithAddress JumpTargetManager::NoMoreTargets =
JumpTargetManager::BlockWithAddress(0, nullptr);