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