Files
revng-revng/lib/StackAnalysis/Intraprocedural.cpp
Alessandro Di Federico 0f7de0c2e0 Call to noreturn functions are not killer
We used to mark call to noreturn functions as killers, but this is not
correct.

Note that this is a temporary solution, we need to explicitly handle
such situations.
2021-02-19 09:39:49 +01:00

1197 lines
38 KiB
C++

/// \file Intraprocedural.cpp
/// \brief Implementation of the intraprocedural portion of the stack analysis
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <iomanip>
#include "Intraprocedural.h"
#include "Cache.h"
#include "InterproceduralAnalysis.h"
using llvm::AllocaInst;
using llvm::ArrayRef;
using llvm::BasicBlock;
using llvm::BlockAddress;
using llvm::CallInst;
using llvm::cast;
using llvm::Constant;
using llvm::ConstantInt;
using llvm::DataLayout;
using llvm::dyn_cast;
using llvm::GlobalVariable;
using llvm::Instruction;
using llvm::isa;
using llvm::LoadInst;
using llvm::Module;
using llvm::Optional;
using llvm::SmallVector;
using llvm::StoreInst;
using llvm::Type;
using llvm::UndefValue;
using llvm::UnreachableInst;
using llvm::User;
using AI = StackAnalysis::Intraprocedural::Interrupt;
using IFS = StackAnalysis::IntraproceduralFunctionSummary;
const IFS EmptyCallSummary = IFS::bottom();
// Loggers
static Logger<> SaFake("sa-fake");
static Logger<> SaTerminator("sa-terminator");
static Logger<> SaBBLog("sa-bb");
// Statistics
RunningStatistics ABIRegistersCountStats("ABIRegistersCount");
static RunningStatistics CacheHitRate("CacheHitRate");
/// \brief Per-function cache hit rate
static std::map<BasicBlock *, RunningStatistics> FunctionCacheHitRate;
/// \brief Round \p Value to \p Digits
template<typename F>
static std::string round(F Value, int Digits) {
std::stringstream Stream;
Stream << std::setprecision(Digits) << Value;
return Stream.str();
}
namespace StackAnalysis {
namespace Intraprocedural {
void Analysis::initialize() {
CacheMustHit = false;
revng_log(SaLog, "Creating Analysis for " << getName(Entry));
Instruction *T = Entry->getTerminator();
revng_assert(T != nullptr);
// Obtain the link register used to call this function
GlobalVariable *LinkRegister = TheCache->getLinkRegister(Entry);
// Get the register indices for for the stack pointer, the program counter
// and the link register
int32_t LinkRegisterIndex = 0;
if (LinkRegister != nullptr)
LinkRegisterIndex = TheCache->getCPUIndex(LinkRegister);
PCIndex = TheCache->getCPUIndex(GCBI->pcReg());
SPIndex = TheCache->getCPUIndex(GCBI->spReg());
// Set the stack pointer to SP0+0
ASSlot StackPointer = ASSlot::create(ASID::cpuID(), SPIndex);
ASSlot StackSlot0 = ASSlot::create(ASID::stackID(), 0);
InitialState = Element::initial();
InitialState.store(Value::fromSlot(StackPointer),
Value::fromSlot(StackSlot0));
// Record the slot where ther return address is stored
ReturnAddressSlot = ((LinkRegister == nullptr) ?
StackSlot0 :
ASSlot::create(ASID::cpuID(), LinkRegisterIndex));
if (SaLog.isEnabled()) {
SaLog << "The return address is in ";
if (LinkRegister != nullptr)
SaLog << LinkRegister->getName().str();
else
SaLog << "the top of the stack";
SaLog << DoLog;
}
TheABIIR.reset();
IncoherentFunctions.clear();
SuccessorsMap.clear();
Base::initialize();
}
/// \brief Class to keep track of the Value associated to each instruction in a
/// basic block
class BasicBlockState {
public:
using ContentMap = std::map<Instruction *, Value>;
private:
BasicBlock *BB;
const Module *M;
ContentMap InstructionContent; ///< Map for the instructions in this BB
ContentMap &VariableContent; ///< Reference to map for allocas
const DataLayout &DL;
const Cache *TheCache;
public:
BasicBlockState(BasicBlock *BB,
ContentMap &VariableContent,
const DataLayout &DL,
const Cache *TheCache) :
BB(BB),
M(getModule(BB)),
VariableContent(VariableContent),
DL(DL),
TheCache(TheCache) {}
/// \brief Gets the Value associated to \p V
///
/// This function handles a couple of type of llvm::Values:
///
/// * AllocaInst/GlobalVariables: represent a part of the CPU state, the
/// result will be an ASSlot relative to the CPU address space.
/// * Constant: represent an absolute address, the result will be an ASSlot
/// relative to the GLB address space with an offset equal to the actual
/// value of the constant.
/// * Instruction: represents the result of a (previously analyzed)
/// Instruction. It can be any Value.
Value get(llvm::Value *V) const {
V = skipCasts(V);
if (auto *CSV = dyn_cast<AllocaInst>(V)) {
return Value::fromSlot(ASID::cpuID(), TheCache->getCPUIndex(CSV));
} else if (auto *CSV = dyn_cast<GlobalVariable>(V)) {
if (TheCache->isCPU(CSV))
return Value::fromSlot(ASID::cpuID(), TheCache->getCPUIndex(CSV));
else
return Value();
} else if (isa<UndefValue>(V)) {
return Value();
} else if (auto *C = dyn_cast<Constant>(V)) {
Type *T = C->getType();
if (T->isPointerTy() or T->getIntegerBitWidth() <= 64) {
int32_t Offset = getZExtValue(C, DL);
return Value::fromSlot(ASID::globalID(), Offset);
} else {
return Value();
}
}
Instruction *I = cast<Instruction>(V);
// I shoudl be in InstructionContent or VariableContent
auto InstructionContentIt = InstructionContent.find(I);
auto VariableContentIt = VariableContent.find(I);
if (InstructionContentIt != InstructionContent.end())
return InstructionContentIt->second;
else if (VariableContentIt != VariableContent.end())
return VariableContentIt->second;
else
revng_abort();
}
/// \brief Register the value of instruction \p I
void set(Instruction *I, Value V) {
revng_assert(I != nullptr);
revng_assert(I->getParent() == BB,
"Instruction from an unexpected basic block");
revng_assert(InstructionContent.count(I) == 0,
"Instruction met more than once in a basic block");
if (SaVerboseLog.isEnabled()) {
SaVerboseLog << "Set " << getName(I) << " to ";
V.dump(M, SaVerboseLog);
SaVerboseLog << DoLog;
}
InstructionContent[I] = V;
}
// TODO: this probably needs to be able to handle casts only
/// \brief Handle automatically an otherwise un-handleable instruction
///
/// This is a fallback handling of instruction not otherwise manually
/// handled. The resulting Value will be a combination of the Values of all
/// its operands.
void handleGenericInstruction(Instruction *I) {
revng_assert(I->getParent() == BB,
"Instruction from an unexpected basic block");
switch (I->getOpcode()) {
case Instruction::BitCast:
case Instruction::IntToPtr:
case Instruction::PtrToInt:
case Instruction::ZExt:
case Instruction::SExt:
revng_assert(I->getNumOperands() == 1);
set(I, get(I->getOperand(0)));
break;
default:
set(I, Value::empty());
break;
}
}
/// \brief Compute the set of BasicBlocks affected by changes in the current
/// one
std::set<BasicBlock *> computeAffected() {
std::set<BasicBlock *> Result;
for (auto &P : InstructionContent) {
Instruction *I = P.first;
Value &NewValue = P.second;
revng_assert(I->getParent() == BB);
if (I->isUsedOutsideOfBlock(BB)) {
bool Changed = false;
// Has this instruction ever been registered?
auto It = VariableContent.find(I);
if (It == VariableContent.end()) {
VariableContent[I] = NewValue;
Changed = true;
} else {
// If not, are we saying something new?
Value &OldValue = It->second;
if (not NewValue.lowerThanOrEqual(OldValue)) {
OldValue = NewValue;
Changed = true;
}
}
if (Changed) {
for (User *U : I->users()) {
if (auto *UserI = dyn_cast<Instruction>(U)) {
BasicBlock *UserBB = UserI->getParent();
if (UserBB != BB)
Result.insert(UserBB);
}
}
}
}
}
return Result;
}
};
static llvm::Value *getModifyAndReassign(Instruction *I) {
auto *Load = dyn_cast<LoadInst>(I->getOperand(0));
if (Load == nullptr)
return nullptr;
for (User *U : I->users()) {
auto *Store = dyn_cast<StoreInst>(U);
if (Store != nullptr and Store->getValueOperand() == I
and Load->getPointerOperand() == Store->getPointerOperand()) {
return Load->getPointerOperand();
}
}
return nullptr;
}
Interrupt Analysis::transfer(BasicBlock *BB) {
auto SP0 = ASID::stackID();
BlockType::Values Type = GCBI->getType(BB);
revng_assert(Type != BlockType::AnyPCBlock
and Type != BlockType::UnexpectedPCBlock);
// Create a copy of the initial state associated to this basic block
auto It = State.find(BB);
revng_assert(It != State.end());
Element Result = It->second.copy();
revng_log(SaBBLog, "Analyzing " << getName(BB));
LoggerIndent<> Y(SaBBLog);
if (SaLog.isEnabled()) {
SaLog << "Analyzing basic block " << getName(BB) << DoLog;
Result.dump(M, SaLog);
SaLog << DoLog;
}
// Reset the basic ABI IR basic block
ABIIRBasicBlock &ABIBB = TheABIIR.get(BB);
ABIBB.clear();
// TODO: prune all the info about dead instructions
// Initialize an object to keep track of the values associated to each
// instruction in the current basic block
BasicBlockState BBState(BB, VariableContent, M->getDataLayout(), TheCache);
for (Instruction &I : *BB) {
revng_log(SaVerboseLog, "NewInstruction: " << getName(&I));
switch (I.getOpcode()) {
case Instruction::Load: {
auto *Load = cast<LoadInst>(&I);
// Get the value associated to the pointer operand and load from it from
// Result
const Value &AddressValue = BBState.get(Load->getPointerOperand());
BBState.set(&I, Result.load(AddressValue));
// If it's not an identity load and we're loading from a register or the
// stack, register the load in the ABI IR
if (not TheCache->isIdentityLoad(Load)) {
if (const ASSlot *Target = AddressValue.directContent()) {
if (isCSV(*Target) or Target->addressSpace() == SP0)
ABIBB.append(ABIIRInstruction::createLoad(*Target));
}
}
} break;
case Instruction::Store: {
auto *Store = cast<StoreInst>(&I);
// Completely ignore identity stores
if (TheCache->isIdentityStore(Store))
break;
// Update slot Address in Result with StoredValue
Value Address = BBState.get(Store->getPointerOperand());
Value StoredValue = BBState.get(Store->getValueOperand());
Result.store(Address, StoredValue);
// If we're loading from a register or the stack register the store in
// the ABI IR
if (const ASSlot *Target = Address.directContent())
if (isCSV(*Target) or Target->addressSpace() == SP0)
ABIBB.append(ABIIRInstruction::createStore(*Target));
} break;
case Instruction::And: {
// If we're masking an address with a mask that is at most as strict as
// the one for instruction alignment, ignore the operation. This allows
// us to correctly track value whose lower bits are suppressed before
// being written to the PC.
// Note that this works if the address is pointing to code, but not
// necessarily if it's pointing to data.
Value FirstOperand = BBState.get(I.getOperand(0));
if (auto *SecondOperand = dyn_cast<ConstantInt>(I.getOperand(1))) {
uint64_t Mask = getSignedLimitedValue(SecondOperand);
uint64_t Flip = ~Mask + 1;
bool IsContiguousMask = Flip and not(Flip & (Flip - 1));
if (IsContiguousMask) {
bool Forward = false;
// Forward any contiguous mask applied to the stack pointer, it's
// likely stack alignment
llvm::Value *Pointer = getModifyAndReassign(&I);
if (Pointer != nullptr and GCBI->isSPReg(Pointer)) {
Forward = true;
} else {
uint64_t SignificantPCBits;
if (GCBI->pcRegSize() == 4) {
SignificantPCBits = std::numeric_limits<uint32_t>::max();
} else {
revng_assert(GCBI->pcRegSize() == 8);
SignificantPCBits = std::numeric_limits<uint64_t>::max();
}
uint64_t AlignmentMask = GCBI->instructionAlignment() - 1;
SignificantPCBits = SignificantPCBits & ~AlignmentMask;
Forward = (SignificantPCBits & Mask) == SignificantPCBits;
}
if (Forward) {
BBState.set(&I, FirstOperand);
break;
}
}
}
// In all other cases, treat it as a regular instruction
BBState.handleGenericInstruction(&I);
} break;
case Instruction::Add:
case Instruction::Sub: {
int Sign = (I.getOpcode() == Instruction::Add) ? +1 : -1;
// If the second operand is constant we can handle it
Value FirstOperand = BBState.get(I.getOperand(0));
if (auto *Addend = dyn_cast<ConstantInt>(I.getOperand(1))) {
if (FirstOperand.add(Sign * getLimitedValue(Addend))) {
BBState.set(&I, FirstOperand);
break;
}
}
// In all other cases, treat it as a regular instruction
BBState.handleGenericInstruction(&I);
} break;
case Instruction::Call: {
auto *Call = cast<CallInst>(&I);
// If the call returns something, introduce a dummy value in BBState
if (not Call->getFunctionType()->getReturnType()->isVoidTy())
BBState.set(&I, Value::empty());
FunctionCall Indirect(nullptr, &I);
const llvm::Function *Callee = getCallee(&I);
revng_assert(Callee != nullptr);
// We should have function calls to helpers, markers, abort or
// intrinsics. Assert in other cases.
revng_assert(isCallToHelper(&I) || isMarker(&I)
|| Callee->getName() == "abort" || Callee->isIntrinsic());
if (isCallToHelper(&I)) {
// Compute the stack size for the call to the helper
Optional<int32_t> CallerStackSize = stackSize(Result);
// Register the call site (as an indirect call) along with the current
// stack size
registerStackSizeAtCallSite(Indirect, CallerStackSize);
// Create in the ABIIR a load for each read register and a store for
// each written register
auto UsedCSVs = GeneratedCodeBasicInfo::getCSVUsedByHelperCall(Call);
for (GlobalVariable *CSV : UsedCSVs.Read)
if (TheCache->isCSV(CSV))
ABIBB.append(ABIIRInstruction::createLoad(slotFromCSV(CSV)));
for (GlobalVariable *CSV : UsedCSVs.Written)
if (TheCache->isCSV(CSV))
ABIBB.append(ABIIRInstruction::createStore(slotFromCSV(CSV)));
}
} break;
case Instruction::Br:
case Instruction::Switch: {
// We're at the end of the basic block, handleTerminator will provide us
// an Interrupt to forward back
Interrupt BBResult = handleTerminator(&I, Result, ABIBB);
// Register all the successors in the ABI IR too
if (BBResult.hasSuccessors())
for (BasicBlock *BB : BBResult)
ABIBB.addSuccessor(&TheABIIR.get(BB));
// Record the type of this branch
BranchesType[BB] = BBResult.type();
// Re-enqueue for analysis all the basic block affected by changes in
// the current one
std::set<BasicBlock *> ToReanalyze = BBState.computeAffected();
for (BasicBlock *BB : ToReanalyze)
if (GCBI->getType(BB) != BlockType::IndirectBranchDispatcherHelperBlock)
registerToVisit(BB);
if (SaLog.isEnabled()) {
SaLog << "Basic block terminated: " << getName(BB) << "\n";
BBResult.dump(M, SaLog);
SaLog << DoLog;
}
return BBResult;
}
case Instruction::Unreachable:
return AI::create(std::move(Result), BranchType::Unreachable);
default:
BBState.handleGenericInstruction(&I);
break;
}
revng_assert(Result.verify());
}
revng_abort();
}
static SmallVector<BasicBlock *, 2>
directSuccessors(GeneratedCodeBasicInfo *GCBI, Instruction *T) {
revng_assert(T->isTerminator());
SmallVector<BasicBlock *, 2> Successors;
for (BasicBlock *Successor : llvm::successors(T)) {
BlockType::Values SuccessorType = GCBI->getType(Successor);
if (SuccessorType != BlockType::UnexpectedPCBlock
and SuccessorType != BlockType::AnyPCBlock)
Successors.push_back(Successor);
}
return Successors;
}
Interrupt Analysis::handleTerminator(Instruction *T,
Element &Result,
ABIIRBasicBlock &ABIBB) {
namespace BT = BranchType;
BasicBlock *BB = T->getParent();
FakeReturns.erase(BB);
revng_assert(T->isTerminator());
revng_assert(not isa<UnreachableInst>(T));
LogOnReturn<> X(SaTerminator);
SaTerminator << T;
Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex);
bool HasUnknownStackSize = not Result.load(StackPointer).hasDirectContent();
if (HasUnknownStackSize)
SaTerminator << " UnknownStackSize";
// 0. Check if it's a direct killer basic block
// TODO: we should move the metadata enums and functions to get their names to
// GCBI
// TODO: this is likely wrong
if (GCBI->isKiller(T)
and GCBI->getKillReason(T) != KillReason::LeadsToKiller) {
SaTerminator << " Killer";
return AI::create(std::move(Result), BT::Killer);
}
// 1. Check if we're dealing with instruction-local control flow (e.g., the if
// generated due to a conditional move)
// 2. Check if it's an indirect branch, which means that "anypc" is among
// its successors
bool IsInstructionLocal = false;
bool IsIndirect = false;
bool IsUnresolvedIndirect = false;
bool JustUnexpected = true;
for (BasicBlock *Successor : llvm::successors(T)) {
BlockType::Values SuccessorType = GCBI->getType(Successor->getTerminator());
// If at least one successor is not a jump target, the branch is instruction
// local
namespace BT = BlockType;
constexpr auto IBDHB = BT::IndirectBranchDispatcherHelperBlock;
IsInstructionLocal = (IsInstructionLocal
or SuccessorType == BT::TranslatedBlock
or SuccessorType == IBDHB);
revng_assert(SuccessorType != BT::RootDispatcherBlock);
IsIndirect = (IsIndirect or SuccessorType == BT::AnyPCBlock
or SuccessorType == BT::UnexpectedPCBlock
or SuccessorType == IBDHB);
IsUnresolvedIndirect = (IsUnresolvedIndirect
or SuccessorType == BT::AnyPCBlock);
JustUnexpected = JustUnexpected and SuccessorType == BT::UnexpectedPCBlock;
}
if (IsIndirect)
SaTerminator << " IsIndirect";
if (IsUnresolvedIndirect)
SaTerminator << " IsUnresolvedIndirect";
if (IsInstructionLocal) {
SaTerminator << " IsInstructionLocal";
return AI::createWithSuccessors(std::move(Result),
BT::InstructionLocalCFG,
directSuccessors(GCBI, T));
}
// 3. Check if this a function call (although the callee might not be a proper
// function)
bool IsFunctionCall = false;
BasicBlock *Callee = nullptr;
BasicBlock *ReturnFromCall = nullptr;
MetaAddress ReturnAddress = MetaAddress::invalid();
if (CallInst *Call = getFunctionCall(T->getParent())) {
IsFunctionCall = true;
auto *Arg0 = Call->getArgOperand(0);
auto *Arg1 = Call->getArgOperand(1);
auto *Arg2 = Call->getArgOperand(2);
if (auto *CalleeBlockAddress = dyn_cast<BlockAddress>(Arg0))
Callee = CalleeBlockAddress->getBasicBlock();
auto *ReturnBlockAddress = cast<BlockAddress>(Arg1);
ReturnFromCall = ReturnBlockAddress->getBasicBlock();
ReturnAddress = MetaAddress::fromConstant(Arg2);
SaTerminator << " IsFunctionCall (callee " << Callee << ", return "
<< ReturnFromCall << ")";
}
// 4. Check if the stack pointer is in position valid for returning
// Get the current value of the stack pointer
// TODO: we should evaluate the approximation introduced here appropriately
Value StackPointerValue = Result.load(StackPointer);
const ASSlot *StackPointerSlot = StackPointerValue.directContent();
auto SP0 = ASID::stackID();
bool IsReadyToReturn = (StackPointerSlot != nullptr
and StackPointerSlot->addressSpace() == SP0
and StackPointerSlot->offset() >= 0);
if (IsReadyToReturn)
SaTerminator << " IsReadyToReturn";
// 5. Are we jumping to the return address? Are we jumping to the return
// address from a fake function?
bool IsReturn = false;
bool IsReturnFromFake = false;
uint64_t FakeFunctionReturnAddress = 0;
if (IsIndirect) {
// Get the current value being stored in the program counter
Value ProgramCounter = Value::fromSlot(ASID::cpuID(), PCIndex);
Value ProgramCounterValue = Result.load(ProgramCounter);
const ASSlot *PCContent = ProgramCounterValue.directContent();
const ASSlot *PCTag = ProgramCounterValue.tag();
// It's a return if the PC has a value with a name matching the name of the
// initial value of the link register
IsReturn = (PCTag != nullptr) and (*PCTag == ReturnAddressSlot);
if (SaTerminator.isEnabled()) {
if (IsReturn) {
SaTerminator << " ReturnsToLinkRegister";
} else {
SaTerminator << " (";
ReturnAddressSlot.dump(M, SaTerminator);
SaTerminator << " != ";
if (PCTag == nullptr)
SaTerminator << "nullptr";
else
PCTag->dump(M, SaTerminator);
SaTerminator << ")";
}
}
if (PCContent != nullptr) {
// Check if it's a return from fake
if (!IsReturn) {
if (PCContent->addressSpace() == ASID::globalID()) {
uint64_t Offset = PCContent->offset();
FakeFunctionReturnAddress = Offset;
IsReturnFromFake = FakeReturnAddresses.count(Offset) != 0;
}
}
}
}
if (IsReturnFromFake)
SaTerminator << " IsReturnFromFake";
// 6. Using the collected information, classify the branch type
// Are we returning to the return address?
if (IsReturn) {
// This looks like an actual return
insert_or_assign(ReturnCandidates, T->getParent(), Result.copy());
return AI::create(std::move(Result), BT::Return);
}
if (IsFunctionCall)
return handleCall(T, Callee, ReturnAddress, ReturnFromCall, Result, ABIBB);
// Is it an indirect jump?
if (IsIndirect) {
// Is it targeting an address that we registered as a return from fake
// function call?
if (IsReturnFromFake) {
// Continue from there
MetaAddress MA = GCBI->fromPC(FakeFunctionReturnAddress);
FakeReturns.insert({ BB, MA });
BasicBlock *ReturnBB = GCBI->getBlockAt(MA);
return AI::createWithSuccessor(std::move(Result),
BT::FakeFunctionReturn,
ReturnBB);
}
// Check if it's a real indirect jump, i.e. we're not 100% of the targets
if (IsUnresolvedIndirect or JustUnexpected) {
if (IsReadyToReturn) {
// If the stack is not in a valid position, we consider it an indirect
// tail call
return handleCall(T,
nullptr,
MetaAddress::invalid(),
nullptr,
Result,
ABIBB);
} else {
// We have an indirect jump with a stack not ready to return: it's a
// longjmp
return AI::create(std::move(Result), BT::LongJmp);
}
}
}
SaTerminator << " FunctionLocalCFG";
return AI::createWithSuccessors(std::move(Result),
BT::FunctionLocalCFG,
directSuccessors(GCBI, T));
}
std::pair<FunctionType::Values, Element> Analysis::finalize() {
MetaAddress EntryPC = getPC(Entry->getTerminator()).first;
#ifndef NDEBUG
// Compute the set of reachable basic blocks
llvm::ReversePostOrderTraversal<ABIIRBasicBlock *> RPOT(TheABIIR.entry());
std::set<BasicBlock *> Reachable;
for (ABIIRBasicBlock *Block : RPOT)
Reachable.insert(Block->basicBlock());
#endif
//
// Return SP election
//
// Combine the value of the stack pointer of each candidate return to see if
// they agree. Meanwhile find the return that is closest (but after) the
// entry point and that has a valid stack size
Value BestSP;
uint64_t ClosestPC = EntryPC.address() - 1;
bool First = true;
Value Combined;
for (auto &P : ReturnCandidates) {
BasicBlock *BB = P.first;
const Element &Result = P.second;
MetaAddress PC = getPC(BB->getTerminator()).first;
uint64_t PCAddress = PC.address();
#ifndef NDEBUG
revng_assert(Reachable.count(BB) != 0);
#endif
Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex);
Value StackPointerValue = Result.load(StackPointer);
if (First) {
Combined = StackPointerValue;
First = false;
} else {
Combined.combine(StackPointerValue);
}
if (const ASSlot *Slot = StackPointerValue.directContent()) {
if (PC.addressGreaterThanOrEqual(EntryPC) and PCAddress < ClosestPC
and Slot->addressSpace() == ASID::stackID() and Slot->offset() >= 0) {
ClosestPC = PCAddress;
BestSP = StackPointerValue;
}
}
}
// Do they all agree on a fixed stack pointer?
if (const ASSlot *Slot = Combined.directContent()) {
if (Slot->addressSpace() == ASID::stackID()) {
if (Slot->offset() >= 0) {
BestSP = Combined;
} else {
// Every return agrees the stack has grown: it's a fake function, let's
// inline it
return { FunctionType::Fake, Element::bottom() };
}
}
}
if (not BestSP.hasDirectContent())
return { FunctionType::NoReturn, Element::bottom() };
// Combine all the values of the non-broken returns, mark as broken all the
// others
First = true;
Element GrandResult = Element::bottom();
for (auto &P : ReturnCandidates) {
BasicBlock *BB = P.first;
Element &ReturnResult = P.second;
Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex);
Value StackPointerValue = ReturnResult.load(StackPointer);
if (BestSP.hasDirectContent() and StackPointerValue == BestSP) {
// Mark as return basic block in the ABI IR
TheABIIR.get(BB).setReturn();
// OK, we're compatible, make ReturnResult part of the final result
if (First) {
GrandResult = std::move(ReturnResult);
First = false;
} else {
GrandResult.combine(std::move(ReturnResult));
}
} else {
// Mark as broken
auto &Type = BranchesType[BB];
if (Type == BranchType::Return)
Type = BranchType::BrokenReturn;
else if (Type == BranchType::IndirectTailCall)
Type = BranchType::LongJmp;
}
}
return { FunctionType::Regular, std::move(GrandResult) };
}
Interrupt Analysis::handleCall(Instruction *Caller,
BasicBlock *Callee,
MetaAddress ReturnAddress,
BasicBlock *ReturnFromCall,
Element &Result,
ABIIRBasicBlock &ABIBB) {
namespace BT = BranchType;
revng_assert(Callee == nullptr or getName(Callee) != "unexpectedpc");
const bool IsRecursive = InProgressFunctions.count(Callee) != 0;
const bool IsIndirect = (Callee == nullptr);
const bool IsIndirectTailCall = IsIndirect and (ReturnFromCall == nullptr);
bool IsKiller = false;
bool ABIOnly = false;
FunctionCall TheFunctionCall = { Callee, Caller };
int32_t PCRegSize = GCBI->pcRegSize();
Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex);
Value OldStackPointer = Result.load(StackPointer);
Value PC = Value::fromSlot(ASID::cpuID(), PCIndex);
// Handle special function types:
//
// 1. Calls to Fake functions will be inlined.
// 2. Calls to NoReturn functions will make the current basic block a Killer
// 3. Calls to IndirectTailCall functions are considered as indirect function
// calls
if (not IsIndirect) {
if (TheCache->isFakeFunction(Callee)) {
// Make sure the CacheMustHit bit is turned off
resetCacheMustHit();
SaTerminator << " IsFakeFunctionCall";
// Assume normal control flow (i.e., inline)
FakeReturnAddresses.insert(ReturnAddress.asPC());
return AI::createWithSuccessor(std::move(Result),
BT::FakeFunctionCall,
Callee);
} else if (TheCache->isNoReturnFunction(Callee)) {
SaTerminator << " IsNoReturnFunction";
ABIOnly = true;
}
}
// If we know the current stack frame size, copy the arguments
Optional<int32_t> CallerStackSize = stackSize(Result);
const IFS *CallSummary = &EmptyCallSummary;
revng_assert(not(IsRecursive && IsIndirect));
// Is it an direct function call?
if (not IsIndirect) {
// We have a direct call
revng_assert(Callee != nullptr);
// It's a direct function call, lookup the <Callee, Context> pair in the
// cache
Optional<const IFS *> CacheEntry;
CacheEntry = TheCache->get(Callee);
if (not CacheMustHit) {
const char *ResultString = nullptr;
if (CacheEntry) {
CacheHitRate.push(1);
FunctionCacheHitRate[Callee].push(1);
ResultString = "hit";
} else {
CacheHitRate.push(0);
FunctionCacheHitRate[Callee].push(0);
ResultString = "miss";
}
if (SaInterpLog.isEnabled()) {
SaInterpLog << "Cache " << ResultString << " for " << Callee << " at "
<< Caller << " (";
auto Mean = FunctionCacheHitRate[Callee].mean();
SaInterpLog << "function hit rate: " << round(100 * Mean, 4) << "%";
SaInterpLog << ", hit rate: " << round(100 * CacheHitRate.mean(), 4)
<< "%) ";
SaInterpLog << DoLog;
}
}
// Do we have a cache hit?
// If we don't we return control the interprocedural part, and we record
// that next time we *must* have a cache hit. If we don't there's the risk
// we're going to loop endlessly.
if (CacheEntry) {
resetCacheMustHit();
// We have a match in the cache
CallSummary = *CacheEntry;
} else {
// Ensure we don't get a cache miss twice in a row
revng_assert(not CacheMustHit);
// Next time the cache will have to hit
CacheMustHit = true;
// We don't have a match in the cache. Ask interprocedural analysis to
// analyze this function call with the current context
return AI::createUnhandledCall(Callee);
}
} // not IsIndirect
// If we got to this point, we now have a cached result of what the callee
// does. Let's apply it.
if (SaLog.isEnabled()) {
SaLog << "The summary result for a call to " << getName(Callee) << " is\n";
CallSummary->dump(M, SaLog);
SaLog << DoLog;
}
if (not ABIOnly and not CallSummary->FinalState.isBottom()) {
// Use the summary from the cache
Result.apply(CallSummary->FinalState);
}
if (IsRecursive or IsIndirect) {
ABIBB.append(ABIIRInstruction::createIndirectCall(TheFunctionCall));
} else {
std::set<int32_t> StackArguments;
if (CallerStackSize and *CallerStackSize >= 0)
StackArguments = CallSummary->FinalState.stackArguments(*CallerStackSize);
ABIBB.append(ABIIRInstruction::createDirectCall(TheFunctionCall,
CallSummary->ABI.copy(),
StackArguments));
}
// Record frame size
registerStackSizeAtCallSite(TheFunctionCall, CallerStackSize);
// Resume the analysis from where we left off
// Restore the stack pointer
GlobalVariable *CalleeLinkRegister = TheCache->getLinkRegister(Callee);
if (CalleeLinkRegister == nullptr) {
// Increase the stack pointer of the size of the PC reg
OldStackPointer.add(PCRegSize);
}
Result.store(StackPointer, OldStackPointer);
// Restore the PC
// TODO: handle return address from indirect tail calls
ASSlot ReturnAddressSlot = ASSlot::create(ASID::globalID(),
ReturnAddress.asPCOrZero());
Result.store(PC, Value::fromSlot(ReturnAddressSlot));
revng_assert(not(IsIndirectTailCall and IsKiller));
if (IsIndirectTailCall) {
// We consider indirect tail calls as returns
insert_or_assign(ReturnCandidates, Caller->getParent(), Result.copy());
return AI::create(std::move(Result), BT::IndirectTailCall);
} else if (IsKiller) {
return AI::create(std::move(Result), BT::Killer);
} else {
revng_assert(ReturnFromCall != nullptr);
auto Reason = IsIndirect ? BT::IndirectCall : BT::HandledCall;
return AI::createWithSuccessor(std::move(Result), Reason, ReturnFromCall);
}
}
ASSlot Analysis::slotFromCSV(llvm::User *U) const {
return ASSlot::create(ASID::cpuID(), TheCache->getCPUIndex(U));
}
IFS Analysis::createSummary() {
auto P = finalize();
FunctionType::Values Type = P.first;
Element GrandResult = std::move(P.second);
// Fake functions need no further analysis (NoReturn functions do)
if (Type == FunctionType::Fake)
return IFS::createFake();
// Finalize the ABI IR (e.g., fill-in reverse links)
TheABIIR.finalize();
FunctionABI ABI;
if (AnalyzeABI) {
if (SaABI.isEnabled()) {
revng_log(SaABI, "Starting analysis of " << Entry);
TheABIIR.dump(SaABI, M);
SaABI << DoLog;
}
revng_assert(TheABIIR.verify(), "The ABI IR is invalid");
// Run the almighty ABI analyses
ABI.analyze(TheABIIR);
}
// Find all the function calls that lead to results incoherent with the
// callees and register them
std::set<int32_t> WrittenRegisters = TheABIIR.writtenRegisters();
IFS Summary;
if (Type == FunctionType::Regular) {
Summary = IFS::createRegular(std::move(GrandResult),
std::move(ABI),
std::move(FrameSizeAtCallSite),
std::move(BranchesType),
std::move(WrittenRegisters),
std::move(FakeReturns));
} else {
Summary = IFS::createNoReturn(std::move(ABI),
std::move(FrameSizeAtCallSite),
std::move(BranchesType),
std::move(WrittenRegisters),
std::move(FakeReturns));
}
findIncoherentFunctions(Summary);
if (SaABI.isEnabled()) {
SaABI << "ABI analyses on " << Entry << " completed:\n";
Summary.dump(M, SaABI);
SaABI << DoLog;
}
return Summary;
}
void Analysis::findIncoherentFunctions(const IFS &ABISummary) {
// TODO: do we need to take into account also all the registers used
// in the various function calls?
const IFS::LocalSlotVector &Slots = ABISummary.LocalSlots;
for (const FunctionCall &FC : TheABIIR.incoherentCalls()) {
revng_log(SaFake,
FC.callee() << " (" << FC.callInstruction() << ") is fake.");
IncoherentFunctions.insert(FC.callee());
}
// Loop over all the function calls in this function
for (const auto &P : FrameSizeAtCallSite) {
const FunctionCall TheFunctionCall = P.first;
BasicBlock *Callee = TheFunctionCall.callee();
// We cannot perform any coherency check on indirect function calls
if (Callee == nullptr)
continue;
// TODO: this is an hack, functions marked as fake should somehow be
// purged from CallsContext
if (TheCache->isFakeFunction(Callee))
continue;
// We might not have an entry, e.g., if they callee is noreturn
Optional<const IFS *> Cache = TheCache->get(Callee);
if (Cache) {
const FunctionABI &CalleeSummary = (*Cache)->ABI;
// Loop over all the slots being considered in this function
for (auto &Slot : Slots) {
if (not isCoherent(ABISummary.ABI,
CalleeSummary,
TheFunctionCall,
Slot)) {
IncoherentFunctions.insert(Callee);
break;
}
}
}
}
}
bool Analysis::isCoherent(const FunctionABI &CallerSummary,
const FunctionABI &CalleeSummary,
FunctionCall TheFunctionCall,
IFS::LocalSlot Slot) const {
int32_t Offset = Slot.first.offset();
BasicBlock *Callee = TheFunctionCall.callee();
switch (Slot.second) {
case LocalSlotType::UsedRegister: {
FunctionRegisterArgument FunctionArgument;
FunctionCallRegisterArgument FunctionCallArgument;
CalleeSummary.applyResults(FunctionArgument, Offset);
CallerSummary.applyResults(FunctionCallArgument, TheFunctionCall, Offset);
FunctionRegisterArgument CombinedArgument = FunctionArgument;
CombinedArgument.combine(FunctionCallArgument);
if (CombinedArgument.isContradiction()) {
if (SaFake.isEnabled()) {
SaFake << "Contradiction at ";
TheFunctionCall.dump(SaFake);
SaFake << " on argument ";
ASSlot::create(ASID::cpuID(), Offset).dump(M, SaFake);
SaFake << ": caller says is ";
FunctionCallArgument.dump(SaFake);
SaFake << ", while callee says is ";
FunctionArgument.dump(SaFake);
SaFake << ", marking " << Callee << " as fake." << DoLog;
}
return false;
}
FunctionReturnValue TheFunctionReturnValue;
FunctionCallReturnValue TheFunctionCallReturnValue;
CalleeSummary.applyResults(TheFunctionReturnValue, Offset);
CallerSummary.applyResults(TheFunctionCallReturnValue,
TheFunctionCall,
Offset);
FunctionReturnValue CombinedReturnValue = TheFunctionReturnValue;
CombinedReturnValue.combine(TheFunctionCallReturnValue);
if (CombinedReturnValue.isContradiction()) {
if (SaFake.isEnabled()) {
SaFake << "Contradiction at ";
TheFunctionCall.dump(SaFake);
SaFake << " on return value ";
ASSlot::create(ASID::cpuID(), Offset).dump(M, SaFake);
SaFake << ": caller says is ";
TheFunctionCallReturnValue.dump(SaFake);
SaFake << ", while callee says is ";
TheFunctionReturnValue.dump(SaFake);
SaFake << ", marking " << Callee << " as fake." << DoLog;
}
return false;
}
} break;
case LocalSlotType::ForwardedArgument:
case LocalSlotType::ForwardedReturnValue:
case LocalSlotType::ExplicitlyCalleeSavedRegister:
break;
}
return true;
}
} // namespace Intraprocedural
} // namespace StackAnalysis