mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
1277 lines
42 KiB
C++
1277 lines
42 KiB
C++
/// \file JumpTargetManager.cpp
|
|
/// 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.
|
|
//
|
|
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/IR/LegacyPassManager.h"
|
|
#include "llvm/Support/Progress.h"
|
|
#include "llvm/Transforms/Scalar.h"
|
|
|
|
#include "revng/Lift/Lift.h"
|
|
#include "revng/Model/VerifyHelper.h"
|
|
#include "revng/Support/FunctionTags.h"
|
|
#include "revng/Support/MetaAddress.h"
|
|
#include "revng/Support/Statistics.h"
|
|
|
|
#include "JumpTargetManager.h"
|
|
#include "RootAnalyzer.h"
|
|
#include "SubGraph.h"
|
|
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
|
|
Logger<> JTCountLog("jtcount");
|
|
Logger<> RegisterJTLog("registerjt");
|
|
|
|
CounterMap<std::string> HarvestingStats("harvesting");
|
|
|
|
RegisterPass<TranslateDirectBranchesPass> X("translate-db",
|
|
"Translate Direct Branches"
|
|
" Pass",
|
|
false,
|
|
false);
|
|
|
|
} // namespace
|
|
|
|
char TranslateDirectBranchesPass::ID = 0;
|
|
|
|
void TranslateDirectBranchesPass::getAnalysisUsage(AnalysisUsage &AU) const {
|
|
AU.addRequired<DominatorTreeWrapperPass>();
|
|
AU.setPreservesAll();
|
|
}
|
|
|
|
static void exitTBCleanup(Instruction *ExitTBCall) {
|
|
// TODO: for some reason we don't always have a terminator
|
|
if (auto *T = nextNonMarker(ExitTBCall))
|
|
eraseFromParent(T);
|
|
}
|
|
|
|
using TDBP = TranslateDirectBranchesPass;
|
|
|
|
TDBP::TranslateDirectBranchesPass(JumpTargetManager *J) :
|
|
ModulePass(ID), JTM(J), PCH(J->programCounterHandler()) {
|
|
}
|
|
|
|
using DispatcherTargets = ProgramCounterHandler::DispatcherTargets;
|
|
void TDBP::pinExitTB(CallInst *ExitTBCall, DispatcherTargets &Destinations) {
|
|
revng_assert(ExitTBCall != nullptr);
|
|
revng_assert(Destinations.size() != 0);
|
|
|
|
LLVMContext &Context = getContext(ExitTBCall);
|
|
BasicBlock *AnyPC = JTM->anyPC();
|
|
BasicBlock *UnexpectedPC = JTM->unexpectedPC();
|
|
BasicBlock *Source = ExitTBCall->getParent();
|
|
|
|
using CI = ConstantInt;
|
|
auto *ExitTBArg = CI::get(Type::getInt32Ty(Context), Destinations.size());
|
|
uint64_t OldTargetsCount = getLimitedValue(ExitTBCall->getArgOperand(0));
|
|
|
|
// TODO: we should check Destinations.size() >= OldTargetsCount
|
|
// TODO: we should also check the destinations are actually the same
|
|
|
|
BasicBlock *BB = ExitTBCall->getParent();
|
|
|
|
if (auto *Dispatcher = dyn_cast<SwitchInst>(BB->getTerminator()))
|
|
PCH->destroyDispatcher(Dispatcher);
|
|
|
|
// Kill everything is after the call to exitTB
|
|
exitTBCleanup(ExitTBCall);
|
|
|
|
// Mark this call to exitTB as handled
|
|
ExitTBCall->setArgOperand(0, ExitTBArg);
|
|
|
|
PCH->buildDispatcher(Destinations,
|
|
BB,
|
|
UnexpectedPC,
|
|
BlockType::IndirectBranchDispatcherHelperBlock);
|
|
|
|
// Move all the markers right before the branch instruction
|
|
Instruction *Last = BB->getTerminator();
|
|
auto It = ExitTBCall->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->recordNewBranches(Source, Destinations.size() - OldTargetsCount);
|
|
}
|
|
|
|
bool TDBP::pinMaterializedValues(Function &F) {
|
|
QuickMetadata QMD(getContext(&F));
|
|
Module *M = F.getParent();
|
|
|
|
// Lazily create the `jump_to_symbol` marker
|
|
auto *JumpToSymbolMarker = M->getFunction("jump_to_symbol");
|
|
if (JumpToSymbolMarker == nullptr) {
|
|
LLVMContext &C = M->getContext();
|
|
auto *FT = FunctionType::get(Type::getVoidTy(C),
|
|
{ Type::getInt8PtrTy(C) },
|
|
false);
|
|
JumpToSymbolMarker = Function::Create(FT,
|
|
GlobalValue::ExternalLinkage,
|
|
"jump_to_symbol",
|
|
M);
|
|
FunctionTags::Marker.addTo(JumpToSymbolMarker);
|
|
}
|
|
|
|
Function *ExitTB = JTM->exitTB();
|
|
for (User *U : ExitTB->users()) {
|
|
if (auto *Call = dyn_cast<CallInst>(U)) {
|
|
if (Call->getParent()->getParent() != &F)
|
|
continue;
|
|
|
|
auto *MD = Call->getMetadata("revng.targets");
|
|
if (auto *T = dyn_cast_or_null<MDTuple>(MD)) {
|
|
// Compute the list of MetaAddress/symbols destinations
|
|
SmallVector<llvm::StringRef> SymbolDestinations;
|
|
SmallVector<MetaAddress> DirectDestinations;
|
|
for (const MDOperand &Operand : T->operands()) {
|
|
auto *Tuple = QMD.extract<MDTuple *>(Operand.get());
|
|
auto SymbolName = QMD.extract<StringRef>(Tuple->getOperand(0).get());
|
|
auto *Value = QMD.extract<ConstantInt *>(Tuple->getOperand(1).get());
|
|
bool HasDynamicSymbol = SymbolName.size() != 0;
|
|
if (HasDynamicSymbol) {
|
|
SymbolDestinations.push_back(SymbolName);
|
|
} else {
|
|
auto Address = MetaAddress::decomposeIntegerPC(Value);
|
|
revng_assert(Address.isValid());
|
|
DirectDestinations.push_back(Address);
|
|
}
|
|
}
|
|
|
|
// We handle two situations: all DirectDestinations or one symbol
|
|
// destination
|
|
bool HasDirectDestinations = DirectDestinations.size() > 0;
|
|
bool HasSymbolDestinations = SymbolDestinations.size() > 0;
|
|
bool HasOneSymbolDestination = SymbolDestinations.size() == 1;
|
|
if (HasDirectDestinations and not HasSymbolDestinations) {
|
|
// We have at least a direct destination, prepare a list of jump
|
|
// targets
|
|
ProgramCounterHandler::DispatcherTargets Values;
|
|
Values.reserve(T->getNumOperands());
|
|
for (const auto &Address : DirectDestinations)
|
|
Values.emplace_back(Address, JTM->getBlockAt(Address));
|
|
pinExitTB(Call, Values);
|
|
} else if (HasOneSymbolDestination) {
|
|
// Jump to a symbol
|
|
|
|
auto *T = Call->getParent()->getTerminator();
|
|
revng_assert(hasMarker(T, getCalledFunction(Call)));
|
|
|
|
// Purge existing marker, if any
|
|
if (CallInst *Marker = getMarker(T, JumpToSymbolMarker))
|
|
Marker->eraseFromParent();
|
|
|
|
// Create the marker
|
|
StringRef SymbolName = SymbolDestinations[0];
|
|
// TODO: in theory we could insert this before T, not Call, but it's
|
|
// violating some assumption somewhere
|
|
CallInst::Create({ JumpToSymbolMarker },
|
|
{ getUniqueString(M, SymbolName) },
|
|
{},
|
|
"",
|
|
Call);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TDBP::pinConstantStoreInternal(MetaAddress Address, CallInst *ExitTBCall) {
|
|
revng_assert(Address.isValid());
|
|
|
|
BasicBlock *TargetBlock = JTM->registerJT(Address, JTReason::DirectJump);
|
|
|
|
const Module *M = getModule(ExitTBCall);
|
|
LLVMContext &Context = getContext(M);
|
|
|
|
// Remove unreachable right after the exit_tb
|
|
BasicBlock::iterator CallIt(ExitTBCall);
|
|
BasicBlock::iterator BlockEnd = ExitTBCall->getParent()->end();
|
|
CallIt++;
|
|
revng_assert(CallIt != BlockEnd and isa<UnreachableInst>(&*CallIt));
|
|
eraseFromParent(&*CallIt);
|
|
|
|
// Cleanup of what's afterwards (only a unconditional jump is
|
|
// allowed)
|
|
CallIt = BasicBlock::iterator(ExitTBCall);
|
|
BlockEnd = ExitTBCall->getParent()->end();
|
|
if (++CallIt != BlockEnd)
|
|
purgeBranch(CallIt);
|
|
|
|
if (TargetBlock != nullptr) {
|
|
// A target was found, jump there
|
|
BranchInst::Create(TargetBlock, ExitTBCall->getParent());
|
|
JTM->recordNewBranches(ExitTBCall->getParent(), 1);
|
|
} else {
|
|
// We're jumping to an unknown or invalid location, jump back to the
|
|
// dispatcher
|
|
// TODO: emit a warning
|
|
BranchInst::Create(JTM->unexpectedPC(), ExitTBCall);
|
|
}
|
|
|
|
eraseFromParent(ExitTBCall);
|
|
}
|
|
|
|
bool TDBP::pinConstantStore(Function &F) {
|
|
auto 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++;
|
|
auto *Call = cast<CallInst>(ExitTBUse.getUser());
|
|
revng_assert(getCalledFunction(Call) == ExitTB);
|
|
|
|
// Look for the last write to the PC
|
|
auto [Result, NextPC] = PCH->getUniqueJumpTarget(Call->getParent());
|
|
|
|
switch (Result) {
|
|
case NextJumpTarget::Unique:
|
|
// A constant store was born
|
|
revng_assert(NextPC.isValid());
|
|
pinConstantStoreInternal(NextPC, Call);
|
|
break;
|
|
|
|
case NextJumpTarget::Multiple:
|
|
// Nothing to do, it's an indirect jump
|
|
break;
|
|
|
|
case NextJumpTarget::Helper:
|
|
forceFallthroughAfterHelper(Call);
|
|
break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TDBP::forceFallthroughAfterHelper(CallInst *Call) {
|
|
// If someone else already took care of the situation, quit
|
|
if (getLimitedValue(Call->getArgOperand(0)) > 0)
|
|
return false;
|
|
|
|
bool ForceFallthrough = false;
|
|
|
|
BasicBlock::reverse_iterator It(++Call->getReverseIterator());
|
|
auto *BB = Call->getParent();
|
|
auto EndIt = BB->rend();
|
|
while (!ForceFallthrough) {
|
|
while (It != EndIt) {
|
|
Instruction *I = &*It;
|
|
if (auto *Store = dyn_cast<StoreInst>(I)) {
|
|
if (PCH->affectsPC(Store)) {
|
|
// We found a PC-store, give up
|
|
return false;
|
|
}
|
|
} else if (isCallToHelper(I)) {
|
|
// 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);
|
|
|
|
IRBuilder<> Builder(Call->getParent());
|
|
Call->setArgOperand(0, Builder.getInt32(1));
|
|
|
|
// Create the fallthrough jump
|
|
MetaAddress NextPC = JTM->getNextPC(Call);
|
|
|
|
// Get the fallthrough basic block and emit a conditional branch, if not
|
|
// possible simply jump to anyPC
|
|
BasicBlock *AnyPC = JTM->anyPC();
|
|
if (BasicBlock *NextPCBB = JTM->registerJT(NextPC, JTReason::PostHelper)) {
|
|
PCH->buildHotPath(Builder, { NextPC, NextPCBB }, AnyPC);
|
|
} else {
|
|
Builder.CreateBr(AnyPC);
|
|
}
|
|
|
|
JTM->recordNewBranches(Call->getParent(), 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TDBP::runOnModule(Module &M) {
|
|
Function &F = *M.getFunction("root");
|
|
pinConstantStore(F);
|
|
pinMaterializedValues(F);
|
|
return true;
|
|
}
|
|
|
|
MaterializedValue JumpTargetManager::readFromPointer(MetaAddress LoadAddress,
|
|
unsigned LoadSize,
|
|
bool IsLittleEndian) {
|
|
auto NewAPInt = [LoadSize](uint64_t V) { return APInt(LoadSize * 8, V); };
|
|
|
|
UnusedCodePointers.erase(LoadAddress);
|
|
|
|
// Prevent overflow when computing the label interval
|
|
MetaAddress EndAddress = LoadAddress + LoadSize;
|
|
if (not EndAddress.isValid())
|
|
return MaterializedValue::invalid();
|
|
|
|
registerReadRange(LoadAddress, EndAddress);
|
|
|
|
//
|
|
// Check relocations
|
|
//
|
|
unsigned MatchCount = 0;
|
|
MaterializedValue Result;
|
|
|
|
// Check dynamic functions-related relocations
|
|
for (const model::DynamicFunction &Function :
|
|
Model->ImportedDynamicFunctions()) {
|
|
for (const model::Relocation &Relocation : Function.Relocations()) {
|
|
uint64_t Addend = Relocation.Addend();
|
|
auto RelocationSize = model::RelocationType::getSize(Relocation.Type());
|
|
if (LoadAddress == Relocation.Address() and LoadSize == RelocationSize) {
|
|
// TODO: add this to model verify
|
|
revng_assert(not StringRef(Function.OriginalName()).contains('\0'));
|
|
Result = MaterializedValue::fromSymbol(Function.OriginalName(),
|
|
NewAPInt(Addend));
|
|
++MatchCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check segment-related relocations
|
|
for (const model::Segment &Segment : Model->Segments()) {
|
|
for (const model::Relocation &Relocation : Segment.Relocations()) {
|
|
uint64_t Addend = Relocation.Addend();
|
|
auto RelocationSize = model::RelocationType::getSize(Relocation.Type());
|
|
if (LoadAddress == Relocation.Address() and LoadSize == RelocationSize) {
|
|
MetaAddress Address = Segment.StartAddress() + Addend;
|
|
if (Address.isValid()) {
|
|
Result = MaterializedValue::fromConstant(NewAPInt(Address.address()));
|
|
++MatchCount;
|
|
} else {
|
|
// TODO: log message
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MatchCount == 1) {
|
|
return Result;
|
|
} else if (MatchCount > 1) {
|
|
// TODO: log message
|
|
}
|
|
|
|
// No labels found, fall back to read the raw value, if available
|
|
auto MaybeValue = BinaryView.readInteger(LoadAddress,
|
|
LoadSize,
|
|
IsLittleEndian);
|
|
|
|
if (MaybeValue)
|
|
return MaterializedValue::fromConstant(NewAPInt(*MaybeValue));
|
|
else
|
|
return MaterializedValue::invalid();
|
|
}
|
|
|
|
JumpTargetManager::JumpTargetManager(Function *TheFunction,
|
|
ProgramCounterHandler *PCH,
|
|
CSAAFactory CreateCSAA,
|
|
const TupleTree<model::Binary> &Model,
|
|
const RawBinaryView &BinaryView) :
|
|
TheModule(*TheFunction->getParent()),
|
|
Context(TheModule.getContext()),
|
|
TheFunction(TheFunction),
|
|
OriginalInstructionAddresses(),
|
|
JumpTargets(),
|
|
ExitTB(nullptr),
|
|
Dispatcher(nullptr),
|
|
DispatcherSwitch(nullptr),
|
|
CurrentCFGForm(CFGForm::UnknownForm),
|
|
CreateCSAA(CreateCSAA),
|
|
PCH(PCH),
|
|
Model(Model),
|
|
BinaryView(BinaryView) {
|
|
|
|
FunctionType *ExitTBTy = FunctionType::get(Type::getVoidTy(Context),
|
|
{ Type::getInt32Ty(Context) },
|
|
false);
|
|
FunctionCallee ExitCallee = TheModule.getOrInsertFunction("exitTB", ExitTBTy);
|
|
ExitTB = cast<Function>(ExitCallee.getCallee());
|
|
FunctionTags::Marker.addTo(ExitTB);
|
|
|
|
prepareDispatcher();
|
|
|
|
// Collect executable ranges from the model
|
|
ExecutableRanges = Model->executableRanges();
|
|
|
|
if (RegisterJTLog.isEnabled()) {
|
|
RegisterJTLog << "Executable ranges:\n";
|
|
for (const auto &[Start, End] : ExecutableRanges) {
|
|
RegisterJTLog << " " << Start.toString() << "-" << End.toString()
|
|
<< "\n";
|
|
}
|
|
RegisterJTLog << DoLog;
|
|
}
|
|
|
|
// 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);
|
|
// Increase the Cap of the clobbering calls (`getClobberingMemoryAccess()`) in
|
|
// EarlyCSE, so MemorySSA is still useful in the Pass. This is needed to avoid
|
|
// using of GVN Pass, which is very slow.
|
|
const char *EarlyCSEOption = "earlycse-mssa-optimization-cap";
|
|
getOption<unsigned>(Options, EarlyCSEOption)->setInitialValue(2000);
|
|
|
|
// getOption<bool>(Options, "enable-pre")->setInitialValue(false);
|
|
// getOption<uint32_t>(Options, "max-recurse-depth")->setInitialValue(10);
|
|
}
|
|
|
|
void JumpTargetManager::harvestGlobalData() {
|
|
// Register ExtraCodeAddresses
|
|
for (MetaAddress Address : Model->ExtraCodeAddresses())
|
|
registerJT(Address, JTReason::GlobalData);
|
|
|
|
for (auto &[Segment, Data] : BinaryView.segments()) {
|
|
MetaAddress StartVirtualAddress = Segment.StartAddress();
|
|
const unsigned char *DataStart = Data.begin();
|
|
const unsigned char *DataEnd = Data.end();
|
|
|
|
using namespace model::Architecture;
|
|
bool IsLittleEndian = isLittleEndian(Model->Architecture());
|
|
auto PointerSize = getPointerSize(Model->Architecture());
|
|
using endianness = support::endianness;
|
|
if (PointerSize == 8) {
|
|
if (IsLittleEndian)
|
|
findCodePointers<uint64_t, endianness::little>(StartVirtualAddress,
|
|
DataStart,
|
|
DataEnd);
|
|
else
|
|
findCodePointers<uint64_t, endianness::big>(StartVirtualAddress,
|
|
DataStart,
|
|
DataEnd);
|
|
} else if (PointerSize == 4) {
|
|
if (IsLittleEndian)
|
|
findCodePointers<uint32_t, endianness::little>(StartVirtualAddress,
|
|
DataStart,
|
|
DataEnd);
|
|
else
|
|
findCodePointers<uint32_t, endianness::big>(StartVirtualAddress,
|
|
DataStart,
|
|
DataEnd);
|
|
}
|
|
}
|
|
|
|
revng_log(JTCountLog,
|
|
"JumpTargets found in global data: " << std::dec
|
|
<< Unexplored.size());
|
|
}
|
|
|
|
template<typename value_type, unsigned endian>
|
|
void JumpTargetManager::findCodePointers(MetaAddress StartVirtualAddress,
|
|
const unsigned char *Start,
|
|
const unsigned char *End) {
|
|
using support::endianness;
|
|
using support::endian::read;
|
|
|
|
constexpr auto Step = sizeof(value_type);
|
|
|
|
auto Cursor = Start;
|
|
|
|
// Align the starting address: we want to scan one step at a time starting
|
|
// from an aligned size
|
|
auto Misalignment = StartVirtualAddress.address() % Step;
|
|
if (Misalignment != 0)
|
|
Cursor += Step - Misalignment;
|
|
|
|
for (; Cursor < End - Step; Cursor += Step) {
|
|
auto Read = read<value_type, static_cast<endianness>(endian), 1>;
|
|
uint64_t RawValue = Read(Cursor);
|
|
MetaAddress Value = fromPC(RawValue);
|
|
if (Value.isInvalid())
|
|
continue;
|
|
|
|
BasicBlock *Result = registerJT(Value, JTReason::GlobalData);
|
|
|
|
if (Result != nullptr)
|
|
UnusedCodePointers.insert(StartVirtualAddress + (Cursor - 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(MetaAddress PC, bool &ShouldContinue) {
|
|
revng_assert(PC.isValid());
|
|
|
|
// 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) {
|
|
BasicBlock *Result = UnexploredIt->second;
|
|
|
|
// Check if we already have a translation for that
|
|
ShouldContinue = Result->empty();
|
|
if (ShouldContinue) {
|
|
// We don't, OK let's explore it next
|
|
Unexplored.erase(UnexploredIt);
|
|
} else {
|
|
// We do, it will be purged at the next `peek`
|
|
revng_assert(ToPurge.contains(Result));
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
// It wasn't planned to visit it, so we've already been there, just jump
|
|
// there
|
|
BasicBlock *BB = JTIt->second.head();
|
|
revng_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.contains(PC)) {
|
|
ShouldContinue = false;
|
|
return registerJT(PC, JTReason::AmbiguousInstruction);
|
|
}
|
|
|
|
// We don't know anything about this PC
|
|
return nullptr;
|
|
}
|
|
|
|
/// Save the PC-Instruction association for future use (jump target)
|
|
void JumpTargetManager::registerInstruction(MetaAddress PC,
|
|
Instruction *Instruction) {
|
|
revng_assert(PC.isValid());
|
|
|
|
// Never save twice a PC
|
|
revng_assert(!OriginalInstructionAddresses.contains(PC));
|
|
OriginalInstructionAddresses[PC] = Instruction;
|
|
revng_assert(Instruction->getParent() != nullptr);
|
|
}
|
|
|
|
// TODO: this is a candidate for BFSVisit
|
|
std::pair<MetaAddress, uint64_t>
|
|
JumpTargetManager::getPC(Instruction *TheInstruction) const {
|
|
CallInst *NewPCCall = nullptr;
|
|
llvm::DenseSet<BasicBlock *> Visited;
|
|
std::queue<BasicBlock::reverse_iterator> WorkList;
|
|
if (TheInstruction->getIterator() == TheInstruction->getParent()->begin())
|
|
WorkList.push(--TheInstruction->getParent()->rend());
|
|
else
|
|
WorkList.push(++TheInstruction->getReverseIterator());
|
|
|
|
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 = getCalledFunction(Marker);
|
|
if (Callee != nullptr && Callee->getName() == "newpc") {
|
|
|
|
// We found two distinct newpc leading to the requested instruction
|
|
if (NewPCCall != nullptr)
|
|
return { MetaAddress::invalid(), 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
|
|
revng_assert(not(isPartOfRootDispatcher(Predecessor)));
|
|
}
|
|
|
|
for (BasicBlock *Predecessor : predecessors(BB)) {
|
|
// Ignore already visited or empty BBs
|
|
if (!Predecessor->empty() && !Visited.contains(Predecessor)) {
|
|
WorkList.push(Predecessor->rbegin());
|
|
Visited.insert(Predecessor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Couldn't find the current PC
|
|
if (NewPCCall == nullptr)
|
|
return { MetaAddress::invalid(), 0 };
|
|
|
|
using namespace NewPCArguments;
|
|
MetaAddress PC = addressFromNewPC(NewPCCall);
|
|
uint64_t Size = getLimitedValue(NewPCCall->getArgOperand(InstructionSize));
|
|
revng_assert(Size != 0);
|
|
return { PC, Size };
|
|
}
|
|
|
|
/// 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.contains(BB))
|
|
return;
|
|
Visited.insert(BB);
|
|
|
|
MetaAddress PC = getPC(BB);
|
|
if (PC.isInvalid())
|
|
SamePC.push(BB);
|
|
else
|
|
NewPC.push({ BB, PC });
|
|
}
|
|
|
|
std::pair<BasicBlock *, MetaAddress> pop() {
|
|
if (!SamePC.empty()) {
|
|
auto Result = SamePC.front();
|
|
SamePC.pop();
|
|
return { Result, MetaAddress::invalid() };
|
|
} 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, MetaAddress::invalid() };
|
|
}
|
|
}
|
|
|
|
private:
|
|
MetaAddress getPC(BasicBlock *BB) {
|
|
if (!BB->empty()) {
|
|
if (auto *Call = getCallTo(&*BB->begin(), "newpc"))
|
|
return addressFromNewPC(Call);
|
|
}
|
|
|
|
return MetaAddress::invalid();
|
|
}
|
|
|
|
private:
|
|
const SwitchInst *Dispatcher = nullptr;
|
|
unsigned JumpTargetIndex;
|
|
unsigned JumpTargetsCount;
|
|
const DataLayout &DL;
|
|
llvm::DenseSet<BasicBlock *> Visited;
|
|
std::queue<BasicBlock *> SamePC;
|
|
std::queue<std::pair<BasicBlock *, MetaAddress>> NewPC;
|
|
};
|
|
|
|
void JumpTargetManager::fixPostHelperPC() {
|
|
for (BasicBlock &BB : *TheFunction) {
|
|
for (Instruction &I : BB) {
|
|
if (auto *Call = getCallToHelper(&I)) {
|
|
auto Written = std::move(getCSVUsedByHelperCall(Call).Written);
|
|
auto WritesPC = [this](GlobalVariable *CSV) {
|
|
return PCH->affectsPC(CSV);
|
|
};
|
|
auto End = Written.end();
|
|
if (std::find_if(Written.begin(), End, WritesPC) != End) {
|
|
IRBuilder<> Builder(Call->getParent(), ++Call->getIterator());
|
|
PCH->deserializePC(Builder);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (getCalledFunction(Call) == ExitTB) {
|
|
|
|
// Look for the last write to the PC
|
|
BasicBlock *CallBB = Call->getParent();
|
|
auto [Result, NextPC] = PCH->getUniqueJumpTarget(CallBB);
|
|
|
|
if (NextPC.isValid() and isExecutableAddress(NextPC)) {
|
|
revng_check(Result != NextJumpTarget::Unique
|
|
and "Direct jumps should not be handled here");
|
|
}
|
|
|
|
if (getLimitedValue(Call->getArgOperand(0)) == 0) {
|
|
exitTBCleanup(Call);
|
|
BranchInst::Create(AnyPC, Call);
|
|
}
|
|
|
|
eraseFromParent(Call);
|
|
}
|
|
}
|
|
}
|
|
|
|
revng_assert(ExitTB->use_empty());
|
|
eraseFromParent(ExitTB);
|
|
ExitTB = nullptr;
|
|
}
|
|
|
|
JumpTargetManager::BlockWithAddress JumpTargetManager::peek() {
|
|
// If we just harvested new branches, keep exploring
|
|
do {
|
|
harvest();
|
|
} while (Unexplored.empty() and NewBranches != 0);
|
|
|
|
// Purge all the partial translations we know might be wrong
|
|
for (BasicBlock *BB : ToPurge)
|
|
purgeTranslation(BB);
|
|
ToPurge.clear();
|
|
|
|
if (Unexplored.empty()) {
|
|
revng_log(JTCountLog, "We're done looking for jump targets");
|
|
return NoMoreTargets;
|
|
} else {
|
|
BlockWithAddress Result = Unexplored.back();
|
|
Unexplored.pop_back();
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
BasicBlock *JumpTargetManager::getBlockAt(MetaAddress PC) {
|
|
revng_assert(PC.isValid());
|
|
|
|
auto TargetIt = JumpTargets.find(PC);
|
|
revng_assert(TargetIt != JumpTargets.end());
|
|
return TargetIt->second.head();
|
|
}
|
|
|
|
/// Check if among \p BB's predecessors there's \p Target
|
|
inline bool hasRootDispatcherPredecessor(llvm::BasicBlock *BB) {
|
|
for (llvm::BasicBlock *Predecessor : predecessors(BB))
|
|
if (isPartOfRootDispatcher(Predecessor))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void JumpTargetManager::purgeTranslation(BasicBlock *Start) {
|
|
OnceQueue<BasicBlock *> Queue;
|
|
Queue.insert(Start);
|
|
|
|
// Collect all the descendants, except if we meet a jump target
|
|
while (!Queue.empty()) {
|
|
BasicBlock *BB = Queue.pop();
|
|
Instruction *Terminator = BB->getTerminator();
|
|
for (BasicBlock *Successor : successors(Terminator)) {
|
|
if (isTranslatedBB(Successor) and not isJumpTarget(Successor)
|
|
and not hasRootDispatcherPredecessor(Successor)) {
|
|
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()) {
|
|
Instruction *I = &*(--BB->end());
|
|
|
|
if (CallInst *Call = getCallTo(I, "newpc")) {
|
|
OriginalInstructionAddresses.erase(addressFromNewPC(Call));
|
|
}
|
|
eraseInstruction(I);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
revng_assert(pred_empty(Predecessor));
|
|
eraseFromParent(Predecessor);
|
|
}
|
|
|
|
revng_assert(BB->use_empty());
|
|
eraseFromParent(BB);
|
|
}
|
|
}
|
|
|
|
// TODO: register Reason
|
|
BasicBlock *JumpTargetManager::registerJT(MetaAddress PC,
|
|
JTReason::Values Reason) {
|
|
revng_check(PC.isValid());
|
|
|
|
if (not isPC(PC))
|
|
return nullptr;
|
|
|
|
revng_log(RegisterJTLog,
|
|
"Registering bb." << nameForAddress(PC) << " for "
|
|
<< JTReason::getName(Reason));
|
|
|
|
// 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);
|
|
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 {
|
|
revng_assert(I != nullptr && I->getIterator() != 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);
|
|
|
|
} 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));
|
|
|
|
std::stringstream Name;
|
|
Name << "bb." << nameForAddress(PC);
|
|
NewBlock->setName(Name.str());
|
|
|
|
// Create a case for the address associated to the new block, if the
|
|
// dispatcher has already been emitted
|
|
if (DispatcherSwitch != nullptr) {
|
|
PCH->addCaseToDispatcher(DispatcherSwitch,
|
|
{ PC, NewBlock },
|
|
BlockType::RootDispatcherHelperBlock);
|
|
}
|
|
|
|
// Associate the PC with the chosen basic block
|
|
auto &NewJumpTarget = JumpTargets[PC];
|
|
NewJumpTarget = JumpTarget(NewBlock, Reason);
|
|
|
|
if (AftedAddingFunctionEntries)
|
|
NewJumpTarget.setReason(JTReason::DependsOnModelFunction);
|
|
|
|
// PC was not a jump target, record it as new
|
|
ValueMaterializerPCWhiteList.insert(PC);
|
|
|
|
return NewBlock;
|
|
}
|
|
|
|
void JumpTargetManager::registerReadRange(MetaAddress StartAddress,
|
|
MetaAddress EndAddress) {
|
|
if (not isMapped(StartAddress, EndAddress))
|
|
return;
|
|
|
|
using interval = boost::icl::interval<MetaAddress, CompareAddress>;
|
|
ReadIntervalSet += interval::right_open(StartAddress, EndAddress);
|
|
}
|
|
|
|
void JumpTargetManager::prepareDispatcher() {
|
|
IRBuilder<> Builder(Context);
|
|
QuickMetadata QMD(Context);
|
|
|
|
// Create the first block of the dispatcher
|
|
BasicBlock *Entry = BasicBlock::Create(Context,
|
|
"dispatcher.entry",
|
|
TheFunction);
|
|
|
|
// The default case of the switch statement it's an unhandled cases
|
|
DispatcherFail = BasicBlock::Create(Context,
|
|
"dispatcher.default",
|
|
TheFunction);
|
|
Builder.SetInsertPoint(DispatcherFail);
|
|
|
|
FunctionCallee UnknownPC = TheModule.getFunction("unknown_pc");
|
|
{
|
|
auto *UnknownPCFunction = cast<Function>(skipCasts(UnknownPC.getCallee()));
|
|
FunctionTags::Exceptional.addTo(UnknownPCFunction);
|
|
}
|
|
|
|
PCH->setCurrentPCPlainMetaAddress(Builder);
|
|
|
|
Builder.CreateCall(UnknownPC);
|
|
|
|
auto *FailUnreachable = Builder.CreateUnreachable();
|
|
setBlockType(FailUnreachable, BlockType::DispatcherFailureBlock);
|
|
|
|
Dispatcher = Entry;
|
|
|
|
// Create basic blocks to handle jumps to any PC and to a PC we didn't expect
|
|
AnyPC = BasicBlock::Create(Context, "anypc", TheFunction);
|
|
UnexpectedPC = BasicBlock::Create(Context, "unexpectedpc", TheFunction);
|
|
}
|
|
|
|
static void purge(BasicBlock *BB) {
|
|
// Allow up to a single instruction in the basic block
|
|
if (!BB->empty())
|
|
eraseFromParent(&*BB->begin());
|
|
revng_assert(BB->empty());
|
|
}
|
|
|
|
llvm::DenseSet<BasicBlock *> JumpTargetManager::computeUnreachable() const {
|
|
ReversePostOrderTraversal<BasicBlock *> RPOT(&TheFunction->getEntryBlock());
|
|
llvm::DenseSet<BasicBlock *> Reachable;
|
|
for (BasicBlock *BB : RPOT)
|
|
Reachable.insert(BB);
|
|
|
|
// TODO: why is isTranslatedBB(&BB) necessary?
|
|
llvm::DenseSet<BasicBlock *> Unreachable;
|
|
for (BasicBlock &BB : *TheFunction)
|
|
if (not Reachable.contains(&BB) and isTranslatedBB(&BB))
|
|
Unreachable.insert(&BB);
|
|
|
|
return Unreachable;
|
|
}
|
|
|
|
void JumpTargetManager::setCFGForm(CFGForm::Values NewForm,
|
|
MetaAddressSet *JumpTargetsWhitelist) {
|
|
revng_assert(CurrentCFGForm != NewForm);
|
|
revng_assert(NewForm != CFGForm::UnknownForm);
|
|
|
|
CFGForm::Values OldForm = CurrentCFGForm;
|
|
CurrentCFGForm = NewForm;
|
|
|
|
//
|
|
// Recreate AnyPC and UnexpectedPC
|
|
//
|
|
switch (NewForm) {
|
|
case CFGForm::SemanticPreserving:
|
|
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 CFGForm::RecoveredOnly:
|
|
case CFGForm::NoFunctionCalls:
|
|
purge(AnyPC);
|
|
new UnreachableInst(Context, AnyPC);
|
|
purge(UnexpectedPC);
|
|
new UnreachableInst(Context, UnexpectedPC);
|
|
break;
|
|
|
|
default:
|
|
revng_abort("Not implemented yet");
|
|
}
|
|
|
|
setBlockType(AnyPC->getTerminator(), BlockType::AnyPCBlock);
|
|
setBlockType(UnexpectedPC->getTerminator(), BlockType::UnexpectedPCBlock);
|
|
|
|
// Adjust successors of jump instructions tagged as function calls:
|
|
//
|
|
// * NoFunctionsCallsCFG: the successor is the fallthrough
|
|
// * otherwise: the successor is the callee
|
|
if (NewForm == CFGForm::NoFunctionCalls
|
|
|| OldForm == CFGForm::NoFunctionCalls) {
|
|
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;
|
|
|
|
Instruction *Terminator = nextNonMarker(Call);
|
|
revng_assert(Terminator->getNumSuccessors() == 1);
|
|
|
|
// Get the correct argument, the first is the callee, the second the
|
|
// return basic block
|
|
int OperandIndex = NewForm == CFGForm::NoFunctionCalls ? 1 : 0;
|
|
Value *Op = Call->getArgOperand(OperandIndex);
|
|
BasicBlock *NewSuccessor = cast<BlockAddress>(Op)->getBasicBlock();
|
|
Terminator->setSuccessor(0, NewSuccessor);
|
|
}
|
|
}
|
|
}
|
|
|
|
rebuildDispatcher(JumpTargetsWhitelist);
|
|
}
|
|
|
|
void JumpTargetManager::rebuildDispatcher(MetaAddressSet *Whitelist) {
|
|
|
|
if (DispatcherSwitch != nullptr) {
|
|
revng_assert(DispatcherSwitch->getParent() == Dispatcher);
|
|
|
|
// Purge the old dispatcher
|
|
PCH->destroyDispatcher(DispatcherSwitch);
|
|
}
|
|
|
|
ProgramCounterHandler::DispatcherTargets Targets;
|
|
|
|
// Add all the (whitelisted) jump targets if we're using the
|
|
// SemanticPreserving, or only those with no predecessors.
|
|
bool IsWhitelistActive = (Whitelist != nullptr);
|
|
for (auto &[PC, JumpTarget] : JumpTargets) {
|
|
BasicBlock *BB = JumpTarget.head();
|
|
bool IsWhitelisted = (not IsWhitelistActive or Whitelist->contains(PC));
|
|
if ((CurrentCFGForm == CFGForm::SemanticPreserving
|
|
or not hasPredecessors(BB))
|
|
and IsWhitelisted) {
|
|
Targets.emplace_back(PC, BB);
|
|
}
|
|
}
|
|
|
|
constexpr auto RDHB = BlockType::RootDispatcherHelperBlock;
|
|
const auto &DispatcherInfo = PCH->buildDispatcher(Targets,
|
|
Dispatcher,
|
|
DispatcherFail,
|
|
RDHB);
|
|
DispatcherSwitch = DispatcherInfo.Switch;
|
|
|
|
// The switch is the terminator of the dispatcher basic block
|
|
setBlockType(DispatcherSwitch, BlockType::RootDispatcherBlock);
|
|
|
|
//
|
|
// Make sure every generated basic block is reachable
|
|
//
|
|
if (CurrentCFGForm != CFGForm::SemanticPreserving) {
|
|
llvm::DenseSet<BasicBlock *> Reachable;
|
|
// Compute the set of jump targets currently reachables from the dispatcher
|
|
for (BasicBlock *DFSBB : llvm::depth_first(DispatcherSwitch->getParent())) {
|
|
Reachable.insert(DFSBB);
|
|
}
|
|
|
|
// Identify all the unreachable jump targets, and add an edge from the
|
|
// dispatcher to them. Note that the order we use to iterate over
|
|
// `JumpTargets` is fundamental, because we want to connect to the
|
|
// dispatcher first the jump target with the lower program counter. At the
|
|
// same time, we will mark as reachable all the jump targets that are
|
|
// transitively reachable from the elected jump target. In this way, we
|
|
// connect to the dispatcher all the blocks belonging to a separate SCC that
|
|
// were not reachable initially (e.g., a function only indirectly called).
|
|
for (const auto &[PC, JT] : JumpTargets) {
|
|
BasicBlock *BB = JT.head();
|
|
bool IsWhitelisted = (not IsWhitelistActive or Whitelist->contains(PC));
|
|
|
|
// Add to the switch all the unreachable jump targets whose reason is not
|
|
// just direct jump
|
|
if (not Reachable.contains(BB) and IsWhitelisted
|
|
and not JT.isOnlyReason(JTReason::DirectJump,
|
|
JTReason::DependsOnModelFunction)) {
|
|
PCH->addCaseToDispatcher(DispatcherSwitch,
|
|
{ PC, BB },
|
|
BlockType::RootDispatcherHelperBlock);
|
|
|
|
// Add to the `Reachable` set also all the jump targets that are now
|
|
// reachable. We do this with a with a simple DFS visit from the
|
|
// newly connected one.
|
|
for (BasicBlock *DFSBB : llvm::depth_first(BB)) {
|
|
Reachable.insert(DFSBB);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool JumpTargetManager::hasPredecessors(BasicBlock *BB) const {
|
|
for (BasicBlock *Pred : predecessors(BB))
|
|
if (isTranslatedBB(Pred))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
CallInst *JumpTargetManager::getJumpTarget(BasicBlock *Target) {
|
|
for (BasicBlock *BB : inverse_depth_first(Target)) {
|
|
if (auto *Call = dyn_cast<CallInst>(&*BB->begin())) {
|
|
auto MA = addressFromNewPC(Call);
|
|
if (MA.isValid() and isJumpTarget(MA))
|
|
return Call;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Harvesting proceeds trying to avoid to run expensive analyses if not strictly
|
|
// necessary. 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() {
|
|
Task T(10, "Harvesting");
|
|
HarvestingStats.push("harvest 0");
|
|
|
|
if (empty()) {
|
|
T.advance("Simple literals");
|
|
HarvestingStats.push("harvest 1: SimpleLiterals");
|
|
revng_log(JTCountLog, "Collecting simple literals");
|
|
for (MetaAddress PC : SimpleLiterals)
|
|
registerJT(PC, JTReason::SimpleLiteral);
|
|
SimpleLiterals.clear();
|
|
}
|
|
|
|
if (empty()) {
|
|
T.advance("SROA + InstCombine + TBDP");
|
|
HarvestingStats.push("harvest 2: SROA + InstCombine + TBDP");
|
|
|
|
// Safely erase all unreachable blocks
|
|
llvm::DenseSet<BasicBlock *> Unreachable = computeUnreachable();
|
|
for (BasicBlock *BB : Unreachable)
|
|
BB->dropAllReferences();
|
|
for (BasicBlock *BB : Unreachable)
|
|
eraseFromParent(BB);
|
|
|
|
// 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
|
|
MetaAddress PC = addressFromNewPC(Call);
|
|
bool IsJT = isJumpTarget(PC);
|
|
Call->setArgOperand(2, Builder.getInt32(static_cast<uint32_t>(IsJT)));
|
|
}
|
|
}
|
|
}
|
|
|
|
revng::verify(&TheModule);
|
|
|
|
revng_log(JTCountLog, "Preliminary harvesting");
|
|
|
|
HarvestingStats.push("InstCombine");
|
|
legacy::FunctionPassManager OptimizingPM(&TheModule);
|
|
OptimizingPM.add(createSROAPass());
|
|
OptimizingPM.add(createInstSimplifyLegacyPass());
|
|
OptimizingPM.doInitialization();
|
|
OptimizingPM.run(*TheFunction);
|
|
OptimizingPM.doFinalization();
|
|
|
|
legacy::PassManager PreliminaryBranchesPM;
|
|
PreliminaryBranchesPM.add(new TranslateDirectBranchesPass(this));
|
|
PreliminaryBranchesPM.run(TheModule);
|
|
|
|
if (empty()) {
|
|
T.advance("Advanced Value Info");
|
|
HarvestingStats.push("harvest 3: cloneOptimizeAndHarvest");
|
|
revng_log(JTCountLog, "Harvesting with Advanced Value Info");
|
|
RootAnalyzer(*this).cloneOptimizeAndHarvest(TheFunction);
|
|
}
|
|
|
|
if (empty()) {
|
|
// Register model::Function entry nodes
|
|
|
|
AftedAddingFunctionEntries = true;
|
|
|
|
DisableTracking Guard(*Model);
|
|
for (const model::Function &Function : Model->Functions())
|
|
registerJT(Function.Entry(), JTReason::FunctionSymbol);
|
|
}
|
|
|
|
// TODO: eventually, `setCFGForm` should be replaced by using a CustomCFG
|
|
// 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(CFGForm::RecoveredOnly);
|
|
|
|
NewBranches = 0;
|
|
legacy::PassManager AnalysisPM;
|
|
AnalysisPM.add(new TranslateDirectBranchesPass(this));
|
|
AnalysisPM.run(TheModule);
|
|
|
|
// Restore the CFG
|
|
setCFGForm(CFGForm::SemanticPreserving);
|
|
|
|
if (JTCountLog.isEnabled()) {
|
|
JTCountLog << std::dec << Unexplored.size() << " new jump targets and "
|
|
<< NewBranches << " new branches were found" << DoLog;
|
|
}
|
|
}
|
|
}
|
|
|
|
using BWA = JumpTargetManager::BlockWithAddress;
|
|
using JTM = JumpTargetManager;
|
|
const BWA JTM::NoMoreTargets = BWA(MetaAddress::invalid(), nullptr);
|