mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
8125a19379
Improve the way in which we connect the non reachable `JumpTargets` when rebuilding the dispatcher for non `SemanticsPreserving` `CFGForm`s. When connecting group of jump targets that are not currently reachable from the entry dispatcher, we elect the jump target with the lowest program counter value, as the one to be connected to the dispatcher. We also mark the jump targets now transitively reachables from the elected one, as reachable, so to avoid adding other unnecessary edges from the dispatcher.
1269 lines
42 KiB
C++
1269 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/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, Call->getCalledFunction()));
|
|
|
|
// 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(Call->getCalledFunction() == 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
|
|
//
|
|
for (const model::Segment &Segment : Model->Segments()) {
|
|
if (Segment.IsExecutable()) {
|
|
if (Segment.Sections().size() > 0) {
|
|
for (const model::Section &Section : Segment.Sections()) {
|
|
if (Section.ContainsCode()) {
|
|
ExecutableRanges.emplace_back(Section.StartAddress(),
|
|
Section.endAddress());
|
|
}
|
|
}
|
|
} else {
|
|
ExecutableRanges.emplace_back(Segment.StartAddress(),
|
|
Segment.endAddress());
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 symbols
|
|
for (const model::Function &Function : Model->Functions())
|
|
registerJT(Function.Entry(), JTReason::FunctionSymbol);
|
|
|
|
// 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 = Marker->getCalledFunction();
|
|
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;
|
|
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 (Call->getCalledFunction() == 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
|
|
JumpTargets[PC] = JumpTarget(NewBlock, Reason);
|
|
|
|
// 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)) {
|
|
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);
|
|
}
|
|
|
|
// 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);
|