Files
revng-revng/variablemanager.cpp
T
Pietro Fezzardi f90f9451fa Add CPUStateAccessAnalysisPass
This commit adds a new analysis pass: `CPUStateAccessAnalysisPass`.

This pass currently performs 4 operations.

1. A preliminary analysis of the call graph, to select the functions
   that are reachable from the root function through direct calls.  All
   the other performed operations are executed on this set of reachable
   functions.

2. An interprocedural forward taint analysis, starting from the uses of
   `env`, the global variable pointing to the QEMU struct continaint the
   CPU. This analysis taints all the instructions that use the address
   of `env`, until a load or a store is met. If a load or a store uses a
   tainted Value as address it means that it is accessing a CSV at a
   given offset (which at this point is still unknown).

3. An interprocedural offset analysis, which deduces the possible
   offsets used by every tainted load/store to access the CSV. This
   analysis initially works backwards, exploring all the Values that
   contribute at the computation of the addresses used by tainted
   load/stores. Once it finds all the sources, it starts propagating the
   values forward, collecting the offsets computed along the way. It
   does this until it reaches the tainted load/stores again. At that
   point the analysis knows all the possible offsets used by each
   tainted load/store to access the CPU state.

4. The results of the previous steps are used to do 3 things:

  * marking all the indirect calls with tainted arguments as illegal;
    this is necessary because those calls may access the CPU State in
    unpredictable ways;
  * attaching metadata to all the call sites to QEMU helpers in the root
    function; these metadata provide information on which parts of the
    CPU State may be accessed from that call site, which is a
    potentially useful information for users of libtinycode that we also
    plan to use in other parts of revamb;
  * substituting loads, stores, and memcpys to and from the CPU state
    with accesses to global variables; this operation effectively
    replaces what was previously done by the CorrectCPUStateUsagePass,
    which is now obsolete and was removed in this commit.
2018-05-31 17:04:16 +02:00

648 lines
22 KiB
C++

/// \file variablemanager.cpp
/// \brief This file handles the creation and management of global variables,
/// i.e. mainly parts of the CPU state
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
// Standard includes
#include <cstdint>
#include <stack>
#include <sstream>
#include <set>
#include <string>
// LLVM includes
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/Casting.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/ValueMapper.h"
// Local includes
#include "debug.h"
#include "ir-helpers.h"
#include "variablemanager.h"
#include "revamb.h"
#include "ptcdump.h"
#include "ptcinterface.h"
using namespace llvm;
class OffsetValueStack {
private:
using OffsetValuePair = std::pair<int64_t, Value *>;
public:
void pushIfNew(int64_t Offset, Value *V) {
OffsetValuePair Element = { Offset, V };
if (!Seen.count(Element)) {
Seen.insert(Element);
Stack.push_back(Element);
}
}
void push(int64_t Offset, Value *V) {
OffsetValuePair Element = { Offset, V };
Stack.push_back(Element);
}
bool empty() { return Stack.empty(); }
std::pair<int64_t, Value *> pop() {
auto Result = Stack.back();
Stack.pop_back();
return Result;
}
// TODO: this is on O(n)
void cloneSisters(Value *Old, Value *New) {
for (auto &OVP : Stack)
if (OVP.second == Old)
push(OVP.first, New);
}
private:
std::set<OffsetValuePair> Seen;
std::vector<OffsetValuePair> Stack;
};
static std::pair<IntegerType *, unsigned>
getTypeAtOffset(const DataLayout *TheLayout, Type *VarType, intptr_t Offset) {
unsigned Depth = 0;
while (1) {
switch (VarType->getTypeID()) {
case llvm::Type::TypeID::PointerTyID:
// BEWARE: here we return { nullptr, 0 } as an intended workaround for
// a specific situation.
//
// We can't use assertions on pointers, as we do for all the other
// unhandled types, because they will be inevitably triggered during the
// execution. Indeed, all the other types are not present in QEMU
// CPUState and we can safely assert it. This is not true for pointers
// that are used in different places in QEMU CPUState.
//
// Given that we have ruled out assertions, we need to handle the
// pointer case so that it keeps working. This function is expected to
// return { nullptr, 0 } when the offset points to a memory location
// associated to padding space. In principle, pointers are not padding
// space, but the result of returning { nullptr, 0 } here is that load
// and store operations treat pointers like padding. This means that
// pointers cannot be read or written, and memcpy simply skips over them
// leaving them alone.
//
// This behavior is intended, because a pointer into the CPUState could
// be used to modify CPU registers indirectly, which is against all the
// assumption of the analysis necessary for the translation, and also
// against what really happens in a CPU, where CPU state cannot be
// addressed.
return { nullptr, 0 };
case llvm::Type::TypeID::IntegerTyID:
return { cast<IntegerType>(VarType), Offset };
case llvm::Type::TypeID::ArrayTyID:
VarType = VarType->getArrayElementType();
Offset %= TheLayout->getTypeAllocSize(VarType);
DBG("type-at-offset", dbg << std::string(Depth++ * 2, ' ')
<< " Is an Array. Offset in Element: " << Offset << '\n');
break;
case llvm::Type::TypeID::StructTyID:
{
StructType *TheStruct = cast<StructType>(VarType);
const StructLayout *Layout = TheLayout->getStructLayout(TheStruct);
unsigned FieldIndex = Layout->getElementContainingOffset(Offset);
uint64_t FieldOffset = Layout->getElementOffset(FieldIndex);
VarType = TheStruct->getTypeAtIndex(FieldIndex);
intptr_t FieldEnd = FieldOffset
+ TheLayout->getTypeAllocSize(VarType);
DBG("type-at-offset", dbg
<< std::string(Depth++ * 2, ' ')
<< " Offset: " << Offset
<< " Struct Name: " << TheStruct->getName().str()
<< " Field Index: " << FieldIndex
<< " Field offset: " << FieldOffset
<< " Field end: " << FieldEnd
<< "\n");
if (Offset >= FieldEnd)
return { nullptr, 0 }; // It's padding
Offset -= FieldOffset;
}
break;
default:
assert(false and "unexpected TypeID");
}
}
}
VariableManager::VariableManager(Module& TheModule,
Module& HelpersModule,
Architecture& TargetArchitecture) :
TheModule(TheModule),
Builder(TheModule.getContext()),
CPUStateType(nullptr),
ModuleLayout(&HelpersModule.getDataLayout()),
EnvOffset(0),
Env(nullptr),
AliasScopeMDKindID(TheModule.getMDKindID("alias.scope")),
NoAliasMDKindID(TheModule.getMDKindID("noalias")),
TargetArchitecture(TargetArchitecture) {
auto *CPUStateAliasDomain = MDNode::getDistinct(TheModule.getContext(),
ArrayRef<Metadata *>());
auto *Temporary = MDNode::get(TheModule.getContext(), ArrayRef<Metadata *>());
auto *CPUStateScope = MDNode::getDistinct(TheModule.getContext(),
ArrayRef<Metadata *>({
Temporary,
CPUStateAliasDomain
}));
CPUStateScope->replaceOperandWith(0, CPUStateScope);
CPUStateScopeSet = MDNode::get(TheModule.getContext(),
ArrayRef<Metadata *>({ CPUStateScope }));
assert(ptc.initialized_env != nullptr);
using ElectionMap = std::map<StructType *, unsigned>;
using ElectionMapElement = std::pair<StructType * const, unsigned>;
ElectionMap EnvElection;
const std::string HelperPrefix = "helper_";
std::set<StructType *> Structs;
for (Function& HelperFunction : HelpersModule) {
FunctionType *HelperType = HelperFunction.getFunctionType();
Type *ReturnType = HelperType->getReturnType();
if (ReturnType->isPointerTy())
Structs.insert(dyn_cast<StructType>(ReturnType->getPointerElementType()));
for (Type *Param : HelperType->params())
if (Param->isPointerTy())
Structs.insert(dyn_cast<StructType>(Param->getPointerElementType()));
if (startsWith(HelperFunction.getName(), HelperPrefix)
&& HelperFunction.getFunctionType()->getNumParams() > 1) {
for (Type *Candidate : HelperType->params()) {
Structs.insert(dyn_cast<StructType>(Candidate));
if (Candidate->isPointerTy()) {
auto *PointeeType = Candidate->getPointerElementType();
auto *EnvType = dyn_cast<StructType>(PointeeType);
// Ensure it is a struct and not a union
if (EnvType != nullptr && EnvType->getNumElements() > 1) {
auto It = EnvElection.find(EnvType);
if (It != EnvElection.end())
EnvElection[EnvType]++;
else
EnvElection[EnvType] = 1;
}
}
}
}
}
Structs.erase(nullptr);
assert(EnvElection.size() > 0);
CPUStateType = std::max_element(EnvElection.begin(),
EnvElection.end(),
[] (ElectionMapElement& It1,
ElectionMapElement& It2) {
return It1.second < It2.second;
})->first;
// Look for structures containing CPUStateType as a member and promove them
// to CPUStateType. Basically this is a flexible way to keep track of the *CPU
// struct too (e.g. MIPSCPU).
std::set<StructType *> Visited;
bool Changed = true;
Visited.insert(CPUStateType);
while (Changed) {
Changed = false;
for (StructType *TheStruct : Structs) {
if (Visited.find(TheStruct) != Visited.end())
continue;
auto Begin = TheStruct->element_begin();
auto End = TheStruct->element_end();
auto Found = std::find(Begin, End, CPUStateType);
if (Found != End) {
unsigned Index = Found - Begin;
const StructLayout *Layout = nullptr;
Layout = ModuleLayout->getStructLayout(TheStruct);
EnvOffset += Layout->getElementOffset(Index);
CPUStateType = TheStruct;
Visited.insert(CPUStateType);
Changed = true;
break;
}
}
}
}
bool VariableManager::storeToCPUStateOffset(IRBuilder<> &Builder,
unsigned StoreSize,
unsigned Offset,
Value *ToStore) {
Value *Target;
unsigned Remaining;
std::tie(Target, Remaining) = getByCPUStateOffsetInternal(Offset);
if (Target == nullptr)
return false;
unsigned ShiftAmount = 0;
if (TargetArchitecture.isLittleEndian())
ShiftAmount = Remaining;
else {
// >> (Size1 - Size2) - Remaining;
Type *PointeeTy = Target->getType()->getPointerElementType();
unsigned GlobalSize = cast<IntegerType>(PointeeTy)->getBitWidth() / 8;
assert(GlobalSize != 0);
ShiftAmount = (GlobalSize - StoreSize) - Remaining;
}
ShiftAmount *= 8;
// Build blanking mask
uint64_t BitMask = (StoreSize == 8 ?
(uint64_t) -1
: ((uint64_t) 1 << StoreSize * 8) - 1);
assert(ShiftAmount != 64);
BitMask <<= ShiftAmount;
BitMask = ~BitMask;
auto *InputStoreTy = cast<IntegerType>(Builder.getIntNTy(StoreSize * 8));
auto *FieldTy = cast<IntegerType>(Target->getType()->getPointerElementType());
unsigned FieldSize = FieldTy->getBitWidth() / 8;
// Are we trying to store more than it fits?
if (StoreSize > FieldSize) {
// If we're storing more than it fits and the following memory is not
// padding the store is not valid.
if (getByCPUStateOffsetInternal(Offset + FieldSize).first != nullptr)
return false;
}
// Truncate value to store
auto *Truncated = Builder.CreateTrunc(ToStore, InputStoreTy);
if (StoreSize > FieldSize)
Truncated = Builder.CreateTrunc(Truncated, FieldTy);
// Re-extend
ToStore = Builder.CreateZExt(Truncated, FieldTy);
if (BitMask != 0) {
// Load the value
auto *LoadEnvField = Builder.CreateLoad(Target);
setAliasScope(LoadEnvField);
auto *Blanked = Builder.CreateAnd(LoadEnvField, BitMask);
// Shift value to store
ToStore = Builder.CreateShl(ToStore, ShiftAmount);
// Combine them
ToStore = Builder.CreateOr(ToStore, Blanked);
}
// Type *TargetPointer = Target->getType()->getPointerElementType();
// Value *ToStore = Builder.CreateZExt(InArguments[0], TargetPointer);
auto *Store = Builder.CreateStore(ToStore, Target);
setAliasScope(Store);
return true;
}
Value *VariableManager::loadFromCPUStateOffset(IRBuilder<> &Builder,
unsigned LoadSize,
unsigned Offset) {
Value *Target;
unsigned Remaining;
std::tie(Target, Remaining) = getByCPUStateOffsetInternal(Offset);
if (Target == nullptr)
return nullptr;
// Load the whole field
auto *LoadEnvField = Builder.CreateLoad(Target);
setAliasScope(LoadEnvField);
// Extract the desired part
// Shift right of the desired amount
unsigned ShiftAmount = 0;
if (TargetArchitecture.isLittleEndian()) {
ShiftAmount = Remaining;
} else {
// >> (Size1 - Size2) - Remaining;
auto *LoadedTy = cast<IntegerType>(LoadEnvField->getType());
unsigned GlobalSize = LoadedTy->getBitWidth() / 8;
assert(GlobalSize != 0);
ShiftAmount = (GlobalSize - LoadSize) - Remaining;
}
ShiftAmount *= 8;
Value *Result = LoadEnvField;
if (ShiftAmount != 0)
Result = Builder.CreateLShr(Result, ShiftAmount);
Type *LoadTy = Builder.getIntNTy(LoadSize * 8);
// Are we trying to load more than its available in the field?
if (auto FieldTy = dyn_cast<IntegerType>(Result->getType())) {
unsigned FieldSize = FieldTy->getBitWidth() / 8;
if (FieldSize < LoadSize) {
// If after what we are loading ther is something that is not padding we
// cannot load safely
if (getByCPUStateOffsetInternal(Offset + FieldSize).first != nullptr)
return nullptr;
Result = Builder.CreateZExt(Result, LoadTy);
}
}
// Truncate of the desired amount
return Builder.CreateTrunc(Result, LoadTy);
}
bool VariableManager::memcpyAtEnvOffset(llvm::IRBuilder<> &Builder,
llvm::CallInst *CallMemcpy,
unsigned InitialEnvOffset,
bool EnvIsSrc) {
Function *Callee = getCallee(CallMemcpy);
// We only support memcpys where the last parameter is constant
assert(Callee != nullptr
and (Callee->getIntrinsicID() == Intrinsic::memcpy
and isa<ConstantInt>(CallMemcpy->getArgOperand(2))));
Value *OtherOp = CallMemcpy->getArgOperand(EnvIsSrc ? 0 : 1);
auto *MemcpySize = cast<Constant>(CallMemcpy->getArgOperand(2));
Value *OtherBasePtr = Builder.CreatePtrToInt(OtherOp, Builder.getInt64Ty());
uint64_t TotalSize = getZExtValue(MemcpySize, *ModuleLayout);
uint64_t Offset = 0;
bool OnlyPointersAndPadding = true;
while (Offset < TotalSize) {
GlobalVariable *EnvVar = getByEnvOffset(InitialEnvOffset + Offset).first;
// Consider the case when there's simply nothing there (alignment space).
if (EnvVar == nullptr) {
if (false and EnvIsSrc) { // TODO: remove "false and", but after adding type based stuff
ConstantInt *ZeroByte = Builder.getInt8(0);
Value *NewAddress = Builder.CreateAdd(Builder.getInt64(Offset), OtherBasePtr);
Value *OtherPtr = Builder.CreateIntToPtr(NewAddress, Builder.getInt8Ty()->getPointerTo());
Builder.CreateStore(ZeroByte, OtherPtr);
OnlyPointersAndPadding = false;
}
Offset++;
continue;
}
OnlyPointersAndPadding = false;
Value *NewAddress = Builder.CreateAdd(Builder.getInt64(Offset), OtherBasePtr);
Value *OtherPtr = Builder.CreateIntToPtr(NewAddress, EnvVar->getType());
Value *Dst = EnvIsSrc ? OtherPtr : EnvVar;
Value *Src = EnvIsSrc ? EnvVar : OtherPtr;
Builder.CreateStore(Builder.CreateLoad(Src), Dst);
Type *PointeeTy = EnvVar->getType()->getPointerElementType();
Offset += ModuleLayout->getTypeAllocSize(PointeeTy);
}
if (OnlyPointersAndPadding)
cast<Instruction>(OtherBasePtr)->eraseFromParent();
return Offset == TotalSize;
}
// TODO: `newFunction` reflects the tcg terminology but in this context is
// highly misleading
void VariableManager::newFunction(Instruction *Delimiter,
PTCInstructionList *Instructions) {
LocalTemporaries.clear();
newBasicBlock(Delimiter, Instructions);
}
/// Informs the VariableManager that a new basic block has begun, so it can
/// discard basic block-level variables.
///
/// \param Delimiter the new point where to insert allocations for local
/// variables.
/// \param Instructions the new PTCInstructionList to use from now on.
void VariableManager::newBasicBlock(Instruction *Delimiter,
PTCInstructionList *Instructions) {
Temporaries.clear();
if (Instructions != nullptr)
this->Instructions = Instructions;
if (Delimiter != nullptr)
Builder.SetInsertPoint(Delimiter);
}
void VariableManager::newBasicBlock(BasicBlock *Delimiter,
PTCInstructionList *Instructions) {
Temporaries.clear();
if (Instructions != nullptr)
this->Instructions = Instructions;
if (Delimiter != nullptr)
Builder.SetInsertPoint(Delimiter);
}
bool VariableManager::isEnv(Value *TheValue) {
auto *Load = dyn_cast<LoadInst>(TheValue);
if (Load != nullptr)
return Load->getPointerOperand() == Env;
return TheValue == Env;
}
static ConstantInt *fromBytes(IntegerType *Type, void *Data) {
switch (Type->getBitWidth()) {
case 8:
return ConstantInt::get(Type, *(static_cast<uint8_t *>(Data)));
case 16:
return ConstantInt::get(Type, *(static_cast<uint16_t *>(Data)));
case 32:
return ConstantInt::get(Type, *(static_cast<uint32_t *>(Data)));
case 64:
return ConstantInt::get(Type, *(static_cast<uint64_t *>(Data)));
}
llvm_unreachable("Unexpected type");
}
// TODO: document that it can return nullptr
GlobalVariable* VariableManager::getByCPUStateOffset(intptr_t Offset,
std::string Name) {
GlobalVariable *Result = nullptr;
unsigned Remaining;
std::tie(Result, Remaining) = getByCPUStateOffsetInternal(Offset, Name);
assert(Remaining == 0);
return Result;
}
std::pair<GlobalVariable*, unsigned>
VariableManager::getByCPUStateOffsetInternal(intptr_t Offset,
std::string Name) {
GlobalsMap::iterator it = CPUStateGlobals.find(Offset);
if (it == CPUStateGlobals.end() ||
(Name.size() != 0 && !it->second->getName().equals_lower(Name))) {
Type *VariableType;
unsigned Remaining;
std::tie(VariableType, Remaining) = getTypeAtOffset(ModuleLayout,
CPUStateType,
Offset);
// Unsupported type, let the caller handle the situation
if (VariableType == nullptr)
return { nullptr, 0 };
// Check we're not trying to go inside an existing variable
if (Remaining != 0) {
GlobalsMap::iterator it = CPUStateGlobals.find(Offset - Remaining);
if (it != CPUStateGlobals.end())
return { it->second, Remaining };
}
if (Name.size() == 0) {
std::stringstream NameStream;
NameStream << "state_0x" << std::hex << Offset;
Name = NameStream.str();
}
// TODO: offset could be negative, we could segfault here
auto *InitialValue = fromBytes(cast<IntegerType>(VariableType),
ptc.initialized_env - EnvOffset + Offset);
auto *NewVariable = new GlobalVariable(TheModule,
VariableType,
false,
GlobalValue::ExternalLinkage,
InitialValue,
Name);
assert(NewVariable != nullptr);
if (it != CPUStateGlobals.end()) {
it->second->replaceAllUsesWith(NewVariable);
it->second->eraseFromParent();
}
CPUStateGlobals[Offset] = NewVariable;
return { NewVariable, Remaining };
} else {
return { it->second, 0 };
}
}
Value *VariableManager::getOrCreate(unsigned TemporaryId, bool Reading) {
assert(Instructions != nullptr);
PTCTemp *Temporary = ptc_temp_get(Instructions, TemporaryId);
Type *VariableType = Temporary->type == PTC_TYPE_I32 ?
Builder.getInt32Ty() : Builder.getInt64Ty();
if (ptc_temp_is_global(Instructions, TemporaryId)) {
// Basically we use fixed_reg to detect "env"
if (Temporary->fixed_reg == 0) {
Value *Result = getByCPUStateOffset(EnvOffset + Temporary->mem_offset,
StringRef(Temporary->name));
assert(Result != nullptr);
return Result;
} else {
GlobalsMap::iterator it = OtherGlobals.find(TemporaryId);
if (it != OtherGlobals.end()) {
return it->second;
} else {
// TODO: what do we have here, apart from env?
auto InitialValue = ConstantInt::get(VariableType, 0);
GlobalVariable *Result = new GlobalVariable(TheModule,
VariableType,
false,
GlobalValue::CommonLinkage,
InitialValue,
StringRef(Temporary->name));
if (Result->getName() == "env")
Env = Result;
OtherGlobals[TemporaryId] = Result;
return Result;
}
}
} else if (Temporary->temp_local) {
auto it = LocalTemporaries.find(TemporaryId);
if (it != LocalTemporaries.end()) {
return it->second;
} else {
AllocaInst *NewTemporary = Builder.CreateAlloca(VariableType);
LocalTemporaries[TemporaryId] = NewTemporary;
return NewTemporary;
}
} else {
auto it = Temporaries.find(TemporaryId);
if (it != Temporaries.end()) {
return it->second;
} else {
// Can't read a temporary if it has never been written, we're probably
// translating rubbish
if (Reading)
return nullptr;
AllocaInst *NewTemporary = Builder.CreateAlloca(VariableType);
Temporaries[TemporaryId] = NewTemporary;
return NewTemporary;
}
}
}
template LoadInst *VariableManager::setAliasScope(LoadInst *);
template StoreInst *VariableManager::setAliasScope(StoreInst *);
template<typename T>
T *VariableManager::setAliasScope(T *Instruction) {
auto *Pointer = Instruction->getPointerOperand();
if (isa<AllocaInst>(Pointer))
return Instruction;
Instruction->setMetadata(AliasScopeMDKindID, CPUStateScopeSet);
return Instruction;
}
template LoadInst *VariableManager::setNoAlias(LoadInst *);
template StoreInst *VariableManager::setNoAlias(StoreInst *);
template<typename T>
T *VariableManager::setNoAlias(T *Instruction) {
Instruction->setMetadata(NoAliasMDKindID, CPUStateScopeSet);
return Instruction;
}
Value *VariableManager::computeEnvAddress(Type *TargetType,
Instruction *InsertBefore,
unsigned Offset) {
auto *LoadEnv = new LoadInst(Env, "", InsertBefore);
Type *EnvType = Env->getType()->getPointerElementType();
Value *Integer = LoadEnv;
if (Offset != 0)
Integer = BinaryOperator::Create(Instruction::Add,
LoadEnv,
ConstantInt::get(EnvType, Offset),
"",
InsertBefore);
return new IntToPtrInst(Integer, TargetType, "", InsertBefore);
}