Files
revng-revng/lib/FunctionCallIdentification/FunctionCallIdentification.cpp
T
Alessandro Di Federico 5009074e9e Introduce AdvancedValueInfo for JT discovery
* Introduce `ShrinkInstructionOperandsPass`: a transformation shrinking
  operands and the results of instructions if they are
  zero/sign-extended immediately before and after the instruction.
* Introduce `ConstantRangeSet`: similar to `ConstantRange` but allows
  disjoint ranges.
* Introduce `MaterializedValue`: a class that can represent a constant
  value or a symbol plus offset pair.
* Introduce `DropHelperCallsPass`: a transformation removing calls to
  helpers and replacing them with a function call reading the CSVs that
  the helper reads and writing the CSVs that the helper writes
  (according to CSAA).
* Introduce `DropRangeMetadataPass`: a transformation dropping the
  `range` metadata, which, in certain situations, lowers the quality of
  the results provided by `LazyValueInfo`.
* Introduce `AdvancedValueInfo`: an analysis exploiting results of
  `LazyValueInfo` but collecting them as `ConstantRangeSet` with a
  monotone framework. It produces `MaterializedValue`.
* Anticipate linking of helpers: `AVI` requires `CSAA`, which requires
  helper functions to be linked in.
* Drop `--no-link`.
* Force x86-64 `DataLayout`.
* Reorganize harvesting to either collect simple literals or go with
  (incremental) `AVI`.
* Drop `SET`, `OSRA`, the reaching definition analysis, the
  `SimplifyComparisonsPass` and all the sumjump-related code: e now
  clone `root`, optimize it and analyze it with `AVI`.
* Temporarily drop the `NoReturnAnalysis`.
* Link `libLLVMInstCombine`, `libLLVMCodeGen` and `libLLVMPasses`.
* Introduce tests for `AdvancedValueInfo`,
  `ShrinkInstructionOperandsPass` and `ConstantRangeSet`.
* Fix test results.
* Add `llvm.bswap.i64` and `@pc` to the LLVM template module for unit
  tests.
2019-05-22 21:30:29 +02:00

338 lines
12 KiB
C++

/// \file functioncallidentification.cpp
/// \brief Implementation of the FunctionCallIdentification pass, which
/// identifies function calls.
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
// Local libraries includes
#include "revng/FunctionCallIdentification/FunctionCallIdentification.h"
#include "revng/Support/Debug.h"
using namespace llvm;
char FunctionCallIdentification::ID = 0;
static RegisterPass<FunctionCallIdentification> X("fci",
"Function Call "
"Identification",
true,
true);
static Logger<> FilteredCFGLog("filtered-cfg");
bool FunctionCallIdentification::runOnModule(llvm::Module &M) {
revng_log(PassesLog, "Starting FunctionCallIdentification");
llvm::Function &F = *M.getFunction("root");
auto &GCBI = getAnalysis<GeneratedCodeBasicInfo>();
FallthroughAddresses.clear();
// Create function call marker
// TODO: we could factor this out
LLVMContext &C = M.getContext();
PointerType *Int8PtrTy = Type::getInt8PtrTy(C);
auto *Int8NullPtr = ConstantPointerNull::get(Int8PtrTy);
auto *PCTy = IntegerType::get(C, GCBI.pcRegSize() * 8);
auto *PCPtrTy = cast<PointerType>(GCBI.pcReg()->getType());
std::initializer_list<Type *> FunctionArgsTy = {
Int8PtrTy, Int8PtrTy, PCTy, PCPtrTy, Int8PtrTy
};
using FT = FunctionType;
auto *Ty = FT::get(Type::getVoidTy(C), FunctionArgsTy, false);
Constant *FunctionCallC = M.getOrInsertFunction("function_call", Ty);
FunctionCall = cast<Function>(FunctionCallC);
// Initialize the function, if necessary
if (FunctionCall->empty()) {
FunctionCall->setLinkage(GlobalValue::InternalLinkage);
auto *EntryBB = BasicBlock::Create(C, "", FunctionCall);
ReturnInst::Create(C, EntryBB);
revng_assert(FunctionCall->user_begin() == FunctionCall->user_end());
}
// Collect function calls
for (BasicBlock &BB : F) {
if (BB.empty() or not GCBI.isTranslated(&BB))
continue;
// Consider the basic block only if it's terminator is an actual jump and it
// hasn't been already marked as a function call
TerminatorInst *Terminator = BB.getTerminator();
if (Terminator != nullptr) {
if (CallInst *Call = getCall(Terminator)) {
FallthroughAddresses.insert(getLimitedValue(Call->getOperand(2)));
continue;
}
}
if (not GCBI.isJump(Terminator))
continue;
// To be a function call we need to find:
//
// * a call to "newpc"
// * a store of the next PC
// * a store to the PC
struct Visitor
: public BFSVisitorBase<false, Visitor, SmallVector<BasicBlock *, 4>> {
public:
using SuccessorsType = SmallVector<BasicBlock *, 4>;
public:
BasicBlock *BB;
const GeneratedCodeBasicInfo &GCBI;
bool SaveRAFound;
bool StorePCFound;
Constant *LinkRegister;
const uint64_t ReturnPC;
uint64_t LastPC;
// We can meet calls up to newpc up to (1 + "size of the delay slot")
// times
uint64_t NewPCLeft;
PointerType *PCPtrTy;
public:
Visitor(BasicBlock *BB,
const GeneratedCodeBasicInfo &GCBI,
uint64_t ReturnPC,
PointerType *PCPtrTy) :
BB(BB),
GCBI(GCBI),
SaveRAFound(false),
StorePCFound(false),
LinkRegister(nullptr),
ReturnPC(ReturnPC),
LastPC(ReturnPC),
NewPCLeft(1),
PCPtrTy(PCPtrTy) {}
public:
VisitAction visit(instruction_range Range) {
for (Instruction &I : Range) {
if (auto *Store = dyn_cast<StoreInst>(&I)) {
Value *V = Store->getValueOperand();
Value *Pointer = skipCasts(Store->getPointerOperand());
auto *TargetCSV = dyn_cast<GlobalVariable>(Pointer);
if (GCBI.isPCReg(TargetCSV)) {
if (TargetCSV != nullptr)
StorePCFound = true;
} else if (auto *Constant = dyn_cast<ConstantInt>(V)) {
// Note that we willingly ignore stores to the PC here
if (Constant->getLimitedValue() == ReturnPC) {
if (SaveRAFound) {
SaveRAFound = false;
return StopNow;
}
SaveRAFound = true;
// Find where the return address is being stored
revng_assert(LinkRegister == nullptr);
if (TargetCSV != nullptr) {
// The return address is being written to a register
LinkRegister = TargetCSV;
} else {
// The return address is likely being written on the stack, we
// have to check the last value on the stack and check if
// we're writing there. This should cover basically all the
// cases, and, if not, expanding this should be
// straightforward
// Reference example:
//
// %1 = load i64, i64* @rsp
// %2 = sub i64 %1, 8
// %3 = inttoptr i64 %2 to i64*
// store i64 4194694, i64* %3
// store i64 %2, i64* @rsp
// store i64 4194704, i64* @pc
// Find the last write to the stack pointer
Value *LastStackPointer = nullptr;
for (Instruction &I : make_range(BB->rbegin(), BB->rend())) {
if (auto *S = dyn_cast<StoreInst>(&I)) {
Value *Pointer = skipCasts(S->getPointerOperand());
auto *P = dyn_cast<GlobalVariable>(Pointer);
if (P != nullptr && GCBI.isSPReg(P)) {
LastStackPointer = Store->getPointerOperand();
break;
}
}
}
revng_assert(LastStackPointer != nullptr);
revng_assert(skipCasts(LastStackPointer) == Pointer);
// If LinkRegister is nullptr it means the return address is
// being pushed on the top of the stack
LinkRegister = ConstantPointerNull::get(PCPtrTy);
}
}
}
} else if (auto *Call = dyn_cast<CallInst>(&I)) {
auto *Callee = Call->getCalledFunction();
if (Callee != nullptr && Callee->getName() == "newpc") {
revng_assert(NewPCLeft > 0);
uint64_t ProgramCounter = getLimitedValue(Call->getOperand(0));
uint64_t InstructionSize = getLimitedValue(Call->getOperand(1));
// Check that, w.r.t. to the last newpc, we're looking at the
// immediately preceeding instruction, if not fail.
if (ProgramCounter + InstructionSize != LastPC)
return StopNow;
// Update the last seen PC
LastPC = ProgramCounter;
NewPCLeft--;
if (NewPCLeft == 0)
return StopNow;
}
}
}
return Continue;
}
SuccessorsType successors(BasicBlock *BB) {
SuccessorsType Successors;
for (BasicBlock *Successor : make_range(pred_begin(BB), pred_end(BB)))
if (not BB->empty() and GCBI.isTranslated(Successor))
Successors.push_back(Successor);
return Successors;
}
};
uint64_t ReturnPC = GCBI.getNextPC(Terminator);
Visitor V(&BB, GCBI, ReturnPC, PCPtrTy);
V.run(Terminator);
BasicBlock *ReturnBB = GCBI.getBlockAt(ReturnPC);
if (V.SaveRAFound and V.StorePCFound and V.NewPCLeft == 0
and ReturnBB != nullptr) {
// It's a function call, register it
// Emit a call to "function_call" with three parameters: the first is the
// callee basic block, the second the return basic block and the third is
// the return address
// If there is a single successor it can be anypc or an actual callee
// basic block, both cases are fine. If there's more than one successor,
// we want to register only the default successor of the switch statement
// (typically anypc).
// TODO: register in the call to function_call multiple call targets
unsigned SuccessorsCount = Terminator->getNumSuccessors();
Value *Callee = nullptr;
if (SuccessorsCount == 0) {
Callee = Int8NullPtr;
} else if (SuccessorsCount == 1) {
Callee = BlockAddress::get(Terminator->getSuccessor(0));
} else {
// If there are multiple successors, at least one should not be a jump
// target
bool Found = false;
for (BasicBlock *Successor : successors(Terminator->getParent())) {
if (!GCBI.isJumpTarget(Successor)) {
// There should be only one non-jump target successor (i.e., anypc
// or unepxectedpc).
revng_assert(!Found);
Found = true;
}
}
revng_assert(Found);
// It's an indirect call
Callee = Int8NullPtr;
}
const std::initializer_list<Value *> Args{ Callee,
BlockAddress::get(ReturnBB),
ConstantInt::get(PCTy,
ReturnPC),
V.LinkRegister,
Int8NullPtr };
FallthroughAddresses.insert(ReturnPC);
// If the instruction before the terminator is a call to exitTB, inject
// the call to function_call before it, so it doesn't get purged
auto It = Terminator->getIterator();
if (It != Terminator->getParent()->begin()) {
auto PrevIt = It;
PrevIt--;
if (isCallTo(&*PrevIt, "exitTB"))
It = PrevIt;
}
CallInst::Create(FunctionCall, Args, "", &*It);
}
}
buildFilteredCFG(F);
revng_log(PassesLog, "Ending FunctionCallIdentification");
return false;
}
void FunctionCallIdentification::buildFilteredCFG(llvm::Function &F) {
auto &GCBI = getAnalysis<GeneratedCodeBasicInfo>();
// We have to create a view on the CFG where:
//
// * We only have translate basic blocks
// * Function call edges proceed towards the falltrough basic block
for (BasicBlock &BB : F) {
if (BB.empty() or not GCBI.isTranslated(&BB))
continue;
CustomCFGNode *Node = FilteredCFG.getNode(&BB);
// Is this a function call?
if (CallInst *Call = GCBI.getFunctionCall(&BB)) {
Value *SecondArgument = Call->getArgOperand(1);
auto *Fallthrough = cast<BlockAddress>(SecondArgument)->getBasicBlock();
Node->addSuccessor(FilteredCFG.getNode(Fallthrough));
} else {
unsigned SuccessorsCount = succ_end(&BB) - succ_begin(&BB);
bool IsReturn = SuccessorsCount > 0;
bool AllZero = true;
auto SuccessorsRange = make_range(succ_begin(&BB), succ_end(&BB));
for (llvm::BasicBlock *Successor : SuccessorsRange) {
if (Successor->empty())
continue;
uint64_t Address = getBasicBlockPC(Successor);
AllZero = AllZero and (Address == 0);
IsReturn = IsReturn and (Address == 0 or isFallthrough(Address));
}
IsReturn = IsReturn and not AllZero;
if (not IsReturn) {
for (BasicBlock *Successor : SuccessorsRange) {
if (Successor->empty() or not GCBI.isTranslated(Successor))
continue;
Node->addSuccessor(FilteredCFG.getNode(Successor));
}
}
}
}
FilteredCFG.buildBackLinks();
if (FilteredCFGLog.isEnabled())
FilteredCFG.dump(FilteredCFGLog);
}