/// \file ArgumentUsageAnalysis.cpp // // This file is distributed under the MIT License. See LICENSE.md for details. // #include "llvm/IR/InstIterator.h" #include "llvm/IR/Intrinsics.h" #include "ArgumentUsageAnalysis.h" static bool isVarArg(const llvm::Function &F) { if (not F.isVarArg()) return false; // Check if the function uses llvm.va_start for (const llvm::Instruction &I : llvm::instructions(F)) if (auto *Call = dyn_cast(&I)) if (Call->getIntrinsicID() == llvm::Intrinsic::vastart) return true; return false; } namespace aua { void ArgumentUsageAnalysis::run() { revng_log(Log, "Running ArgumentUsageAnalysis"); LoggerIndent Indent(Log); // Analyze functions in post order llvm::CallGraph CG(M); for (llvm::CallGraphNode *Node : llvm::post_order(&CG)) { llvm::Function *F = Node->getFunction(); if (F != nullptr and not F->isDeclaration() and not isVarArg(*F)) analyzeFunction(*F); } } void ArgumentUsageAnalysis::analyzeFunction(llvm::Function &F) { revng_log(Log, "Analyzing " << F.getName()); LoggerIndent Indent(Log); auto [It, New] = Results.insert({ &F, Function(TheContext) }); revng_assert(New); Function &FunctionResults = It->second; // Initialize arguments for (auto &&[Index, Argument] : llvm::enumerate(F.args())) FunctionResults.set(Argument, TheContext.getArgument(Index)); // Initialize the analysis with a conservative approach by associating each // instruction to FunctionOf the arguments that might affect it. taintAnalysis(FunctionResults, F); // At this point the results are correct and we could stop. // Do a single sweep of results refinement with more sophisticated // expressions { revng_log(Log, "Running analyzeInstruction"); LoggerIndent Indent(Log); // We do one sweep in reverse-post order. This means that, before we visit // an instruction all of its operands will have been visited already. The // only exception are "recursive" phis. For those, we accept to employ // correct but suboptimal results compute by tainAnalysis. llvm::ReversePostOrderTraversal RPOT(&F); for (const llvm::BasicBlock *BB : RPOT) for (const llvm::Instruction &I : *BB) if (not I.isDebugOrPseudoInst()) if (const Value *V = analyzeInstruction(FunctionResults, I)) FunctionResults.set(I, *V); } { // Do a final pass to register memory accesses (load, store, memcpy), // function calls and escaped arguments revng_log(Log, "Running registerFunctionResults"); LoggerIndent Indent(Log); for (llvm::BasicBlock &BB : F) for (llvm::Instruction &I : BB) if (not I.isDebugOrPseudoInst()) registerFunctionResults(FunctionResults, I); // Handle functions returning void or not returning at all if (FunctionResults.returnValue() == nullptr) FunctionResults.registerReturnValue(TheContext, TheContext.getUnknown()); } } void ArgumentUsageAnalysis::taintAnalysis(Function &FunctionResults, const llvm::Function &F) { revng_log(Log, "Running taint analysis"); LoggerIndent Indent(Log); revng_assert(not isVarArg(F)); revng_assert(F.arg_size() <= 64); // Create a map associating to each instruction a set of bits representing // the set of arguments that affect it llvm::DenseMap Taint; for (const auto &[Index, Argument] : llvm::enumerate(F.args())) { // Define the queue and helper lambda to enqueue more stuff SmallVector Queue; auto EnqueueUses = [&Queue](const llvm::Value &V) { for (const llvm::Use &U : V.uses()) { if (auto *I = dyn_cast(U.getUser())) { if (not I->isDebugOrPseudoInst() and not isa(I) and not I->getType()->isVoidTy()) { Queue.push_back(&U); } } } }; // Initialize the queue with the uses of arguments EnqueueUses(Argument); // Taint users of elements in the queue, and keep iterating until fixed // point. Note that we only re-enqueue only if the set of // arguments has grown. Given the number of arguments is finite, this will // converge. while (not Queue.empty()) { auto *U = Queue.pop_back_val(); auto *I = cast(U->getUser()); // If necessary, create an entry for I in the taint map auto It = Taint.find(I); if (It == Taint.end()) It = Taint.insert({ I, 0 }).first; // In case of a call, if we already analyzed the callee, propagate from // actual argument to return value only if, according to the current // results, the return value is affected by the corresponding actual // argument. if (auto *Call = dyn_cast(I)) { if (const Value *Result = getCallResult(*Call)) { if (Call->isArgOperand(U)) { unsigned ActualArgumentIndex = Call->getArgOperandNo(U); if (not Result->collectArguments().contains(ActualArgumentIndex)) { continue; } } } } uint64_t &MapEntry = It->second; uint64_t OldValue = MapEntry; MapEntry |= 1 << Index; // Re-enqueue only if the entry has changed if (OldValue != MapEntry) EnqueueUses(*I); } } // The analysis has terminated: we now know what arguments affect each // instruction (modulo escaped arguments, but those are handled later) // Initialize all the tainted instructions with FunctionOf(Arg0, Arg3, ...) for (const llvm::Instruction &I : llvm::instructions(&F)) { if (I.getType()->isVoidTy()) continue; SmallVector Values; auto It = Taint.find(&I); if (It != Taint.end()) { uint64_t ArgumentSet = It->second; while (ArgumentSet) { unsigned Index = llvm::findFirstSet(ArgumentSet); Values.push_back(&TheContext.getArgument(Index)); ArgumentSet &= (ArgumentSet - 1); } } FunctionResults.set(I, TheContext.getFunctionOf(std::move(Values))); } } const Value * ArgumentUsageAnalysis::analyzeInstruction(Function &FunctionResults, const llvm::Instruction &I) { if (I.getType()->isVoidTy()) return nullptr; auto &C = TheContext; auto Get = [&FunctionResults](const llvm::Value &V) -> const Value & { return FunctionResults.get(V); }; switch (I.getOpcode()) { case llvm::Instruction::Load: return nullptr; case llvm::Instruction::Add: return &C.getAdd(Get(*I.getOperand(0)), Get(*I.getOperand(1))); case llvm::Instruction::Sub: return &C.getSubtract(Get(*I.getOperand(0)), Get(*I.getOperand(1))); case llvm::Instruction::Mul: return &C.getMultiply(Get(*I.getOperand(0)), Get(*I.getOperand(1))); case llvm::Instruction::GetElementPtr: { const auto *GEP = cast(&I); const llvm::Value *Base = GEP->getPointerOperand(); llvm::Type *BaseType = GEP->getSourceElementType(); const Value &BaseValue = Get(*Base); SmallVector Indices; llvm::Type *NextType = BaseType; // Handle zero-th index auto ElementSize = DL.getTypeAllocSize(GEP->getSourceElementType()); llvm::Value &FirstIndex = **GEP->idx_begin(); const Value &IndexValue = Get(FirstIndex); const auto *Result = &C.getAdd(BaseValue, C.getMultiply(IndexValue, C.getConstant(ElementSize))); Indices.push_back(&FirstIndex); NextType = llvm::GetElementPtrInst::getIndexedType(BaseType, Indices); // Iterate over other indices for (const llvm::Use &IndexUse : skip(GEP->indices(), 1)) { llvm::Value *Index = IndexUse.get(); const Value &IndexValue = Get(*Index); if (auto *ArrayType = dyn_cast(NextType)) { auto ElementSize = DL.getTypeAllocSize(NextType->getArrayElementType()); auto &C = TheContext; Result = &C.getAdd(*Result, C.getMultiply(IndexValue, C.getConstant(ElementSize))); } else if (auto *StructType = dyn_cast(NextType)) { auto FieldIndex = getLimitedValue(Index); const llvm::StructLayout *Layout = DL.getStructLayout(StructType); auto Offset = Layout->getElementOffset(FieldIndex); Result = &C.getAdd(*Result, C.getConstant(Offset)); } else { revng_abort(); } Indices.push_back(Index); NextType = llvm::GetElementPtrInst::getIndexedType(BaseType, Indices); } return Result; } case llvm::Instruction::Call: return handleCall(FunctionResults, *cast(&I)); case llvm::Instruction::PHI: { // Handle phis by emitting an AnyOf with one entry per incoming of the phi llvm::SmallVector Alternatives; for (const llvm::Value *Incoming : I.operands()) Alternatives.push_back(&Get(*Incoming)); return &C.getAnyOf(std::move(Alternatives)); } case llvm::Instruction::IntToPtr: case llvm::Instruction::PtrToInt: case llvm::Instruction::BitCast: case llvm::Instruction::ZExt: case llvm::Instruction::SExt: case llvm::Instruction::Trunc: // TODO: should we distinguish zero- vs sign-extending? return &Get(*I.getOperand(0)); default: { // Everything else is just FunctionOf the arguments used in its operands llvm::SmallVector ArgumentsInOperand; for (const llvm::Use &Operand : I.operands()) { for (const ArgumentValue *Argument : Get(*Operand.get()).collect()) { ArgumentsInOperand.push_back(Argument); } } return &C.getFunctionOf(std::move(ArgumentsInOperand)); } } } void ArgumentUsageAnalysis::registerFunctionResults(Function &FunctionResults, llvm::Instruction &I) { auto Get = [&FunctionResults](const llvm::Value &V) -> const Value & { return FunctionResults.get(V); }; switch (I.getOpcode()) { case llvm::Instruction::Load: registerLoad(FunctionResults, *cast(&I)); break; case llvm::Instruction::Store: registerStore(FunctionResults, *cast(&I)); break; case llvm::Instruction::Ret: if (cast(&I)->getNumOperands() != 0) FunctionResults.registerReturnValue(TheContext, Get(*I.getOperand(0))); break; case llvm::Instruction::Call: { auto *Call = cast(&I); if ((Call->getIntrinsicID() == llvm::Intrinsic::memcpy or Call->getIntrinsicID() == llvm::Intrinsic::memmove) and isa(Call->getArgOperand(2))) { registerMemcpy(FunctionResults, *Call); } else if ((Call->getIntrinsicID() == llvm::Intrinsic::memset) and isa(Call->getArgOperand(2))) { registerWrite(FunctionResults, Call->getArgOperandUse(0)); } else { registerCall(FunctionResults, *Call); } } break; default: break; } } void ArgumentUsageAnalysis::registerCall(Function &FunctionResults, llvm::CallInst &Call) { auto CallSite = analyzeCallSite(Call); if (Log.isEnabled()) { Log << "registerCall: "; dumpCall(Log, &Call); Log << DoLog; } LoggerIndent Indent(Log); if (Log.isEnabled()) { CallSite.dump(Log); Log << DoLog; } if (CallSite.IsNoReturn or (CallSite.IsDeclared and CallSite.IsPure)) { // We do not consider noreturn calls and pure functions as escaping revng_log(Log, "The callee is norerutrn or pure. Ignoring call."); } else if (CallSite.IsIndirect or CallSite.IsDeclared or not CallSite.HasBeenAnalyzed or CallSite.IsVarArg) { revng_log(Log, "Can't handle this call."); for (const llvm::Value *Argument : Call.args()) { const Value &Value = FunctionResults.get(*Argument); FunctionResults.logEscapedValue("call " + getName(&Call), Value); FunctionResults.registerEscapedValue(Call, Value); } } else { revng_log(Log, "Registering."); // Register the call const Function &CalleeResults = Results.at(CallSite.Callee); aua::Call NewCall(Call, CalleeResults); for (auto &&[Index, Argument] : llvm::enumerate(Call.args())) { const Value &ArgumentValue = FunctionResults.get(*Argument.get()); NewCall.registerActualArgument(Index, ArgumentValue); } FunctionResults.registerCall(std::move(NewCall)); } } ArgumentUsageAnalysis::CallSite ArgumentUsageAnalysis::analyzeCallSite(const llvm::CallInst &Call) { CallSite Result; Result.Callee = getCalledFunction(&Call); Result.IsIndirect = Result.Callee == nullptr; Result.IsDeclared = not Result.IsIndirect and Result.Callee->isDeclaration(); Result.IsVarArg = not Result.IsIndirect and isVarArg(*Result.Callee); Result.HasBeenAnalyzed = Results.contains(Result.Callee); Result.IsNoReturn = Call.doesNotReturn() or (not Result.IsIndirect and Result.Callee->doesNotReturn()); Result.IsPure = Call.doesNotAccessMemory(); return Result; } const Value *ArgumentUsageAnalysis::getCallResult(const llvm::CallInst &Call) { auto CallSite = analyzeCallSite(Call); if (CallSite.IsIndirect or CallSite.IsDeclared or not CallSite.HasBeenAnalyzed or CallSite.IsVarArg) { if (Log.isEnabled()) { Log << "The following call cannot be handled "; dumpCall(Log, &Call); Log << "\n"; CallSite.dump(Log); Log << DoLog; } // We have a call we cannot handle, return nullptr so we preserve the // result of the taint analysis, which is conservative since it assumes // the result is a FunctionOf all the arguments passed in. return nullptr; } return Results.at(CallSite.Callee).returnValue(); } const Value *ArgumentUsageAnalysis::handleCall(Function &FunctionResults, const llvm::CallInst &Call) { // Compute result of the call if (const Value *Result = getCallResult(Call)) { llvm::DenseMap ActualArguments; for (auto &&[ArgumentIndex, Argument] : llvm::enumerate(Call.args())) ActualArguments[ArgumentIndex] = &FunctionResults.get(*Argument); return TheContext.replaceArguments(*Result, std::move(ActualArguments)); } // This means we haven't analyzed the function yet, leave the // conservative value left by taintAnalysis return nullptr; } } // namespace aua