xref: /llvm-project/llvm/lib/ExecutionEngine/Orc/Debugging/PerfSupportPlugin.cpp (revision 2ccf7ed277df28651b94bbee9fccefdf22fb074f)
1 //===----- PerfSupportPlugin.cpp --- Utils for perf support -----*- C++ -*-===//
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 // Handles support for registering code with perf
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "llvm/ExecutionEngine/Orc/Debugging/PerfSupportPlugin.h"
14 
15 #include "llvm/ExecutionEngine/Orc/Debugging/DebugInfoSupport.h"
16 #include "llvm/ExecutionEngine/Orc/LookupAndRecordAddrs.h"
17 #include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h"
18 
19 #define DEBUG_TYPE "orc"
20 
21 using namespace llvm;
22 using namespace llvm::orc;
23 using namespace llvm::jitlink;
24 
25 namespace {
26 
27 // Creates an EH frame header prepared for a 32-bit relative relocation
28 // to the start of the .eh_frame section. Absolute injects a 64-bit absolute
29 // address space offset 4 bytes from the start instead of 4 bytes
30 Expected<std::string> createX64EHFrameHeader(Section &EHFrame,
31                                              llvm::endianness endianness,
32                                              bool absolute) {
33   uint8_t Version = 1;
34   uint8_t EhFramePtrEnc = 0;
35   if (absolute) {
36     EhFramePtrEnc |= dwarf::DW_EH_PE_sdata8 | dwarf::DW_EH_PE_absptr;
37   } else {
38     EhFramePtrEnc |= dwarf::DW_EH_PE_sdata4 | dwarf::DW_EH_PE_datarel;
39   }
40   uint8_t FDECountEnc = dwarf::DW_EH_PE_omit;
41   uint8_t TableEnc = dwarf::DW_EH_PE_omit;
42   // X86_64_64 relocation to the start of the .eh_frame section
43   uint32_t EHFrameRelocation = 0;
44   // uint32_t FDECount = 0;
45   // Skip the FDE binary search table
46   // We'd have to reprocess the CIEs to get this information,
47   // which seems like more trouble than it's worth
48   // TODO consider implementing this.
49   // binary search table goes here
50 
51   size_t HeaderSize =
52       (sizeof(Version) + sizeof(EhFramePtrEnc) + sizeof(FDECountEnc) +
53        sizeof(TableEnc) +
54        (absolute ? sizeof(uint64_t) : sizeof(EHFrameRelocation)));
55   std::string HeaderContent(HeaderSize, '\0');
56   BinaryStreamWriter Writer(
57       MutableArrayRef<uint8_t>(
58           reinterpret_cast<uint8_t *>(HeaderContent.data()), HeaderSize),
59       endianness);
60   if (auto Err = Writer.writeInteger(Version))
61     return std::move(Err);
62   if (auto Err = Writer.writeInteger(EhFramePtrEnc))
63     return std::move(Err);
64   if (auto Err = Writer.writeInteger(FDECountEnc))
65     return std::move(Err);
66   if (auto Err = Writer.writeInteger(TableEnc))
67     return std::move(Err);
68   if (absolute) {
69     uint64_t EHFrameAddr = SectionRange(EHFrame).getStart().getValue();
70     if (auto Err = Writer.writeInteger(EHFrameAddr))
71       return std::move(Err);
72   } else {
73     if (auto Err = Writer.writeInteger(EHFrameRelocation))
74       return std::move(Err);
75   }
76   return HeaderContent;
77 }
78 
79 constexpr StringRef RegisterPerfStartSymbolName =
80     "llvm_orc_registerJITLoaderPerfStart";
81 constexpr StringRef RegisterPerfEndSymbolName =
82     "llvm_orc_registerJITLoaderPerfEnd";
83 constexpr StringRef RegisterPerfImplSymbolName =
84     "llvm_orc_registerJITLoaderPerfImpl";
85 
86 static PerfJITCodeLoadRecord
87 getCodeLoadRecord(const Symbol &Sym, std::atomic<uint64_t> &CodeIndex) {
88   PerfJITCodeLoadRecord Record;
89   auto Name = *Sym.getName();
90   auto Addr = Sym.getAddress();
91   auto Size = Sym.getSize();
92   Record.Prefix.Id = PerfJITRecordType::JIT_CODE_LOAD;
93   // Runtime sets PID
94   Record.Pid = 0;
95   // Runtime sets TID
96   Record.Tid = 0;
97   Record.Vma = Addr.getValue();
98   Record.CodeAddr = Addr.getValue();
99   Record.CodeSize = Size;
100   Record.CodeIndex = CodeIndex++;
101   Record.Name = Name.str();
102   // Initialize last, once all the other fields are filled
103   Record.Prefix.TotalSize =
104       (2 * sizeof(uint32_t)   // id, total_size
105        + sizeof(uint64_t)     // timestamp
106        + 2 * sizeof(uint32_t) // pid, tid
107        + 4 * sizeof(uint64_t) // vma, code_addr, code_size, code_index
108        + Name.size() + 1      // symbol name
109        + Record.CodeSize      // code
110       );
111   return Record;
112 }
113 
114 static std::optional<PerfJITDebugInfoRecord>
115 getDebugInfoRecord(const Symbol &Sym, DWARFContext &DC) {
116   auto &Section = Sym.getBlock().getSection();
117   auto Addr = Sym.getAddress();
118   auto Size = Sym.getSize();
119   auto SAddr = object::SectionedAddress{Addr.getValue(), Section.getOrdinal()};
120   LLVM_DEBUG(dbgs() << "Getting debug info for symbol " << Sym.getName()
121                     << " at address " << Addr.getValue() << " with size "
122                     << Size << "\n"
123                     << "Section ordinal: " << Section.getOrdinal() << "\n");
124   auto LInfo = DC.getLineInfoForAddressRange(
125       SAddr, Size, DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath);
126   if (LInfo.empty()) {
127     // No line info available
128     LLVM_DEBUG(dbgs() << "No line info available\n");
129     return std::nullopt;
130   }
131   PerfJITDebugInfoRecord Record;
132   Record.Prefix.Id = PerfJITRecordType::JIT_CODE_DEBUG_INFO;
133   Record.CodeAddr = Addr.getValue();
134   for (const auto &Entry : LInfo) {
135     auto Addr = Entry.first;
136     // The function re-created by perf is preceded by a elf
137     // header. Need to adjust for that, otherwise the results are
138     // wrong.
139     Addr += 0x40;
140     Record.Entries.push_back({Addr, Entry.second.Line,
141                               Entry.second.Discriminator,
142                               Entry.second.FileName});
143   }
144   size_t EntriesBytes = (2   // record header
145                          + 2 // record fields
146                          ) *
147                         sizeof(uint64_t);
148   for (const auto &Entry : Record.Entries) {
149     EntriesBytes +=
150         sizeof(uint64_t) + 2 * sizeof(uint32_t); // Addr, Line/Discrim
151     EntriesBytes += Entry.Name.size() + 1;       // Name
152   }
153   Record.Prefix.TotalSize = EntriesBytes;
154   LLVM_DEBUG(dbgs() << "Created debug info record\n"
155                     << "Total size: " << Record.Prefix.TotalSize << "\n"
156                     << "Nr entries: " << Record.Entries.size() << "\n");
157   return Record;
158 }
159 
160 static Expected<PerfJITCodeUnwindingInfoRecord>
161 getUnwindingRecord(LinkGraph &G) {
162   PerfJITCodeUnwindingInfoRecord Record;
163   Record.Prefix.Id = PerfJITRecordType::JIT_CODE_UNWINDING_INFO;
164   Record.Prefix.TotalSize = 0;
165   auto Eh_frame = G.findSectionByName(".eh_frame");
166   if (!Eh_frame) {
167     LLVM_DEBUG(dbgs() << "No .eh_frame section found\n");
168     return Record;
169   }
170   if (!G.getTargetTriple().isOSBinFormatELF()) {
171     LLVM_DEBUG(dbgs() << "Not an ELF file, will not emit unwinding info\n");
172     return Record;
173   }
174   auto SR = SectionRange(*Eh_frame);
175   auto EHFrameSize = SR.getSize();
176   auto Eh_frame_hdr = G.findSectionByName(".eh_frame_hdr");
177   if (!Eh_frame_hdr) {
178     if (G.getTargetTriple().getArch() == Triple::x86_64) {
179       auto Hdr = createX64EHFrameHeader(*Eh_frame, G.getEndianness(), true);
180       if (!Hdr)
181         return Hdr.takeError();
182       Record.EHFrameHdr = std::move(*Hdr);
183     } else {
184       LLVM_DEBUG(dbgs() << "No .eh_frame_hdr section found\n");
185       return Record;
186     }
187     Record.EHFrameHdrAddr = 0;
188     Record.EHFrameHdrSize = Record.EHFrameHdr.size();
189     Record.UnwindDataSize = EHFrameSize + Record.EHFrameHdrSize;
190     Record.MappedSize = 0; // Because the EHFrame header was not mapped
191   } else {
192     auto SR = SectionRange(*Eh_frame_hdr);
193     Record.EHFrameHdrAddr = SR.getStart().getValue();
194     Record.EHFrameHdrSize = SR.getSize();
195     Record.UnwindDataSize = EHFrameSize + Record.EHFrameHdrSize;
196     Record.MappedSize = Record.UnwindDataSize;
197   }
198   Record.EHFrameAddr = SR.getStart().getValue();
199   Record.Prefix.TotalSize =
200       (2 * sizeof(uint32_t) // id, total_size
201        + sizeof(uint64_t)   // timestamp
202        +
203        3 * sizeof(uint64_t) // unwind_data_size, eh_frame_hdr_size, mapped_size
204        + Record.UnwindDataSize // eh_frame_hdr, eh_frame
205       );
206   LLVM_DEBUG(dbgs() << "Created unwind record\n"
207                     << "Total size: " << Record.Prefix.TotalSize << "\n"
208                     << "Unwind size: " << Record.UnwindDataSize << "\n"
209                     << "EHFrame size: " << EHFrameSize << "\n"
210                     << "EHFrameHdr size: " << Record.EHFrameHdrSize << "\n");
211   return Record;
212 }
213 
214 static PerfJITRecordBatch getRecords(ExecutionSession &ES, LinkGraph &G,
215                                      std::atomic<uint64_t> &CodeIndex,
216                                      bool EmitDebugInfo, bool EmitUnwindInfo) {
217   std::unique_ptr<DWARFContext> DC;
218   StringMap<std::unique_ptr<MemoryBuffer>> DCBacking;
219   if (EmitDebugInfo) {
220     auto EDC = createDWARFContext(G);
221     if (!EDC) {
222       ES.reportError(EDC.takeError());
223       EmitDebugInfo = false;
224     } else {
225       DC = std::move(EDC->first);
226       DCBacking = std::move(EDC->second);
227     }
228   }
229   PerfJITRecordBatch Batch;
230   for (auto Sym : G.defined_symbols()) {
231     if (!Sym->hasName() || !Sym->isCallable())
232       continue;
233     if (EmitDebugInfo) {
234       auto DebugInfo = getDebugInfoRecord(*Sym, *DC);
235       if (DebugInfo)
236         Batch.DebugInfoRecords.push_back(std::move(*DebugInfo));
237     }
238     Batch.CodeLoadRecords.push_back(getCodeLoadRecord(*Sym, CodeIndex));
239   }
240   if (EmitUnwindInfo) {
241     auto UWR = getUnwindingRecord(G);
242     if (!UWR) {
243       ES.reportError(UWR.takeError());
244     } else {
245       Batch.UnwindingRecord = std::move(*UWR);
246     }
247   } else {
248     Batch.UnwindingRecord.Prefix.TotalSize = 0;
249   }
250   return Batch;
251 }
252 } // namespace
253 
254 PerfSupportPlugin::PerfSupportPlugin(ExecutorProcessControl &EPC,
255                                      ExecutorAddr RegisterPerfStartAddr,
256                                      ExecutorAddr RegisterPerfEndAddr,
257                                      ExecutorAddr RegisterPerfImplAddr,
258                                      bool EmitDebugInfo, bool EmitUnwindInfo)
259     : EPC(EPC), RegisterPerfStartAddr(RegisterPerfStartAddr),
260       RegisterPerfEndAddr(RegisterPerfEndAddr),
261       RegisterPerfImplAddr(RegisterPerfImplAddr), CodeIndex(0),
262       EmitDebugInfo(EmitDebugInfo), EmitUnwindInfo(EmitUnwindInfo) {
263   cantFail(EPC.callSPSWrapper<void()>(RegisterPerfStartAddr));
264 }
265 PerfSupportPlugin::~PerfSupportPlugin() {
266   cantFail(EPC.callSPSWrapper<void()>(RegisterPerfEndAddr));
267 }
268 
269 void PerfSupportPlugin::modifyPassConfig(MaterializationResponsibility &MR,
270                                          LinkGraph &G,
271                                          PassConfiguration &Config) {
272   Config.PostFixupPasses.push_back([this](LinkGraph &G) {
273     auto Batch = getRecords(EPC.getExecutionSession(), G, CodeIndex,
274                             EmitDebugInfo, EmitUnwindInfo);
275     G.allocActions().push_back(
276         {cantFail(shared::WrapperFunctionCall::Create<
277                   shared::SPSArgList<shared::SPSPerfJITRecordBatch>>(
278              RegisterPerfImplAddr, Batch)),
279          {}});
280     return Error::success();
281   });
282 }
283 
284 Expected<std::unique_ptr<PerfSupportPlugin>>
285 PerfSupportPlugin::Create(ExecutorProcessControl &EPC, JITDylib &JD,
286                           bool EmitDebugInfo, bool EmitUnwindInfo) {
287   if (!EPC.getTargetTriple().isOSBinFormatELF()) {
288     return make_error<StringError>(
289         "Perf support only available for ELF LinkGraphs!",
290         inconvertibleErrorCode());
291   }
292   auto &ES = EPC.getExecutionSession();
293   ExecutorAddr StartAddr, EndAddr, ImplAddr;
294   if (auto Err = lookupAndRecordAddrs(
295           ES, LookupKind::Static, makeJITDylibSearchOrder({&JD}),
296           {{ES.intern(RegisterPerfStartSymbolName), &StartAddr},
297            {ES.intern(RegisterPerfEndSymbolName), &EndAddr},
298            {ES.intern(RegisterPerfImplSymbolName), &ImplAddr}}))
299     return std::move(Err);
300   return std::make_unique<PerfSupportPlugin>(EPC, StartAddr, EndAddr, ImplAddr,
301                                              EmitDebugInfo, EmitUnwindInfo);
302 }
303