// // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include "llvm/ADT/StringExtras.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/Verifier.h" #include "llvm/Pass.h" #include "llvm/Support/Casting.h" #include "llvm/Support/FormatVariadic.h" #include "revng/ABI/ModelHelpers.h" #include "revng/Lift/LoadBinaryPass.h" #include "revng/Model/Architecture.h" #include "revng/Model/Binary.h" #include "revng/Model/FunctionTags.h" #include "revng/Model/LoadModelPass.h" #include "revng/Model/NameBuilder.h" #include "revng/Model/RawBinaryView.h" #include "revng/Pipeline/Context.h" #include "revng/Pipeline/Contract.h" #include "revng/Pipeline/Kind.h" #include "revng/Pipeline/LLVMContainer.h" #include "revng/Pipeline/RegisterPipe.h" #include "revng/Pipes/FileContainer.h" #include "revng/Pipes/FunctionPass.h" #include "revng/Pipes/Kinds.h" #include "revng/RemoveLiftingArtifacts/MakeSegmentRef.h" #include "revng/Support/Debug.h" #include "revng/Support/IRHelpers.h" #include "revng/Support/MetaAddress.h" #include "revng/Support/OpaqueFunctionsPool.h" using namespace llvm; // Generating function pointers so far relied on the fact that all the // isolated functions were in the same LLVM module. Since in the new pipeline // this is no longer the case (there's one isolated function per LLVM module) // the generating of function pointers needs to be reworked. As a temporary fix // the new pipeline will emit pointers to segments. To allow comparison between // the old and new pipeline, this option allows the old pipeline to also have // the same behaviour. static cl::opt DisableFunctionPointersOpt("disable-function-pointers", cl::desc("Disable emitting " "pointers to " "functions through " "segments"), cl::init(false)); struct MakeSegmentRefPassImpl : public pipeline::FunctionPassImpl { private: const model::Binary &Binary; llvm::Module &M; llvm::LLVMContext &Context; OpaqueFunctionsPool SegmentRefPool; OpaqueFunctionsPool AddressOfPool; OpaqueFunctionsPool StringLiteralPool; RawBinaryView &BinaryView; bool DisableFunctionPointers = false; public: MakeSegmentRefPassImpl(llvm::ModulePass &Pass, const model::Binary &Binary, llvm::Module &M) : pipeline::FunctionPassImpl(Pass), Binary(Binary), M(M), Context(M.getContext()), SegmentRefPool(FunctionTags::SegmentRef.getPool(M)), AddressOfPool(FunctionTags::AddressOf.getPool(M)), StringLiteralPool(FunctionTags::StringLiteral.getPool(M)), BinaryView(getAnalysis().get()), DisableFunctionPointers(DisableFunctionPointersOpt) {} MakeSegmentRefPassImpl(const model::Binary &Binary, llvm::Module &M, RawBinaryView &BinaryView) : pipeline::FunctionPassImpl(), Binary(Binary), M(M), Context(M.getContext()), SegmentRefPool(FunctionTags::SegmentRef.getPool(M)), AddressOfPool(FunctionTags::AddressOf.getPool(M)), StringLiteralPool(FunctionTags::StringLiteral.getPool(M)), BinaryView(BinaryView), // TODO: once functions pointers are derived post-lift this can be removed DisableFunctionPointers(true) {} bool runOnFunction(const model::Function &ModelFunction, llvm::Function &Function) override; public: static void getAnalysisUsage(llvm::AnalysisUsage &AU); }; void MakeSegmentRefPassImpl::getAnalysisUsage(llvm::AnalysisUsage &AU) { AU.addRequired(); AU.addRequired(); } static std::optional> findSegmentContainingLiteral(const model::Binary &Binary, uint64_t Literal) { std::optional> Result = std::nullopt; auto Architecture = Binary.Architecture(); for (const auto &Segment : Binary.Segments()) { if (Segment.contains(MetaAddress::fromGeneric(Architecture, Literal))) { revng_assert(not Result.has_value()); Result = { { Segment.StartAddress(), Segment.VirtualSize() } }; } } return Result; } static std::optional getStringLiteral(RawBinaryView &BinaryView, MetaAddress SegmentAddress, uint64_t SegmentVirtualSize, uint64_t StringOffsetInSegment) { // If the segment is not read only it's not a string literal const bool IsReadOnly = BinaryView.isReadOnly(SegmentAddress, SegmentVirtualSize); if (not IsReadOnly) return std::nullopt; MetaAddress StringAddress = SegmentAddress + StringOffsetInSegment; uint64_t SegmentSizeFromString = SegmentVirtualSize - StringOffsetInSegment; // If it's not there, it's not a string literal auto DataOrNone = BinaryView.getStringByAddress(StringAddress, SegmentSizeFromString); if (not DataOrNone.has_value()) return std::nullopt; llvm::StringRef StringView = DataOrNone.value(); // If it doesn't end with \0 it's not a string literal auto NullTerminatorPos = StringView.find('\0'); if (NullTerminatorPos == llvm::StringLiteral::npos) return std::nullopt; StringView = StringView.take_front(NullTerminatorPos); // If some of the characters are not printable, is not a string literal const auto IsPrintableInCString = [](const char C) { return not llvm::isPrint(C) and not llvm::isSpace(C); }; if (llvm::any_of(StringView, IsPrintableInCString)) return std::nullopt; return StringView; } static bool isBitcast(const ConstantExpr *E) { auto OpCode = E->getOpcode(); return OpCode == Instruction::BitCast or OpCode == Instruction::IntToPtr or OpCode == Instruction::PtrToInt; } static Value *skipConstexprBitcasts(Value *V) { while (isa(V) and isBitcast(cast(V))) V = cast(V)->getOperand(0); return V; } bool MakeSegmentRefPassImpl::runOnFunction(const model::Function &ModelFunction, llvm::Function &F) { model::CNameBuilder NameBuilder(Binary); using llvm::DominatorTree; DominatorTree DT; DT.recalculate(F); std::map FunctionEntries; for (const model::Function &Function : Binary.Functions()) FunctionEntries[Function.Entry().toGeneric()] = Function.Entry(); // TODO: the checks should be enabled conditionally based on the user. revng::NonDebugInfoCheckingIRBuilder IRB(Context); bool Changed = false; llvm::Type *PtrSizedInteger = getPointerSizedInteger(Context, Binary.Architecture()); for (Instruction &I : instructions(F)) { if (isCallToTagged(&I, FunctionTags::AllocatesLocalVariable)) continue; // In the following loop we're going to look at each constant operand of I, // and see if it can be replaced either with a string literal or with a // reference to a segment, or with a reference to a function. // // If we do the replacement blindly, and the same constant is used many // times in the same instruction, we will end up replacing that constant // many times in the same instruction, replacing it with redundant // instructions that could be CSE's. // // In addition, if I is a PHINode, we could end up generating invalid IR, if // the PHI has the same constant incoming value many times coming from the // same predecessor block. In that case, the IR would be valid, but if we // replace the same incoming constant with many different expressions (even // if equivalent), the IR doesn't verify anymore, because now we have many // different incoming values (the replacement instructions) from the same // predecessor block. // // To avoid this problem, we need map to track how we replace each constant // operand in a given instruction. In this way, if an instruction uses the // same constant many times, we we'll emit a single expression as a // replacement, that will have many uses in the user instruction. // Given that we have to do this for PHINodes for verification reasons, we // might as well do it for all instructions, to keep the generated IR // somewhat smaller. // // This is correct only if all the Values we generate as a replacement // don't have any side effects. // The key is Constant, but we expect them to be only ConstantInt and // cast-like ConstantExpr. DenseMap ConstantOpReplacements; for (Use &Op : I.operands()) { // Operands of calls to OpaqueExtractValue should never be promoted to // references to segments if (isCallToTagged(Op.getUser(), FunctionTags::OpaqueExtractValue)) continue; if (isa(&I) && (cast(&I)->getCondition() != Op)) continue; if (auto *BI = dyn_cast(&I)) if (BI->isConditional()) if (BI->getCondition() != Op) continue; // If Op is not a constant it cannot be a reference to a segment, nor // a string literal, nor a function. // TODO: this will change when we properly support PIE code. But when that // happens this part of the code should be gone already. if (not isa(Op) and not isa(skipConstexprBitcasts(Op))) continue; ConstantInt *LeafConstOp = cast(skipConstexprBitcasts(Op)); // Skip stuff that is not pointer-sized { using namespace model::Architecture; auto PointerSize = getPointerSize(Binary.Architecture()); if (LeafConstOp->getBitWidth() != (8 * PointerSize)) continue; } auto *ConstOp = cast(Op); // We've already this very same constant used as another operand of this // very same instruction. Just reuse the replacement that we've computed // earlier. if (auto It = ConstantOpReplacements.find(ConstOp); It != ConstantOpReplacements.end()) { I.setOperand(Op.getOperandNo(), It->second); Changed = true; revng::verify(&M); continue; } uint64_t ConstantAddress = LeafConstOp->getZExtValue(); if (auto Segment = findSegmentContainingLiteral(Binary, ConstantAddress); Segment) { const auto &[StartAddress, VirtualSize] = *Segment; auto OffsetInSegment = ConstantAddress - StartAddress.address(); MetaAddress Address = StartAddress + OffsetInSegment; auto *PHI = dyn_cast(&I); if (PHI) { BasicBlock *InsertionBlock = DT[PHI->getParent()]->getIDom()->getBlock(); IRB.SetInsertPoint(InsertionBlock->getTerminator()); } else { IRB.SetInsertPoint(&I); } Value *ConstantOpReplacement = nullptr; // If Address matches a function entry, the constant operand must be // replaced with a function reference. auto It = FunctionEntries.find(Address); if (It != FunctionEntries.end() and not DisableFunctionPointers) { auto Name = llvmName(Binary.Functions().at(It->second)); auto *ReferencedFunction = M.getFunction(Name); revng_assert(ReferencedFunction != nullptr); ConstantOpReplacement = IRB.CreatePtrToInt(ReferencedFunction, Op->getType()); } else { // Then we we check to see if the address matches a string literal. // Check if the use of this constant is a icmp. If it is we cannot // replace it with a string literal, because comparisons between // string literals are undefined behavior in C. bool UseIsComparison = llvm::isa(Op.getUser()); // Check if the Op is large as a pointer. If it isn't it can't be a // string literal. // See if we can find a string literal there. auto OptString = getStringLiteral(BinaryView, StartAddress, VirtualSize, OffsetInSegment); if (not UseIsComparison and OptString.has_value()) { auto Str = OptString.value(); Constant *ConstExpr = getUniqueString(&M, Str); auto RealOpType = Op->getType(); FunctionTags::StringLiteralPoolKey Key = { StartAddress, VirtualSize, OffsetInSegment, RealOpType }; auto LiteralFunction = StringLiteralPool .get(Key, RealOpType, { ConstExpr->getType() }, "cstringLiteral"); if (not hasStringLiteralMetadata(*LiteralFunction)) { setStringLiteralMetadata(*LiteralFunction, StartAddress, VirtualSize, OffsetInSegment, Str.size(), RealOpType); } CallInst *Call = IRB.CreateCall(LiteralFunction, { ConstExpr }); ConstantOpReplacement = Call; } else { // If it cannot be emitted as a string literal we emit it as a // reference to a segment. IntegerType *OperandType = LeafConstOp->getType(); FunctionTags::SegmentRefPoolKey Key = { StartAddress, VirtualSize, OperandType }; auto *SegmentRefFunction = SegmentRefPool.get(Key, OperandType, {}, "segmentRef"); if (not hasSegmentKeyMetadata(*SegmentRefFunction)) { setSegmentKeyMetadata(*SegmentRefFunction, StartAddress, VirtualSize); } // Inject call to SegmentRef auto *SegmentRefCall = IRB.CreateCall(SegmentRefFunction); // Inject call to AddressOf auto SegmentRefType = SegmentRefCall->getType(); auto *AddressOfFunctionType = getAddressOfType(PtrSizedInteger, SegmentRefType); auto *AddressOfFunction = AddressOfPool.get({ PtrSizedInteger, SegmentRefType }, AddressOfFunctionType, "AddressOf"); Constant *ModelTypeString = nullptr; if (const auto &SegmentType = Binary.Segments() .at({ StartAddress, VirtualSize }) .Type()) { ModelTypeString = toLLVMString(SegmentType, M); } else { auto Byte = model::PrimitiveType::makeGeneric(1); auto Arr = model::ArrayType::make(std::move(Byte), VirtualSize); ModelTypeString = toLLVMString(Arr, M); } Value *AddressOfCall = IRB.CreateCall(AddressOfFunction, { ModelTypeString, SegmentRefCall }); AddressOfCall = IRB.CreateZExtOrTrunc(AddressOfCall, OperandType); // Inject LLVM add if (OffsetInSegment > 0) { auto *Offset = ConstantInt::get(OperandType, OffsetInSegment); AddressOfCall = IRB.CreateAdd(AddressOfCall, Offset); } if (auto *CE = dyn_cast(Op)) if (CE->getType()->isPointerTy()) AddressOfCall = IRB.CreateIntToPtr(AddressOfCall, CE->getType()); ConstantOpReplacement = AddressOfCall; } } revng_assert(nullptr != ConstantOpReplacement); I.setOperand(Op.getOperandNo(), ConstantOpReplacement); Changed = true; revng::verify(&M); ConstantOpReplacements[ConstOp] = ConstantOpReplacement; } } } return Changed; } template<> char pipeline::FunctionPass::ID = 0; namespace revng::pipes { class MakeSegmentRef { public: static constexpr auto Name = "make-segment-ref"; std::array getContract() const { pipeline::Contract BinaryPart(kinds::Binary, 0, kinds::Binary, 0); pipeline::Contract FunctionsPart(kinds::StackAccessesSegregated, 1, kinds::StackAccessesSegregated, 1); return { pipeline::ContractGroup({ BinaryPart, FunctionsPart }) }; } void run(pipeline::ExecutionContext &EC, const BinaryFileContainer &SourceBinary, pipeline::LLVMContainer &Output); }; void MakeSegmentRef::run(pipeline::ExecutionContext &EC, const BinaryFileContainer &SourceBinary, pipeline::LLVMContainer &ModuleContainer) { if (not SourceBinary.exists()) return; const TupleTree &Model = getModelFromContext(EC); auto BufferOrError = llvm::MemoryBuffer::getFileOrSTDIN(*SourceBinary.path()); auto Buffer = cantFail(errorOrToExpected(std::move(BufferOrError))); RawBinaryView RawBinary(*Model, Buffer->getBuffer()); llvm::legacy::PassManager PM; PM.add(new pipeline::LoadExecutionContextPass(&EC, ModuleContainer.name())); PM.add(new LoadModelWrapperPass(Model)); PM.add(new LoadBinaryWrapperPass(Buffer->getBuffer())); PM.add(new pipeline::FunctionPass); PM.run(ModuleContainer.getModule()); } } // namespace revng::pipes static pipeline::RegisterPipe RegMSRPipe; namespace revng::pypeline::piperuns { // TODO: merge MakeSegmentRefPassImpl into MakeSegmentRef once we dismiss the // old pipeline void MakeSegmentRef::runOnLLVMFunction(const model::Function &Function, llvm::Function &LLVMFunction) { MakeSegmentRefPassImpl Impl(Binary, *LLVMFunction.getParent(), BinaryView); Impl.runOnFunction(Function, LLVMFunction); } } // namespace revng::pypeline::piperuns