Files
revng-revng/lib/StackAnalysis/functionssummary.cpp
T
Alessandro Di Federico 7fe00c08dd Rewrite the stack and introduce the ABI analyses
This is a very large commit importing the reviewed (and heavily
simplified) stack analysis and the new ABI analysis, which provides
information on the calling convention of each function and so on.

For an overview of the new analyses please consult OVERVIEW.md.
2018-09-18 15:58:20 +02:00

499 lines
13 KiB
C++

/// \file abianalyses.cpp
/// \brief Implementation of the classes representing an argument/return value
/// in a register
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
// Boost includes
#include <boost/icl/interval_set.hpp>
// Local includes
#include "asslot.h"
#include "ir-helpers.h"
#include "revng/StackAnalysis/functionssummary.h"
using llvm::BasicBlock;
using llvm::BlockAddress;
using llvm::CallInst;
using llvm::cast;
using llvm::cast_or_null;
using llvm::dyn_cast;
using llvm::dyn_cast_or_null;
using llvm::GlobalVariable;
using llvm::MDNode;
using llvm::MDString;
using llvm::MDTuple;
using llvm::Metadata;
using llvm::Module;
using llvm::SmallVector;
using llvm::StringRef;
using llvm::TerminatorInst;
using llvm::User;
namespace StackAnalysis {
template class RegisterArgument<true>;
template class RegisterArgument<false>;
template<>
void
FunctionRegisterArgument::combine(const FunctionCallRegisterArgument &Other) {
// TODO: we're handling this as a special case
if (Value == No || Other.Value == FunctionCallRegisterArgument::No) {
Value = No;
return;
}
assert(Other.Value == FunctionCallRegisterArgument::Maybe
|| Other.Value == FunctionCallRegisterArgument::Yes);
assert(Value == NoOrDead || Value == Maybe || Value == Contradiction
|| Value == Yes || Value == Dead);
if (Other.Value == FunctionCallRegisterArgument::Yes) {
switch (Value) {
case NoOrDead:
Value = Dead;
break;
case Maybe:
Value = Yes;
break;
case Contradiction:
Value = Contradiction;
break;
case Yes:
Value = Yes;
break;
case Dead:
Value = Dead;
break;
case No:
abort();
}
} else {
switch (Value) {
case NoOrDead:
Value = NoOrDead;
break;
case Maybe:
Value = Maybe;
break;
case Contradiction:
Value = Contradiction;
break;
case Yes:
Value = Yes;
break;
case Dead:
Value = Dead;
break;
case No:
abort();
}
}
}
template<>
void
FunctionCallRegisterArgument::combine(const FunctionRegisterArgument &Other) {
// TODO: we're handling this as a special case
if (Value == No || Other.Value == FunctionRegisterArgument::No) {
Value = No;
return;
}
assert(Value == Maybe || Value == Yes);
assert(Other.Value == FunctionRegisterArgument::NoOrDead
|| Other.Value == FunctionRegisterArgument::Maybe
|| Other.Value == FunctionRegisterArgument::Contradiction
|| Other.Value == FunctionRegisterArgument::Yes);
if (Value == Yes) {
switch (Other.Value) {
case FunctionRegisterArgument::NoOrDead:
Value = Dead;
break;
case FunctionRegisterArgument::Maybe:
Value = Yes;
break;
case FunctionRegisterArgument::Contradiction:
Value = Contradiction;
break;
case FunctionRegisterArgument::Yes:
Value = Yes;
break;
default:
abort();
}
} else {
switch (Other.Value) {
case FunctionRegisterArgument::NoOrDead:
Value = NoOrDead;
break;
case FunctionRegisterArgument::Maybe:
Value = Maybe;
break;
case FunctionRegisterArgument::Contradiction:
Value = Contradiction;
break;
case FunctionRegisterArgument::Yes:
Value = Yes;
break;
default:
abort();
}
}
}
void FunctionReturnValue::combine(const FunctionCallReturnValue &Other) {
// TODO: we're handling this as a special case
if (Value == No || Other.Value == FunctionCallReturnValue::No) {
Value = No;
return;
}
// *this has seen only URVOF, which can only have Maybe or Yes value
assert(Value == Maybe || Value == YesCandidate || Value == Dead
|| Value == Yes || Value == NoOrDead);
// Other is affected by URVOFC and DRVOFC, so that possible states are Maybe,
// NoOrDead and Yes
assert(Other.Value == FunctionCallReturnValue::Maybe
|| Other.Value == FunctionCallReturnValue::Contradiction
|| Other.Value == FunctionCallReturnValue::NoOrDead
|| Other.Value == FunctionCallReturnValue::Yes);
switch (Value) {
case YesCandidate:
switch (Other.Value) {
case FunctionCallReturnValue::Maybe:
Value = YesCandidate;
break;
case FunctionCallReturnValue::NoOrDead:
Value = Dead;
break;
case FunctionCallReturnValue::Yes:
Value = Yes;
break;
case FunctionCallReturnValue::Contradiction:
Value = Contradiction;
break;
default:
abort();
}
break;
case Maybe:
switch (Other.Value) {
case FunctionCallReturnValue::Maybe:
Value = Maybe;
break;
case FunctionCallReturnValue::NoOrDead:
Value = NoOrDead;
break;
case FunctionCallReturnValue::Yes:
Value = Yes;
break;
case FunctionCallReturnValue::Contradiction:
Value = Contradiction;
break;
default:
abort();
}
break;
case Dead:
switch (Other.Value) {
case FunctionCallReturnValue::Maybe:
Value = Dead;
break;
case FunctionCallReturnValue::NoOrDead:
Value = Dead;
break;
case FunctionCallReturnValue::Yes:
Value = Yes;
break;
case FunctionCallReturnValue::Contradiction:
Value = Contradiction;
break;
default:
abort();
}
break;
case Yes:
switch (Other.Value) {
case FunctionCallReturnValue::Maybe:
Value = Yes;
break;
case FunctionCallReturnValue::NoOrDead:
// TODO: contradiction?
Value = Yes;
break;
case FunctionCallReturnValue::Yes:
Value = Yes;
break;
case FunctionCallReturnValue::Contradiction:
Value = Contradiction;
break;
default:
abort();
}
break;
case NoOrDead:
switch (Other.Value) {
case FunctionCallReturnValue::Maybe:
Value = NoOrDead;
break;
case FunctionCallReturnValue::NoOrDead:
Value = NoOrDead;
break;
case FunctionCallReturnValue::Yes:
Value = Yes;
break;
case FunctionCallReturnValue::Contradiction:
Value = Contradiction;
break;
default:
abort();
}
break;
default:
abort();
}
}
void FunctionCallReturnValue::combine(const FunctionReturnValue &Other) {
// TODO: we're handling this as a special case
if (Value == No || Other.Value == FunctionReturnValue::No) {
Value = No;
return;
}
// *this has seen only URVOF, which can only have Maybe or Yes value
assert(Other.Value == FunctionReturnValue::Maybe
|| Other.Value == FunctionReturnValue::YesCandidate);
// Other is affected by URVOFC and DRVOFC, so that possible states are Maybe,
// NoOrDead, Yes and Contradiction
assert(Value == Maybe || Value == NoOrDead || Value == Yes
|| Value == Contradiction);
if (Other.Value == FunctionReturnValue::YesCandidate) {
switch (Value) {
case Maybe:
Value = Yes;
break;
case NoOrDead:
Value = Dead;
break;
case Yes:
Value = Yes;
break;
case Contradiction:
Value = Contradiction;
break;
default:
abort();
}
} else {
switch (Value) {
case Maybe:
Value = Maybe;
break;
case NoOrDead:
Value = NoOrDead;
break;
case Yes:
Value = Yes;
break;
case Contradiction:
Value = Contradiction;
break;
default:
abort();
}
}
}
void FunctionsSummary::dumpInternal(const Module *M,
StreamWrapperBase &&Stream) const {
std::stringstream Output;
// Register the range of addresses covered by each basic block
using interval_set = boost::icl::interval_set<uint64_t>;
using interval = boost::icl::interval<uint64_t>;
std::map<BasicBlock *, interval_set> Coverage;
for (User *U : M->getFunction("newpc")->users()) {
auto *Call = dyn_cast<CallInst>(U);
if (Call == nullptr)
continue;
BasicBlock *BB = Call->getParent();
uint64_t Address = getLimitedValue(Call->getOperand(0));
uint64_t Size = getLimitedValue(Call->getOperand(1));
assert(Address > 0 && Size > 0);
Coverage[BB] += interval::right_open(Address, Address + Size);
}
// Sort the functions by name, for extra determinism!
using Pair = std::pair<BasicBlock *, const FunctionDescription *>;
std::vector<Pair> SortedFunctions;
for (auto &P : Functions)
SortedFunctions.push_back({ P.first, &P.second });
auto Compare = [](const Pair &A, const Pair &B) {
return getName(A.first) < getName(B.first);
};
std::sort(SortedFunctions.begin(), SortedFunctions.end(), Compare);
const char *FunctionDelimiter = "";
Output << "[";
for (auto &P : SortedFunctions) {
Output << FunctionDelimiter << "\n {\n";
BasicBlock *Entry = P.first;
const FunctionDescription &Function = *P.second;
Output << " \"entry_point\": \"";
if (Entry != nullptr)
Output << getName(Entry);
Output << "\",\n";
Output << " \"entry_point_address\": \"";
if (Entry != nullptr)
Output << "0x" << std::hex << getBasicBlockPC(Entry);
Output << "\",\n";
Output << " \"jt-reasons\": [";
if (Entry != nullptr) {
const char *JTReasonsDelimiter = "";
TerminatorInst *T = Entry->getTerminator();
assert(T != nullptr);
MDNode *Node = T->getMetadata("revamb.jt.reasons");
SmallVector<StringRef, 4> Reasons;
if (auto *Tuple = cast_or_null<MDTuple>(Node)) {
// Collect reasons
for (Metadata *ReasonMD : Tuple->operands())
Reasons.push_back(cast<MDString>(ReasonMD)->getString());
// Sort the output to make it more deterministic
std::sort(Reasons.begin(), Reasons.end());
// Print out
for (StringRef Reason : Reasons) {
Output << JTReasonsDelimiter << "\"" << Reason.data() << "\"";
JTReasonsDelimiter = ", ";
}
}
}
Output << "],\n";
Output << " \"type\": \"" << getName(Function.Type) << "\",\n";
interval_set FunctionCoverage;
const char *BasicBlockDelimiter = "";
Output << " \"basic_blocks\": [";
for (auto &P : Function.BasicBlocks) {
BasicBlock *BB = P.first;
BranchType::Values Type = P.second;
const char *TypeName = BranchType::getName(Type);
Output << BasicBlockDelimiter;
Output << "{\"name\": \"" << getName(BB) << "\", ";
Output << "\"type\": \"" << TypeName << "\", ";
auto It = Coverage.find(BB);
if (It != Coverage.end()) {
const interval_set &IntervalSet = It->second;
FunctionCoverage += IntervalSet;
assert(IntervalSet.iterative_size() == 1);
const auto &Range = *(IntervalSet.begin());
Output << "\"start\": \"0x" << std::hex << Range.lower() << "\", ";
Output << "\"end\": \"0x" << std::hex << Range.upper() << "\"";
} else {
Output << "\"start\": \"\", \"end\": \"\"";
}
Output << "}";
BasicBlockDelimiter = ", ";
}
Output << "],\n";
Output << " \"slots\": [";
const char *SlotDelimiter = "";
for (auto &Q : Function.RegisterSlots) {
Output << SlotDelimiter;
Output << "{\"slot\": \"" << Q.first->getName().data() << "\", ";
Output << "\"argument\": \"";
Q.second.Argument.dump(Output);
Output << "\", ";
Output << "\"return_value\": \"";
Q.second.ReturnValue.dump(Output);
Output << "\"}";
SlotDelimiter = ", ";
}
Output << "],\n";
Output << " \"clobbered\": [";
const char *ClobberedDelimiter = "";
for (const GlobalVariable *CSV : Function.ClobberedRegisters) {
Output << ClobberedDelimiter;
Output << "\"" << CSV->getName().data() << "\"";
ClobberedDelimiter = ", ";
}
Output << "],\n";
const char *CoverageDelimiter = "";
Output << " \"coverage\": [";
for (const auto &Range : FunctionCoverage) {
Output << CoverageDelimiter;
Output << "{";
Output << "\"start\": \"0x" << std::hex << Range.lower() << "\", ";
Output << "\"end\": \"0x" << std::hex << Range.upper() << "\"";
Output << "}";
CoverageDelimiter = ", ";
}
Output << "],\n";
const char *FunctionCallDelimiter = "";
Output << " \"function_calls\": [";
for (const CallSiteDescription &CallSite : Function.CallSites) {
Output << FunctionCallDelimiter << "\n";
Output << " {\n";
Output << " \"caller\": ";
Output << "\"" << getName(CallSite.Call) << "\",\n";
// TODO: caller address
// TODO: callee
// TODO: callee address
Output << " \"slots\": [";
const char *FunctionCallSlotsDelimiter = "";
for (auto &Q : CallSite.RegisterSlots) {
Output << FunctionCallSlotsDelimiter;
Output << "{\"slot\": \"" << Q.first->getName().data() << "\", ";
Output << "\"argument\": \"";
Q.second.Argument.dump(Output);
Output << "\", ";
Output << "\"return_value\": \"";
Q.second.ReturnValue.dump(Output);
Output << "\"}";
FunctionCallSlotsDelimiter = ", ";
}
Output << "]\n";
Output << " }";
FunctionCallDelimiter = ",";
}
Output << "\n ]\n";
Output << " }";
FunctionDelimiter = ",";
Stream.flush(Output);
}
Output << "\n]\n";
Stream.flush(Output);
}
} // namespace StackAnalysis