| 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 | |