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