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
createX64EHFrameHeader(Section & EHFrame,llvm::endianness endianness,bool absolute)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
getCodeLoadRecord(const Symbol & Sym,std::atomic<uint64_t> & CodeIndex)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>
getDebugInfoRecord(const Symbol & Sym,DWARFContext & DC)115 getDebugInfoRecord(const Symbol &Sym, DWARFContext &DC) {
116 auto &Section = Sym.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>
getUnwindingRecord(LinkGraph & G)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
getRecords(ExecutionSession & ES,LinkGraph & G,std::atomic<uint64_t> & CodeIndex,bool EmitDebugInfo,bool EmitUnwindInfo)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
PerfSupportPlugin(ExecutorProcessControl & EPC,ExecutorAddr RegisterPerfStartAddr,ExecutorAddr RegisterPerfEndAddr,ExecutorAddr RegisterPerfImplAddr,bool EmitDebugInfo,bool EmitUnwindInfo)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 }
~PerfSupportPlugin()265 PerfSupportPlugin::~PerfSupportPlugin() {
266 cantFail(EPC.callSPSWrapper<void()>(RegisterPerfEndAddr));
267 }
268
modifyPassConfig(MaterializationResponsibility & MR,LinkGraph & G,PassConfiguration & Config)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>>
Create(ExecutorProcessControl & EPC,JITDylib & JD,bool EmitDebugInfo,bool EmitUnwindInfo)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