//===--------- ELFDebugObjectPlugin.cpp - JITLink debug objects -----------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // FIXME: Update Plugin to poke the debug object into a new JITLink section, // rather than creating a new allocation. // //===----------------------------------------------------------------------===// #include "llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/ExecutionEngine/JITLink/JITLink.h" #include "llvm/ExecutionEngine/JITLink/JITLinkDylib.h" #include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h" #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" #include "llvm/ExecutionEngine/Orc/Shared/MemoryFlags.h" #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" #include "llvm/IR/Instructions.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/Error.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/MSVCErrorWorkarounds.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" #include "llvm/Support/raw_ostream.h" #include #define DEBUG_TYPE "orc" using namespace llvm::jitlink; using namespace llvm::object; namespace llvm { namespace orc { // Helper class to emit and fixup an individual debug object class DebugObject { public: using FinalizedAlloc = JITLinkMemoryManager::FinalizedAlloc; DebugObject(StringRef Name, SimpleSegmentAlloc Alloc, JITLinkContext &Ctx, ExecutionSession &ES) : Name(Name), WorkingMem(std::move(Alloc)), MemMgr(Ctx.getMemoryManager()), ES(ES) {} ~DebugObject() { assert(!FinalizeFuture.valid()); if (Alloc) { std::vector Allocs; Allocs.push_back(std::move(Alloc)); if (Error Err = MemMgr.deallocate(std::move(Allocs))) ES.reportError(std::move(Err)); } } MutableArrayRef getBuffer() { auto SegInfo = WorkingMem.getSegInfo(MemProt::Read); return SegInfo.WorkingMem; } SimpleSegmentAlloc collectTargetAlloc() { FinalizeFuture = FinalizePromise.get_future(); return std::move(WorkingMem); } void trackFinalizedAlloc(FinalizedAlloc FA) { Alloc = std::move(FA); } bool hasPendingTargetMem() const { return FinalizeFuture.valid(); } Expected awaitTargetMem() { assert(FinalizeFuture.valid() && "FinalizeFuture is not valid. Perhaps there is no pending target " "memory transaction?"); return FinalizeFuture.get(); } void reportTargetMem(ExecutorAddrRange TargetMem) { FinalizePromise.set_value(TargetMem); } void failMaterialization(Error Err) { FinalizePromise.set_value(std::move(Err)); } void releasePendingResources() { if (FinalizeFuture.valid()) { // Error before step 4: Finalization error was not reported Expected TargetMem = FinalizeFuture.get(); if (!TargetMem) ES.reportError(TargetMem.takeError()); } else { // Error before step 3: WorkingMem was not collected WorkingMem.abandon( [ES = &this->ES](Error Err) { ES->reportError(std::move(Err)); }); } } using GetLoadAddressFn = llvm::unique_function; Error visitSections(GetLoadAddressFn Callback); template Error visitSectionLoadAddresses(GetLoadAddressFn Callback); private: std::string Name; SimpleSegmentAlloc WorkingMem; JITLinkMemoryManager &MemMgr; ExecutionSession &ES; std::promise> FinalizePromise; std::future> FinalizeFuture; FinalizedAlloc Alloc; }; template Error DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) { using SectionHeader = typename ELFT::Shdr; MutableArrayRef Buffer = getBuffer(); StringRef BufferRef(Buffer.data(), Buffer.size()); Expected> ObjRef = ELFFile::create(BufferRef); if (!ObjRef) return ObjRef.takeError(); Expected> Sections = ObjRef->sections(); if (!Sections) return Sections.takeError(); for (const SectionHeader &Header : *Sections) { Expected Name = ObjRef->getSectionName(Header); if (!Name) return Name.takeError(); if (Name->empty()) continue; ExecutorAddr LoadAddress = Callback(*Name); if (LoadAddress) const_cast(Header).sh_addr = static_cast(LoadAddress.getValue()); } LLVM_DEBUG({ dbgs() << "Section load-addresses in debug object for \"" << Name << "\":\n"; for (const SectionHeader &Header : *Sections) { StringRef Name = cantFail(ObjRef->getSectionName(Header)); if (uint64_t Addr = Header.sh_addr) { dbgs() << formatv(" {0:x16} {1}\n", Addr, Name); } else { dbgs() << formatv(" {0}\n", Name); } } }); return Error::success(); } Error DebugObject::visitSections(GetLoadAddressFn Callback) { unsigned char Class, Endian; MutableArrayRef Buf = getBuffer(); std::tie(Class, Endian) = getElfArchType(StringRef(Buf.data(), Buf.size())); switch (Class) { case ELF::ELFCLASS32: if (Endian == ELF::ELFDATA2LSB) return visitSectionLoadAddresses(std::move(Callback)); if (Endian == ELF::ELFDATA2MSB) return visitSectionLoadAddresses(std::move(Callback)); break; case ELF::ELFCLASS64: if (Endian == ELF::ELFDATA2LSB) return visitSectionLoadAddresses(std::move(Callback)); if (Endian == ELF::ELFDATA2MSB) return visitSectionLoadAddresses(std::move(Callback)); break; default: break; } llvm_unreachable("Checked class and endian in notifyMaterializing()"); } ELFDebugObjectPlugin::ELFDebugObjectPlugin(ExecutionSession &ES, bool RequireDebugSections, bool AutoRegisterCode, Error &Err) : ES(ES), RequireDebugSections(RequireDebugSections), AutoRegisterCode(AutoRegisterCode) { // Pass bootstrap symbol for registration function to enable debugging ErrorAsOutParameter _(&Err); Err = ES.getExecutorProcessControl().getBootstrapSymbols( {{RegistrationAction, rt::RegisterJITLoaderGDBAllocActionName}}); } ELFDebugObjectPlugin::~ELFDebugObjectPlugin() = default; static const std::set DwarfSectionNames = { #define HANDLE_DWARF_SECTION(ENUM_NAME, ELF_NAME, CMDLINE_NAME, OPTION) \ ELF_NAME, #include "llvm/BinaryFormat/Dwarf.def" #undef HANDLE_DWARF_SECTION }; static bool isDwarfSection(StringRef SectionName) { return DwarfSectionNames.count(SectionName) == 1; } void ELFDebugObjectPlugin::notifyMaterializing( MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx, MemoryBufferRef InputObj) { if (InputObj.getBufferSize() == 0) return; if (G.getTargetTriple().getObjectFormat() != Triple::ELF) return; unsigned char Class, Endian; std::tie(Class, Endian) = getElfArchType(InputObj.getBuffer()); if (Class != ELF::ELFCLASS64 && Class != ELF::ELFCLASS32) return ES.reportError( createStringError(object_error::invalid_file_type, "Skipping debug object registration: Invalid arch " "0x%02x in ELF LinkGraph %s", Class, G.getName().c_str())); if (Endian != ELF::ELFDATA2LSB && Endian != ELF::ELFDATA2MSB) return ES.reportError( createStringError(object_error::invalid_file_type, "Skipping debug object registration: Invalid endian " "0x%02x in ELF LinkGraph %s", Endian, G.getName().c_str())); // Step 1: We copy the raw input object into the working memory of a // single-segment read-only allocation size_t Size = InputObj.getBufferSize(); auto Alignment = sys::Process::getPageSizeEstimate(); SimpleSegmentAlloc::Segment Segment{Size, Align(Alignment)}; auto Alloc = SimpleSegmentAlloc::Create( Ctx.getMemoryManager(), ES.getSymbolStringPool(), ES.getTargetTriple(), Ctx.getJITLinkDylib(), {{MemProt::Read, Segment}}); if (!Alloc) { ES.reportError(Alloc.takeError()); return; } std::lock_guard Lock(PendingObjsLock); assert(PendingObjs.count(&MR) == 0 && "One debug object per materialization"); PendingObjs[&MR] = std::make_unique( InputObj.getBufferIdentifier(), std::move(*Alloc), Ctx, ES); MutableArrayRef Buffer = PendingObjs[&MR]->getBuffer(); memcpy(Buffer.data(), InputObj.getBufferStart(), Size); } DebugObject * ELFDebugObjectPlugin::getPendingDebugObj(MaterializationResponsibility &MR) { std::lock_guard Lock(PendingObjsLock); auto It = PendingObjs.find(&MR); return It == PendingObjs.end() ? nullptr : It->second.get(); } void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR, LinkGraph &G, PassConfiguration &PassConfig) { if (!getPendingDebugObj(MR)) return; PassConfig.PostAllocationPasses.push_back([this, &MR](LinkGraph &G) -> Error { size_t SectionsPatched = 0; bool HasDebugSections = false; DebugObject *DebugObj = getPendingDebugObj(MR); assert(DebugObj && "Don't inject passes if we have no debug object"); // Step 2: Once the target memory layout is ready, we write the // addresses of the LinkGraph sections into the load-address fields of the // section headers in our debug object allocation Error Err = DebugObj->visitSections( [&G, &SectionsPatched, &HasDebugSections](StringRef Name) { Section *S = G.findSectionByName(Name); if (!S) { // The section may have been merged into a different one during // linking, ignore it. return ExecutorAddr(); } SectionsPatched += 1; if (isDwarfSection(Name)) HasDebugSections = true; return SectionRange(*S).getStart(); }); if (Err) return Err; if (!SectionsPatched) { LLVM_DEBUG(dbgs() << "Skipping debug registration for LinkGraph '" << G.getName() << "': no debug info\n"); return Error::success(); } if (RequireDebugSections && !HasDebugSections) { LLVM_DEBUG(dbgs() << "Skipping debug registration for LinkGraph '" << G.getName() << "': no debug info\n"); return Error::success(); } // Step 3: We start copying the debug object into target memory SimpleSegmentAlloc Alloc = DebugObj->collectTargetAlloc(); // FIXME: FA->getAddress() below is supposed to be the address of the memory // range on the target, but InProcessMemoryManager returns the address of a // FinalizedAllocInfo helper instead auto ROSeg = Alloc.getSegInfo(MemProt::Read); ExecutorAddrRange R(ROSeg.Addr, ROSeg.WorkingMem.size()); Alloc.finalize([this, R, &MR](Expected FA) { // Bail out if materialization failed in the meantime std::lock_guard Lock(PendingObjsLock); auto It = PendingObjs.find(&MR); if (It == PendingObjs.end()) { if (!FA) ES.reportError(FA.takeError()); return; } DebugObject *DebugObj = It->second.get(); if (!FA) DebugObj->failMaterialization(FA.takeError()); // Keep allocation alive until the corresponding code is removed DebugObj->trackFinalizedAlloc(std::move(*FA)); // Unblock post-fixup pass DebugObj->reportTargetMem(R); }); return Error::success(); }); PassConfig.PostFixupPasses.push_back([this, &MR](LinkGraph &G) -> Error { // Step 4: We wait for the debug object copy to finish, so we can // register the memory range with the GDB JIT Interface in an allocation // action of the LinkGraph's own allocation DebugObject *DebugObj = getPendingDebugObj(MR); assert(DebugObj && "Don't inject passes if we have no debug object"); // Post-allocation phases would bail out if there is no debug section, // in which case we wouldn't collect target memory and therefore shouldn't // wait for the transaction to finish. if (!DebugObj->hasPendingTargetMem()) return Error::success(); Expected R = DebugObj->awaitTargetMem(); if (!R) return R.takeError(); // Step 5: We have to keep the allocation alive until the corresponding // code is removed Error Err = MR.withResourceKeyDo([&](ResourceKey K) { std::lock_guard LockPending(PendingObjsLock); std::lock_guard LockRegistered(RegisteredObjsLock); auto It = PendingObjs.find(&MR); RegisteredObjs[K].push_back(std::move(It->second)); PendingObjs.erase(It); }); if (Err) return Err; if (R->empty()) return Error::success(); using namespace shared; G.allocActions().push_back( {cantFail(WrapperFunctionCall::Create< SPSArgList>( RegistrationAction, *R, AutoRegisterCode)), {/* no deregistration */}}); return Error::success(); }); } Error ELFDebugObjectPlugin::notifyFailed(MaterializationResponsibility &MR) { std::lock_guard Lock(PendingObjsLock); auto It = PendingObjs.find(&MR); It->second->releasePendingResources(); PendingObjs.erase(It); return Error::success(); } void ELFDebugObjectPlugin::notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, ResourceKey SrcKey) { // Debug objects are stored by ResourceKey only after registration. // Thus, pending objects don't need to be updated here. std::lock_guard Lock(RegisteredObjsLock); auto SrcIt = RegisteredObjs.find(SrcKey); if (SrcIt != RegisteredObjs.end()) { // Resources from distinct MaterializationResponsibilitys can get merged // after emission, so we can have multiple debug objects per resource key. for (std::unique_ptr &DebugObj : SrcIt->second) RegisteredObjs[DstKey].push_back(std::move(DebugObj)); RegisteredObjs.erase(SrcIt); } } Error ELFDebugObjectPlugin::notifyRemovingResources(JITDylib &JD, ResourceKey Key) { // Removing the resource for a pending object fails materialization, so they // get cleaned up in the notifyFailed() handler. std::lock_guard Lock(RegisteredObjsLock); RegisteredObjs.erase(Key); // TODO: Implement unregister notifications. return Error::success(); } } // namespace orc } // namespace llvm