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