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