| 1 | //===--------- ELFDebugObjectPlugin.cpp - JITLink debug objects -----------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // FIXME: Update Plugin to poke the debug object into a new JITLink section, |
| 10 | // rather than creating a new allocation. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h" |
| 15 | |
| 16 | #include "llvm/ADT/ArrayRef.h" |
| 17 | #include "llvm/ADT/StringMap.h" |
| 18 | #include "llvm/ADT/StringRef.h" |
| 19 | #include "llvm/BinaryFormat/ELF.h" |
| 20 | #include "llvm/ExecutionEngine/JITLink/JITLink.h" |
| 21 | #include "llvm/ExecutionEngine/JITLink/JITLinkDylib.h" |
| 22 | #include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h" |
| 23 | #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" |
| 24 | #include "llvm/ExecutionEngine/Orc/Shared/MemoryFlags.h" |
| 25 | #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" |
| 26 | #include "llvm/IR/Instructions.h" |
| 27 | #include "llvm/Object/ELFObjectFile.h" |
| 28 | #include "llvm/Object/Error.h" |
| 29 | #include "llvm/Support/Errc.h" |
| 30 | #include "llvm/Support/Error.h" |
| 31 | #include "llvm/Support/MSVCErrorWorkarounds.h" |
| 32 | #include "llvm/Support/MemoryBuffer.h" |
| 33 | #include "llvm/Support/Process.h" |
| 34 | #include "llvm/Support/raw_ostream.h" |
| 35 | |
| 36 | #include <set> |
| 37 | |
| 38 | #define DEBUG_TYPE "orc" |
| 39 | |
| 40 | using namespace llvm::jitlink; |
| 41 | using namespace llvm::object; |
| 42 | |
| 43 | namespace llvm { |
| 44 | namespace orc { |
| 45 | |
| 46 | // Helper class to emit and fixup an individual debug object |
| 47 | class DebugObject { |
| 48 | public: |
| 49 | using FinalizedAlloc = JITLinkMemoryManager::FinalizedAlloc; |
| 50 | |
| 51 | DebugObject(StringRef Name, SimpleSegmentAlloc Alloc, JITLinkContext &Ctx, |
| 52 | ExecutionSession &ES) |
| 53 | : Name(Name), WorkingMem(std::move(Alloc)), |
| 54 | MemMgr(Ctx.getMemoryManager()), ES(ES) {} |
| 55 | |
| 56 | ~DebugObject() { |
| 57 | assert(!FinalizeFuture.valid()); |
| 58 | if (Alloc) { |
| 59 | std::vector<FinalizedAlloc> Allocs; |
| 60 | Allocs.push_back(x: std::move(Alloc)); |
| 61 | if (Error Err = MemMgr.deallocate(Allocs: std::move(Allocs))) |
| 62 | ES.reportError(Err: std::move(Err)); |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | MutableArrayRef<char> getBuffer() { |
| 67 | auto SegInfo = WorkingMem.getSegInfo(AG: MemProt::Read); |
| 68 | return SegInfo.WorkingMem; |
| 69 | } |
| 70 | |
| 71 | SimpleSegmentAlloc collectTargetAlloc() { |
| 72 | FinalizeFuture = FinalizePromise.get_future(); |
| 73 | return std::move(WorkingMem); |
| 74 | } |
| 75 | |
| 76 | void trackFinalizedAlloc(FinalizedAlloc FA) { Alloc = std::move(FA); } |
| 77 | |
| 78 | bool hasPendingTargetMem() const { return FinalizeFuture.valid(); } |
| 79 | |
| 80 | Expected<ExecutorAddrRange> awaitTargetMem() { |
| 81 | assert(FinalizeFuture.valid() && |
| 82 | "FinalizeFuture is not valid. Perhaps there is no pending target " |
| 83 | "memory transaction?" ); |
| 84 | return FinalizeFuture.get(); |
| 85 | } |
| 86 | |
| 87 | void reportTargetMem(ExecutorAddrRange TargetMem) { |
| 88 | FinalizePromise.set_value(TargetMem); |
| 89 | } |
| 90 | |
| 91 | void failMaterialization(Error Err) { |
| 92 | FinalizePromise.set_value(std::move(Err)); |
| 93 | } |
| 94 | |
| 95 | void releasePendingResources() { |
| 96 | if (FinalizeFuture.valid()) { |
| 97 | // Error before step 4: Finalization error was not reported |
| 98 | Expected<ExecutorAddrRange> TargetMem = FinalizeFuture.get(); |
| 99 | if (!TargetMem) |
| 100 | ES.reportError(Err: TargetMem.takeError()); |
| 101 | } else { |
| 102 | // Error before step 3: WorkingMem was not collected |
| 103 | WorkingMem.abandon( |
| 104 | OnAbandoned: [ES = &this->ES](Error Err) { ES->reportError(Err: std::move(Err)); }); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | using GetLoadAddressFn = llvm::unique_function<ExecutorAddr(StringRef)>; |
| 109 | Error visitSections(GetLoadAddressFn Callback); |
| 110 | |
| 111 | template <typename ELFT> |
| 112 | Error visitSectionLoadAddresses(GetLoadAddressFn Callback); |
| 113 | |
| 114 | private: |
| 115 | std::string Name; |
| 116 | SimpleSegmentAlloc WorkingMem; |
| 117 | JITLinkMemoryManager &MemMgr; |
| 118 | ExecutionSession &ES; |
| 119 | |
| 120 | std::promise<MSVCPExpected<ExecutorAddrRange>> FinalizePromise; |
| 121 | std::future<MSVCPExpected<ExecutorAddrRange>> FinalizeFuture; |
| 122 | |
| 123 | FinalizedAlloc Alloc; |
| 124 | }; |
| 125 | |
| 126 | template <typename ELFT> |
| 127 | Error DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) { |
| 128 | using = typename ELFT::Shdr; |
| 129 | |
| 130 | MutableArrayRef<char> Buffer = getBuffer(); |
| 131 | StringRef BufferRef(Buffer.data(), Buffer.size()); |
| 132 | Expected<ELFFile<ELFT>> ObjRef = ELFFile<ELFT>::create(BufferRef); |
| 133 | if (!ObjRef) |
| 134 | return ObjRef.takeError(); |
| 135 | |
| 136 | Expected<ArrayRef<SectionHeader>> Sections = ObjRef->sections(); |
| 137 | if (!Sections) |
| 138 | return Sections.takeError(); |
| 139 | |
| 140 | for (const SectionHeader & : *Sections) { |
| 141 | Expected<StringRef> Name = ObjRef->getSectionName(Header); |
| 142 | if (!Name) |
| 143 | return Name.takeError(); |
| 144 | if (Name->empty()) |
| 145 | continue; |
| 146 | ExecutorAddr LoadAddress = Callback(*Name); |
| 147 | if (LoadAddress) |
| 148 | const_cast<SectionHeader &>(Header).sh_addr = |
| 149 | static_cast<typename ELFT::uint>(LoadAddress.getValue()); |
| 150 | } |
| 151 | |
| 152 | LLVM_DEBUG({ |
| 153 | dbgs() << "Section load-addresses in debug object for \"" << Name |
| 154 | << "\":\n" ; |
| 155 | for (const SectionHeader &Header : *Sections) { |
| 156 | StringRef Name = cantFail(ObjRef->getSectionName(Header)); |
| 157 | if (uint64_t Addr = Header.sh_addr) { |
| 158 | dbgs() << formatv(" {0:x16} {1}\n" , Addr, Name); |
| 159 | } else { |
| 160 | dbgs() << formatv(" {0}\n" , Name); |
| 161 | } |
| 162 | } |
| 163 | }); |
| 164 | |
| 165 | return Error::success(); |
| 166 | } |
| 167 | |
| 168 | Error DebugObject::visitSections(GetLoadAddressFn Callback) { |
| 169 | unsigned char Class, Endian; |
| 170 | MutableArrayRef<char> Buf = getBuffer(); |
| 171 | std::tie(args&: Class, args&: Endian) = getElfArchType(Object: StringRef(Buf.data(), Buf.size())); |
| 172 | |
| 173 | switch (Class) { |
| 174 | case ELF::ELFCLASS32: |
| 175 | if (Endian == ELF::ELFDATA2LSB) |
| 176 | return visitSectionLoadAddresses<ELF32LE>(Callback: std::move(Callback)); |
| 177 | if (Endian == ELF::ELFDATA2MSB) |
| 178 | return visitSectionLoadAddresses<ELF32BE>(Callback: std::move(Callback)); |
| 179 | break; |
| 180 | |
| 181 | case ELF::ELFCLASS64: |
| 182 | if (Endian == ELF::ELFDATA2LSB) |
| 183 | return visitSectionLoadAddresses<ELF64LE>(Callback: std::move(Callback)); |
| 184 | if (Endian == ELF::ELFDATA2MSB) |
| 185 | return visitSectionLoadAddresses<ELF64BE>(Callback: std::move(Callback)); |
| 186 | break; |
| 187 | |
| 188 | default: |
| 189 | break; |
| 190 | } |
| 191 | llvm_unreachable("Checked class and endian in notifyMaterializing()" ); |
| 192 | } |
| 193 | |
| 194 | ELFDebugObjectPlugin::ELFDebugObjectPlugin(ExecutionSession &ES, |
| 195 | bool RequireDebugSections, |
| 196 | bool AutoRegisterCode, Error &Err) |
| 197 | : ES(ES), RequireDebugSections(RequireDebugSections), |
| 198 | AutoRegisterCode(AutoRegisterCode) { |
| 199 | // Pass bootstrap symbol for registration function to enable debugging |
| 200 | ErrorAsOutParameter _(&Err); |
| 201 | Err = ES.getExecutorProcessControl().getBootstrapSymbols( |
| 202 | Pairs: {{RegistrationAction, rt::RegisterJITLoaderGDBAllocActionName}}); |
| 203 | } |
| 204 | |
| 205 | ELFDebugObjectPlugin::~ELFDebugObjectPlugin() = default; |
| 206 | |
| 207 | static const std::set<StringRef> DwarfSectionNames = { |
| 208 | #define HANDLE_DWARF_SECTION(ENUM_NAME, ELF_NAME, CMDLINE_NAME, OPTION) \ |
| 209 | ELF_NAME, |
| 210 | #include "llvm/BinaryFormat/Dwarf.def" |
| 211 | #undef HANDLE_DWARF_SECTION |
| 212 | }; |
| 213 | |
| 214 | static bool isDwarfSection(StringRef SectionName) { |
| 215 | return DwarfSectionNames.count(x: SectionName) == 1; |
| 216 | } |
| 217 | |
| 218 | void ELFDebugObjectPlugin::notifyMaterializing( |
| 219 | MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx, |
| 220 | MemoryBufferRef InputObj) { |
| 221 | if (InputObj.getBufferSize() == 0) |
| 222 | return; |
| 223 | if (G.getTargetTriple().getObjectFormat() != Triple::ELF) |
| 224 | return; |
| 225 | |
| 226 | unsigned char Class, Endian; |
| 227 | std::tie(args&: Class, args&: Endian) = getElfArchType(Object: InputObj.getBuffer()); |
| 228 | if (Class != ELF::ELFCLASS64 && Class != ELF::ELFCLASS32) |
| 229 | return ES.reportError( |
| 230 | Err: createStringError(EC: object_error::invalid_file_type, |
| 231 | Fmt: "Skipping debug object registration: Invalid arch " |
| 232 | "0x%02x in ELF LinkGraph %s" , |
| 233 | Vals: Class, Vals: G.getName().c_str())); |
| 234 | if (Endian != ELF::ELFDATA2LSB && Endian != ELF::ELFDATA2MSB) |
| 235 | return ES.reportError( |
| 236 | Err: createStringError(EC: object_error::invalid_file_type, |
| 237 | Fmt: "Skipping debug object registration: Invalid endian " |
| 238 | "0x%02x in ELF LinkGraph %s" , |
| 239 | Vals: Endian, Vals: G.getName().c_str())); |
| 240 | |
| 241 | // Step 1: We copy the raw input object into the working memory of a |
| 242 | // single-segment read-only allocation |
| 243 | size_t Size = InputObj.getBufferSize(); |
| 244 | auto Alignment = sys::Process::getPageSizeEstimate(); |
| 245 | SimpleSegmentAlloc::Segment Segment{Size, Align(Alignment)}; |
| 246 | |
| 247 | auto Alloc = SimpleSegmentAlloc::Create( |
| 248 | MemMgr&: Ctx.getMemoryManager(), SSP: ES.getSymbolStringPool(), TT: ES.getTargetTriple(), |
| 249 | JD: Ctx.getJITLinkDylib(), Segments: {{MemProt::Read, Segment}}); |
| 250 | if (!Alloc) { |
| 251 | ES.reportError(Err: Alloc.takeError()); |
| 252 | return; |
| 253 | } |
| 254 | |
| 255 | std::lock_guard<std::mutex> Lock(PendingObjsLock); |
| 256 | assert(PendingObjs.count(&MR) == 0 && "One debug object per materialization" ); |
| 257 | PendingObjs[&MR] = std::make_unique<DebugObject>( |
| 258 | args: InputObj.getBufferIdentifier(), args: std::move(*Alloc), args&: Ctx, args&: ES); |
| 259 | |
| 260 | MutableArrayRef<char> Buffer = PendingObjs[&MR]->getBuffer(); |
| 261 | memcpy(dest: Buffer.data(), src: InputObj.getBufferStart(), n: Size); |
| 262 | } |
| 263 | |
| 264 | DebugObject * |
| 265 | ELFDebugObjectPlugin::getPendingDebugObj(MaterializationResponsibility &MR) { |
| 266 | std::lock_guard<std::mutex> Lock(PendingObjsLock); |
| 267 | auto It = PendingObjs.find(x: &MR); |
| 268 | return It == PendingObjs.end() ? nullptr : It->second.get(); |
| 269 | } |
| 270 | |
| 271 | void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR, |
| 272 | LinkGraph &G, |
| 273 | PassConfiguration &PassConfig) { |
| 274 | if (!getPendingDebugObj(MR)) |
| 275 | return; |
| 276 | |
| 277 | PassConfig.PostAllocationPasses.push_back(x: [this, &MR](LinkGraph &G) -> Error { |
| 278 | size_t SectionsPatched = 0; |
| 279 | bool HasDebugSections = false; |
| 280 | DebugObject *DebugObj = getPendingDebugObj(MR); |
| 281 | assert(DebugObj && "Don't inject passes if we have no debug object" ); |
| 282 | |
| 283 | // Step 2: Once the target memory layout is ready, we write the |
| 284 | // addresses of the LinkGraph sections into the load-address fields of the |
| 285 | // section headers in our debug object allocation |
| 286 | Error Err = DebugObj->visitSections( |
| 287 | Callback: [&G, &SectionsPatched, &HasDebugSections](StringRef Name) { |
| 288 | Section *S = G.findSectionByName(Name); |
| 289 | if (!S) { |
| 290 | // The section may have been merged into a different one during |
| 291 | // linking, ignore it. |
| 292 | return ExecutorAddr(); |
| 293 | } |
| 294 | |
| 295 | SectionsPatched += 1; |
| 296 | if (isDwarfSection(SectionName: Name)) |
| 297 | HasDebugSections = true; |
| 298 | return SectionRange(*S).getStart(); |
| 299 | }); |
| 300 | |
| 301 | if (Err) |
| 302 | return Err; |
| 303 | if (!SectionsPatched) { |
| 304 | LLVM_DEBUG(dbgs() << "Skipping debug registration for LinkGraph '" |
| 305 | << G.getName() << "': no debug info\n" ); |
| 306 | return Error::success(); |
| 307 | } |
| 308 | |
| 309 | if (RequireDebugSections && !HasDebugSections) { |
| 310 | LLVM_DEBUG(dbgs() << "Skipping debug registration for LinkGraph '" |
| 311 | << G.getName() << "': no debug info\n" ); |
| 312 | return Error::success(); |
| 313 | } |
| 314 | |
| 315 | // Step 3: We start copying the debug object into target memory |
| 316 | SimpleSegmentAlloc Alloc = DebugObj->collectTargetAlloc(); |
| 317 | |
| 318 | // FIXME: FA->getAddress() below is supposed to be the address of the memory |
| 319 | // range on the target, but InProcessMemoryManager returns the address of a |
| 320 | // FinalizedAllocInfo helper instead |
| 321 | auto ROSeg = Alloc.getSegInfo(AG: MemProt::Read); |
| 322 | ExecutorAddrRange R(ROSeg.Addr, ROSeg.WorkingMem.size()); |
| 323 | Alloc.finalize(OnFinalized: [this, R, &MR](Expected<DebugObject::FinalizedAlloc> FA) { |
| 324 | // Bail out if materialization failed in the meantime |
| 325 | std::lock_guard<std::mutex> Lock(PendingObjsLock); |
| 326 | auto It = PendingObjs.find(x: &MR); |
| 327 | if (It == PendingObjs.end()) { |
| 328 | if (!FA) |
| 329 | ES.reportError(Err: FA.takeError()); |
| 330 | return; |
| 331 | } |
| 332 | |
| 333 | DebugObject *DebugObj = It->second.get(); |
| 334 | if (!FA) |
| 335 | DebugObj->failMaterialization(Err: FA.takeError()); |
| 336 | |
| 337 | // Keep allocation alive until the corresponding code is removed |
| 338 | DebugObj->trackFinalizedAlloc(FA: std::move(*FA)); |
| 339 | |
| 340 | // Unblock post-fixup pass |
| 341 | DebugObj->reportTargetMem(TargetMem: R); |
| 342 | }); |
| 343 | |
| 344 | return Error::success(); |
| 345 | }); |
| 346 | |
| 347 | PassConfig.PostFixupPasses.push_back(x: [this, &MR](LinkGraph &G) -> Error { |
| 348 | // Step 4: We wait for the debug object copy to finish, so we can |
| 349 | // register the memory range with the GDB JIT Interface in an allocation |
| 350 | // action of the LinkGraph's own allocation |
| 351 | DebugObject *DebugObj = getPendingDebugObj(MR); |
| 352 | assert(DebugObj && "Don't inject passes if we have no debug object" ); |
| 353 | // Post-allocation phases would bail out if there is no debug section, |
| 354 | // in which case we wouldn't collect target memory and therefore shouldn't |
| 355 | // wait for the transaction to finish. |
| 356 | if (!DebugObj->hasPendingTargetMem()) |
| 357 | return Error::success(); |
| 358 | Expected<ExecutorAddrRange> R = DebugObj->awaitTargetMem(); |
| 359 | if (!R) |
| 360 | return R.takeError(); |
| 361 | |
| 362 | // Step 5: We have to keep the allocation alive until the corresponding |
| 363 | // code is removed |
| 364 | Error Err = MR.withResourceKeyDo(F: [&](ResourceKey K) { |
| 365 | std::lock_guard<std::mutex> LockPending(PendingObjsLock); |
| 366 | std::lock_guard<std::mutex> LockRegistered(RegisteredObjsLock); |
| 367 | auto It = PendingObjs.find(x: &MR); |
| 368 | RegisteredObjs[K].push_back(x: std::move(It->second)); |
| 369 | PendingObjs.erase(position: It); |
| 370 | }); |
| 371 | |
| 372 | if (Err) |
| 373 | return Err; |
| 374 | |
| 375 | if (R->empty()) |
| 376 | return Error::success(); |
| 377 | |
| 378 | using namespace shared; |
| 379 | G.allocActions().push_back( |
| 380 | x: {.Finalize: cantFail(ValOrErr: WrapperFunctionCall::Create< |
| 381 | SPSArgList<SPSExecutorAddrRange, bool>>( |
| 382 | FnAddr: RegistrationAction, Args: *R, Args: AutoRegisterCode)), |
| 383 | .Dealloc: {/* no deregistration */}}); |
| 384 | return Error::success(); |
| 385 | }); |
| 386 | } |
| 387 | |
| 388 | Error ELFDebugObjectPlugin::notifyFailed(MaterializationResponsibility &MR) { |
| 389 | std::lock_guard<std::mutex> Lock(PendingObjsLock); |
| 390 | auto It = PendingObjs.find(x: &MR); |
| 391 | It->second->releasePendingResources(); |
| 392 | PendingObjs.erase(position: It); |
| 393 | return Error::success(); |
| 394 | } |
| 395 | |
| 396 | void ELFDebugObjectPlugin::notifyTransferringResources(JITDylib &JD, |
| 397 | ResourceKey DstKey, |
| 398 | ResourceKey SrcKey) { |
| 399 | // Debug objects are stored by ResourceKey only after registration. |
| 400 | // Thus, pending objects don't need to be updated here. |
| 401 | std::lock_guard<std::mutex> Lock(RegisteredObjsLock); |
| 402 | auto SrcIt = RegisteredObjs.find(x: SrcKey); |
| 403 | if (SrcIt != RegisteredObjs.end()) { |
| 404 | // Resources from distinct MaterializationResponsibilitys can get merged |
| 405 | // after emission, so we can have multiple debug objects per resource key. |
| 406 | for (std::unique_ptr<DebugObject> &DebugObj : SrcIt->second) |
| 407 | RegisteredObjs[DstKey].push_back(x: std::move(DebugObj)); |
| 408 | RegisteredObjs.erase(position: SrcIt); |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | Error ELFDebugObjectPlugin::notifyRemovingResources(JITDylib &JD, |
| 413 | ResourceKey Key) { |
| 414 | // Removing the resource for a pending object fails materialization, so they |
| 415 | // get cleaned up in the notifyFailed() handler. |
| 416 | std::lock_guard<std::mutex> Lock(RegisteredObjsLock); |
| 417 | RegisteredObjs.erase(x: Key); |
| 418 | |
| 419 | // TODO: Implement unregister notifications. |
| 420 | return Error::success(); |
| 421 | } |
| 422 | |
| 423 | } // namespace orc |
| 424 | } // namespace llvm |
| 425 | |