mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
069ae70d3e
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.
1409 lines
47 KiB
C++
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);
|