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