Files
revng-revng/jumptargetmanager.cpp
T
Alessandro Di Federico 069ae70d3e Fix handling of devirtualized calls
When we have an indirect call (or jump) we are sometimes able to
identify one or more possible targets, therefore, as an optimization,
before performing the indirect jump we check if the target is one of the
expected ones.

This optimization however was creating two issues with the handling of
indirect function calls: 1) the call to the `function_call` marker was
no longer positioned right before the terminator and 2) the function
call was no longer identified as an indirect function call but as call
to `anyPC`. This commit fixes these two issues.

These issues have been identified thanks to a report from Andrea
Gussoni.
2017-08-13 16:47:18 +02:00

1409 lines
47 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, true);
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,
bool JumpDirectly) {
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);