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
24using namespace llvm;
25using namespace llvm::jitlink;
26using namespace llvm::orc;
27
28static const char *SynthDebugSectionName = "__jitlink_synth_debug_object";
29
30namespace {
31
32class MachODebugObjectSynthesizerBase
33 : public GDBJITDebugInfoRegistrationPlugin::DebugSectionSynthesizer {
34public:
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
81protected:
82 LinkGraph &G;
83 ExecutorAddr RegisterActionAddr;
84};
85
86template <typename MachOTraits>
87class MachODebugObjectSynthesizer : public MachODebugObjectSynthesizerBase {
88public:
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
297private:
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
325namespace llvm {
326namespace orc {
327
328Expected<std::unique_ptr<GDBJITDebugInfoRegistrationPlugin>>
329GDBJITDebugInfoRegistrationPlugin::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
344Error GDBJITDebugInfoRegistrationPlugin::notifyFailed(
345 MaterializationResponsibility &MR) {
346 return Error::success();
347}
348
349Error GDBJITDebugInfoRegistrationPlugin::notifyRemovingResources(
350 JITDylib &JD, ResourceKey K) {
351 return Error::success();
352}
353
354void GDBJITDebugInfoRegistrationPlugin::notifyTransferringResources(
355 JITDylib &JD, ResourceKey DstKey, ResourceKey SrcKey) {}
356
357void 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
372void 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