/// \file FixHelpers.cpp // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/PointerUnion.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Function.h" #include "llvm/IR/GlobalValue.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/IR/Verifier.h" #include "llvm/Support/NativeFormatting.h" #include "revng/HelperArgumentsAnalysis/Annotation.h" #include "revng/Lift/LibTcg.h" #include "revng/Lift/VariableManager.h" #include "revng/Support/CommandLine.h" #include "revng/Support/Debug.h" #include "revng/Support/IRBuilder.h" #include "revng/Support/IRHelpers.h" #include "revng/Model/Generated/Early/Architecture.h" using namespace llvm; using std::string; static Logger Log("fix-helpers"); static cl::opt ArchitectureName("fix-helpers-architecture", cl::desc("architecture of the helper " "of the input file for " "fix-helpers."), cl::cat(MainCategory)); static void setMetadata(VariableManager &Variables, Function &F, StringRef MetadataName, const aua::Annotation::OffsetAndSizeSet &Offsets) { revng_log(Log, "Parsing " << MetadataName << ":"); LoggerIndent Indent(Log); QuickMetadata QMD(getContext(&F)); SmallVector CSVList; for (auto [Offset, Size] : Offsets) { int32_t RemainingSize = Size; while (RemainingSize != 0) { auto [CSV, OffsetInField] = Variables.getByCPUStateOffsetWithRemainder(Offset); if (CSV == nullptr) { revng_log(Log, "Couldn't find CSV for offset " << Offset); break; } revng_log(Log, CSV->getName()); revng_assert(CSV->getParent() == F.getParent()); auto CSVSize = CSV->getValueType()->getIntegerBitWidth() / 8; CSVList.push_back(QMD.get(CSV->getName())); // If necessary, move on to the next field int64_t AvailableSize = CSVSize - OffsetInField; RemainingSize = std::max(0L, RemainingSize - AvailableSize); Offset = 0; } } if (Offsets.size() > 0) { revng_log(Log, "Identified " << CSVList.size() << " CSVs out of " << Offsets.size() << " offsets"); } MDNode *MDAnnotation = QMD.tuple({ QMD.get(0), QMD.tuple(CSVList) }); F.setMetadata(MetadataName, MDAnnotation); } static void convertToCSVAnnotation(VariableManager &Variables, Function &F, const aua::Annotation &Annotation) { revng_log(Log, "Converting CSV to annotations"); LoggerIndent Indent(Log); setMetadata(Variables, F, "revng.csvaccess.offsets.load", Annotation.Reads); setMetadata(Variables, F, "revng.csvaccess.offsets.store", Annotation.Writes); } class AccessFixer { public: enum AccessType { Read, Write }; private: VariableManager &Variables; Type &IntPtrType; revng::NonDebugInfoCheckingIRBuilder Builder; Function &Abort; public: AccessFixer(Module &M, VariableManager &Variables, Type &IntPtrType) : Variables(Variables), IntPtrType(IntPtrType), Builder(IntPtrType.getContext()), Abort(notNull(M.getFunction("abort"))) {} public: void fixMemoryAccess(Instruction &I, const aua::Annotation &Annotation); CallInst *emitAbort(Function &F) { Builder.SetInsertPointPastAllocas(&F); auto *AbortCall = Builder.CreateCall(&Abort); AbortCall->setMetadata("dbg", nullptr); return AbortCall; } private: template void handle(Instruction &I, const aua::Annotation::OffsetAndSizeSet &Targets); template Value *emit(Instruction &I, uint64_t Offset, uint64_t Size); CallInst *emitAbort(revng::IRBuilder &Builder) { return Builder.CreateCall(&Abort); } CallInst *emitAbort(Instruction &Before) { Builder.SetInsertPoint(&Before); return emitAbort(Builder); } std::pair decomposeMemcpy(llvm::Instruction &I); template std::pair getOffsetAndSize(Instruction &I); }; template<> Value *AccessFixer::emit(Instruction &I, uint64_t Offset, uint64_t Size) { revng_log(Log, "Handling a write to " << Offset << " of size " << Size); if (auto *Store = dyn_cast(&I)) { Value *ToStore = Store->getValueOperand(); auto MaybeStore = Variables.storeToCPUStateOffset(Builder, Size, Offset, ToStore); if (MaybeStore) { return *MaybeStore; } else { // storeToCPUStateOffset can fail in case of padding and the like revng_log(Log, "Nothing emitted"); return nullptr; } } else if (auto *Call = dyn_cast(&I)) { Variables.memOpAtCPUStateOffset(Builder, Call, Offset, false); return nullptr; } else { revng_abort(); } } template<> Value *AccessFixer::emit(Instruction &I, uint64_t Offset, uint64_t Size) { revng_log(Log, "Handling a read from " << Offset << " of size " << Size); if (auto *Load = dyn_cast(&I)) { if (auto *Result = Variables.loadFromCPUStateOffset(Builder, Size, Offset)) { return Result; } else { // loadFromCPUStateOffset can fail in case of padding and the like revng_log(Log, "Nothing emitted"); return llvm::UndefValue::get(I.getType()); } } else if (auto *Call = dyn_cast(&I)) { Variables.memOpAtCPUStateOffset(Builder, Call, Offset, true); return nullptr; } else { revng_abort(); } } template<> std::pair AccessFixer::getOffsetAndSize(Instruction &I) { uint64_t Size = 0; Value *Address = nullptr; if (auto *Store = dyn_cast(&I)) { Address = Store->getPointerOperand(); Size = Store->getValueOperand()->getType()->getIntegerBitWidth() / 8; } else if (auto *Call = dyn_cast(&I)) { Address = Call->getArgOperand(0); Size = cast(Call->getOperand(2))->getLimitedValue(); } else { revng_abort(); } return { Builder.CreatePtrToInt(Address, &IntPtrType), Size }; } template<> std::pair AccessFixer::getOffsetAndSize(Instruction &I) { uint64_t Size = 0; Value *Address = nullptr; if (auto *Load = dyn_cast(&I)) { Address = Load->getPointerOperand(); Size = Load->getType()->getIntegerBitWidth() / 8; } else if (auto *Call = dyn_cast(&I)) { Address = Call->getArgOperand(1); Size = cast(Call->getOperand(2))->getLimitedValue(); } else { revng_abort(); } return { Builder.CreatePtrToInt(Address, &IntPtrType), Size }; } template void AccessFixer::handle(Instruction &I, const aua::Annotation::OffsetAndSizeSet &Targets) { LLVMContext &Context = getContext(&I); Function *F = I.getFunction(); Builder.SetInsertPoint(&I); auto [Address, Size] = getOffsetAndSize(I); auto *AddressType = cast(Address->getType()); revng_log(Log, "Handling " << getName(&I) << ", which has " << Targets.size() << " targets"); LoggerIndent Indent(Log); // Handle the single-offset situation if (Targets.size() == 1) { Value *Result = emit(I, Targets.begin()->first, Size); if (Result != nullptr) I.replaceAllUsesWith(Result); return; } // There are multiple offsets. We need to emit a switch. // Split the block BasicBlock *Before = I.getParent(); BasicBlock *After = Before->splitBasicBlock(I.getIterator()); Before->getTerminator()->eraseFromParent(); // Create the default case BasicBlock *DefaultCase = BasicBlock::Create(Context, "", F, After); Builder.SetInsertPoint(DefaultCase); emitAbort(*Builder.CreateUnreachable()); // Create the switch Builder.SetInsertPoint(Before); auto *Switch = Builder.CreateSwitch(Address, DefaultCase, Targets.size()); PHINode *Phi = nullptr; // If the instruction provides a result, we also need to emit a phi bool HasResult = not I.getType()->isVoidTy(); if (HasResult) Phi = PHINode::Create(I.getType(), Targets.size(), "", &*After->begin()); // For each target, create a basic block, add a case to the switch and an // incoming to the phi for (auto &&[Offset, Size] : Targets) { BasicBlock *OffsetCase = BasicBlock::Create(Context, "", F, DefaultCase); Builder.SetInsertPoint(OffsetCase); Value *Result = emit(I, Offset, Size); Builder.CreateBr(After); revng_assert(not OffsetCase->empty()); Switch->addCase(ConstantInt::get(AddressType, Offset), OffsetCase); if (HasResult) Phi->addIncoming(Result, OffsetCase); } if (HasResult) I.replaceAllUsesWith(Phi); } static void setArgumentAlignment(CallBase &Call, unsigned ArgIdx, unsigned Align) { LLVMContext &Context = Call.getContext(); AttributeList Attrs = Call.getAttributes(); auto AlignAttribute = Attribute::get(Context, Attribute::Alignment, Align); AttributeList NewAttrs = Attrs.addParamAttribute(Context, ArgIdx, AlignAttribute); Call.setAttributes(NewAttrs); } std::pair AccessFixer::decomposeMemcpy(llvm::Instruction &I) { auto &Call = cast(I); revng_assert(Call.getIntrinsicID() == Intrinsic::memcpy); auto *Size = cast(Call.getArgOperand(2)); BasicBlock &Entry = I.getFunction()->getEntryBlock(); Builder.SetInsertPoint(&Entry, Entry.begin()); auto *UInt8Type = IntegerType::getInt8Ty(Abort.getContext()); auto *Storage = Builder.CreateAlloca(UInt8Type, 0, Size); uint64_t Alignment = Storage->getAlign().value(); auto &Read = Call; auto &Write = *cast(Call.clone()); Write.insertAfter(&Read); Read.setArgOperand(0, Storage); setArgumentAlignment(Read, 0, Alignment); Write.setArgOperand(1, Storage); setArgumentAlignment(Write, 1, Alignment); return { Read, Write }; } void AccessFixer::fixMemoryAccess(Instruction &I, const aua::Annotation &Annotation) { revng_log(Log, "Handling memory access " << getName(&I)); LoggerIndent Indent(Log); revng_assert(not I.isTerminator()); auto *Load = dyn_cast(&I); auto *Store = dyn_cast(&I); if (Annotation.Escapes) { revng_log(Log, "It escapes, emitting abort."); emitAbort(I); } else if (Load != nullptr and Load->getType()->isPointerTy()) { revng_log(Log, "Loading a pointer, emitting abort."); emitAbort(I); } else if (Store != nullptr and Store->getValueOperand()->getType()->isPointerTy()) { revng_log(Log, "Storing a pointer, emitting abort."); emitAbort(I); } else { revng_log(Log, "It's a memcpy-like."); bool IsRead = Annotation.Reads.size() != 0; bool IsWrite = Annotation.Writes.size() != 0; if (IsRead and IsWrite) { revng_log(Log, "Decomposing memcpy."); const auto &[Read, Write] = decomposeMemcpy(I); handle(Read, Annotation.Reads); handle(Write, Annotation.Writes); // Read is I and will be erased later Write.eraseFromParent(); } else if (IsWrite) { handle(I, Annotation.Writes); } else if (IsRead) { handle(I, Annotation.Reads); } else { revng_abort(); } I.eraseFromParent(); } } static void fixHelpers(VariableManager &Variables, Module &Module) { LLVMContext &Context = Module.getContext(); auto &DL = Module.getDataLayout(); AccessFixer Fixer(Module, Variables, *DL.getIntPtrType(Context)); for (Function &F : Module) { revng_log(Log, "Handling " << F.getName()); LoggerIndent Indent(Log); auto Annotation = aua::Annotation::deserialize(F) .value_or(aua::Annotation()); // Upgrade { Offset, Size } annotations to reference the CSV for // consumptions of other passes convertToCSVAnnotation(Variables, F, Annotation); if (Annotation.Escapes) { revng_log(Log, "CPU state escapes, emitting an abort"); Fixer.emitAbort(F); } // Go through all instructions and replace memory accesses in env with // accesses to CSVs SmallVector Annotated; for (Instruction &I : instructions(F)) if (aua::Annotation::isAnnotated(I)) Annotated.push_back(&I); for (Instruction *I : Annotated) { Fixer.fixMemoryAccess(*I, *aua::Annotation::deserialize(*I)); } } revng::verify(&Module); } class FixHelpers : public llvm::ModulePass { public: static char ID; public: FixHelpers() : llvm::ModulePass(ID) {} void getAnalysisUsage(llvm::AnalysisUsage &AU) const override {} bool runOnModule(llvm::Module &M) override { auto Architecture = model::Architecture::fromQEMUName(ArchitectureName); revng_assert(Architecture != model::Architecture::Invalid); auto TheLibTcg = LibTcg::get(Architecture); // TODO: fix TargetIsLittleEndian once we support non-x86-64 targets VariableManager Variables(M, /* TargetIsLittleEndian */ true, TheLibTcg.archInfo().env_offset, TheLibTcg.envPointer(), TheLibTcg.globalNames()); fixHelpers(Variables, M); return true; } }; char FixHelpers::ID = 0; using Register = RegisterPass; static Register X("fix-helpers", "Fix helper functions", true, true);