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> (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 = |
53 | (sizeof(Version) + sizeof(EhFramePtrEnc) + sizeof(FDECountEnc) + |
54 | sizeof(TableEnc) + |
55 | (absolute ? sizeof(uint64_t) : sizeof(EHFrameRelocation))); |
56 | std::string (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(Value: Version)) |
62 | return std::move(Err); |
63 | if (auto Err = Writer.writeInteger(Value: EhFramePtrEnc)) |
64 | return std::move(Err); |
65 | if (auto Err = Writer.writeInteger(Value: FDECountEnc)) |
66 | return std::move(Err); |
67 | if (auto Err = Writer.writeInteger(Value: TableEnc)) |
68 | return std::move(Err); |
69 | if (absolute) { |
70 | uint64_t EHFrameAddr = SectionRange(EHFrame).getStart().getValue(); |
71 | if (auto Err = Writer.writeInteger(Value: EHFrameAddr)) |
72 | return std::move(Err); |
73 | } else { |
74 | if (auto Err = Writer.writeInteger(Value: 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{.Address: Addr.getValue(), .SectionIndex: 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 | Address: SAddr, Size, Specifier: 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(x: {.Addr: Addr, .Lineno: Entry.second.Line, |
142 | .Discrim: Entry.second.Discriminator, |
143 | .Name: 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(Name: ".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(Name: ".eh_frame_hdr" ); |
178 | if (!Eh_frame_hdr) { |
179 | if (G.getTargetTriple().getArch() == Triple::x86_64) { |
180 | auto Hdr = createX64EHFrameHeader(EHFrame&: *Eh_frame, endianness: G.getEndianness(), absolute: 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(Err: 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: *Sym, DC&: *DC); |
236 | if (DebugInfo) |
237 | Batch.DebugInfoRecords.push_back(x: std::move(*DebugInfo)); |
238 | } |
239 | Batch.CodeLoadRecords.push_back(x: getCodeLoadRecord(Sym: *Sym, CodeIndex)); |
240 | } |
241 | if (EmitUnwindInfo) { |
242 | auto UWR = getUnwindingRecord(G); |
243 | if (!UWR) { |
244 | ES.reportError(Err: 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(Err: EPC.callSPSWrapper<void()>(WrapperFnAddr: RegisterPerfStartAddr)); |
265 | } |
266 | PerfSupportPlugin::~PerfSupportPlugin() { |
267 | cantFail(Err: EPC.callSPSWrapper<void()>(WrapperFnAddr: RegisterPerfEndAddr)); |
268 | } |
269 | |
270 | void PerfSupportPlugin::modifyPassConfig(MaterializationResponsibility &MR, |
271 | LinkGraph &G, |
272 | PassConfiguration &Config) { |
273 | Config.PostFixupPasses.push_back(x: [this](LinkGraph &G) { |
274 | auto Batch = getRecords(ES&: EPC.getExecutionSession(), G, CodeIndex, |
275 | EmitDebugInfo, EmitUnwindInfo); |
276 | G.allocActions().push_back( |
277 | x: {.Finalize: cantFail(ValOrErr: shared::WrapperFunctionCall::Create< |
278 | shared::SPSArgList<shared::SPSPerfJITRecordBatch>>( |
279 | FnAddr: RegisterPerfImplAddr, Args: Batch)), |
280 | .Dealloc: {}}); |
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 | Args: "Perf support only available for ELF LinkGraphs!" , |
291 | Args: inconvertibleErrorCode()); |
292 | } |
293 | auto &ES = EPC.getExecutionSession(); |
294 | ExecutorAddr StartAddr, EndAddr, ImplAddr; |
295 | if (auto Err = lookupAndRecordAddrs( |
296 | ES, K: LookupKind::Static, SearchOrder: makeJITDylibSearchOrder(JDs: {&JD}), |
297 | Pairs: {{ES.intern(SymName: RegisterPerfStartSymbolName), &StartAddr}, |
298 | {ES.intern(SymName: RegisterPerfEndSymbolName), &EndAddr}, |
299 | {ES.intern(SymName: RegisterPerfImplSymbolName), &ImplAddr}})) |
300 | return std::move(Err); |
301 | return std::make_unique<PerfSupportPlugin>(args&: EPC, args&: StartAddr, args&: EndAddr, args&: ImplAddr, |
302 | args&: EmitDebugInfo, args&: EmitUnwindInfo); |
303 | } |
304 | |