diff --git a/CMakeLists.txt b/CMakeLists.txt index 10737d6f5..be14e8ec2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,12 +111,28 @@ endif() set(CMAKE_INSTALL_RPATH "\$ORIGIN/../lib${LLVM_LIBDIR_SUFFIX}") -add_executable(revamb ptcdump.cpp main.cpp debughelper.cpp variablemanager.cpp - jumptargetmanager.cpp instructiontranslator.cpp codegenerator.cpp debug.cpp - osra.cpp set.cpp simplifycomparisons.cpp reachingdefinitions.cpp - functionboundariesdetection.cpp noreturnanalysis.cpp binaryfile.cpp - generatedcodebasicinfo.cpp functioncallidentification.cpp argparse/argparse.c - externaljumpshandler.cpp statistics.cpp) +add_executable(revamb + binaryfile.cpp + codegenerator.cpp + cpustateaccessanalysis.cpp + debug.cpp + debughelper.cpp + externaljumpshandler.cpp + functionboundariesdetection.cpp + functioncallidentification.cpp + generatedcodebasicinfo.cpp + instructiontranslator.cpp + jumptargetmanager.cpp + main.cpp + noreturnanalysis.cpp + osra.cpp + ptcdump.cpp + reachingdefinitions.cpp + set.cpp + simplifycomparisons.cpp + statistics.cpp + variablemanager.cpp + argparse/argparse.c) target_link_libraries(revamb dl m ${LLVM_LIBRARIES}) install(TARGETS revamb RUNTIME DESTINATION bin) diff --git a/codegenerator.cpp b/codegenerator.cpp index d736899fe..35c2cd046 100644 --- a/codegenerator.cpp +++ b/codegenerator.cpp @@ -950,7 +950,7 @@ void CodeGenerator::translate(uint64_t VirtualAddress) { legacy::PassManager PM; PM.add(createSROAPass()); PM.add(new CpuLoopExitPass(&Variables)); - PM.add(Variables.createCorrectCPUStateUsagePass()); + PM.add(Variables.createCPUStateAccessAnalysisPass()); PM.add(createDeadCodeEliminationPass()); PM.run(*TheModule); diff --git a/cpustateaccessanalysis.cpp b/cpustateaccessanalysis.cpp new file mode 100644 index 000000000..7ec6a1a48 --- /dev/null +++ b/cpustateaccessanalysis.cpp @@ -0,0 +1,2783 @@ +/// \file cpustateaccessanalysis.cpp +/// \brief This file performs an analysis for reconstructing the access +/// patterns to the CPU State Variables (CSV). + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +// Standard includes +#include +#include +#include +#include + +// LLVM includes +#include "llvm/ADT/SmallSet.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Value.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Support/Casting.h" + +// Local includes +#include "cpustateaccessanalysis.h" +#include "debug.h" +#include "ir-helpers.h" +#include "variablemanager.h" + +namespace llvm { +class DataLayout; +} + +using namespace llvm; +using ConstFunctionPtrSet = std::set; +using InstrPtrSet = std::set; +using CallPtrSet = std::set; +using ConstValuePtrSet = std::set; + +/// \brief Logger for forwardTaintAnalysis +static auto TaintLog = Logger<>("cpustate-taint-analysis"); +/// \brief Logger for the creation of WorkItem +static auto CSVAccessLog = Logger<>("cpustate-access-analysis"); +/// \brief Logger for fixing the accesses to CPUState +static auto FixAccessLog = Logger<>("cpustate-fix-access"); + +void writeToLog(Logger &L, const CSVOffsets &O, int /*Ignore*/) { + L << "Kind: " << O.OffsetKind; + L << " Offsets = { "; + for (const auto &Offset : O) + L << Offset << ' '; + L << "}"; +} + +/// \brief Computes the set of Functions reachable from a given Function through +/// direct calls. +/// +/// \param RootFunction is a pointer to the Function from which is the +/// starting point for computing reachability. +/// \return set of pointers to the reachable Functions +/// +/// This function can probably be implemented using the call graph utilities +/// already present in LLVM, but when I tried it I spent a day doing it and I +/// gave up because of bugs in the functions coming from LLVM. Then I +/// implemented my own version, i.e. this function +static ConstFunctionPtrSet +computeDirectlyReachableFunctions(const Function *RootFunction) { + std::map CallGraph; + const Module &M = *RootFunction->getParent(); + + // Initialize empty CallGraph + for (const Function &F : M) + CallGraph[&F] = {}; + + for (const Function &F : M) { + for (const Use &U : F.uses()) { + const User *TheUser = U.getUser(); + if (const auto *TheCall = dyn_cast(TheUser)) { + const Function *Caller = TheCall->getParent()->getParent(); + const Function *Callee = getCallee(TheCall); + if (Callee == &F) { + CallGraph[Caller].insert(Callee); + } + } else if (const auto *CExpr = dyn_cast(TheUser)) { + assert(CExpr->getOpcode() == Instruction::BitCast); + SmallSet CurBitCasts; + SmallSet NextBitCasts; + CurBitCasts.insert(CExpr); + while (not CurBitCasts.empty()) { + NextBitCasts.clear(); + for (const ConstantExpr *BitCast : CurBitCasts) { + for (const User *BitCastUser : BitCast->users()) { + const auto *TheCall = dyn_cast(BitCastUser); + const auto *NewCExpr = dyn_cast(BitCastUser); + if (TheCall) { + const Function *Caller = TheCall->getParent()->getParent(); + const Function *Callee = getCallee(TheCall); + if (Callee == &F) { + CallGraph[Caller].insert(Callee); + } + } else if (NewCExpr) { + assert(NewCExpr->getOpcode() == Instruction::BitCast); + NextBitCasts.insert(NewCExpr); + } + } + } + std::swap(CurBitCasts, NextBitCasts); + } + } + } + } + + ConstFunctionPtrSet ReachableFunctions = { RootFunction }; + ConstFunctionPtrSet CurrentChildren = { RootFunction }; + ConstFunctionPtrSet NextChildren; + while (not CurrentChildren.empty()) { + NextChildren.clear(); + for (const Function *F : CurrentChildren) { + for (const Function *Callee : CallGraph.at(F)) { + bool NewInsertion = ReachableFunctions.insert(Callee).second; + if (NewInsertion) + NextChildren.insert(Callee); + } + } + std::swap(CurrentChildren, NextChildren); + } + + return ReachableFunctions; +} + +struct TaintResults { + + // A set of Instructions that access the CSV to load data. They can be + // LoadInst or CallInst to Instrinsic::memcpy for which the size is known. + InstrPtrSet TaintedLoads; + + // A set of Instructions that access the CSV to store data. They can be + // LoadInst or CallInst to Instrinsic::memcpy for which the size is known. + InstrPtrSet TaintedStores; + + // A set of Values that are tainted during the analysis. + ConstValuePtrSet TaintedValues; + + // A set of CallInst that are considered illegal. These include indirect + // calls, calls to functions without body, and calls to Intrinsinc::memcpy + // with unknown size. They are considered illegal because we have no way of + // knowing how they access the CSV. + CallPtrSet IllegalCalls; + + bool empty() const noexcept { + return TaintedLoads.empty() and TaintedStores.empty() + and IllegalCalls.empty(); + } +}; + +/// \brief Interprocedural forward taint analysis. +// +/// \param CPUStatePtr is a pointer to the CPU State Variable, which is a global +/// variable. This variable is the Value that taints all the others. +/// \param ReachableFunctions is a set of functions that are reachable from +/// the root function. The analysis is restricted to those functions. +/// \return a TaintResults containing information on: +/// 1) a set of instructions that access the CSV to load data; +/// 2) a set of instructions that access the CSV to store data; +/// 3) a set of illegal calls, for which it is impossible to understand +/// if and how they will access the CSV. +static TaintResults +forwardTaintAnalysis(GlobalVariable *CPUStatePtr, + const ConstFunctionPtrSet &ReachableFunctions) { + // + // Interprocedural Forward Taint Analysis + // + + // This analysis aims to understand all the Values that are affected by the + // CPUStatePtr pointer, that points to the CPUStateVariables (CSVs). + // The main idea is that we explore the use chains depth first, propagating + // interprocedurally when we find a Use whose User is a CallInstr. + // + // The function is structured as follows: + // 1. Iterate on the users of `CPUStatePtr` + // 2. For each user of `CPUStatePtr`, consider its next user, + // building a WorkList of `Use`s in exploration + // 3. If we find unexplored uses keep pushing them on the WorkList. If we + // find a Load or a Store we taint it and don't push anything on the + // WorkList. If it's not a Load or Store we mark it as tainted separately + // 3(a) If the next `Use` to explore is a `CallInst` the taint is + // propagated interprocedurally to the callee, through the arguments + // (propagation from caller to callee) + // 3(b) If the next `Use` to explore is a `RetInst` the taint is + // propagated interprocedurally to the Function. + // (propagation from the callee to all call sites) + // 4. If we didn't push anything on the WorkList we can start exploring the + // other `Use`s of the item that is currently on top of the WorkList + // 5. If we didn't push anything on the WorkList we can start popping `Use`s + // from the WorkList, until we reach a `Value` that still has unexplored + // `Use`s + // 5(a) If we're popping an argument of a function this means that we've + // finished analyzing the uses of that argument. We have to make sure + // that, if the taint reached the return instructions in the function, + // the taint is propagated to the call sites. + // 6. After popping the top of the WorkList in 5., if the new top of the + // WorkList still has unexplored uses start to explore them. + + TaintResults Results; + std::set> + FunctionArgTaintsReturn; + + assert(CPUStatePtr != nullptr); + assert(CPUStatePtr->getType()->isPointerTy()); + + struct CallSiteInfo { + const CallInst *CallSite; + const Argument *Arg; + const unsigned ArgNo; + CallSiteInfo(const CallInst *C, const Argument *A, const unsigned N) : + CallSite(C), + Arg(A), + ArgNo(N) {} + }; + + // 1. Iterate on the users of `CPUStatePtr` + for (const User *U : CPUStatePtr->users()) { + + // During the analysis we keep two stacks. + // ToTaintWorkList is a stack representing the Values currently enqued that + // must be tainted and for which we still have to analyze the uses. + // CallSites is a stack representing the CallInst that we entered for + // interprocedural propagation. + std::stack ToTaintWorkList; + std::stack CallSiteInfos; + + // Sanity check for the uses of CPUStatePtr. + // They must all be direct Loads from CPUStatePtr. + const auto *Load = cast(U); + assert(Load->getPointerOperand() == CPUStatePtr); + + // Push the first use on the WorkList + const Function *F = Load->getParent()->getParent(); + if (Load->getNumUses() != 0 + and ReachableFunctions.find(F) != ReachableFunctions.end()) { + TaintLog << "Tainted origin: " << Load << DoLog; + ToTaintWorkList.push(&*Load->use_begin()); + TaintLog.indent(); + } + + // 2. For each user of `CPUStatePtr`, consider its next user, + // building a WorkList of `Use`s in exploration + while (not ToTaintWorkList.empty()) { + const Use *TheUse = ToTaintWorkList.top(); + assert(TheUse != nullptr); + auto *TheUser = cast(TheUse->getUser()); + const auto OpCode = TheUser->getOpcode(); + if (TaintLog.isEnabled()) { + TaintLog << "Inst: " << TheUser << DoLog; + TheUser->dump(); + } + + const auto size = ToTaintWorkList.size(); + + // 3. If we find unexplored uses keep pushing them on the WorkList. If we + // find a Load or a Store we taint it and don't push anything on the + // WorkList. If it's not a Load or Store we mark it as tainted separately + + // This switch explores the use-chains depth-first, pushing unexplored + // uses on the ToTaintWorkList if necessary. + switch (OpCode) { + case Instruction::Load: { + TaintLog << "LOAD" << DoLog; + assert(TheUse->getOperandNo() == LoadInst::getPointerOperandIndex()); + auto *L = cast(TheUser); + if (TheUse->get() == L->getPointerOperand()) { + if (TaintLog.isEnabled()) { + TaintLog << "TAINT: " << TheUser << DoLog; + TheUser->dump(); + } + Results.TaintedLoads.insert(TheUser); + } + } break; + case Instruction::Store: { + TaintLog << "STORE" << DoLog; + assert(TheUse->getOperandNo() == StoreInst::getPointerOperandIndex()); + auto *S = cast(TheUser); + if (TheUse->get() == S->getPointerOperand()) { + if (TaintLog.isEnabled()) { + TaintLog << "TAINT: " << TheUser << DoLog; + TheUser->dump(); + } + Results.TaintedStores.insert(TheUser); + } + } break; + case Instruction::Trunc: + case Instruction::ZExt: + case Instruction::SExt: + case Instruction::BitCast: + case Instruction::IntToPtr: + case Instruction::PtrToInt: + case Instruction::GetElementPtr: + case Instruction::PHI: + case Instruction::Add: { + TaintLog << "OP" << DoLog; + auto OperandId = GetElementPtrInst::getPointerOperandIndex(); + assert(OpCode != Instruction::GetElementPtr + or TheUse->getOperandNo() == OperandId); + + // Taint TheUser, and if this is the first time we taint it we also push + // on the ToTaintWorkList its first use that is not tainted + bool JustTainted = Results.TaintedValues.insert(TheUser).second; + if (TaintLog.isEnabled()) { + TaintLog << "TAINT: " << TheUser << DoLog; + TheUser->dump(); + } + if (JustTainted) { + TaintLog << "Just Tainted" << DoLog; + for (const Use &U : TheUser->uses()) { + TaintLog << "User: " << U.getUser() << DoLog; + if (Results.TaintedValues.count(U.getUser()) == 0) { + TaintLog << "PUSH" << DoLog; + ToTaintWorkList.push(&U); + TaintLog.indent(); + break; + } + } + } + } break; + case Instruction::Call: { + + // 3(a) If the next `Use` to explore is a `CallInst` the taint is + // propagated interprocedurally to the callee, through the arguments + // (propagation from caller to callee) + TaintLog << "CALL" << DoLog; + auto *TheCall = cast(TheUser); + Function *Callee = getCallee(TheCall); + + // Indirect calls, calls to functions without body, and calls to + // Intrinsic::memcpy with non-constant size are considered illegal, + // because we cannot know how they will affect the CPU State + if (Callee == nullptr) { + TaintLog << "Illegal -- indirect call" << DoLog; + Results.IllegalCalls.insert(TheCall); + break; + } + if (Callee->getIntrinsicID() == Intrinsic::memcpy) { + unsigned OpNo = TheUse->getOperandNo(); + assert(OpNo == 0 or OpNo == 1); + if (isa(TheCall->getArgOperand(2))) { + if (OpNo == 0) { + if (TaintLog.isEnabled()) { + TaintLog << "TAINT: " << TheUser << DoLog; + TheUser->dump(); + } + Results.TaintedStores.insert(TheUser); + } + if (OpNo == 1) { + if (TaintLog.isEnabled()) { + TaintLog << "TAINT: " << TheUser << DoLog; + TheUser->dump(); + } + Results.TaintedLoads.insert(TheUser); + } + } else { + TaintLog << "Illegal -- unknwon size memcpy" << DoLog; + Results.IllegalCalls.insert(TheCall); + } + break; + } else if (Callee->empty()) { + TaintLog << "Illegal -- no body" << DoLog; + Results.IllegalCalls.insert(TheCall); + break; + } + assert(ReachableFunctions.count(Callee) != 0); + + // Select the correct formal argument associated with this use + const Argument *FormalArgument = nullptr; + unsigned ArgNo = 0; + for (const Argument &Arg : Callee->args()) { + ArgNo = Arg.getArgNo(); + if (TheUse->getOperandNo() == ArgNo) { + FormalArgument = &Arg; + break; + } + } + TaintLog << "Found Argument" << DoLog; + assert(FormalArgument != nullptr); + + // Taint the Argument, and if this is the first time we taint it we + // also push on the ToTaintWorkList its first use that is not tainted. + if (TaintLog.isEnabled()) { + TaintLog << "Argument: " << FormalArgument << DoLog; + std::string ArgLog; + raw_string_ostream OStream(ArgLog); + FormalArgument->print(OStream); + CSVAccessLog << ArgLog << DoLog; + } + bool JustTainted = Results.TaintedValues.insert(FormalArgument).second; + if (JustTainted) { + TaintLog << "Just Tainted" << DoLog; + for (const Use &U : FormalArgument->uses()) { + if (TaintLog.isEnabled()) { + TaintLog << "User: " << U.getUser() << DoLog; + std::string UserLog; + raw_string_ostream OStream(UserLog); + U.getUser()->print(OStream); + CSVAccessLog << UserLog << DoLog; + } + if (Results.TaintedValues.count(U.getUser()) == 0) { + TaintLog << "PUSH" << DoLog; + ToTaintWorkList.push(&U); + TaintLog.indent(); + + // We also push the call to the CallSites stack, because if we put + // the uses of the argument on the ToTaintWorkList we are actually + // starting to perform the analysis inside the callee. + CallSiteInfos.push(CallSiteInfo(TheCall, FormalArgument, ArgNo)); + break; + } + } + } else if (FunctionArgTaintsReturn.count({ Callee, FormalArgument })) { + // It means that a previous exploration of the graph has reached this + // argument, and propagated a taint until a return value. This means + // that this we must taint the call and start explorint its unexplored + // users if any. + bool JustTainted = Results.TaintedValues.insert(TheUser).second; + if (TaintLog.isEnabled()) { + TaintLog << "TAINT: " << TheUser << DoLog; + TheUser->dump(); + } + if (JustTainted) { + TaintLog << "Just Tainted" << DoLog; + for (const Use &U : TheUser->uses()) { + TaintLog << "User: " << U.getUser() << DoLog; + if (Results.TaintedValues.count(U.getUser()) == 0) { + TaintLog << "PUSH" << DoLog; + ToTaintWorkList.push(&U); + TaintLog.indent(); + break; + } + } + } + } + } break; + case Instruction::Ret: { + + // 3(b) If the next `Use` to explore is a `RetInst` the taint is + // propagated interprocedurally to the Function. + // (propagation from the callee to all call sites) + TaintLog << "RET" << DoLog; + assert(not CallSiteInfos.empty()); + + // Taint the return instruction, then, if this is the first time that we + // taint also the call site, so that it's marked for propagation of the + // taint analysis to its uses. + if (TaintLog.isEnabled()) { + TaintLog << "TAINT: " << TheUser << DoLog; + TheUser->dump(); + } + bool JustTainted = Results.TaintedValues.insert(TheUser).second; + if (JustTainted) { + const CallSiteInfo &CSInfo = CallSiteInfos.top(); + + Results.TaintedValues.insert(CSInfo.CallSite); + + const Function *Callee = getCallee(CSInfo.CallSite); + FunctionArgTaintsReturn.insert({ Callee, CSInfo.Arg }); + + if (TaintLog.isEnabled()) { + TaintLog << "TAINT: " << CSInfo.CallSite << DoLog; + CallSiteInfos.top().CallSite->dump(); + std::string Name = getCallee(CSInfo.CallSite)->getName(); + TaintLog << "pair: < " << Name << ", " << CSInfo.ArgNo << " > " + << DoLog; + } + } + } break; + case Instruction::Switch: + case Instruction::ICmp: + case Instruction::And: + case Instruction::Or: + break; + default: + abort(); + break; + } + + // If we pushed something new on the ToTaintWorkList we want to keep + // exploring its uses until we reach a leaf. + if (size < ToTaintWorkList.size()) + continue; + TaintLog << "not grown" << DoLog; + + // 4. If we didn't push anything on the WorkList we can start exploring + // the other `Use`s of the item that is currently on top of the WorkList + if (size == ToTaintWorkList.size()) { + Use *NextUse = TheUse->getNext(); + if (NextUse != nullptr) { + TaintLog << "advance" << DoLog; + ToTaintWorkList.top() = NextUse; + continue; + } + } + + TaintLog << "Done" << DoLog; + + // 5. If we didn't push anything on the WorkList we can start popping + // `Use`s from the WorkList, until we reach a `Value` that still has + // unexplored `Use`s + // + // If we reach this point we have finished to explore all the uses of the + // item that is currently on top of the ToTaintWorkList stack. + // We want to pop it and to handle Arguments in a special way. + const Use *UnexploredUse = nullptr; + while (not ToTaintWorkList.empty() and UnexploredUse == nullptr) { + const Use *PoppedTopUse = ToTaintWorkList.top(); + if (TaintLog.isEnabled()) { + TaintLog << "POP : " << PoppedTopUse->get() << DoLog; + PoppedTopUse->get()->dump(); + } + if (TaintLog.isEnabled()) { + TaintLog << "PoppedUser : " << PoppedTopUse->getUser() << DoLog; + PoppedTopUse->getUser()->dump(); + } + ToTaintWorkList.pop(); + TaintLog.unindent(); + Argument *Arg = dyn_cast(PoppedTopUse->get()); + bool PoppedHasAllExploredSources = PoppedTopUse->getNext() == nullptr; + if (Arg and PoppedHasAllExploredSources) { + // 5(a) If we're popping an argument of a function this means that we + // finished analyzing the uses of that argument. We have to make sure + // that, if the taint reached the return instructions in the function, + // the taint is propagated to the call sites. + TaintLog << "Finish Argument" << DoLog; + + const CallSiteInfo &CSInfo = CallSiteInfos.top(); + assert(CSInfo.Arg == Arg); + + unsigned ArgNo = Arg->getArgNo(); + assert(CSInfo.ArgNo == ArgNo); + + const User *ArgUser = ToTaintWorkList.top()->getUser(); + const auto *CallSite = cast(ArgUser); + const Function *Callee = getCallee(CallSite); + + if (TaintLog.isEnabled()) { + TaintLog << "CallSite: " << CallSite << DoLog; + CallSite->dump(); + } + + // If the CallSite was tainted it means that the taint analysis + // reached at least one of the return values of the callee. + // Hence, the taint can propagate to the uses of the call. + // The same holds if we already know that the pair (Callee, ArgNo) + // taints the return. + if (Results.TaintedValues.count(CallSite) != 0 + or FunctionArgTaintsReturn.count({ Callee, Arg }) != 0) { + if (CallSite->getNumUses() != 0) { + UnexploredUse = &*CallSite->use_begin(); + } + } + + // If PoppedTopUse is an Use of an Argument the next available Use on + // the stack must be an argument of a CallInst. This CallInst was the + // call from where we started analyzing the uses of the function + // Argument for which we just popped the PoppedTopUse. + CallSiteInfos.pop(); + } else { + TaintLog << "NOT Finished or NOT Argument" << DoLog; + UnexploredUse = PoppedTopUse->getNext(); + } + } + + // 6. After popping the top of the WorkList in 5., if the new top of the + // WorkList still has unexplored uses start to explore them. + + // If we have a new UnexploredUse we push it and we continue + // because that's the new use that must be analyzed. + if (UnexploredUse != nullptr) { + TaintLog << "PUSH: " << UnexploredUse->get() << DoLog; + TaintLog << "User: " << UnexploredUse->getUser() << DoLog; + ToTaintWorkList.push(UnexploredUse); + TaintLog.indent(); + } + } + } + + Module *M = CPUStatePtr->getParent(); + QuickMetadata QMD(M->getContext()); + for (CallInst *Call : Results.IllegalCalls) { + CallInst *Abort = CallInst::Create(M->getFunction("abort"), {}, Call); + auto IllegalCallsMDKind = M->getContext().getMDKindID("IllegalCalls"); + Abort->setMetadata(IllegalCallsMDKind, QMD.tuple((uint32_t) 0)); + } + return Results; +} + +class WorkItem { + +public: + using size_type = SmallVector::size_type; + +private: + // The value whose sources we're analyzing + Value *CurrentValue; + + // Sources are kind of the opposite of `Use`s. Every pointer in this vector + // points to a `Use` whose `User` is the `Value` pointed by the `CurrentValue` + // member of this `WorkItem`. + SmallVector Sources; + + // The index of the Source that is currently considered for the analysis + size_type SourceIndex; + +public: + WorkItem() : CurrentValue(nullptr), Sources(), SourceIndex(0) {} + + explicit WorkItem(Instruction *I) : + CurrentValue(I), + Sources(), + SourceIndex(0) { + if (not isa(I)) { + for (const Use &OpUse : I->operands()) { + Sources.push_back(&OpUse); + } + } else { + const auto PtrOpNum = StoreInst::getPointerOperandIndex(); + Sources.push_back(&I->getOperandUse(PtrOpNum)); + } + assert(not Sources.empty()); + } + + explicit WorkItem(Argument *A, + const ConstFunctionPtrSet &ReachableFunctions) : + CurrentValue(A), + Sources(), + SourceIndex(0) { + const Function *F = A->getParent(); + assert(not F->empty()); + CSVAccessLog << "Function: " << F << DoLog; + const unsigned ArgNo = A->getArgNo(); + CSVAccessLog << "ArgNo: " << ArgNo << DoLog; + for (const Use &FUse : F->uses()) { + const User *FUser = FUse.getUser(); + CSVAccessLog << "FUser: " << FUser << DoLog; + if (const auto *FCall = dyn_cast(FUser)) { + CSVAccessLog << "Is a Call" << DoLog; + const Function *Caller = FCall->getParent()->getParent(); + CSVAccessLog << "Caller: " << Caller << DoLog; + if (ReachableFunctions.find(Caller) != ReachableFunctions.end()) { + CSVAccessLog << "Is Reachable" << DoLog; + CSVAccessLog << "CallInst:" << FCall << DoLog; + const Use &actualArgUse = FCall->getArgOperandUse(ArgNo); + CSVAccessLog << "ActualUse:" << actualArgUse.getUser() << DoLog; + Sources.push_back(&actualArgUse); + } else { + CSVAccessLog << "NOT Reachable" << DoLog; + } + } else if (const auto *CExpr = dyn_cast(FUser)) { + CSVAccessLog << "BitCast" << DoLog; + const auto OpCode = CExpr->getOpcode(); + assert(OpCode == Instruction::BitCast); + for (const User *RealCall : CExpr->users()) { + CSVAccessLog << "RealCall:" << RealCall << DoLog; + const auto *FCall = dyn_cast(RealCall); + CSVAccessLog << "CallInst:" << FCall << DoLog; + if (FCall) { + const Function *Caller = FCall->getParent()->getParent(); + CSVAccessLog << "Caller: " << Caller << DoLog; + if (ReachableFunctions.find(Caller) != ReachableFunctions.end()) { + const Use &actualArgUse = FCall->getArgOperandUse(ArgNo); + CSVAccessLog << "ActualUse:" << actualArgUse.getUser() << DoLog; + Sources.push_back(&actualArgUse); + } + } + } + } + } + // This might be too strict, because the arguments of the root function + // don't have any sources. However, we assume that we never reach them. + assert(not Sources.empty()); + } + + explicit WorkItem(CallInst *C, bool IsLoad) : + CurrentValue(C), + Sources(), + SourceIndex(0) { + const Function *F = getCallee(C); + assert(F != nullptr); // Assume no indirect calls + if (F->getIntrinsicID() == Intrinsic::memcpy) { + const Use &AddrOp = C->getOperandUse(IsLoad ? 1 : 0); + Sources.push_back(&AddrOp); + const Use &SizeOp = C->getOperandUse(2); + Sources.push_back(&SizeOp); + } else { + for (const BasicBlock &BB : *F) { + const TerminatorInst *I = BB.getTerminator(); + if (I and isa(I) and I->getNumOperands() != 0) { + assert(I->getNumOperands() == 1); + const Use &RetValUse = I->getOperandUse(0); + Sources.push_back(&RetValUse); + } + } + } + assert(not Sources.empty()); + } + +public: + friend inline void writeToLog(Logger &L, const WorkItem &I, int) { + L << "Value: " << I.Val() << DoLog; + L << "Sources = {" << DoLog; + L.indent(); + for (const Use *U : I.sources()) + L << U->get() << DoLog; + L << "}" << DoLog; + L << "Curr Src Id: " << I.SourceIndex; + L.unindent(); + } + +public: + Value *Val() const { return CurrentValue; }; + + const Use *currentSourceUse() const { + if (SourceIndex < Sources.size()) + return Sources[SourceIndex]; + return nullptr; + } + + Value *currentSourceValue() const { + const Use *CurrUse = currentSourceUse(); + return CurrUse ? CurrUse->get() : nullptr; + } + + const Use *nextSourceUse() const { + const auto size = Sources.size(); + if (SourceIndex < size and (SourceIndex + 1) < size) + return Sources[SourceIndex + 1]; + return nullptr; + } + + const Value *nextSourceValue() const { + const Use *CurrUse = nextSourceUse(); + return CurrUse ? CurrUse->get() : nullptr; + } + + void advanceToNextSource() { + const auto size = Sources.size(); + if (SourceIndex < size) + SourceIndex++; + } + + llvm::iterator_range::const_iterator> + sources() const { + return { Sources.begin(), Sources.end() }; + } + + SmallVector::size_type getNumSources() const { + return Sources.size(); + } +}; + +/// \brief Gets a valid pointer to `CallInst` if the current source of `Item` is +/// a call from `Root` +/// +/// This function returns `nullptr` if the current source of `Item` is not a call +/// from `Root` +static CallInst * +getCurSourceRootCall(const WorkItem &Item, const Function *Root) { + CallInst *RootCall = nullptr; + if (isa(Item.Val())) { + User *ActualArgUser = Item.currentSourceUse()->getUser(); + auto *Call = cast(ActualArgUser); + if (Call->getParent()->getParent() == Root) + RootCall = Call; + } + return RootCall; +} + +/// \brief Gets a valid pointer to `CallInst` if the next source of `Item` is +/// a call from `Root` +/// +/// This function return `nullptr` if the next source of `Item` is not a call +/// from `Root` +static CallInst * +getNextSourceRootCall(const WorkItem &Item, const Function *Root) { + CallInst *RootCall = nullptr; + if (isa(Item.Val())) { + const Use *NextSrcUse = Item.nextSourceUse(); + if (NextSrcUse != nullptr) { + User *ActualArgUser = NextSrcUse->getUser(); + auto *Call = cast(ActualArgUser); + if (Call->getParent()->getParent() == Root) + RootCall = Call; + } + } + return RootCall; +} + +/// \brief Gets the `Shift`-th bit of `Input` +static int GetBit(uint64_t Input, int Shift) { + return (Input >> Shift) & 1; +}; + +using CallSiteOffsetMap = std::map; +using ValueCallSiteOffsetMap = std::map; + +/// \brief This class is used to fold constant offsets on different instructions +template +class CRTPOffsetFolder { + +protected: + using offset_iterator = std::set::const_iterator; + using offset_iterator_range = llvm::iterator_range; + using OffsetPair = std::pair; + +protected: + // These should be constant but ConstantInt::get() does not have + // const-qualifier on the first argument: + // static ConstantInt *get(IntegerType *Ty, uint64_t V, bool isSigned=false) + // However, it does not really change Ty, because it only call const + // methods on it, so it should be safe. + IntegerType *Int64Ty; + IntegerType *Int32Ty; + const DataLayout &DL; + +public: + CRTPOffsetFolder(const Module &M) : + Int64Ty(IntegerType::get(M.getContext(), 64)), + Int32Ty(IntegerType::get(M.getContext(), 32)), + DL(M.getDataLayout()) {} + static void insertOrCombine(Value *V, + CallInst *C, + CSVOffsets &&O, + ValueCallSiteOffsetMap &OffsetMap) { + CSVAccessLog << "MAP: " << V << DoLog; + bool Inserted; + CallSiteOffsetMap::iterator It; + std::tie(It, Inserted) = OffsetMap[V].insert(std::make_pair(C, O)); + if (not Inserted) + It->second.combine(O); + } + +public: + /// \brief This method folds the offsets on the sources ot Item + /// + /// \param Item is the `WorkItem` whose sources must be folded + /// \param [in, out] is the Map used to retrieve the values of the offsets of + /// the sources of `Item`, and also to store the result of + /// the folded offsets for `Item`. + void fold(const WorkItem &Item, ValueCallSiteOffsetMap &OffsetMap) { + WorkItem::size_type NumSrcs = Item.getNumSources(); + assert(NumSrcs); + SmallVector Operands(NumSrcs, nullptr); + // Collect Call Sites across all the sources of this User + CallPtrSet CallSites; + SmallVector SrcCallSiteOffsetsPtrs; + SmallVector NonRootOffsetsPtrs; + SrcCallSiteOffsetsPtrs.reserve(NumSrcs); + NonRootOffsetsPtrs.reserve(NumSrcs); + for (const Use *U : Item.sources()) { + const CallSiteOffsetMap &CallSiteOffsets = OffsetMap.at(U->get()); + assert(not CallSiteOffsets.empty()); + const CSVOffsets *NonRootOffsets = nullptr; + for (const auto &CSO : CallSiteOffsets) { + CallInst *TheCall = CSO.first; + CallSites.insert(TheCall); + if (TheCall == nullptr) { + NonRootOffsets = &CSO.second; + } + } + + // The order of iteration on SrcCallSiteOffsetsPtrs is the same + // as the order of iteration on Item.sources() + SrcCallSiteOffsetsPtrs.push_back(&CallSiteOffsets); + + // The order of iteration on NonRootOffsets is the same as the order + // of iteration on Item.sources() + NonRootOffsetsPtrs.push_back(NonRootOffsets); + } + assert(NumSrcs == SrcCallSiteOffsetsPtrs.size()); + assert(NumSrcs == NonRootOffsetsPtrs.size()); + + if (CSVAccessLog.isEnabled()) + for (const CallInst *C : CallSites) + CSVAccessLog << "C: " << C << DoLog; + + // Check that each source has all the callsites or nullptr + for (const auto &CSOffsets : SrcCallSiteOffsetsPtrs) { + bool found_nullptr = false; + if (CSOffsets->find(nullptr) != CSOffsets->end()) { + found_nullptr = true; + } + + bool found_all_calls = true; + for (CallInst *C : CallSites) { + if (C != nullptr and CSOffsets->find(C) == CSOffsets->end()) { + found_all_calls = false; + break; + } + } + assert(found_nullptr or found_all_calls); + } + + for (CallInst *C : CallSites) { + SmallVector SrcOffsets(NumSrcs, + OffsetPair(nullptr, nullptr)); + bool empty_pair = false; + for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { + const CSVOffsets *NonRootOffset = NonRootOffsetsPtrs[SI]; + if (C != nullptr) + SrcOffsets[SI].second = NonRootOffset; + const auto CallSiteOffsets = SrcCallSiteOffsetsPtrs[SI]; + auto OffsetsEnd = CallSiteOffsets->end(); + auto OffsetsIt = CallSiteOffsets->find(C); + if (OffsetsIt != OffsetsEnd) + SrcOffsets[SI].first = &OffsetsIt->second; + if (SrcOffsets[SI].first == nullptr + and SrcOffsets[SI].second == nullptr) { + // This means that one the pairs is empty and we can drop entirely + // this call. This happens when C == nullptr and one of the sources + // has no nullptr Callsite + empty_pair = true; + } + } + if (empty_pair) + continue; + CSVAccessLog << "start" << DoLog; + assert(NumSrcs < (8ULL * sizeof(uint64_t))); + uint64_t combinations = 1ULL << NumSrcs; + Value *V = Item.Val(); + Instruction *I = cast(V); + for (uint64_t i = 0; i < combinations; ++i) { + CSVAccessLog << "i:" << i << DoLog; + SmallVector OffsetTuple; + OffsetTuple.reserve(NumSrcs); + + // Build the tuple of offset sets that we want to use to compute the + // transfer function. If the analyzed call is nullptr we can skip + // some stuff and keep the computation smaller. + CSVAccessLog << "callsite: " << C << DoLog; + if (C != nullptr) { + for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { + int bit = GetBit(i, SI); + const CSVOffsets *O0 = SrcOffsets[SI].first; + const CSVOffsets *O1 = SrcOffsets[SI].second; + const CSVOffsets *O = bit ? O1 : O0; + if (O == nullptr) + break; + CSVAccessLog << "nonnull" << DoLog; + OffsetTuple.push_back(O); + } + } else { + for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { + const CSVOffsets *O = NonRootOffsetsPtrs[SI]; + if (O == nullptr) + break; + CSVAccessLog << "nonnull" << DoLog; + OffsetTuple.push_back(O); + } + } + CSVAccessLog << "NumSrcs:" << NumSrcs << DoLog; + CSVAccessLog << "Tuple Size:" << OffsetTuple.size() << DoLog; + if (OffsetTuple.size() != NumSrcs) + continue; + + bool Valid; + CSVOffsets::Kind ResKind; + std::tie(Valid, ResKind) = T::checkOffsetTupleIsValid(OffsetTuple, I); + if (not Valid) { + insertOrCombine(V, C, CSVOffsets(ResKind), OffsetMap); + continue; + } + CSVAccessLog << "valid tuple" << DoLog; + + SmallVector OffsetsRanges; + SmallVector OffsetsIt; + OffsetsRanges.reserve(NumSrcs); + OffsetsIt.reserve(NumSrcs); + + WorkItem::size_type CartesianSize = 1; + for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { + const auto *Tuple = OffsetTuple[SI]; + OffsetsRanges.push_back(make_range(Tuple->begin(), Tuple->end())); + OffsetsIt.push_back(Tuple->begin()); + const WorkItem::size_type OffsetSize = OffsetTuple[SI]->size(); + assert(OffsetSize); + assert(CartesianSize <= CartesianSize * OffsetSize); + CartesianSize *= OffsetSize; + } + + do { + CSVOffsets ResOffset = foldOffsets(ResKind, NumSrcs, I, OffsetsIt); + insertOrCombine(V, C, std::move(ResOffset), OffsetMap); + // Advance the iterators + { + WorkItem::size_type SI = 0; + bool wrapped = false; + do { + CSVAccessLog << "SI :" << SI << DoLog; + if (std::next(OffsetsIt[SI]) == OffsetsRanges[SI].end()) { + OffsetsIt[SI] = OffsetsRanges[SI].begin(); + wrapped = true; + CSVAccessLog << "WRAP" << DoLog; + } else { + CSVAccessLog << "NO-WRAP" << DoLog; + std::advance(OffsetsIt[SI], 1); + wrapped = false; + } + } while (wrapped and ++SI < NumSrcs); + CSVAccessLog << "incremented" << DoLog; + } + } while (--CartesianSize); + } + } + } + +private: + CSVOffsets foldOffsets(CSVOffsets::Kind ResKind, + WorkItem::size_type NumSrcs, + const Instruction *I, + const SmallVector &OffsetsIt) { + return static_cast(this)->foldOffsets(ResKind, NumSrcs, I, OffsetsIt); + } +}; + +/// \brief Specialization of CRTPOffsetFolder for sums and subtractions +class AddSubOffsetFolder : public CRTPOffsetFolder { + +public: + AddSubOffsetFolder(const Module &M) : + CRTPOffsetFolder(M) {} + +public: + friend class CRTPOffsetFolder; + +private: + static std::pair + checkOffsetTupleIsValid(const SmallVector &OffsetTuple, + const Instruction *I) { + auto OpCode = I->getOpcode(); + assert(OpCode == Instruction::Add or OpCode == Instruction::Sub); + assert(OffsetTuple.size() == 2); + const auto O0 = OffsetTuple[0], O1 = OffsetTuple[1]; + if (OpCode == Instruction::Add) { + // Cannot add pointers + assert(not(O0->isPtr() and O1->isPtr())); + } else { + // Cannot subtract a pointer from something else + assert(not O1->isPtr()); + } + + if (O0->isUnknown() or O1->isUnknown()) { + if (O0->isOnlyInPtr() or O1->isOnlyInPtr()) + return { false, CSVOffsets::Kind::UnknownInPtr }; + if (O0->isInOutPtr() or O1->isInOutPtr()) + return { false, CSVOffsets::Kind::OutAndUnknownInPtr }; + return { false, CSVOffsets::Kind::Unknown }; + } + + { + bool Num0 = O0->isNumeric(); + if (Num0 or O1->isNumeric()) { + CSVOffsets::Kind ResKind = Num0 ? O1->getKind() : O0->getKind(); + if (O0->isUnknownInPtr() or O1->isUnknownInPtr()) + return { false, ResKind }; + else + return { true, ResKind }; + } + } + abort(); + } + + CSVOffsets foldOffsets(CSVOffsets::Kind ResultKind, + WorkItem::size_type NumSrcs, + const Instruction *I, + const SmallVector &OffsetsIt) { + auto OpCode = I->getOpcode(); + assert(OpCode == Instruction::Add or OpCode == Instruction::Sub); + SmallVector Operands(NumSrcs, nullptr); + // Setup operands + for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { + const int64_t o = *OffsetsIt[SI]; + Operands[SI] = ConstantInt::get(Int64Ty, APInt(64, o, true)); + } + // Constant fold the operation with the selected operands + ArrayRef TmpOp(Operands); + Constant *Res = ConstantFoldInstOperands(OpCode, Int64Ty, TmpOp, DL); + const ConstantInt *R = cast(Res); + const int64_t ResO = R->getSExtValue(); + return CSVOffsets(ResultKind, ResO); + } +}; + +/// \brief Specialization of CRTPOffsetFolder for GEPs +class GEPOffsetFolder : public CRTPOffsetFolder { + +public: + GEPOffsetFolder(const Module &M) : CRTPOffsetFolder(M) {} + +public: + friend class CRTPOffsetFolder; + +private: + static std::pair + checkOffsetTupleIsValid(const SmallVector &OffsetTuple, + const Instruction *I) { + auto OpCode = I->getOpcode(); + assert(OpCode == Instruction::GetElementPtr); + size_t NOperands = OffsetTuple.size(); + assert(NOperands > 1); + CSVOffsets::Kind GEPOp0Kind = OffsetTuple[0]->getKind(); + if (CSVOffsets::isUnknownInPtr(GEPOp0Kind) + or CSVOffsets::isUnknown(GEPOp0Kind)) + return { false, GEPOp0Kind }; + + for (size_t O = 1; O < NOperands; O++) { + assert(not OffsetTuple[O]->isPtr()); + if (OffsetTuple[O]->isUnknown()) { + // TODO: handle cases with empty operands so that when the GEP is accessing arrays we + // can use the underlying type to avoid emitting Unknown and + // UnknownInCSV is not strictly necessary + return { false, CSVOffsets::makeUnknown(GEPOp0Kind) }; + } + } + return { true, GEPOp0Kind }; + } + + CSVOffsets foldOffsets(CSVOffsets::Kind ResultKind, + WorkItem::size_type NumSrcs, + const Instruction *I, + const SmallVector &OffsetsIt) { + const auto *GEP = cast(I); + const auto OpResTy = GEP->getType(); + const auto PtrOpTy = GEP->getPointerOperand()->getType(); + SmallVector Operands(NumSrcs, nullptr); + // Setup operands + int64_t ptr_o = *OffsetsIt[0]; + Constant *int_c = ConstantInt::get(Int64Ty, APInt(64, ptr_o, true)); + Constant *ptr_c = ConstantExpr::getIntToPtr(int_c, PtrOpTy); + Operands[0] = ptr_c; + for (WorkItem::size_type SI = 1; SI < NumSrcs; ++SI) { + const int64_t o = *OffsetsIt[SI]; + Operands[SI] = ConstantInt::get(Int32Ty, APInt(32, o, true)); + } + // Constant fold the operation with the selected operands + ArrayRef TmpOp(Operands); + auto OpCode = I->getOpcode(); + Constant *Res = ConstantFoldInstOperands(OpCode, OpResTy, TmpOp, DL); + const ConstantInt *R = nullptr; + if (Res->getType()->isPointerTy()) { + if (isa(Res)) { + auto Const = ConstantInt::get(Int32Ty, APInt(32, 0, true)); + R = cast(Const); + } else if (auto *PtrCast = dyn_cast(Res)) { + auto OpCode = PtrCast->getAsInstruction()->getOpcode(); + assert(OpCode == Instruction::IntToPtr); + R = cast(PtrCast->getOperand(0)); + } else { + abort(); + } + } else { + R = cast(Res); + } + const int64_t ResO = R->getSExtValue(); + return CSVOffsets(ResultKind, ResO); + } +}; + +using AccessOffsetMap = std::map; + +class CPUStateAccessOffsetAnalysis { + +private: + const Module &M; + const Value *CPUStatePtr; + const Function *RootFunction; + const ConstFunctionPtrSet &ReachableFunctions; + const TaintResults &TaintedAccesses; + VariableManager *Variables; + + AccessOffsetMap &LoadOffsets; // result, maps load or load-memcpy to offsets + AccessOffsetMap &StoreOffsets; // result, maps store or store-memcpy to + // offsets + CallSiteOffsetMap &CallSiteLoadOffsets; // result, maps call in root to load + // offsets + CallSiteOffsetMap &CallSiteStoreOffsets; // result, maps call in root to store + // offsets + + // ValueCallSiteOffsets is used to keep track of the offsets associated with + // each value. The primary key is the `Value` for which we're tracking the + // offsets. The secondary key is a `CallInst` representing a call site in + // root. This call site represents the call from which the mapped offsets are + // possible. The mapped value is a `CSVOffsets`. + ValueCallSiteOffsetMap ValueCallSiteOffsets; + + // The two following maps have the same structure as ValueCallSiteOffsets, but + // they are use to hold results on the specific loads and stores that access + // CSV. They are used after the computation to generate the metadata to attach + // to the root call sites and the final results of the analysis. + ValueCallSiteOffsetMap LoadCallSiteOffsets; + ValueCallSiteOffsetMap StoreCallSiteOffsets; + + CallPtrSet CrossedCallSites; + std::vector WorkList; + ConstValuePtrSet InExploration; + AddSubOffsetFolder AddSubFolder; + GEPOffsetFolder GEPFolder; + +public: + CPUStateAccessOffsetAnalysis(const Module &Mod, + const Value *EnvPtr, + const Function *Root, + const ConstFunctionPtrSet &Reachable, + const TaintResults &Tainted, + VariableManager *Vars, + AccessOffsetMap &LoadOff, + AccessOffsetMap &StoreOff, + CallSiteOffsetMap &CallSiteLoadOff, + CallSiteOffsetMap &CallSiteStoreOff) : + M(Mod), + CPUStatePtr(EnvPtr), + RootFunction(Root), + ReachableFunctions(Reachable), + TaintedAccesses(Tainted), + Variables(Vars), + LoadOffsets(LoadOff), + StoreOffsets(StoreOff), + CallSiteLoadOffsets(CallSiteLoadOff), + CallSiteStoreOffsets(CallSiteLoadOff), + ValueCallSiteOffsets(), + LoadCallSiteOffsets(), + StoreCallSiteOffsets(), + CrossedCallSites(), + WorkList(), + InExploration(), + AddSubFolder(M), + GEPFolder(M) {} + +public: + bool run(); + +private: + void cleanup() { + ValueCallSiteOffsets = {}; + LoadCallSiteOffsets = {}; + StoreCallSiteOffsets = {}; + CrossedCallSites = {}; + WorkList = {}; + InExploration = {}; + } + + /// \brief Analyzes the access to env performed by \p I, saving results + /// according to \p IsLoad + /// \param I is the `Instruction` whose accesses are analyzed + /// \param IsLoad must be true if called when analyzing loads, false if + /// called when analyzing stores. This is important because it is used to + /// update the correct ValueCallSiteOffsetMap (either LoadCallSiteOffsets or + /// StoreCallSiteOffsets) if during the exploration the analysis ends because + /// all the immediate sources are already resolved. + void analyzeAccess(Instruction *I, bool IsLoad); + + /// \brief Explores the sources of `V` and pushes a `WorkItem` on `WorkList` + /// if something new is found + /// \param V is the `Value` whose sources are analyzed + /// \param IsLoad must be true if called when propagating loads, false if + /// called when propagating stores. This is important because it is used to + /// update the correct ValueCallSiteOffsetMap (either LoadCallSiteOffsets or + /// StoreCallSiteOffsets) if during the exploration the analysis ends because + /// all the immediate sources are already resolved. + void exploreImmediateSources(Value *V, bool IsLoad); + + /// \brief Returns true if V has unexplored sources + /// + /// \param V the `Value` whose sources must be explored. + /// \param [out] W a `WorkItem` initialized with the unexplored sources of `V` + /// if any. + /// \param [out] O a `CSVOffsets` initialized with the Offsets of `V` it they + /// are already known. + /// + /// \return true (and sets W) if there are unexplored sources, false (and sets + /// O) otherwise + /// + bool getUnexploredSrcWorkItem(Value *V, + WorkItem &W, + CSVOffsets &O, + bool IsLoad) const; + + void insertCallSiteOffset(Value *V, CSVOffsets &&Offset); + + /// \brief Removes the root call site associated with `Item` (if any) from + /// `CrossedCallSites` + /// \return `true` if it was removed, `false` otherwise + bool removeCurCrossedCallSite(const WorkItem &Item) { + CallInst *RootCallSite = getCurSourceRootCall(Item, RootFunction); + if (RootCallSite) { + auto NumErased = CrossedCallSites.erase(RootCallSite); + assert(NumErased); + return true; + } + return false; + } + + /// \brief Returns true if `V` is visited for the first time with the callsite + /// `NewCallSite` + /// \param V is the `Value` that is being visited + /// \param NewCallSite is the new call site from which we're exploring V and + /// we want to check if it's the first time we visit V with that + /// particular call site + /// + /// This function returns `true` if this is the first visit, `false` otherwise + bool isNewVisitWithCallSite(Value *V, CallInst *NewCallSite) const { + + // Handle constants in a special way. Constants are kind of global values + // that can be used across different functions without properly propagating + // on the call graph across call sites. + // This has two consequences. + // 1) On the one hand we don't want to track their exact call site, because + // they can propagate independently of the call sites. Hence all constants + // are collected with an 'artificial' nullptr callsite (see the else). + // 2) On the other hand, when we cross root call sites and we find that one + // of the arguments is a constant, we want to mark it every time as a new + // visit. If we don't mark it as a new visit, the root call site will not be + // crossed, hence it will not be inserted in the `CrossedCallSites`, which + // is not what we want because it would lead to errors in computing the root + // call sites that are active for a given exploration. For this reason, if V + // is a constant and (NewCallSite != nullptr) we always say it's a new + // visit, because it means we're crossing a root call site towards a + // constant argument. + if (isa(V)) { + if (NewCallSite != nullptr) + return true; + else + NewCallSite = nullptr; + } + + const auto CallSiteOffsetIt = ValueCallSiteOffsets.find(V); + // If the ValueCallSiteOffsets does not contain V it's a new visit + if (CallSiteOffsetIt == ValueCallSiteOffsets.end()) + return true; + + // If the ValueCallSiteOffsets contains V we have already analyzed visited + // this value, but we don't know which call sites were contained in + // CrossedCallSites during the last visit. + + // If NewCallSite is not nullptr, we are crossing a new callsite in the root + // function, so we start looking in the ValueCallSiteOffsets for an entry + // associated to NewCallSite + if (NewCallSite) { + // If we find that ValueCallSiteOffsets still does not contain an entry + // associated to NewCallSite this visit is considered new + const auto OffsetIt = CallSiteOffsetIt->second.find(NewCallSite); + if (OffsetIt == CallSiteOffsetIt->second.end()) + return true; + } + + // If CrossedCallSites is not empty, we have crossed at least one call site + // in the root function, so we need to look in CrossedCallSites if there is + // a new call site to analyze + if (CrossedCallSites.size()) { + for (CallInst *Call : CrossedCallSites) { + // If we find a call site in CrossedCallSites for which the + // ValueCallSiteOffsets + // still does not contain a result for this value this visit is + // considered new + const auto OffsetIt = CallSiteOffsetIt->second.find(Call); + if (OffsetIt == CallSiteOffsetIt->second.end()) + return true; + } + } else { + // If CrossedCallSites is empty, we haven't crossed any call site in the + // root function, so we look for nullptr. + const auto OffsetIt = CallSiteOffsetIt->second.find(nullptr); + if (OffsetIt == CallSiteOffsetIt->second.end()) + return true; + } + + // If we reach this point the ValueCallSiteOffsets already contains an entry + // for V, and all the current CrossedCallSites have already been computed + // for that entry, so the visit is not new + return false; + } + + /// \brief If it's a new visit, insert the call `RootCall` (associated with + /// `U`) in `CrossedCallSites` + /// + /// \param RootCall must be nullptr or a valid call instruction in root + /// \param U if RootCall is not `nullptr` this is a `Use` whose `User` must be + /// RootCall + /// \return `true` if it was a new visit, `false` otherwise. + /// + /// If this is the first visit, `RootCall` is inserted in `CrossedCallSites` + /// and the function returns `true`. + /// If this is not the first visit or `RootCall` it returns `false` + bool checkNewVisitAndInsertCrossedCallSite(CallInst *RootCall, const Use *U) { + if (isNewVisitWithCallSite(U->get(), RootCall)) { + if (RootCall) { + bool New = CrossedCallSites.insert(RootCall).second; + assert(New); + } + return true; + } + return false; + } + + bool checkNewVisitAndInsertCurCrossedCallSite(const WorkItem &Item) { + CallInst *RootCallSite = getCurSourceRootCall(Item, RootFunction); + return checkNewVisitAndInsertCrossedCallSite(RootCallSite, Item.currentSourceUse()); + } + + bool checkNewVisitAndInsertNextCrossedCallSite(const WorkItem &Item) { + CallInst *RootCallSite = getNextSourceRootCall(Item, RootFunction); + return checkNewVisitAndInsertCrossedCallSite(RootCallSite, Item.nextSourceUse()); + } + + /// \brief Selects the next source of `Item`, if possible, returning true on + /// success. + bool selectNextSource(WorkItem &Item) { + const Use *NextSrcUse = Item.nextSourceUse(); + if (NextSrcUse != nullptr) { + removeCurCrossedCallSite(Item); + checkNewVisitAndInsertNextCrossedCallSite(Item); + Item.advanceToNextSource(); + return true; + } + return false; + } + + void push(WorkItem &&Item) { + InExploration.insert(Item.Val()); + WorkList.push_back(Item); + CSVAccessLog.indent(2); + } + + void pop() { + InExploration.erase(WorkList.back().Val()); + WorkList.pop_back(); + CSVAccessLog.unindent(2); + } + + bool isInExploration(const Value *V) { + return InExploration.find(V) != InExploration.end(); + } + + void computeOffsetsFromSources(const WorkItem &Item, bool IsLoad); + + template + void computeAggregatedOffsets(); +}; + +using CPUSAOA = CPUStateAccessOffsetAnalysis; + +void CPUSAOA::computeOffsetsFromSources(const WorkItem &Item, bool IsLoad) { + Value *ItemVal = Item.Val(); + if (isa(ItemVal) or isa(ItemVal) + or isa(ItemVal)) { + + // These three cases represent points of convergence of information coming + // from different origins. + // + // For `PHINode` the information comes from the different branches of the + // phi. As an example the 'then' branch of an 'if' could compute an offsets, + // whereas the 'else' branch could compute a different offset. + // + // For `Argument` the different sources of information are all the actual + // arguments of all the calls to the function that are associated to the + // formal `Argument` that we're analyzing. An example is that if the + // function `int a(int b, int c)` if called in two places, such as `f(1,2)` + // and `f(3,4)`, we have that 1 and 3 are the sources of the argument `b`. + // + // For `CallInst` we are considering the propagation of values from the + // `return` instructions inside the callee to the call site. + // For example if we have: + // ``` + // int a(int b) { + // if (b) { + // int ret1 = b >> 2; + // return ret1; + // } else { + // int ret2 = b << 2; + // return ret2; + // } + // } + // ``` + // and `a` is called such as: + // ``` + // y = f(x); + // ... + // ``` + // the sources of the `CallInst` are `ret1` and `ret2`. + + CSVAccessLog << "POP JOIN" << DoLog; + + SmallVector SrcCallSiteOffsets; + SrcCallSiteOffsets.reserve(Item.getNumSources()); + std::map> CallSiteSrcIds; + + // This loop fills `SrcCallSiteOffsets` so that its n-th element will point + // to the `CallSiteOffsetMap` associated with the n-th source of the Value + // that we're considering. + WorkItem::size_type SI = 0; + for (const Use *Src : Item.sources()) { + Value *SrcVal = Src->get(); + CSVAccessLog << "SrcVal: " << SrcVal << DoLog; + CSVAccessLog << "SrcValPtr: " << reinterpret_cast(SrcVal) + << DoLog; + const CallSiteOffsetMap &CallSiteOffset = ValueCallSiteOffsets.at(SrcVal); + + // The `CallSiteOffsetMap` associated with `SrcVal` is pushed back into + // `SrcCallSiteOffsets`, into position `SI`. + SrcCallSiteOffsets.push_back(&CallSiteOffset); + + // Then, this loop inserts the source index `SI` into the set of source + // indices associated with the call site `C.first` + for (const auto &C : CallSiteOffset) + CallSiteSrcIds[C.first].insert(SI); + ++SI; + } + + // Here `SrcCallSiteOffsets[i]` contains a pointer the the + // `CallSiteOffsetMap` associated with the source number `i` of the analyzed + // `Value`. + // Here, for a given call site `C` in `root` the value + // `CallSiteSrcIds.at(C)` is the set of source indices for which the value + // currently analyzed is reached from the call site `C`. + + auto Call = dyn_cast(ItemVal); + const Function *Callee = Call ? getCallee(Call) : nullptr; + if (Callee != nullptr and Callee->getIntrinsicID() == Intrinsic::memcpy) { + + // Separately handle Intrinsic::memcpy; + // Calls to Intrinsic::memcpy are a special case. + // Given that they don't generate any Value, they cannot be pushed on the + // worklist as a consequence of the backward exploration towards sources. + // For this reason, if we reach a point where we're trying to compute the + // offsets from the sources of a call to Intrinsic::memcpy it must be the + // memcpy where we started from and we don't really want to propagate from + // the return to the call site, but we want to compute the result and + // store it in the proper map. + + CSVAccessLog << "MAP Instrinsic::memcpy: " << ItemVal << DoLog; + + assert(isa(Call->getArgOperand(2))); + ValueCallSiteOffsetMap &VCSOffsets = IsLoad ? LoadCallSiteOffsets : + StoreCallSiteOffsets; + Value *PtrOp = IsLoad ? Call->getArgOperand(1) : Call->getArgOperand(0); + auto CSOff = std::make_pair(ItemVal, ValueCallSiteOffsets.at(PtrOp)); + bool New = VCSOffsets.insert(CSOff).second; + assert(New); + + } else { + + // This loop iterates over each call instruction in `CallSiteSrcIds`, + // and, for all the source indices associated to that call site, it + // combines all of the `CSVOffsets` to compute the new `CSVOffsets` of the + // node that we're popping. + for (const auto &CallSrc : CallSiteSrcIds) { + Optional New; + CallInst *TheCall = CallSrc.first; + for (const auto i : CallSrc.second) { + CSVAccessLog << "AT: " << TheCall << DoLog; + const CSVOffsets &SrcOffset = SrcCallSiteOffsets[i]->at(TheCall); + if (New) + New.getValue().combine(SrcOffset); + else + New = SrcOffset; + } + CSVAccessLog << "MAP JOIN: " << ItemVal << DoLog; + CSVAccessLog << " " << New.getValue() << DoLog; + + // Insert the `New` in the `ValueCallSiteOffsets` + ValueCallSiteOffsets[ItemVal][TheCall] = std::move(New.getValue()); + CSVAccessLog << "CallSite: " << TheCall << DoLog; + CSVAccessLog << " " << ValueCallSiteOffsets.at(ItemVal).at(TheCall) + << DoLog; + } + } + + removeCurCrossedCallSite(Item); + + } else if (auto *Instr = dyn_cast(ItemVal)) { + + CSVAccessLog << "POP INST" << DoLog; + + const auto OpCode = Instr->getOpcode(); + switch (OpCode) { + case Instruction::ZExt: + case Instruction::SExt: + case Instruction::Trunc: + case Instruction::PtrToInt: + case Instruction::IntToPtr: + case Instruction::BitCast: { + CSVAccessLog << "MAP CAST: " << ItemVal << DoLog; + Value *Op = Instr->getOperand(0); + ValueCallSiteOffsets[ItemVal] = ValueCallSiteOffsets.at(Op); + } break; + case Instruction::Sub: + case Instruction::Add: { + CSVAccessLog << "Add/Sub" << DoLog; + AddSubFolder.fold(Item, ValueCallSiteOffsets); + } break; + case Instruction::GetElementPtr: { + CSVAccessLog << "GEP" << DoLog; + GEPFolder.fold(Item, ValueCallSiteOffsets); + } break; + case Instruction::Load: { + Value *AddressValue = cast(Instr)->getPointerOperand(); + auto LoadCSOff = std::make_pair(ItemVal, + ValueCallSiteOffsets.at(AddressValue)); + bool New = LoadCallSiteOffsets.insert(LoadCSOff).second; + if (CSVAccessLog.isEnabled()) { + CSVAccessLog << "Load "; + std::string InstrLog; + raw_string_ostream OStream(InstrLog); + Instr->print(OStream); + CSVAccessLog << InstrLog << DoLog; + for (const auto & CS2O : LoadCSOff.second) { + CSVAccessLog << "CallSite: "; + std::string CallLog; + raw_string_ostream CallOStream(CallLog); + if (CS2O.first) + Instr->print(CallOStream); + else + CallOStream << "nullptr"; + CSVAccessLog << CallLog << DoLog; + CSVAccessLog << CS2O.second << DoLog; + } + } + assert(New); + } break; + case Instruction::Store: { + Value *AddressValue = cast(Instr)->getPointerOperand(); + auto StoreCSOff = std::make_pair(ItemVal, + ValueCallSiteOffsets.at(AddressValue)); + bool New = StoreCallSiteOffsets.insert(StoreCSOff).second; + if (CSVAccessLog.isEnabled()) { + CSVAccessLog << "Store "; + std::string InstrLog; + raw_string_ostream OStream(InstrLog); + Instr->print(OStream); + CSVAccessLog << InstrLog << DoLog; + for (const auto & CS2O : StoreCSOff.second) { + CSVAccessLog << "CallSite: "; + std::string CallLog; + raw_string_ostream CallOStream(CallLog); + if (CS2O.first) + Instr->print(CallOStream); + else + CallOStream << "nullptr"; + CSVAccessLog << CallLog << DoLog; + CSVAccessLog << CS2O.second << DoLog; + } + } + assert(New); + } break; + default: + abort(); + break; + } + } else { + abort(); + } +} + +void CPUSAOA::insertCallSiteOffset(Value *V, CSVOffsets &&Offset) { + CSVAccessLog << "MAP INSERT: " << V << DoLog; + // If CrossedCallSites is empty we haven't reached the root function during + // the backward exploration, so the only active call site is nullptr. + // The same holds if we're inserting the offset for a ConstantInt. The problem + // with constants is that they are not propagated through the call graph, but + // they are in a global context and they infect all the places where they're + // used independently of the call sites crossed during the exploration. For + // this reason, if we accumulate all the call sites for the constants we may + // end up in situation where on a given constant we have lots of call sites in + // the map, but all with the same CSVOffsets. This is bad for two reasons: + // 1) it increases the size of the map for no reason; + // 2) it potentially propagates wrong call sites where the constants are used. + // For this reason we used this workaround, to only insert null call sites for + // constants. + if (CrossedCallSites.empty() or isa(V)) { + ValueCallSiteOffsets[V][nullptr] = Offset; + CSVAccessLog << "CallSite: nullptr" << DoLog; + CSVAccessLog << " " << Offset << DoLog; + } else { + // In all the other cases use the active set of crossed call sites + for (const auto &Call : CrossedCallSites) { + ValueCallSiteOffsets[V][Call] = Offset; + CSVAccessLog << "CallSite: " << Call << DoLog; + CSVAccessLog << " " << Offset << DoLog; + } + } +} + +bool CPUSAOA::getUnexploredSrcWorkItem(Value *V, + WorkItem &Item, + CSVOffsets &O, + bool IsLoad) const { + if (auto *Call = dyn_cast(V)) { + if (CSVAccessLog.isEnabled()){ + CSVAccessLog << "CALL" << DoLog; + Call->dump(); + } + Item = WorkItem(Call, IsLoad); + } else if (auto *Arg = dyn_cast(V)) { + CSVAccessLog << "ARG" << DoLog; + Item = WorkItem(Arg, ReachableFunctions); + } else if (auto *Instr = dyn_cast(V)) { + CSVAccessLog << "INST" << DoLog; + const auto OpCode = Instr->getOpcode(); + switch (OpCode) { + case Instruction::Load: { + CSVAccessLog << "LOAD" << DoLog; + const auto *Load = cast(Instr); + const Value *Ptr = Load->getPointerOperand(); + if (const auto *CSV = dyn_cast(Ptr)) { + CSVAccessLog << "GLOBAL" << DoLog; + if (CSV == CPUStatePtr) { + CSVAccessLog << "ENV" << DoLog; + O = CSVOffsets(CSVOffsets::Kind::KnownInPtr, 0); + } else { + CSVAccessLog << "NOT-ENV" << DoLog; + O = CSVOffsets(CSVOffsets::Kind::Unknown); + } + } else { + CSVAccessLog << "NOT-GLOBAL" << DoLog; + O = CSVOffsets(CSVOffsets::Kind::Unknown); + } + return false; + } break; + case Instruction::Alloca: { + CSVAccessLog << "ALLOCA" << DoLog; + O = CSVOffsets(CSVOffsets::Kind::Unknown); + return false; + } break; + case Instruction::Or: + case Instruction::And: + case Instruction::ICmp: { + CSVAccessLog << "CMP" << DoLog; + O = CSVOffsets(CSVOffsets::Kind::Unknown); + return false; + } break; + case Instruction::Store: + abort(); + break; + default: + break; + } + // If we reach this point the CSVOffsets of this instruction are not known + Item = WorkItem(Instr); + } else if (const auto *IntConst = dyn_cast(V)) { + int64_t Offset = IntConst->getSExtValue(); + CSVAccessLog << "CONST: " << Offset << DoLog; + O = CSVOffsets(CSVOffsets::Kind::Numeric, Offset); + return false; + } else { + abort(); + } + return true; +} + +void CPUSAOA::exploreImmediateSources(Value *V, bool IsLoad) { + // Try to get new unexplored sources for V. + WorkItem NewItem; + CSVOffsets ConstantKnownOffsets; + if (not getUnexploredSrcWorkItem(V, NewItem, ConstantKnownOffsets, IsLoad)) { + CSVAccessLog << "ConstantOffset" << DoLog; + + // If we reach this point, V only has a constant know CSVOffsets and does + // not really have sources that must be explored. In this case we can just + // insert the ConstantKnownOffsets in the map and we're done. + insertCallSiteOffset(V, std::move(ConstantKnownOffsets)); + return; + } + CSVAccessLog << "New!: " << NewItem << DoLog; + // If we reach this point NewItem is valid and contains a vector of sources + // for V. Iterate an all the sources, looking for the first unexplored one. + for (const Use *U : NewItem.sources()) { + CSVAccessLog << "Src: " << NewItem << DoLog; + // Cut recursion + if (isInExploration(NewItem.Val())) { + CSVAccessLog << "RECURSION: " << U->get() << DoLog; + const ConstValuePtrSet &Tainted = TaintedAccesses.TaintedValues; + CSVOffsets::Kind NewKind = Tainted.find(NewItem.Val()) != Tainted.end() ? + CSVOffsets::Kind::UnknownInPtr : + CSVOffsets::Kind::Unknown; + insertCallSiteOffset(U->get(), CSVOffsets(NewKind)); + continue; + } + // If we're visiting the sources of an argument we are crossing a + // new call site, which might lead us into the root function. + // If it does, we want to register it in the CrossedCallSites + if (checkNewVisitAndInsertCurCrossedCallSite(NewItem)) { + CSVAccessLog << "Found" << DoLog; + push(std::move(NewItem)); + return; + } + // Adjust the SourceIndex, to set the correct Source + if (NewItem.nextSourceUse() != nullptr) { + NewItem.advanceToNextSource(); + CSVAccessLog << "NextSrc" << DoLog; + } + } + // If we reach this point the current Value V only has source that were + // already explored. + if (NewItem.getNumSources()) { + CSVAccessLog << "DONE" << DoLog; + computeOffsetsFromSources(NewItem, IsLoad); + } +} + +bool callsBuiltinMemcpy(const Instruction *TheCall) { + const Function *Callee = getCallee(TheCall); + return Callee != nullptr and Callee->getIntrinsicID() == Intrinsic::memcpy; +} + +void CPUSAOA::analyzeAccess(Instruction *LoadOrStore, bool IsLoad) { + + // This analysis starts from the Instruction LoadOrStore and works in two + // alternate steps: + // 1) it iterates backward, looking for all the values that generate their + // pointer operands; + // 2) as soon as the backward exploration reaches the leaves (i.e. the initial + // values that are used as building blocks for the computation) it starts + // working forward. At each step of the forward propagation it tries to + // constant-fold the sources of the current value. If all the offsets of all + // the sources are known they are constant folded and the analysis keeps + // working forward. Otherwise, if there is a source that it still unexplored + // (i.e. it has no known offsets), the analysis starts working backward again, + // until also the unexplored source is resolved and can be constant folded. + + // Initialization + if (not callsBuiltinMemcpy(LoadOrStore)) + push(WorkItem(LoadOrStore)); + else + push(WorkItem(cast(LoadOrStore), IsLoad)); + + while (not WorkList.empty()) { + const auto size = WorkList.size(); + Value *CurSrcVal = WorkList.back().currentSourceValue(); + if (CSVAccessLog.isEnabled()) { + const auto *CurVal = WorkList.back().Val(); + CSVAccessLog << "Val : " << CurVal << DoLog; + CSVAccessLog << "ValPtr: " << reinterpret_cast(CurVal) + << DoLog; + CSVAccessLog << "Src : " << CurSrcVal << DoLog; + CSVAccessLog << "SrcPtr: " << reinterpret_cast(CurSrcVal) + << DoLog; + } + + // Explore CurSrcVal's immediate sources (going backward) + exploreImmediateSources(CurSrcVal, IsLoad); + + // If we pushed something on the WorkList we want to keep exploring back + if (size < WorkList.size()) + continue; + CSVAccessLog << "not grown" << DoLog; + + // If we didn't push anything, we are done exploring backward the current + // source and we want to explore backward the other sources of this value + if (size == WorkList.size()) + if (selectNextSource(WorkList.back())) + continue; + + CSVAccessLog << "Done" << DoLog; + + // If we reach this point we have finished exploring all the sources of + // the item that is currently on top of the WorkList. + // The backward propagation is complete for now. + // We want to fold the results of the sources on the Item, store the + // result in the OffsetMap, and then pop the Item + do { + const WorkItem &Item = WorkList.back(); + if (CSVAccessLog.isEnabled()) { + const auto *Val = Item.Val(); + CSVAccessLog << "TopItemVal: " << Val << DoLog; + CSVAccessLog << "TopItemPtr: " << reinterpret_cast(Val) + << DoLog; + const auto *SrcVal = Item.currentSourceValue(); + CSVAccessLog << "CurSrc : " << SrcVal << DoLog; + CSVAccessLog << "CurSrcPtr : " << reinterpret_cast(SrcVal) + << DoLog; + } + + // Constant fold the finished value and pop it. + computeOffsetsFromSources(Item, IsLoad); + pop(); + } while (not WorkList.empty() + and WorkList.back().nextSourceValue() == nullptr); + + if (not WorkList.empty()) { + selectNextSource(WorkList.back()); + } + } +} + +template +void CPUSAOA::computeAggregatedOffsets() { + const InstrPtrSet &Tainted = IsLoad ? TaintedAccesses.TaintedLoads : + TaintedAccesses.TaintedStores; + ValueCallSiteOffsetMap &AccessCSOffsets = IsLoad ? LoadCallSiteOffsets : + StoreCallSiteOffsets; + CallSiteOffsetMap &CallSiteOffsets = IsLoad ? CallSiteLoadOffsets : + CallSiteStoreOffsets; + AccessOffsetMap &AccessOffsets = IsLoad ? LoadOffsets : StoreOffsets; + + for (std::pair &ACSO : AccessCSOffsets) { + + Value *I = ACSO.first; + auto DL = M.getDataLayout(); + + bool isInstr = isa(I); + bool isCorrectAccessType = IsLoad ? isa(I) : isa(I); + bool isCallToBuiltinMemcpy = callsBuiltinMemcpy(dyn_cast(I)); + assert(isInstr and (isCorrectAccessType or isCallToBuiltinMemcpy)); + auto *Instr = dyn_cast(I); + assert(Tainted.count(Instr) != 0); + + int64_t AccessSize; + if (isCallToBuiltinMemcpy) { + auto Call = cast(I); + auto SizeParam = cast(Call->getArgOperand(2)); + AccessSize = SizeParam->getSExtValue(); + } else if (IsLoad) { + auto *Load = cast(Instr); + auto *PtrTy = cast(Load->getPointerOperand()->getType()); + Type *LoadedType = PtrTy->getElementType(); + AccessSize = DL.getTypeAllocSize(LoadedType); + } else { + auto Store = cast(Instr); + auto *PtrTy = cast(Store->getPointerOperand()->getType()); + Type *StoredType = PtrTy->getElementType(); + AccessSize = DL.getTypeAllocSize(StoredType); + } + assert(AccessSize != 0); + + CallSiteOffsetMap &CallSiteMap = ACSO.second; + for (std::pair &CSO : CallSiteMap) { + CallInst *const Call = CSO.first; + CSVOffsets &O = CSO.second; + assert(O.isPtr()); + + bool Inserted; + + // Compute AccessOffsets, i.e. the set of offsets accessed by each access + { + AccessOffsetMap::iterator AccessOffsetIt; + auto Offset = std::make_pair(Instr, O); + std::tie(AccessOffsetIt, Inserted) = AccessOffsets.insert(Offset); + if (not Inserted) + AccessOffsetIt->second.combine(O); + } + + // Compute CallSiteOffsets, i.e. the set of offsets that might be accessed + // from a given call in root. + // This is a little more tricky than computing AccessOffsets, since here, + // when we collapse on the call site, we lose the information on the + // specific instruction that caused a given offset to be computed, hence + // also losing the size of the access. For this reason here we have to + // take into account the sizes of all the accesses. + { + CSVOffsets New; + if (not O.hasOffsetSet()) { + New = O; + } else { + New = CSVOffsets(O.getKind()); // Empty, but with the correct type + // Now compute the fine-grained offsets + for (const int64_t Coarse : O) { + int64_t Refined = Coarse; + int64_t End = Coarse + AccessSize; + while (Refined < End) { + unsigned InternalOffset = 0; + GlobalVariable *AccessedVar; + std::tie(AccessedVar, + InternalOffset) = Variables->getByEnvOffset(Refined); + assert(InternalOffset == 0); + int64_t SizeAtOffset = 0; + if (AccessedVar != nullptr) { + Type *AccessedTy = AccessedVar->getType(); + SizeAtOffset = DL.getTypeAllocSize(AccessedTy); + New.insert(Refined); + CSVAccessLog << "Value: " << I << DoLog; + CSVAccessLog << "Insert Refined: " << Refined << DoLog; + } else { + // Skip padding one byte at a time, without adding offsets + SizeAtOffset = 1; + } + assert(SizeAtOffset != 0); + Refined += SizeAtOffset; + } + } + } + // Finally insert them or combine them + CallSiteOffsetMap::iterator CallOffsetIt; + std::tie(CallOffsetIt, + Inserted) = CallSiteOffsets.insert({ Call, New }); + if (not Inserted) + CallOffsetIt->second.combine(New); + } + } + } +} + +bool CPUSAOA::run() { + + // Analyze load and store + for (Instruction *I : TaintedAccesses.TaintedLoads) + analyzeAccess(I, true); + for (Instruction *I : TaintedAccesses.TaintedStores) + analyzeAccess(I, false); + + if (CSVAccessLog.isEnabled()) { + TaintLog << "== ACCESS ANALYSIS RESULTS ==\n"; + TaintLog << "== Loads ==\n"; + for (Instruction *LoadOrStore : TaintedAccesses.TaintedLoads) { + TaintLog << "INSTRUCTION: " << LoadOrStore << DoLog; + LoadOrStore->dump(); + TaintLog << DoLog; + TaintLog.indent(4); + for (const auto &CSO : LoadCallSiteOffsets.at(LoadOrStore)) { + TaintLog << "CallSite: " << CSO.first << '\n'; + CSO.first->dump(); + TaintLog << DoLog; + TaintLog << CSO.second << '\n'; + } + TaintLog.unindent(4); + TaintLog << DoLog; + } + TaintLog << "== Stores ==\n"; + for (Instruction *LoadOrStore : TaintedAccesses.TaintedStores) { + TaintLog << "INSTRUCTION: " << LoadOrStore << DoLog; + LoadOrStore->dump(); + TaintLog << DoLog; + TaintLog.indent(4); + for (const auto &CSO : StoreCallSiteOffsets.at(LoadOrStore)) { + TaintLog << "CallSite: " << CSO.first << '\n'; + CSO.first->dump(); + TaintLog << DoLog; + TaintLog << CSO.second << '\n'; + } + TaintLog.unindent(4); + TaintLog << DoLog; + } + TaintLog << DoLog; + } + + // ValueCallSiteOffset is not needed anymore here, because it's only used + // across different calls to analyzeAccess to optimize the runtime avoiding + // multiple iterations on the same Values. + ValueCallSiteOffsets = {}; + + // Aggregate the results: + // - from LoadCallSiteOffsets to CallSiteLoadOffsets and LoadOffsets + // - from StoreCallSiteOffsets to CallSiteStoreOffsets and StoreOffsets + computeAggregatedOffsets(); + computeAggregatedOffsets(); + + cleanup(); + return not TaintedAccesses.empty(); +} + +class CPUStateAccessAnalysis { + +private: + // A reference to the analyzed Module + const Module &M; + + VariableManager *Variables; + + // References to the maps that will be filled by this analysis. + // Every map maps an Instruction to the CSVOffset representing all the + // possible offsets that are accessed by that Instruction, being it either a + // load, a store or a call to Intrinsic::memcpy. + // We hold two separate maps, one for loads and one for stores, so that calls + // to memcpy can be in both maps, with different associated offsets. + AccessOffsetMap CSVLoadOffsetMap; + AccessOffsetMap CSVStoreOffsetMap; + +public: + CPUStateAccessAnalysis(const Module &Mod, VariableManager *V) : + M(Mod), + Variables(V), + EnvStructType(V->getCPUStateType()), + DL(Mod.getDataLayout()), + EnvStructSize(DL.getTypeAllocSize(EnvStructType)), + Builder(Mod.getContext()), + Int64Ty(Builder.getInt64Ty()), + SizeOfEnv(ConstantInt::get(Int64Ty, APInt(64, EnvStructSize, true))), + Zero(ConstantInt::get(Int64Ty, APInt(64, 0, true))), + CPUStatePtr(Mod.getGlobalVariable("env")) {} + +public: + bool run(); + +private: + template + std::tuple + setupOutEnvAccess(Instruction *AccessToFix); + + template + void correctCPUStateAccesses(); + + void setupLoadInEnv(Instruction *LoadToFix, + int64_t EnvOffset, + SwitchInst *Switch, + BasicBlock *NextBB, + PHINode *Phi); + + void setupStoreInEnv(Instruction *LoadToFix, + int64_t EnvOffset, + SwitchInst *Switch, + BasicBlock *NextBB); + +private: + Type *EnvStructType; + const DataLayout &DL; + int64_t EnvStructSize; + IRBuilder<> Builder; + Type *Int64Ty; + Constant *SizeOfEnv; + Constant *Zero; + GlobalVariable *CPUStatePtr; +}; + +static void addAccessMetadata(const CallSiteOffsetMap &OffsetMap, + VariableManager *Variables, + QuickMetadata &QMD, + unsigned MDKind) { + for (auto &AccessOffsets : OffsetMap) { + const CSVOffsets &Offsets = AccessOffsets.second; + CallInst *const CallSite = AccessOffsets.first; + if (CallSite == nullptr) + continue; + assert(Offsets.isPtr()); + + ConstantAsMetadata *UnknownAccess = nullptr; + MDTuple *AccessedVariablesTuple = nullptr; + SmallVector OffsetMetadata; + OffsetMetadata.reserve(Offsets.size()); + SmallSet AccessedVarNames; + if (Offsets.isUnknownInPtr()) { + CSVAccessLog << "Unknown access to CSV" << DoLog; + UnknownAccess = QMD.get((uint32_t) 1); + AccessedVariablesTuple = QMD.tuple(OffsetMetadata); + } else { + UnknownAccess = QMD.get((uint32_t) 0); + for (const int64_t O : Offsets) { + CSVAccessLog << "CallSite: " << CallSite << DoLog; + CSVAccessLog << "Refined: " << O << DoLog; + GlobalVariable *AccessedVar = Variables->getByEnvOffset(O).first; + std::string VarName = AccessedVar->getName(); + MDString *VarNameMD = QMD.get(VarName); + bool NewlyInserted = AccessedVarNames.insert(VarName).second; + if (NewlyInserted) { + OffsetMetadata.push_back(VarNameMD); + } + CSVAccessLog << "Accessed Var: " << AccessedVar << " Name: " << VarName + << " Offset: " << Variables->getByEnvOffset(O).second + << DoLog; + } + AccessedVariablesTuple = QMD.tuple(OffsetMetadata); + } + SmallVector AccessMetadata = { UnknownAccess, + AccessedVariablesTuple }; + CallSite->setMetadata(MDKind, QMD.tuple(AccessMetadata)); + } +} + +static Value *getLoadAddressValue(Instruction *I) { + auto OpCode = I->getOpcode(); + Value *Address = nullptr; + switch (OpCode) { + case Instruction::Load: { + Address = cast(I)->getPointerOperand(); + } break; + case Instruction::Call: { + auto *Call = cast(I); + Function *Callee = getCallee(Call); + // We only support memcpys where the last parameter is constant + assert(Callee != nullptr + and (Callee->getIntrinsicID() == Intrinsic::memcpy + and isa(Call->getArgOperand(2)))); + Address = Call->getArgOperand(1); + } break; + default: { abort(); } break; + } + return Address; +} + +static Type *getLoadedType(Instruction *I) { + if (auto Load = dyn_cast(I)) { + auto *PtrTy = cast(Load->getPointerOperand()->getType()); + return PtrTy->getElementType(); + } + return nullptr; +} + +static Value *getStoreAddressValue(Instruction *I) { + auto OpCode = I->getOpcode(); + Value *Address = nullptr; + switch (OpCode) { + case Instruction::Store: { + Address = cast(I)->getPointerOperand(); + } break; + case Instruction::Call: { + auto *Call = cast(I); + Function *Callee = getCallee(Call); + // We only support memcpys where the last parameter is constant + assert(Callee != nullptr + and (Callee->getIntrinsicID() == Intrinsic::memcpy + and isa(Call->getArgOperand(2)))); + Address = Call->getArgOperand(0); + } break; + default: { abort(); } break; + } + return Address; +} + +static void fixEnv2EnvMemCopies(const Module &M, + AccessOffsetMap &CSVLoadOffsetMap, + AccessOffsetMap &CSVStoreOffsetMap) { + QuickMetadata QMD(M.getContext()); + IRBuilder<> Builder(M.getContext()); + Type *CharTy = Builder.getInt8Ty(); + + std::set AccessToRemove; + AccessOffsetMap NewLoadCSOffsets; + AccessOffsetMap NewStoreCSOffsets; + + for (auto &InstCSOffset : CSVLoadOffsetMap) { + Instruction *Instr = InstCSOffset.first; + auto It = CSVStoreOffsetMap.find(Instr); + if (It != CSVStoreOffsetMap.end()) { + auto *Call = cast(Instr); + Function *Memcpy = getCallee(Call); + assert(Memcpy != nullptr + and (Memcpy->getIntrinsicID() == Intrinsic::memcpy + and isa(Call->getArgOperand(2)))); + + Value *MemcpySize = Call->getArgOperand(2); + Value *MemcpySrc = Call->getArgOperand(1); + Value *MemcpyDst = Call->getArgOperand(0); + + Function *F = Instr->getParent()->getParent(); + Builder.SetInsertPoint(&*F->getEntryBlock().begin()); + AllocaInst *TmpBuffer = Builder.CreateAlloca(CharTy, MemcpySize); + + Builder.SetInsertPoint(Instr); + CallInst *MemcpyLoad = Builder.CreateMemCpy(TmpBuffer, MemcpySrc, MemcpySize, TmpBuffer->getAlignment()); + NewLoadCSOffsets.insert({ MemcpyLoad, InstCSOffset.second }); + + CallInst *MemcpyStore = Builder.CreateMemCpy(MemcpyDst, TmpBuffer, MemcpySize, TmpBuffer->getAlignment()); + NewStoreCSOffsets.insert({ MemcpyStore, It->second }); + + AccessToRemove.insert(Instr); + } + } + + for (Instruction *Instr : AccessToRemove) { + CSVLoadOffsetMap.erase(Instr); + CSVStoreOffsetMap.erase(Instr); + Instr->eraseFromParent(); + } + CSVLoadOffsetMap.insert(NewLoadCSOffsets.begin(), NewLoadCSOffsets.end()); + CSVStoreOffsetMap.insert(NewStoreCSOffsets.begin(), NewStoreCSOffsets.end()); +} + +static ConstantInt *getConstantOffset(Type *Int64Ty, int64_t O) { + auto *EnvOffsetConst = ConstantInt::get(Int64Ty, APInt(64, O, true)); + return cast(EnvOffsetConst); +} + +static Value *buildEnvOffsetValue(IRBuilder<> Builder, + GlobalVariable *CPUStatePtr, + Value *Address, + Type *OffsetTy) { + LoadInst *LoadEnv = Builder.CreateLoad(CPUStatePtr); + Value *EnvAsInt64 = Builder.CreateZExtOrBitCast(LoadEnv, OffsetTy); + Value *AddressAsInt64 = Builder.CreatePtrToInt(Address, OffsetTy); + return Builder.CreateSub(AddressAsInt64, EnvAsInt64); +} + +template +std::tuple +CPUStateAccessAnalysis::setupOutEnvAccess(Instruction *AccessToFix) { + LLVMContext &Context = M.getContext(); + BasicBlock *AccessToFixBB = AccessToFix->getParent(); + Function *F = AccessToFixBB->getParent(); + auto InstrIt = AccessToFix->getIterator(); + assert(InstrIt != AccessToFixBB->end()); + assert(std::next(InstrIt) != AccessToFixBB->end()); + // Create a new block NextBB and move there all the instructions after + // the access + BasicBlock *NextBB = BasicBlock::Create(Context, "AfterAccess", F); + AccessToFixBB->replaceSuccessorsPhiUsesWith(NextBB); + NextBB->getInstList().splice(NextBB->end(), + AccessToFixBB->getInstList(), + std::next(InstrIt), + AccessToFixBB->getInstList().end()); + assert(not NextBB->empty()); + + // Create a new block OutAccessBB only for accesses outside env, and + // clone the accessing instruction in there. This clone of the + // accessing instruction in the OutAccessBB will be leaved untouched + // by the substitution performed later. + BasicBlock *OutAccessBB = BasicBlock::Create(Context, "OutAccess", F); + Builder.SetInsertPoint(OutAccessBB); + BranchInst *OutToNextBranchInst = Builder.CreateBr(NextBB); + Instruction *OutAccess = AccessToFix->clone(); + OutAccess->insertBefore(OutToNextBranchInst); + + // Create a new block InAccessBB only for accesses inside env (if any). + // The accessing instruction is cloned into the new InAccessBB + // and we insert a branch instruction to the NextBB. + BasicBlock *InAccessBB = BasicBlock::Create(Context, "InAccess", F); + Builder.SetInsertPoint(InAccessBB); + BranchInst *InToNextBranchInst = Builder.CreateBr(NextBB); + Instruction *InAccessToFix = AccessToFix->clone(); + InAccessToFix->insertBefore(InToNextBranchInst); + + // Create a conditional branch to jump to InAccessBB if the access is + // going to be in env, or to OutAccessBB if the access is going to be + // out of env + Value *Address = IsLoad ? getLoadAddressValue(AccessToFix) : + getStoreAddressValue(AccessToFix); + + Builder.SetInsertPoint(AccessToFixBB); + Value *OffsetValue = buildEnvOffsetValue(Builder, + CPUStatePtr, + Address, + Int64Ty); + Value *GEZero = Builder.CreateICmpSGE(OffsetValue, Zero); + Value *LTSizeOf = Builder.CreateICmpSLT(OffsetValue, SizeOfEnv); + Value *IsInCSV = Builder.CreateOr(GEZero, LTSizeOf); + Builder.CreateCondBr(IsInCSV, InAccessBB, OutAccessBB); + + if (IsLoad) { + Type *LoadedType = getLoadedType(AccessToFix); + if (LoadedType != nullptr) { + Builder.SetInsertPoint(&NextBB->front()); + PHINode *PN = Builder.CreatePHI(LoadedType, 2); + AccessToFix->replaceAllUsesWith(PN); + PN->addIncoming(OutAccess, OutAccessBB); + PN->addIncoming(InAccessToFix, InAccessBB); + } + return std::make_tuple(InAccessToFix, LoadedType, OffsetValue); + } + return std::make_tuple(InAccessToFix, nullptr, OffsetValue); +} + +void CPUStateAccessAnalysis::setupLoadInEnv(Instruction *LoadToFix, + int64_t EnvOffset, + SwitchInst *Switch, + BasicBlock *NextBB, + PHINode *Phi) { + LLVMContext &Context = M.getContext(); + Function *F = LoadToFix->getParent()->getParent(); + auto *OffsetConstInt = getConstantOffset(Int64Ty, EnvOffset); + BasicBlock *CaseBlock = BasicBlock::Create(Context, "CaseInLoad"); + Builder.SetInsertPoint(CaseBlock); + BranchInst *Break = Builder.CreateBr(NextBB); + + Instruction *Clone = LoadToFix->clone(); + Clone->insertBefore(Break); + + bool Ok = false; + Builder.SetInsertPoint(Clone); + switch (LoadToFix->getOpcode()) { + + case Instruction::Load: { + + Type *OriginalLoadedType = Clone->getType(); + unsigned Size = DL.getTypeAllocSize(OriginalLoadedType); + assert(Size != 0); + auto *Loaded = Variables->loadFromEnvOffset(Builder, Size, EnvOffset); + Ok = Loaded != nullptr; + if (Ok) { + Type *LoadedType = Loaded->getType(); + if (LoadedType != OriginalLoadedType) { + unsigned LoadedSize = DL.getTypeAllocSize(LoadedType); + assert(LoadedSize == Size); + Loaded = Builder.CreateIntToPtr(Loaded, OriginalLoadedType); + } + CaseBlock->insertInto(F); + Switch->addCase(OffsetConstInt, CaseBlock); + // Add an incoming edge for the PHI after the switch if necessary. + if (Phi != nullptr) + Phi->addIncoming(Loaded, CaseBlock); + Clone->replaceAllUsesWith(Loaded); + Clone->eraseFromParent(); + } else { + delete CaseBlock; + CaseBlock = nullptr; // Prevent this from being used + } + + } break; + + case Instruction::Call: { + CallInst *Call = cast(Clone); + Ok = Variables->memcpyAtEnvOffset(Builder, Call, EnvOffset, true); + Clone->eraseFromParent(); + if (Ok) { + CaseBlock->insertInto(F); + Switch->addCase(OffsetConstInt, CaseBlock); + } else { + delete CaseBlock; + CaseBlock = nullptr; // Prevent this from being used + } + + } break; + + default: + abort(); + break; + } +} + +void CPUStateAccessAnalysis::setupStoreInEnv(Instruction *StoreToFix, + int64_t EnvOffset, + SwitchInst *Switch, + BasicBlock *NextBB) { + LLVMContext &Context = M.getContext(); + Function *F = StoreToFix->getParent()->getParent(); + auto *OffsetConstInt = getConstantOffset(Int64Ty, EnvOffset); + BasicBlock *CaseBlock = BasicBlock::Create(Context, "CaseInStore"); + Builder.SetInsertPoint(CaseBlock); + BranchInst *Break = Builder.CreateBr(NextBB); + + Instruction *Clone = StoreToFix->clone(); + Clone->insertBefore(Break); + + bool Ok = false; + Builder.SetInsertPoint(Clone); + switch (StoreToFix->getOpcode()) { + + case Instruction::Store: { + auto *Store = cast(Clone); + auto *ToStore = Store->getValueOperand(); + unsigned Size = DL.getTypeAllocSize(ToStore->getType()); + assert(Size != 0); + Ok = Variables->storeToEnvOffset(Builder, Size, EnvOffset, ToStore); + } break; + + case Instruction::Call: { + CallInst *Call = cast(Clone); + Ok = Variables->memcpyAtEnvOffset(Builder, Call, EnvOffset, false); + } break; + + default: + abort(); + break; + } + + Clone->eraseFromParent(); + if (Ok) { + CaseBlock->insertInto(F); + Switch->addCase(OffsetConstInt, CaseBlock); + } else { + delete CaseBlock; + } +} + +template +void CPUStateAccessAnalysis::correctCPUStateAccesses() { + AccessOffsetMap &CSVAccessOffsetMap = IsLoad ? CSVLoadOffsetMap : + CSVStoreOffsetMap; + + if (IsLoad) + FixAccessLog << "######## Fixing Loads ########" << DoLog; + else + FixAccessLog << "######## Fixing Stores ########" << DoLog; + + for (std::pair &IOff : CSVAccessOffsetMap) { + Instruction *const I = IOff.first; + CSVOffsets &Offsets = IOff.second; + CSVOffsets::Kind OKind = Offsets.getKind(); + FixAccessLog << "Fixing access: " << I + << "\nCSVOffsets Kind: " << CSVOffsets::toString(OKind) + << DoLog; + assert(CSVOffsets::isPtr(OKind)); + Function *F = I->getParent()->getParent(); + Instruction *AccessToFix = I; + + switch (OKind) { + case CSVOffsets::Kind::Unknown: + case CSVOffsets::Kind::Numeric: + assert(false); + continue; // to the next access, but this point should never be reached + break; + case CSVOffsets::Kind::OutAndKnownInPtr: + case CSVOffsets::Kind::UnknownInPtr: + case CSVOffsets::Kind::OutAndUnknownInPtr: + case CSVOffsets::Kind::KnownInPtr: { + + if (FixAccessLog.isEnabled()) { + FixAccessLog << "Before: " << DoLog; + F->dump(); + FixAccessLog << DoLog; + } + + // This is necessary to get the correct debug info. + // Setting the insert point to an Instruction also updates the Builder + // to use its debug info until the insert point is set to a new + // instruction. + // Given that in the rest of the code we mostly use + // SetInsertPoint(BasicBlock *), which does not reset the debug info, we + // want to do it now, otherwise we might end up using the wrong debug + // info from an instruction of a previous iteration of this loop. + Builder.SetInsertPoint(I); + + Value *Address = nullptr; + Type *LoadedType = nullptr; // This is not used if IsLoad is false + if (IsLoad) { + Address = getLoadAddressValue(I); + LoadedType = getLoadedType(I); + } else { + Address = getStoreAddressValue(I); + } + + Value *OffsetValue = nullptr; + if (CSVOffsets::isInOutPtr(OKind)) + std::tie(AccessToFix, + LoadedType, + OffsetValue) = setupOutEnvAccess(I); + + assert(AccessToFix != nullptr); + LLVMContext &Context = M.getContext(); + QuickMetadata QMD(Context); + + // filter out cases where a switch is not necessary + if (Offsets.size() == 1) { + int64_t Offset = *Offsets.begin(); + + Instruction *Clone = AccessToFix->clone(); + Clone->insertAfter(AccessToFix); + AccessToFix->replaceAllUsesWith(Clone); + + Builder.SetInsertPoint(Clone); + bool Ok = false; + switch (AccessToFix->getOpcode()) { + + case Instruction::Load: { + if (IsLoad) { + Type *OriginalLoadedType = Clone->getType(); + unsigned Size = DL.getTypeAllocSize(OriginalLoadedType); + assert(Size != 0); + auto *Loaded = Variables->loadFromEnvOffset(Builder, Size, Offset); + Ok = Loaded != nullptr; + if (Ok) { + Type *LoadedType = Loaded->getType(); + if (LoadedType != OriginalLoadedType) { + unsigned LoadedSize = DL.getTypeAllocSize(LoadedType); + assert(LoadedSize == Size); + Loaded = Builder.CreateIntToPtr(Loaded, OriginalLoadedType); + } + Clone->replaceAllUsesWith(Loaded); + } + } else { + abort(); + } + } break; + + case Instruction::Store: { + if (not IsLoad) { + auto *Store = cast(Clone); + auto *ToStore = Store->getValueOperand(); + unsigned Size = DL.getTypeAllocSize(ToStore->getType()); + assert(Size != 0); + Ok = Variables->storeToEnvOffset(Builder, Size, Offset, ToStore); + } else { + abort(); + } + } break; + + case Instruction::Call: { + CallInst *Call = cast(Clone); + Ok = Variables->memcpyAtEnvOffset(Builder, Call, Offset, IsLoad); + } break; + + default: + abort(); + break; + } + + if (not Ok) { + Builder.SetInsertPoint(Clone); + CallInst *CallAbort = Builder.CreateCall(M.getFunction("abort")); + auto InvalidMDKind = Context.getMDKindID("InvalidUniqueInAccess"); + CallAbort->setMetadata(InvalidMDKind, QMD.tuple((uint32_t) 0)); + } else { + Clone->eraseFromParent(); + } + + break; // out of the big switch to the verify and cleanup code + } + + // Create a new block NextBB, after the Switch, and move there all the + // instructions after the access + BasicBlock *AccessToFixBB = AccessToFix->getParent(); + auto InstrIt = AccessToFix->getIterator(); + assert(InstrIt != AccessToFixBB->end()); + assert(std::next(InstrIt) != AccessToFixBB->end()); + BasicBlock *NextBB = BasicBlock::Create(Context, "AfterInAccess", F); + AccessToFixBB->replaceSuccessorsPhiUsesWith(NextBB); + NextBB->getInstList().splice(NextBB->end(), + AccessToFixBB->getInstList(), + std::next(InstrIt), + AccessToFixBB->getInstList().end()); + assert(not NextBB->empty()); + assert(std::next(InstrIt) == AccessToFixBB->end()); + // If we're processing loads, add a PHI in NextBB if necessary + PHINode *Phi = nullptr; + if (IsLoad) { + if (LoadedType != nullptr) { + Builder.SetInsertPoint(&NextBB->front()); + Phi = Builder.CreatePHI(LoadedType, Offsets.size()); + AccessToFix->replaceAllUsesWith(Phi); + } + } + + // Create the default BB for the switch, calling abort() + BasicBlock *Default = BasicBlock::Create(Context, "DefaultInAccess", F); + Builder.SetInsertPoint(Default); + CallInst *CallAbort = Builder.CreateCall(M.getFunction("abort")); + auto UnexpectedInMDKind = Context.getMDKindID("UnexpectedInAccess"); + CallAbort->setMetadata(UnexpectedInMDKind, QMD.tuple((uint32_t) 0)); + Builder.CreateUnreachable(); + + // Create the offset value to use as a variable for the switch if + // necessary + Builder.SetInsertPoint(AccessToFixBB); + if (OffsetValue == nullptr) + OffsetValue = buildEnvOffsetValue(Builder, + CPUStatePtr, + Address, + Int64Ty); + assert(OffsetValue != nullptr); + + if (CSVOffsets::isUnknownInPtr(OKind)) { + SwitchInst *SwitchOffset = Builder.CreateSwitch(OffsetValue, + Default, + EnvStructSize); + for (int64_t CurrEnvOff = 0; CurrEnvOff < EnvStructSize; ++CurrEnvOff) { + if (IsLoad) + setupLoadInEnv(AccessToFix, CurrEnvOff, SwitchOffset, NextBB, Phi); + else + setupStoreInEnv(AccessToFix, CurrEnvOff, SwitchOffset, NextBB); + } + assert(SwitchOffset->getNumCases() > 0); + break; // out from the switch, to the verify and cleanup code + } + + assert(Offsets.size() > 0); + SwitchInst *SwitchOffset = Builder.CreateSwitch(OffsetValue, + Default, + Offsets.size()); + for (const int64_t CurrEnvOff : Offsets) { + if (IsLoad) + setupLoadInEnv(AccessToFix, CurrEnvOff, SwitchOffset, NextBB, Phi); + else + setupStoreInEnv(AccessToFix, CurrEnvOff, SwitchOffset, NextBB); + } + + if (IsLoad) { + if (Phi != nullptr and Phi->getNumIncomingValues() == 0) { + Builder.SetInsertPoint(Phi); + + CallInst *CallAbort = Builder.CreateCall(M.getFunction("abort")); + auto NeverValidInMDKind = Context.getMDKindID("NeverValidInLoad"); + CallAbort->setMetadata(NeverValidInMDKind, QMD.tuple((uint32_t) 0)); + + Instruction *DisabledInLoad = AccessToFix->clone(); + DisabledInLoad->insertBefore(Phi); + Phi->replaceAllUsesWith(DisabledInLoad); + Phi->eraseFromParent(); + } + } + } break; + } + + // Verify the transformation and cleanup the access to fix + if (FixAccessLog.isEnabled()) { + FixAccessLog << "After: " << DoLog; + F->dump(); + FixAccessLog << DoLog; + } + FixAccessLog << "Erasing: " << I << DoLog; + if (I != AccessToFix) + AccessToFix->eraseFromParent(); + I->eraseFromParent(); + } +} + +bool CPUStateAccessAnalysis::run() { + + // Get the root Function + const Function *RootFunction = M.getFunction("root"); + assert(RootFunction); + + // Preprocessing: detect all the functions that are directly reachable from + // the RootFunction + auto ReachedFunctions = computeDirectlyReachableFunctions(RootFunction); + + // Start with a forward taint analysis, to detect all the tainted Values, + // and all the tainted loads and stores. + CSVAccessLog << "Before Taint Analysis" << DoLog; + const auto TaintResults = forwardTaintAnalysis(CPUStatePtr, ReachedFunctions); + CSVAccessLog << "After Taint Analysis" << DoLog; + + // If there are no tainted loads and stores we don't need to run the CPUSAOA. + if (TaintResults.TaintedLoads.empty() and TaintResults.TaintedStores.empty()) + return TaintResults.IllegalCalls.size(); + + if (TaintLog.isEnabled()) { + TaintLog << "==== Tainted Loads =====\n"; + for (const Instruction *I : TaintResults.TaintedLoads) { + TaintLog << I << DoLog; + I->dump(); + std::string Name = I->getParent()->getParent()->getName(); + TaintLog << "In Function: " << Name << DoLog; + } + TaintLog << "==== Tainted Stores ====\n"; + for (const Instruction *I : TaintResults.TaintedStores) { + TaintLog << I << DoLog; + I->dump(); + std::string Name = I->getParent()->getParent()->getName(); + TaintLog << "In Function: " << Name << DoLog; + } + TaintLog << "==== Illegal Calls =====\n"; + for (const Instruction *I : TaintResults.IllegalCalls) { + TaintLog << I << DoLog; + I->dump(); + std::string Name = I->getParent()->getParent()->getName(); + TaintLog << "In Function: " << Name << DoLog; + } + TaintLog << "========================" << DoLog; + } + + CallSiteOffsetMap CallSiteLoadOffset; + CallSiteOffsetMap CallSiteStoreOffset; + auto AccessOffsetAnalysis = CPUSAOA(M, + CPUStatePtr, + RootFunction, + ReachedFunctions, + TaintResults, + Variables, + CSVLoadOffsetMap, + CSVStoreOffsetMap, + CallSiteLoadOffset, + CallSiteStoreOffset); + bool Found = AccessOffsetAnalysis.run(); + LLVMContext &Context = M.getContext(); + QuickMetadata QMD(Context); + if (Found) { + auto LoadMDKind = Context.getMDKindID("revamb.csvaccess.offsets." + "load"); + addAccessMetadata(CallSiteLoadOffset, Variables, QMD, LoadMDKind); + auto StoreMDKind = Context.getMDKindID("revamb.csvaccess.offsets." + "store"); + addAccessMetadata(CallSiteStoreOffset, Variables, QMD, StoreMDKind); + } + + // CallSiteLoadOffset and CallSiteStoreOffset are not used anymore + CallSiteLoadOffset = {}; + CallSiteStoreOffset = {}; + + // Decompose memcpy from env to env into two separate memcpy, the fisrt to do + // the load, the second to do the store + fixEnv2EnvMemCopies(M, CSVLoadOffsetMap, CSVStoreOffsetMap); + // Fix loads + correctCPUStateAccesses(); + // Fix stores + correctCPUStateAccesses(); + assert(not verifyModule(M, &dbgs())); + + return Found; +} + +bool CPUStateAccessAnalysisPass::runOnModule(Module &Mod) { + DBG("access-analysis-results", Mod.dump()); + CPUStateAccessAnalysis CSVAccessAnalysis(Mod, Variables); + return CSVAccessAnalysis.run(); +} + +char CPUStateAccessAnalysisPass::ID = 0; + +using RegisterCPUSAAP = RegisterPass; +static RegisterCPUSAAP + X("cpustate-access-analysis", "CPUState Access Analysis Pass", false, false); diff --git a/cpustateaccessanalysis.h b/cpustateaccessanalysis.h new file mode 100644 index 000000000..215c82f82 --- /dev/null +++ b/cpustateaccessanalysis.h @@ -0,0 +1,225 @@ +#ifndef _CPUSTATEACCESSANALYSIS_H +#define _CPUSTATEACCESSANALYSIS_H + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +// Standard includes +#include +#include +#include + +// LLVM includes +#include "llvm/Pass.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { +class Instruction; +} + +template +class Logger; +class VariableManager; + +/// \brief Different types of accesses to the CPU State Variables (CSVs), with a +/// set of possible offsets. +class CSVOffsets { + +private: + using OffsetSet = std::set; + +public: + using iterator = OffsetSet::iterator; + using const_iterator = OffsetSet::const_iterator; + using size_type = OffsetSet::size_type; + +public: + enum Kind { + Unknown, + Numeric, + KnownInPtr, + OutAndKnownInPtr, + UnknownInPtr, + OutAndUnknownInPtr, + }; + + static const char *toString(const Kind K) { + switch (K) { + case Unknown: + return "Unknown"; + case Numeric: + return "Numeric"; + case KnownInPtr: + return "KnownInPtr"; + case OutAndKnownInPtr: + return "OutAndKnownInPtr"; + case UnknownInPtr: + return "UnknownInPtr"; + case OutAndUnknownInPtr: + return "OutAndUnknownInPtr"; + default: + abort(); + } + return ""; + } + +private: + Kind OffsetKind; + OffsetSet Offsets; + +public: + CSVOffsets() : OffsetKind(Kind::Numeric), Offsets() {} + CSVOffsets(Kind K) : OffsetKind(K), Offsets() {} + CSVOffsets(Kind K, int64_t O) : OffsetKind(K), Offsets({ O }) {} + +public: + friend void writeToLog(Logger &L, const CSVOffsets &O, int /*Ignore*/); + +private: + explicit operator Kind() const { return OffsetKind; } + +public: + iterator begin() { return Offsets.begin(); } + iterator end() { return Offsets.end(); } + + const_iterator begin() const { return Offsets.cbegin(); } + const_iterator end() const { return Offsets.cend(); } + + size_type size() const { return Offsets.size(); } + size_type empty() const { return Offsets.empty(); } + + static bool isUnknown(const Kind K) { return K == Kind::Unknown; } + bool isUnknown() const { return isUnknown(OffsetKind); } + + static bool isNumeric(const Kind K) { return K == Kind::Numeric; } + bool isNumeric() const { return isNumeric(OffsetKind); } + + static bool isPtr(const Kind K) { + return not isNumeric(K) and not isUnknown(K); + } + bool isPtr() const { return isPtr(OffsetKind); } + + static bool isOnlyInPtr(const Kind K) { + return K == Kind::UnknownInPtr or K == Kind::KnownInPtr; + } + bool isOnlyInPtr() const { return isOnlyInPtr(OffsetKind); } + + static bool isInOutPtr(const Kind K) { + return isPtr(K) and not isOnlyInPtr(K); + } + bool isInOutPtr() const { return isInOutPtr(OffsetKind); } + + static bool isUnknownInPtr(const Kind K) { + return K == Kind::UnknownInPtr or K == Kind::OutAndUnknownInPtr; + } + bool isUnknownInPtr() const { return isUnknownInPtr(OffsetKind); } + + static bool isKnownInPtr(const Kind K) { + return isPtr(K) and not isUnknownInPtr(K); + } + bool isKnownInPtr() const { return isKnownInPtr(OffsetKind); } + + static bool hasOffsetSet(const Kind K) { + return K == Kind::KnownInPtr or K == Kind::OutAndKnownInPtr + or K == Kind::Numeric; + } + bool hasOffsetSet() const { return hasOffsetSet(OffsetKind); } + + enum Kind getKind() const { return OffsetKind; } + static enum Kind makeUnknown(const Kind K) { + switch (K) { + case Kind::Numeric: + return Kind::Unknown; + case Kind::KnownInPtr: + return Kind::UnknownInPtr; + case Kind::OutAndKnownInPtr: + return Kind::OutAndUnknownInPtr; + default: + assert(false); + break; + } + return K; + } + + void insert(int64_t O) { Offsets.insert(O); } + void combine(const CSVOffsets &other) { + Kind K0 = OffsetKind; + Kind K1 = other.OffsetKind; + // For equal kinds just merge the offsets + if (K0 == K1) { + Offsets.insert(other.Offsets.begin(), other.Offsets.end()); + return; + } + + // If one is OutAndUnknownInPtr always return OutAndUnknownInPtr + if (K0 == Kind::OutAndUnknownInPtr or K1 == Kind::OutAndUnknownInPtr) { + OffsetKind = Kind::OutAndUnknownInPtr; + Offsets = {}; + return; + } + + if (K0 == Kind::UnknownInPtr or K1 == Kind::UnknownInPtr) { + // If K0 and K1 are equal, or one of them is a OnlyInPtr stay in + // UnknownInPtr, in all the other cases also access out, hence returning + // OutAndUnknownInPtr + if (isOnlyInPtr(K0) or isOnlyInPtr(K1) or K0 == K1) + OffsetKind = Kind::UnknownInPtr; + else + OffsetKind = Kind::OutAndUnknownInPtr; + + Offsets = {}; + return; + } + + if (K0 == Kind::OutAndKnownInPtr or K1 == Kind::OutAndKnownInPtr) { + // If one is Unknown it wipes away the knowledge, otherwise we merge the + // Offsets + if (isUnknown(K0) or isUnknown(K1)) { + OffsetKind = Kind::OutAndUnknownInPtr; + Offsets = {}; + } else { + OffsetKind = Kind::OutAndKnownInPtr; + Offsets.insert(other.Offsets.begin(), other.Offsets.end()); + } + return; + } + + { + bool KIP0 = K0 == Kind::KnownInPtr; + if (KIP0 or K1 == Kind::KnownInPtr) { + assert(isNumeric(K0) or isNumeric(K1) or isUnknown(K0) + or isUnknown(K1)); + OffsetKind = Kind::OutAndKnownInPtr; + if (not KIP0) + Offsets = other.Offsets; + return; + } + } + assert((isNumeric(K0) and isUnknown(K1)) + or (isNumeric(K0) and isUnknown(K1))); + OffsetKind = Kind::Unknown; + Offsets = {}; + } +}; + +/// \brief LLVM pass to analyze the access patterns to the CPU State Variable +class CPUStateAccessAnalysisPass : public llvm::ModulePass { + +private: + VariableManager *Variables; + +public: + static char ID; + +public: + CPUStateAccessAnalysisPass() : llvm::ModulePass(ID), Variables(nullptr){}; + + CPUStateAccessAnalysisPass(VariableManager *VM) : + llvm::ModulePass(ID), + Variables(VM){}; + +public: + bool runOnModule(llvm::Module &TheModule) override; +}; +#endif diff --git a/ir-helpers.h b/ir-helpers.h index 64f0d0bda..3febd9aad 100644 --- a/ir-helpers.h +++ b/ir-helpers.h @@ -547,6 +547,16 @@ static inline const llvm::Function *getCallee(const llvm::Instruction *I) { return nullptr; } +static inline llvm::Function *getCallee(llvm::Instruction *I) { + assert(I != nullptr); + + using namespace llvm; + if (auto *Call = dyn_cast(I)) + return llvm::dyn_cast(skipCasts(Call->getCalledValue())); + else + return nullptr; +} + static inline bool isCallTo(const llvm::Instruction *I, llvm::StringRef Name) { assert(I != nullptr); const llvm::Function *Callee = getCallee(I); diff --git a/variablemanager.cpp b/variablemanager.cpp index 6495a28da..cee15087b 100644 --- a/variablemanager.cpp +++ b/variablemanager.cpp @@ -72,402 +72,6 @@ private: std::vector Stack; }; -static const int64_t ErrorOffset = std::numeric_limits::max(); - -bool CorrectCPUStateUsagePass::runOnModule(Module& TheModule) { - OffsetValueStack WorkList; - - Value *CPUStatePtr = TheModule.getGlobalVariable("env"); - - // Do we even have "env"? - if (CPUStatePtr == nullptr) - return false; - - assert(CPUStatePtr->getType()->isPointerTy()); - - struct Specialization { - Function *F; - Function *Original; - std::vector> SpecializedArgs; - }; - - std::vector Specializations; - std::map OffsetFunctions; - - const DataLayout& DL = TheModule.getDataLayout(); - - while (true) { - if (WorkList.empty()) { - for (Use& CPUStateUse : CPUStatePtr->uses()) { - auto *Load = cast(CPUStateUse.getUser()); - assert(Load->getPointerOperand() == CPUStatePtr); - - WorkList.pushIfNew(Variables->EnvOffset, Load); - } - } - - if (WorkList.empty()) - break; - - - int64_t CurrentOffset; - Value *CurrentValue; - std::tie(CurrentOffset, CurrentValue) = WorkList.pop(); - - std::vector> Replacements; - - for (Use& TheUse : CurrentValue->uses()) { - Instruction *TheUser = cast(TheUse.getUser()); - auto Opcode = TheUser->getOpcode(); - - if (CurrentOffset == ErrorOffset - && Opcode != Instruction::Load - && Opcode != Instruction::Store) { - // Not loading or storing, propagate the error value - WorkList.push(ErrorOffset, TheUser); - continue; - } - - switch(Opcode) { - case Instruction::Load: - case Instruction::Store: - { - auto *Load = dyn_cast(TheUser); - auto *Store = dyn_cast(TheUser); - - IRBuilder<> Builder(cast(TheUser)); - - bool Success = false; - if (Load != nullptr) { - unsigned Size = DL.getTypeAllocSize(TheUser->getType()); - assert(Size != 0); - - unsigned CurrentEnvOffset = CurrentOffset - EnvOffset; - auto *Loaded = Variables->loadFromEnvOffset(Builder, - Size, - CurrentEnvOffset); - Success = Loaded != nullptr; - if (Success) - TheUser->replaceAllUsesWith(Loaded); - } else { - Value *ToStore = Store->getValueOperand(); - unsigned Size = DL.getTypeAllocSize(ToStore->getType()); - assert(Size != 0); - - unsigned CurrentEnvOffset = CurrentOffset - EnvOffset; - Success = Variables->storeToEnvOffset(Builder, - Size, - CurrentEnvOffset, - ToStore); - } - - if (Success) - Replacements.push_back(std::make_tuple(TheUser, nullptr, nullptr)); - else - Builder.CreateCall(TheModule.getFunction("abort")); - - break; - } - case Instruction::IntToPtr: - case Instruction::BitCast: - { - // A bitcast, just propagate it - WorkList.push(CurrentOffset, TheUser); - break; - } - case Instruction::GetElementPtr: - { - // A GEP requires to update the offset - auto *GEP = cast(TheUser); - unsigned AS = GEP->getPointerAddressSpace(); - APInt APOffset(DL.getPointerSizeInBits(AS), 0, true); - bool Result = GEP->accumulateConstantOffset(DL, APOffset); - - // TODO: do some kind of warning reporting here - // TODO: split the basic block and add an unreachable here - if (!Result) { - CallInst::Create(TheModule.getFunction("abort"), { }, GEP); - continue; - } - - int64_t NewOffset = APOffset.getSExtValue(); - WorkList.push(CurrentOffset + NewOffset, TheUser); - break; - } - case Instruction::Add: - { - unsigned OtherOperandIndex = 1 - TheUse.getOperandNo(); - Value *OtherOperand = TheUser->getOperand(OtherOperandIndex); - - if (!isa(OtherOperand)) { - auto *InvalidInst = cast(TheUser); - CallInst::Create(TheModule.getFunction("abort"), { }, InvalidInst); - continue; - } - - int64_t Addend = cast(OtherOperand)->getSExtValue(); - WorkList.push(CurrentOffset + Addend, TheUser); - break; - } - case Instruction::Call: - { - auto *Call = cast(TheUser); - Function *Callee = Call->getCalledFunction(); - - // Some casting with constant expressions? - if (Callee == nullptr) { - if (auto *Cast = dyn_cast(Call->getCalledValue())) { - assert(Cast->getOpcode() == Instruction::BitCast); - Callee = cast(Cast->getOperand(0)); - } - } - - if (Callee != nullptr - && Callee->getIntrinsicID() == Intrinsic::dbg_declare) - continue; - - // We only support memcpys where the last parameter is constant - if (Callee == nullptr - || (Callee->getIntrinsicID() == Intrinsic::memcpy - && !isa(Call->getArgOperand(2)))) { - auto *InvalidInst = cast(TheUser); - CallInst::Create(TheModule.getFunction("abort"), { }, InvalidInst); - continue; - } - - // We're memcpy'ing to the env - if (Callee->getIntrinsicID() == Intrinsic::memcpy) { - IRBuilder<> Builder(TheModule.getContext()); - Builder.SetInsertPoint(Call); - - unsigned EnvOpIndex = (Call->getArgOperand(0) == CurrentValue ? - 0 : 1); - Value *BaseOp = Call->getArgOperand(1 - EnvOpIndex); - auto *ValueOp = cast(Call->getArgOperand(2)); - Value *BasePtr = Builder.CreatePtrToInt(BaseOp, - Builder.getInt64Ty()); - - uint64_t TotalSize = getZExtValue(ValueOp, DL); - uint64_t Offset = 0; - - while (Offset < TotalSize) { - GlobalVariable *Var = nullptr; - Var = Variables->getByCPUStateOffset(CurrentOffset + Offset); - - // Consider the case when there's simply nothing there (alignment - // space) - if (Var == nullptr) { - Offset++; - continue; - } - - Type *PointeeTy = Var->getType()->getPointerElementType(); - uint64_t Size = DL.getTypeAllocSize(PointeeTy); - - Value *Address = Builder.CreateAdd(Builder.getInt64(Offset), - BasePtr); - Value *Ptr = Builder.CreateIntToPtr(Address, Var->getType()); - - if (EnvOpIndex == 0) - Builder.CreateStore(Builder.CreateLoad(Ptr), Var); - else - Builder.CreateStore(Builder.CreateLoad(Var), Ptr); - - Offset += Size; - } - - if (Offset != TotalSize) { - auto *InvalidInstruction = cast(TheUser); - CallInst::Create(TheModule.getFunction("abort"), - { }, - InvalidInstruction); - continue; - } - - // Set memcpy size to 0 - auto *Zero = ConstantInt::get(Call->getArgOperand(2)->getType(), 0); - Call->setArgOperand(2, Zero); - continue; - } - - assert((Callee->getName().startswith("helper") - || !Callee->empty()) - && "external functions are not supported"); - - if (Callee->empty()) - break; - - // TODO: move all the specialization-handling code outside - // Is the callee already a specialization? - auto Comparison = [&Callee] (Specialization &S) { - return S.F == Callee; - }; - auto CurrentSpecialization = std::find_if(Specializations.begin(), - Specializations.end(), - Comparison); - - Function *Original = Callee; - std::vector> SpecializedArgs; - - // If the callee was already a specialization, preserve its - // specialized arguments - if (CurrentSpecialization != Specializations.end()) { - - // Check if we're good with this specialization - bool SpecializationMatches = false; - for (auto &P : CurrentSpecialization->SpecializedArgs) { - if (P.first == TheUse.getOperandNo()) { - assert(P.second == CurrentOffset); - SpecializationMatches = true; - break; - } - } - - if (SpecializationMatches) - continue; - - Original = CurrentSpecialization->Original; - SpecializedArgs = CurrentSpecialization->SpecializedArgs; - } - - // Add the new argument to specialize - SpecializedArgs.push_back({ TheUse.getOperandNo(), CurrentOffset }); - - // Does the specialization we want already exists? - Specialization *Matching = nullptr; - for (Specialization &S : Specializations) { - if (S.Original == Original - && S.SpecializedArgs.size() == SpecializedArgs.size()) { - - Matching = &S; - for (std::pair A : SpecializedArgs) { - - bool Found = false; - for (std::pair B : S.SpecializedArgs) { - if (A.first == B.first && A.second == B.second) { - Found = true; - break; - } - } - if (!Found) { - Matching = nullptr; - break; - } - } - - if (Matching != nullptr) - break; - - } - } - - if (Matching == nullptr) { - // We need a new specialization - ValueToValueMapTy VTV; - SmallVector Returns; - - // Clone existing function - std::stringstream NewName; - NewName << Callee->getName().str() << "_" << Specializations.size(); - Callee->setLinkage(GlobalValue::InternalLinkage); - Function *NewFunc = Function::Create(Callee->getFunctionType(), - GlobalValue::InternalLinkage, - NewName.str(), - Callee->getParent()); - - unsigned I = 0; - auto CalleeArg = Callee->arg_begin(); - auto NewArg = NewFunc->arg_begin(); - for (CalleeArg = Callee->arg_begin(); - CalleeArg != Callee->arg_end(); - CalleeArg++) { - NewArg->setName(CalleeArg->getName()); - - WorkList.cloneSisters(&*CalleeArg, &*NewArg); - - VTV[&*CalleeArg] = &*NewArg++; - } - - CloneFunctionInto(NewFunc, Callee, VTV, true, Returns); - - Specialization New; - New.F = NewFunc; - New.Original = Original; - New.SpecializedArgs = SpecializedArgs; - Specializations.push_back(New); - Matching = &Specializations.back(); - - // The function is new, we have to explore its argument usage - - // Find the corresponding argument - auto ArgsI = NewFunc->arg_begin(); - - for (I = 0; - I < Call->getNumArgOperands() && ArgsI != NewFunc->arg_end(); - I++, ArgsI++) { - Use& ArgUse = Call->getArgOperandUse(I); - if (ArgUse.getOperandNo() == TheUse.getOperandNo()) - break; - } - assert(I < Call->getNumArgOperands() - && ArgsI != NewFunc->arg_end()); - - Value *TargetArg = static_cast(&*ArgsI); - - if (TargetArg->use_begin() != TargetArg->use_end()) { - assert(!NewFunc->isVarArg()); - - // If not already considered, enqueue the argument to the worklist - WorkList.pushIfNew(CurrentOffset, TargetArg); - } - } - - auto It = OffsetFunctions.find(Matching->F); - if (It != OffsetFunctions.end()) - WorkList.push(It->second, static_cast(Call)); - - auto *OriginalCalleeTy = Call->getCalledValue()->getType(); - Call->setCalledFunction(ConstantExpr::getBitCast(Matching->F, - OriginalCalleeTy)); - - break; - } - case Instruction::Ret: - { - // This function returns a pointer to the state - Function *CurrentFunction = TheUser->getParent()->getParent(); - OffsetFunctions[CurrentFunction] = CurrentOffset; - for (User *FunctionUse : CurrentFunction->users()) { - auto Call = cast(FunctionUse); - assert(Call->getCalledFunction() == CurrentFunction); - WorkList.pushIfNew(CurrentOffset, static_cast(Call)); - } - break; - } - default: - // Unhandled situation, propagate an error value until the next load - WorkList.push(ErrorOffset, TheUser); - } - } - - for (auto Replacement : Replacements) - if (std::get<1>(Replacement) == nullptr) - cast(std::get<0>(Replacement))->eraseFromParent(); - else - std::get<0>(Replacement)->replaceUsesOfWith(std::get<1>(Replacement), - std::get<2>(Replacement)); - } - - return true; -} - -char CorrectCPUStateUsagePass::ID = 0; - -static RegisterPass X("correct-cpustate-usage", - "Correct CPUState Usage Pass", - false, - false); - static std::pair getTypeAtOffset(const DataLayout *TheLayout, Type *VarType, intptr_t Offset) { unsigned Depth = 0; @@ -529,7 +133,7 @@ getTypeAtOffset(const DataLayout *TheLayout, Type *VarType, intptr_t Offset) { << "\n"); if (Offset >= FieldEnd) - return { nullptr, 0 }; + return { nullptr, 0 }; // It's padding Offset -= FieldOffset; } @@ -655,8 +259,6 @@ bool VariableManager::storeToCPUStateOffset(IRBuilder<> &Builder, unsigned Remaining; std::tie(Target, Remaining) = getByCPUStateOffsetInternal(Offset); - assert(Target != nullptr); - if (Target == nullptr) return false; @@ -684,16 +286,19 @@ bool VariableManager::storeToCPUStateOffset(IRBuilder<> &Builder, auto *FieldTy = cast(Target->getType()->getPointerElementType()); unsigned FieldSize = FieldTy->getBitWidth() / 8; - // Truncate value to store - auto *Truncated = Builder.CreateTrunc(ToStore, InputStoreTy); - // Are we trying to store more than it fits? if (StoreSize > FieldSize) { - // It's OK as long as after what we're storing there's a hole - assert(getByCPUStateOffsetInternal(Offset + FieldSize).first == nullptr); - Truncated = Builder.CreateTrunc(Truncated, FieldTy); + // 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); @@ -736,9 +341,9 @@ Value *VariableManager::loadFromCPUStateOffset(IRBuilder<> &Builder, // Extract the desired part // Shift right of the desired amount unsigned ShiftAmount = 0; - if (TargetArchitecture.isLittleEndian()) + if (TargetArchitecture.isLittleEndian()) { ShiftAmount = Remaining; - else { + } else { // >> (Size1 - Size2) - Remaining; auto *LoadedTy = cast(LoadEnvField->getType()); unsigned GlobalSize = LoadedTy->getBitWidth() / 8; @@ -757,8 +362,10 @@ Value *VariableManager::loadFromCPUStateOffset(IRBuilder<> &Builder, if (auto FieldTy = dyn_cast(Result->getType())) { unsigned FieldSize = FieldTy->getBitWidth() / 8; if (FieldSize < LoadSize) { - // It's OK as long as after what we can't load there's a hole - assert(getByCPUStateOffsetInternal(Offset + FieldSize).first == nullptr); + // 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); } } @@ -767,6 +374,59 @@ Value *VariableManager::loadFromCPUStateOffset(IRBuilder<> &Builder, 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(CallMemcpy->getArgOperand(2)))); + + Value *OtherOp = CallMemcpy->getArgOperand(EnvIsSrc ? 0 : 1); + auto *MemcpySize = cast(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(OtherBasePtr)->eraseFromParent(); + + return Offset == TotalSize; +} + // TODO: `newFunction` reflects the tcg terminology but in this context is // highly misleading void VariableManager::newFunction(Instruction *Delimiter, @@ -837,9 +497,6 @@ GlobalVariable* VariableManager::getByCPUStateOffset(intptr_t Offset, std::pair VariableManager::getByCPUStateOffsetInternal(intptr_t Offset, std::string Name) { - if (Offset == ErrorOffset) - return { nullptr, 0 }; - GlobalsMap::iterator it = CPUStateGlobals.find(Offset); if (it == CPUStateGlobals.end() || (Name.size() != 0 && !it->second->getName().equals_lower(Name))) { @@ -849,6 +506,10 @@ VariableManager::getByCPUStateOffsetInternal(intptr_t Offset, 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); @@ -856,10 +517,6 @@ VariableManager::getByCPUStateOffsetInternal(intptr_t Offset, return { it->second, Remaining }; } - // Unsupported type, let the caller handle the situation - if (VariableType == nullptr) - return { nullptr, 0 }; - if (Name.size() == 0) { std::stringstream NameStream; NameStream << "state_0x" << std::hex << Offset; diff --git a/variablemanager.h b/variablemanager.h index 71b37615b..85f2f2d3c 100644 --- a/variablemanager.h +++ b/variablemanager.h @@ -16,6 +16,7 @@ // Local includes #include "ptcdump.h" +#include "cpustateaccessanalysis.h" #include "revamb.h" namespace llvm { @@ -29,35 +30,7 @@ class Value; } class VariableManager; - -/// \brief LLVM pass to change all the access to the CPU state to the -/// corresponding global variables. -/// -/// If it's not possible to determine statically which part of the CPU is being -/// accessed an abort is emitted. -/// This pass also performs heavy specialization in case an helper is invoked -/// with different constant parameters. -class CorrectCPUStateUsagePass : public llvm::ModulePass { -public: - static char ID; - - CorrectCPUStateUsagePass() : - llvm::ModulePass(ID), - Variables(nullptr), - EnvOffset(0) { } - - CorrectCPUStateUsagePass(VariableManager *Variables, unsigned EnvOffset) : - llvm::ModulePass(ID), - Variables(Variables), - EnvOffset(EnvOffset) { } - -public: - bool runOnModule(llvm::Module& TheModule) override; - -private: - VariableManager *Variables; - unsigned EnvOffset; -}; +class CPUStateAccessAnalysisPass; /// \brief Maintain the list of variables required by PTC /// @@ -69,8 +42,6 @@ public: llvm::Module& HelpersModule, Architecture &TargetArchitecture); - friend class CorrectCPUStateUsagePass; - /// \brief Get or create the LLVM value associated to a PTC temporary /// /// Given a PTC temporary identifier, checks if it already exists in the @@ -128,8 +99,8 @@ public: /// Returns true if the given variable is the env variable bool isEnv(llvm::Value *TheValue); - CorrectCPUStateUsagePass *createCorrectCPUStateUsagePass() { - return new CorrectCPUStateUsagePass(this, EnvOffset); + CPUStateAccessAnalysisPass *createCPUStateAccessAnalysisPass() { + return new CPUStateAccessAnalysisPass(this); } llvm::Value *computeEnvAddress(llvm::Type *TargetType, @@ -167,6 +138,11 @@ public: return storeToCPUStateOffset(Builder, StoreSize, ActualOffset, ToStore); } + bool memcpyAtEnvOffset(llvm::IRBuilder<> &Builder, + llvm::CallInst *CallMemcpy, + unsigned Offset, + bool EnvIsSrc); + /// \brief Perform finalization steps on variables /// /// \param ExternalCSVs true if CSVs linkage should not be turned into static. @@ -179,6 +155,11 @@ public: } } + /// \brief Gets the CPUStateType + llvm::StructType *getCPUStateType() const { + return CPUStateType; + } + private: llvm::Value *loadFromCPUStateOffset(llvm::IRBuilder<> &Builder, unsigned LoadSize,