| 1 | //===------- DebuggerSupportPlugin.cpp - Utils for debugger support -------===// |
| 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 | // |
| 10 | //===----------------------------------------------------------------------===// |
| 11 | |
| 12 | #include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.h" |
| 13 | #include "llvm/ExecutionEngine/Orc/MachOBuilder.h" |
| 14 | |
| 15 | #include "llvm/ADT/SmallVector.h" |
| 16 | #include "llvm/BinaryFormat/MachO.h" |
| 17 | #include "llvm/DebugInfo/DWARF/DWARFContext.h" |
| 18 | #include "llvm/DebugInfo/DWARF/DWARFDebugLine.h" |
| 19 | |
| 20 | #include <chrono> |
| 21 | |
| 22 | #define DEBUG_TYPE "orc" |
| 23 | |
| 24 | using namespace llvm; |
| 25 | using namespace llvm::jitlink; |
| 26 | using namespace llvm::orc; |
| 27 | |
| 28 | static const char *SynthDebugSectionName = "__jitlink_synth_debug_object" ; |
| 29 | |
| 30 | namespace { |
| 31 | |
| 32 | class MachODebugObjectSynthesizerBase |
| 33 | : public GDBJITDebugInfoRegistrationPlugin::DebugSectionSynthesizer { |
| 34 | public: |
| 35 | static bool isDebugSection(Section &Sec) { |
| 36 | return Sec.getName().starts_with(Prefix: "__DWARF," ); |
| 37 | } |
| 38 | |
| 39 | MachODebugObjectSynthesizerBase(LinkGraph &G, ExecutorAddr RegisterActionAddr) |
| 40 | : G(G), RegisterActionAddr(RegisterActionAddr) {} |
| 41 | ~MachODebugObjectSynthesizerBase() override = default; |
| 42 | |
| 43 | Error preserveDebugSections() { |
| 44 | if (G.findSectionByName(Name: SynthDebugSectionName)) { |
| 45 | LLVM_DEBUG({ |
| 46 | dbgs() << "MachODebugObjectSynthesizer skipping graph " << G.getName() |
| 47 | << " which contains an unexpected existing " |
| 48 | << SynthDebugSectionName << " section.\n" ; |
| 49 | }); |
| 50 | return Error::success(); |
| 51 | } |
| 52 | |
| 53 | LLVM_DEBUG({ |
| 54 | dbgs() << "MachODebugObjectSynthesizer visiting graph " << G.getName() |
| 55 | << "\n" ; |
| 56 | }); |
| 57 | for (auto &Sec : G.sections()) { |
| 58 | if (!isDebugSection(Sec)) |
| 59 | continue; |
| 60 | // Preserve blocks in this debug section by marking one existing symbol |
| 61 | // live for each block, and introducing a new live, anonymous symbol for |
| 62 | // each currently unreferenced block. |
| 63 | LLVM_DEBUG({ |
| 64 | dbgs() << " Preserving debug section " << Sec.getName() << "\n" ; |
| 65 | }); |
| 66 | SmallPtrSet<Block *, 8> PreservedBlocks; |
| 67 | for (auto *Sym : Sec.symbols()) { |
| 68 | bool NewPreservedBlock = |
| 69 | PreservedBlocks.insert(Ptr: &Sym->getBlock()).second; |
| 70 | if (NewPreservedBlock) |
| 71 | Sym->setLive(true); |
| 72 | } |
| 73 | for (auto *B : Sec.blocks()) |
| 74 | if (!PreservedBlocks.count(Ptr: B)) |
| 75 | G.addAnonymousSymbol(Content&: *B, Offset: 0, Size: 0, IsCallable: false, IsLive: true); |
| 76 | } |
| 77 | |
| 78 | return Error::success(); |
| 79 | } |
| 80 | |
| 81 | protected: |
| 82 | LinkGraph &G; |
| 83 | ExecutorAddr RegisterActionAddr; |
| 84 | }; |
| 85 | |
| 86 | template <typename MachOTraits> |
| 87 | class MachODebugObjectSynthesizer : public MachODebugObjectSynthesizerBase { |
| 88 | public: |
| 89 | MachODebugObjectSynthesizer(ExecutionSession &ES, LinkGraph &G, |
| 90 | ExecutorAddr RegisterActionAddr) |
| 91 | : MachODebugObjectSynthesizerBase(G, RegisterActionAddr), |
| 92 | Builder(ES.getPageSize()) {} |
| 93 | |
| 94 | using MachODebugObjectSynthesizerBase::MachODebugObjectSynthesizerBase; |
| 95 | |
| 96 | Error startSynthesis() override { |
| 97 | LLVM_DEBUG({ |
| 98 | dbgs() << "Creating " << SynthDebugSectionName << " for " << G.getName() |
| 99 | << "\n" ; |
| 100 | }); |
| 101 | |
| 102 | for (auto &Sec : G.sections()) { |
| 103 | if (Sec.blocks().empty()) |
| 104 | continue; |
| 105 | |
| 106 | // Skip sections whose name's don't fit the MachO standard. |
| 107 | if (Sec.getName().empty() || Sec.getName().size() > 33 || |
| 108 | Sec.getName().find(',') > 16) |
| 109 | continue; |
| 110 | |
| 111 | if (isDebugSection(Sec)) |
| 112 | DebugSections.push_back({&Sec, nullptr}); |
| 113 | else if (Sec.getMemLifetime() != MemLifetime::NoAlloc) |
| 114 | NonDebugSections.push_back({&Sec, nullptr}); |
| 115 | } |
| 116 | |
| 117 | // Bail out early if no debug sections. |
| 118 | if (DebugSections.empty()) |
| 119 | return Error::success(); |
| 120 | |
| 121 | // Write MachO header and debug section load commands. |
| 122 | Builder.Header.filetype = MachO::MH_OBJECT; |
| 123 | if (auto CPUType = MachO::getCPUType(T: G.getTargetTriple())) |
| 124 | Builder.Header.cputype = *CPUType; |
| 125 | else |
| 126 | return CPUType.takeError(); |
| 127 | if (auto CPUSubType = MachO::getCPUSubType(G.getTargetTriple())) |
| 128 | Builder.Header.cpusubtype = *CPUSubType; |
| 129 | else |
| 130 | return CPUSubType.takeError(); |
| 131 | |
| 132 | Seg = &Builder.addSegment("" ); |
| 133 | |
| 134 | StringMap<std::unique_ptr<MemoryBuffer>> DebugSectionMap; |
| 135 | StringRef DebugLineSectionData; |
| 136 | for (auto &DSec : DebugSections) { |
| 137 | auto [SegName, SecName] = DSec.GraphSec->getName().split(','); |
| 138 | DSec.BuilderSec = &Seg->addSection(SecName, SegName); |
| 139 | |
| 140 | SectionRange SR(*DSec.GraphSec); |
| 141 | DSec.BuilderSec->Content.Size = SR.getSize(); |
| 142 | if (!SR.empty()) { |
| 143 | DSec.BuilderSec->align = Log2_64(Value: SR.getFirstBlock()->getAlignment()); |
| 144 | StringRef SectionData(SR.getFirstBlock()->getContent().data(), |
| 145 | SR.getFirstBlock()->getSize()); |
| 146 | DebugSectionMap[SecName.drop_front(2)] = // drop "__" prefix. |
| 147 | MemoryBuffer::getMemBuffer(SectionData, G.getName(), false); |
| 148 | if (SecName == "__debug_line" ) |
| 149 | DebugLineSectionData = SectionData; |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | std::optional<StringRef> FileName; |
| 154 | if (!DebugLineSectionData.empty()) { |
| 155 | assert((G.getEndianness() == llvm::endianness::big || |
| 156 | G.getEndianness() == llvm::endianness::little) && |
| 157 | "G.getEndianness() must be either big or little" ); |
| 158 | auto DWARFCtx = |
| 159 | DWARFContext::create(DebugSectionMap, G.getPointerSize(), |
| 160 | G.getEndianness() == llvm::endianness::little); |
| 161 | DWARFDataExtractor DebugLineData( |
| 162 | DebugLineSectionData, G.getEndianness() == llvm::endianness::little, |
| 163 | G.getPointerSize()); |
| 164 | uint64_t Offset = 0; |
| 165 | DWARFDebugLine::Prologue P; |
| 166 | |
| 167 | // Try to parse line data. Consume error on failure. |
| 168 | if (auto Err = P.parse(Data: DebugLineData, OffsetPtr: &Offset, RecoverableErrorHandler: consumeError, Ctx: *DWARFCtx)) { |
| 169 | handleAllErrors(std::move(Err), [&](ErrorInfoBase &EIB) { |
| 170 | LLVM_DEBUG({ |
| 171 | dbgs() << "Cannot parse line table for \"" << G.getName() << "\": " ; |
| 172 | EIB.log(dbgs()); |
| 173 | dbgs() << "\n" ; |
| 174 | }); |
| 175 | }); |
| 176 | } else { |
| 177 | for (auto &FN : P.FileNames) |
| 178 | if ((FileName = dwarf::toString(V: FN.Name))) { |
| 179 | LLVM_DEBUG({ |
| 180 | dbgs() << "Using FileName = \"" << *FileName |
| 181 | << "\" from DWARF line table\n" ; |
| 182 | }); |
| 183 | break; |
| 184 | } |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | // If no line table (or unable to use) then use graph name. |
| 189 | // FIXME: There are probably other debug sections we should look in first. |
| 190 | if (!FileName) { |
| 191 | LLVM_DEBUG({ |
| 192 | dbgs() << "Could not find source name from DWARF line table. " |
| 193 | "Using FileName = \"\"\n" ; |
| 194 | }); |
| 195 | FileName = "" ; |
| 196 | } |
| 197 | |
| 198 | Builder.addSymbol("" , MachO::N_SO, 0, 0, 0); |
| 199 | Builder.addSymbol(*FileName, MachO::N_SO, 0, 0, 0); |
| 200 | auto TimeStamp = std::chrono::duration_cast<std::chrono::seconds>( |
| 201 | d: std::chrono::system_clock::now().time_since_epoch()) |
| 202 | .count(); |
| 203 | Builder.addSymbol("" , MachO::N_OSO, 3, 1, TimeStamp); |
| 204 | |
| 205 | for (auto &NDSP : NonDebugSections) { |
| 206 | auto [SegName, SecName] = NDSP.GraphSec->getName().split(','); |
| 207 | NDSP.BuilderSec = &Seg->addSection(SecName, SegName); |
| 208 | SectionRange SR(*NDSP.GraphSec); |
| 209 | if (!SR.empty()) |
| 210 | NDSP.BuilderSec->align = Log2_64(Value: SR.getFirstBlock()->getAlignment()); |
| 211 | |
| 212 | // Add stabs. |
| 213 | for (auto *Sym : NDSP.GraphSec->symbols()) { |
| 214 | // Skip anonymous symbols. |
| 215 | if (!Sym->hasName()) |
| 216 | continue; |
| 217 | |
| 218 | uint8_t SymType = Sym->isCallable() ? MachO::N_FUN : MachO::N_GSYM; |
| 219 | |
| 220 | Builder.addSymbol("" , MachO::N_BNSYM, 1, 0, 0); |
| 221 | StabSymbols.push_back( |
| 222 | {*Sym, Builder.addSymbol(*Sym->getName(), SymType, 1, 0, 0), |
| 223 | Builder.addSymbol(*Sym->getName(), SymType, 0, 0, 0)}); |
| 224 | Builder.addSymbol("" , MachO::N_ENSYM, 1, 0, 0); |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | Builder.addSymbol("" , MachO::N_SO, 1, 0, 0); |
| 229 | |
| 230 | // Lay out the debug object, create a section and block for it. |
| 231 | size_t DebugObjectSize = Builder.layout(); |
| 232 | |
| 233 | auto &SDOSec = G.createSection(Name: SynthDebugSectionName, Prot: MemProt::Read); |
| 234 | MachOContainerBlock = &G.createMutableContentBlock( |
| 235 | SDOSec, G.allocateBuffer(Size: DebugObjectSize), orc::ExecutorAddr(), 8, 0); |
| 236 | |
| 237 | return Error::success(); |
| 238 | } |
| 239 | |
| 240 | Error completeSynthesisAndRegister() override { |
| 241 | if (!MachOContainerBlock) { |
| 242 | LLVM_DEBUG({ |
| 243 | dbgs() << "Not writing MachO debug object header for " << G.getName() |
| 244 | << " since createDebugSection failed\n" ; |
| 245 | }); |
| 246 | |
| 247 | return Error::success(); |
| 248 | } |
| 249 | ExecutorAddr MaxAddr; |
| 250 | for (auto &NDSec : NonDebugSections) { |
| 251 | SectionRange SR(*NDSec.GraphSec); |
| 252 | NDSec.BuilderSec->addr = SR.getStart().getValue(); |
| 253 | NDSec.BuilderSec->size = SR.getSize(); |
| 254 | NDSec.BuilderSec->offset = SR.getStart().getValue(); |
| 255 | if (SR.getEnd() > MaxAddr) |
| 256 | MaxAddr = SR.getEnd(); |
| 257 | } |
| 258 | |
| 259 | for (auto &DSec : DebugSections) { |
| 260 | if (DSec.GraphSec->blocks_size() != 1) |
| 261 | return make_error<StringError>( |
| 262 | Args: "Unexpected number of blocks in debug info section" , |
| 263 | Args: inconvertibleErrorCode()); |
| 264 | |
| 265 | if (ExecutorAddr(DSec.BuilderSec->addr) + DSec.BuilderSec->size > MaxAddr) |
| 266 | MaxAddr = ExecutorAddr(DSec.BuilderSec->addr) + DSec.BuilderSec->size; |
| 267 | |
| 268 | auto &B = **DSec.GraphSec->blocks().begin(); |
| 269 | DSec.BuilderSec->Content.Data = B.getContent().data(); |
| 270 | DSec.BuilderSec->Content.Size = B.getContent().size(); |
| 271 | DSec.BuilderSec->flags |= MachO::S_ATTR_DEBUG; |
| 272 | } |
| 273 | |
| 274 | LLVM_DEBUG({ |
| 275 | dbgs() << "Writing MachO debug object header for " << G.getName() << "\n" ; |
| 276 | }); |
| 277 | |
| 278 | // Update stab symbol addresses. |
| 279 | for (auto &SS : StabSymbols) { |
| 280 | SS.StartStab.nlist().n_value = SS.Sym.getAddress().getValue(); |
| 281 | SS.EndStab.nlist().n_value = SS.Sym.getSize(); |
| 282 | } |
| 283 | |
| 284 | Builder.write(MachOContainerBlock->getAlreadyMutableContent()); |
| 285 | |
| 286 | static constexpr bool AutoRegisterCode = true; |
| 287 | SectionRange R(MachOContainerBlock->getSection()); |
| 288 | G.allocActions().push_back( |
| 289 | {cantFail(shared::WrapperFunctionCall::Create< |
| 290 | shared::SPSArgList<shared::SPSExecutorAddrRange, bool>>( |
| 291 | RegisterActionAddr, R.getRange(), AutoRegisterCode)), |
| 292 | {}}); |
| 293 | |
| 294 | return Error::success(); |
| 295 | } |
| 296 | |
| 297 | private: |
| 298 | struct SectionPair { |
| 299 | Section *GraphSec = nullptr; |
| 300 | typename MachOBuilder<MachOTraits>::Section *BuilderSec = nullptr; |
| 301 | }; |
| 302 | |
| 303 | struct StabSymbolsEntry { |
| 304 | using RelocTarget = typename MachOBuilder<MachOTraits>::RelocTarget; |
| 305 | |
| 306 | StabSymbolsEntry(Symbol &Sym, RelocTarget StartStab, RelocTarget EndStab) |
| 307 | : Sym(Sym), StartStab(StartStab), EndStab(EndStab) {} |
| 308 | |
| 309 | Symbol &Sym; |
| 310 | RelocTarget StartStab, EndStab; |
| 311 | }; |
| 312 | |
| 313 | using BuilderType = MachOBuilder<MachOTraits>; |
| 314 | |
| 315 | Block *MachOContainerBlock = nullptr; |
| 316 | MachOBuilder<MachOTraits> Builder; |
| 317 | typename MachOBuilder<MachOTraits>::Segment *Seg = nullptr; |
| 318 | std::vector<StabSymbolsEntry> StabSymbols; |
| 319 | SmallVector<SectionPair, 16> DebugSections; |
| 320 | SmallVector<SectionPair, 16> NonDebugSections; |
| 321 | }; |
| 322 | |
| 323 | } // end anonymous namespace |
| 324 | |
| 325 | namespace llvm { |
| 326 | namespace orc { |
| 327 | |
| 328 | Expected<std::unique_ptr<GDBJITDebugInfoRegistrationPlugin>> |
| 329 | GDBJITDebugInfoRegistrationPlugin::Create(ExecutionSession &ES, |
| 330 | JITDylib &ProcessJD, |
| 331 | const Triple &TT) { |
| 332 | auto RegisterActionAddr = |
| 333 | TT.isOSBinFormatMachO() |
| 334 | ? ES.intern(SymName: "_llvm_orc_registerJITLoaderGDBAllocAction" ) |
| 335 | : ES.intern(SymName: "llvm_orc_registerJITLoaderGDBAllocAction" ); |
| 336 | |
| 337 | if (auto RegisterSym = ES.lookup(SearchOrder: {&ProcessJD}, Symbol: RegisterActionAddr)) |
| 338 | return std::make_unique<GDBJITDebugInfoRegistrationPlugin>( |
| 339 | args: RegisterSym->getAddress()); |
| 340 | else |
| 341 | return RegisterSym.takeError(); |
| 342 | } |
| 343 | |
| 344 | Error GDBJITDebugInfoRegistrationPlugin::notifyFailed( |
| 345 | MaterializationResponsibility &MR) { |
| 346 | return Error::success(); |
| 347 | } |
| 348 | |
| 349 | Error GDBJITDebugInfoRegistrationPlugin::notifyRemovingResources( |
| 350 | JITDylib &JD, ResourceKey K) { |
| 351 | return Error::success(); |
| 352 | } |
| 353 | |
| 354 | void GDBJITDebugInfoRegistrationPlugin::notifyTransferringResources( |
| 355 | JITDylib &JD, ResourceKey DstKey, ResourceKey SrcKey) {} |
| 356 | |
| 357 | void GDBJITDebugInfoRegistrationPlugin::modifyPassConfig( |
| 358 | MaterializationResponsibility &MR, LinkGraph &LG, |
| 359 | PassConfiguration &PassConfig) { |
| 360 | |
| 361 | if (LG.getTargetTriple().getObjectFormat() == Triple::MachO) |
| 362 | modifyPassConfigForMachO(MR, LG, PassConfig); |
| 363 | else { |
| 364 | LLVM_DEBUG({ |
| 365 | dbgs() << "GDBJITDebugInfoRegistrationPlugin skipping unspported graph " |
| 366 | << LG.getName() << "(triple = " << LG.getTargetTriple().str() |
| 367 | << "\n" ; |
| 368 | }); |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | void GDBJITDebugInfoRegistrationPlugin::modifyPassConfigForMachO( |
| 373 | MaterializationResponsibility &MR, jitlink::LinkGraph &LG, |
| 374 | jitlink::PassConfiguration &PassConfig) { |
| 375 | |
| 376 | switch (LG.getTargetTriple().getArch()) { |
| 377 | case Triple::x86_64: |
| 378 | case Triple::aarch64: |
| 379 | // Supported, continue. |
| 380 | assert(LG.getPointerSize() == 8 && "Graph has incorrect pointer size" ); |
| 381 | assert(LG.getEndianness() == llvm::endianness::little && |
| 382 | "Graph has incorrect endianness" ); |
| 383 | break; |
| 384 | default: |
| 385 | // Unsupported. |
| 386 | LLVM_DEBUG({ |
| 387 | dbgs() << "GDBJITDebugInfoRegistrationPlugin skipping unsupported " |
| 388 | << "MachO graph " << LG.getName() |
| 389 | << "(triple = " << LG.getTargetTriple().str() |
| 390 | << ", pointer size = " << LG.getPointerSize() << ", endianness = " |
| 391 | << (LG.getEndianness() == llvm::endianness::big ? "big" : "little" ) |
| 392 | << ")\n" ; |
| 393 | }); |
| 394 | return; |
| 395 | } |
| 396 | |
| 397 | // Scan for debug sections. If we find one then install passes. |
| 398 | bool HasDebugSections = false; |
| 399 | for (auto &Sec : LG.sections()) |
| 400 | if (MachODebugObjectSynthesizerBase::isDebugSection(Sec)) { |
| 401 | HasDebugSections = true; |
| 402 | break; |
| 403 | } |
| 404 | |
| 405 | if (HasDebugSections) { |
| 406 | LLVM_DEBUG({ |
| 407 | dbgs() << "GDBJITDebugInfoRegistrationPlugin: Graph " << LG.getName() |
| 408 | << " contains debug info. Installing debugger support passes.\n" ; |
| 409 | }); |
| 410 | |
| 411 | auto MDOS = std::make_shared<MachODebugObjectSynthesizer<MachO64LE>>( |
| 412 | args&: MR.getTargetJITDylib().getExecutionSession(), args&: LG, args&: RegisterActionAddr); |
| 413 | PassConfig.PrePrunePasses.push_back( |
| 414 | x: [=](LinkGraph &G) { return MDOS->preserveDebugSections(); }); |
| 415 | PassConfig.PostPrunePasses.push_back( |
| 416 | x: [=](LinkGraph &G) { return MDOS->startSynthesis(); }); |
| 417 | PassConfig.PostFixupPasses.push_back( |
| 418 | x: [=](LinkGraph &G) { return MDOS->completeSynthesisAndRegister(); }); |
| 419 | } else { |
| 420 | LLVM_DEBUG({ |
| 421 | dbgs() << "GDBJITDebugInfoRegistrationPlugin: Graph " << LG.getName() |
| 422 | << " contains no debug info. Skipping.\n" ; |
| 423 | }); |
| 424 | } |
| 425 | } |
| 426 | |
| 427 | } // namespace orc |
| 428 | } // namespace llvm |
| 429 | |