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
22using namespace llvm;
23using namespace llvm::orc;
24using namespace llvm::jitlink;
25
26namespace {
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
31Expected<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
80constexpr StringRef RegisterPerfStartSymbolName =
81    "llvm_orc_registerJITLoaderPerfStart";
82constexpr StringRef RegisterPerfEndSymbolName =
83    "llvm_orc_registerJITLoaderPerfEnd";
84constexpr StringRef RegisterPerfImplSymbolName =
85    "llvm_orc_registerJITLoaderPerfImpl";
86
87static PerfJITCodeLoadRecord
88getCodeLoadRecord(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
115static std::optional<PerfJITDebugInfoRecord>
116getDebugInfoRecord(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
161static Expected<PerfJITCodeUnwindingInfoRecord>
162getUnwindingRecord(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
215static 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
255PerfSupportPlugin::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}
266PerfSupportPlugin::~PerfSupportPlugin() {
267  cantFail(EPC.callSPSWrapper<void()>(RegisterPerfEndAddr));
268}
269
270void 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
285Expected<std::unique_ptr<PerfSupportPlugin>>
286PerfSupportPlugin::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