| 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/LookupAndApply.h" |
| 17 | #include "llvm/ExecutionEngine/Orc/SPSProxySpec.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 | // Controller-interface descriptors for the executor's perf-support wrapper |
| 27 | // calls. |
| 28 | namespace llvm::orc::perf_sps_ci { |
| 29 | struct RegisterPerfStart { |
| 30 | static constexpr SymbolNameSpec Name = |
| 31 | SymbolNameSpec::c(Name: "llvm_orc_registerJITLoaderPerfStart" ); |
| 32 | using SPSSig = void(); |
| 33 | }; |
| 34 | struct RegisterPerfEnd { |
| 35 | static constexpr SymbolNameSpec Name = |
| 36 | SymbolNameSpec::c(Name: "llvm_orc_registerJITLoaderPerfEnd" ); |
| 37 | using SPSSig = void(); |
| 38 | }; |
| 39 | } // namespace llvm::orc::perf_sps_ci |
| 40 | |
| 41 | namespace { |
| 42 | |
| 43 | // Creates an EH frame header prepared for a 32-bit relative relocation |
| 44 | // to the start of the .eh_frame section. Absolute injects a 64-bit absolute |
| 45 | // address space offset 4 bytes from the start instead of 4 bytes |
| 46 | Expected<std::string> (Section &EHFrame, |
| 47 | llvm::endianness endianness, |
| 48 | bool absolute) { |
| 49 | uint8_t Version = 1; |
| 50 | uint8_t EhFramePtrEnc = 0; |
| 51 | if (absolute) { |
| 52 | EhFramePtrEnc |= dwarf::DW_EH_PE_sdata8 | dwarf::DW_EH_PE_absptr; |
| 53 | } else { |
| 54 | EhFramePtrEnc |= dwarf::DW_EH_PE_sdata4 | dwarf::DW_EH_PE_datarel; |
| 55 | } |
| 56 | uint8_t FDECountEnc = dwarf::DW_EH_PE_omit; |
| 57 | uint8_t TableEnc = dwarf::DW_EH_PE_omit; |
| 58 | // X86_64_64 relocation to the start of the .eh_frame section |
| 59 | uint32_t EHFrameRelocation = 0; |
| 60 | // uint32_t FDECount = 0; |
| 61 | // Skip the FDE binary search table |
| 62 | // We'd have to reprocess the CIEs to get this information, |
| 63 | // which seems like more trouble than it's worth |
| 64 | // TODO consider implementing this. |
| 65 | // binary search table goes here |
| 66 | |
| 67 | size_t = |
| 68 | (sizeof(Version) + sizeof(EhFramePtrEnc) + sizeof(FDECountEnc) + |
| 69 | sizeof(TableEnc) + |
| 70 | (absolute ? sizeof(uint64_t) : sizeof(EHFrameRelocation))); |
| 71 | std::string (HeaderSize, '\0'); |
| 72 | BinaryStreamWriter Writer( |
| 73 | MutableArrayRef<uint8_t>( |
| 74 | reinterpret_cast<uint8_t *>(HeaderContent.data()), HeaderSize), |
| 75 | endianness); |
| 76 | if (auto Err = Writer.writeInteger(Value: Version)) |
| 77 | return std::move(Err); |
| 78 | if (auto Err = Writer.writeInteger(Value: EhFramePtrEnc)) |
| 79 | return std::move(Err); |
| 80 | if (auto Err = Writer.writeInteger(Value: FDECountEnc)) |
| 81 | return std::move(Err); |
| 82 | if (auto Err = Writer.writeInteger(Value: TableEnc)) |
| 83 | return std::move(Err); |
| 84 | if (absolute) { |
| 85 | uint64_t EHFrameAddr = SectionRange(EHFrame).getStart().getValue(); |
| 86 | if (auto Err = Writer.writeInteger(Value: EHFrameAddr)) |
| 87 | return std::move(Err); |
| 88 | } else { |
| 89 | if (auto Err = Writer.writeInteger(Value: EHFrameRelocation)) |
| 90 | return std::move(Err); |
| 91 | } |
| 92 | return HeaderContent; |
| 93 | } |
| 94 | |
| 95 | constexpr SymbolNameSpec RegisterPerfImplSymbolName = |
| 96 | SymbolNameSpec::c(Name: "llvm_orc_registerJITLoaderPerfImpl" ); |
| 97 | |
| 98 | static PerfJITCodeLoadRecord |
| 99 | getCodeLoadRecord(const Symbol &Sym, std::atomic<uint64_t> &CodeIndex) { |
| 100 | PerfJITCodeLoadRecord Record; |
| 101 | auto Name = *Sym.getName(); |
| 102 | auto Addr = Sym.getAddress(); |
| 103 | auto Size = Sym.getSize(); |
| 104 | Record.Prefix.Id = PerfJITRecordType::JIT_CODE_LOAD; |
| 105 | // Runtime sets PID |
| 106 | Record.Pid = 0; |
| 107 | // Runtime sets TID |
| 108 | Record.Tid = 0; |
| 109 | Record.Vma = Addr.getValue(); |
| 110 | Record.CodeAddr = Addr.getValue(); |
| 111 | Record.CodeSize = Size; |
| 112 | Record.CodeIndex = CodeIndex++; |
| 113 | Record.Name = Name.str(); |
| 114 | // Initialize last, once all the other fields are filled |
| 115 | Record.Prefix.TotalSize = |
| 116 | (2 * sizeof(uint32_t) // id, total_size |
| 117 | + sizeof(uint64_t) // timestamp |
| 118 | + 2 * sizeof(uint32_t) // pid, tid |
| 119 | + 4 * sizeof(uint64_t) // vma, code_addr, code_size, code_index |
| 120 | + Name.size() + 1 // symbol name |
| 121 | + Record.CodeSize // code |
| 122 | ); |
| 123 | return Record; |
| 124 | } |
| 125 | |
| 126 | static std::optional<PerfJITDebugInfoRecord> |
| 127 | getDebugInfoRecord(const Symbol &Sym, DWARFContext &DC) { |
| 128 | auto &Section = Sym.getSection(); |
| 129 | auto Addr = Sym.getAddress(); |
| 130 | auto Size = Sym.getSize(); |
| 131 | auto SAddr = object::SectionedAddress{.Address: Addr.getValue(), .SectionIndex: Section.getOrdinal()}; |
| 132 | LLVM_DEBUG(dbgs() << "Getting debug info for symbol " << Sym.getName() |
| 133 | << " at address " << Addr.getValue() << " with size " |
| 134 | << Size << "\n" |
| 135 | << "Section ordinal: " << Section.getOrdinal() << "\n" ); |
| 136 | auto LInfo = DC.getLineInfoForAddressRange( |
| 137 | Address: SAddr, Size, Specifier: DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath); |
| 138 | if (LInfo.empty()) { |
| 139 | // No line info available |
| 140 | LLVM_DEBUG(dbgs() << "No line info available\n" ); |
| 141 | return std::nullopt; |
| 142 | } |
| 143 | PerfJITDebugInfoRecord Record; |
| 144 | Record.Prefix.Id = PerfJITRecordType::JIT_CODE_DEBUG_INFO; |
| 145 | Record.CodeAddr = Addr.getValue(); |
| 146 | for (const auto &Entry : LInfo) { |
| 147 | auto Addr = Entry.first; |
| 148 | // The function re-created by perf is preceded by a elf |
| 149 | // header. Need to adjust for that, otherwise the results are |
| 150 | // wrong. |
| 151 | Addr += 0x40; |
| 152 | Record.Entries.push_back(x: {.Addr: Addr, .Lineno: Entry.second.Line, |
| 153 | .Discrim: Entry.second.Discriminator, |
| 154 | .Name: Entry.second.FileName}); |
| 155 | } |
| 156 | size_t EntriesBytes = (2 // record header |
| 157 | + 2 // record fields |
| 158 | ) * |
| 159 | sizeof(uint64_t); |
| 160 | for (const auto &Entry : Record.Entries) { |
| 161 | EntriesBytes += |
| 162 | sizeof(uint64_t) + 2 * sizeof(uint32_t); // Addr, Line/Discrim |
| 163 | EntriesBytes += Entry.Name.size() + 1; // Name |
| 164 | } |
| 165 | Record.Prefix.TotalSize = EntriesBytes; |
| 166 | LLVM_DEBUG(dbgs() << "Created debug info record\n" |
| 167 | << "Total size: " << Record.Prefix.TotalSize << "\n" |
| 168 | << "Nr entries: " << Record.Entries.size() << "\n" ); |
| 169 | return Record; |
| 170 | } |
| 171 | |
| 172 | static Expected<PerfJITCodeUnwindingInfoRecord> |
| 173 | getUnwindingRecord(LinkGraph &G) { |
| 174 | PerfJITCodeUnwindingInfoRecord Record; |
| 175 | Record.Prefix.Id = PerfJITRecordType::JIT_CODE_UNWINDING_INFO; |
| 176 | Record.Prefix.TotalSize = 0; |
| 177 | auto Eh_frame = G.findSectionByName(Name: ".eh_frame" ); |
| 178 | if (!Eh_frame) { |
| 179 | LLVM_DEBUG(dbgs() << "No .eh_frame section found\n" ); |
| 180 | return Record; |
| 181 | } |
| 182 | if (!G.getTargetTriple().isOSBinFormatELF()) { |
| 183 | LLVM_DEBUG(dbgs() << "Not an ELF file, will not emit unwinding info\n" ); |
| 184 | return Record; |
| 185 | } |
| 186 | auto SR = SectionRange(*Eh_frame); |
| 187 | auto EHFrameSize = SR.getSize(); |
| 188 | auto Eh_frame_hdr = G.findSectionByName(Name: ".eh_frame_hdr" ); |
| 189 | if (!Eh_frame_hdr) { |
| 190 | if (G.getTargetTriple().getArch() == Triple::x86_64) { |
| 191 | auto Hdr = createX64EHFrameHeader(EHFrame&: *Eh_frame, endianness: G.getEndianness(), absolute: true); |
| 192 | if (!Hdr) |
| 193 | return Hdr.takeError(); |
| 194 | Record.EHFrameHdr = std::move(*Hdr); |
| 195 | } else { |
| 196 | LLVM_DEBUG(dbgs() << "No .eh_frame_hdr section found\n" ); |
| 197 | return Record; |
| 198 | } |
| 199 | Record.EHFrameHdrAddr = 0; |
| 200 | Record.EHFrameHdrSize = Record.EHFrameHdr.size(); |
| 201 | Record.UnwindDataSize = EHFrameSize + Record.EHFrameHdrSize; |
| 202 | Record.MappedSize = 0; // Because the EHFrame header was not mapped |
| 203 | } else { |
| 204 | auto SR = SectionRange(*Eh_frame_hdr); |
| 205 | Record.EHFrameHdrAddr = SR.getStart().getValue(); |
| 206 | Record.EHFrameHdrSize = SR.getSize(); |
| 207 | Record.UnwindDataSize = EHFrameSize + Record.EHFrameHdrSize; |
| 208 | Record.MappedSize = Record.UnwindDataSize; |
| 209 | } |
| 210 | Record.EHFrameAddr = SR.getStart().getValue(); |
| 211 | Record.Prefix.TotalSize = |
| 212 | (2 * sizeof(uint32_t) // id, total_size |
| 213 | + sizeof(uint64_t) // timestamp |
| 214 | + |
| 215 | 3 * sizeof(uint64_t) // unwind_data_size, eh_frame_hdr_size, mapped_size |
| 216 | + Record.UnwindDataSize // eh_frame_hdr, eh_frame |
| 217 | ); |
| 218 | LLVM_DEBUG(dbgs() << "Created unwind record\n" |
| 219 | << "Total size: " << Record.Prefix.TotalSize << "\n" |
| 220 | << "Unwind size: " << Record.UnwindDataSize << "\n" |
| 221 | << "EHFrame size: " << EHFrameSize << "\n" |
| 222 | << "EHFrameHdr size: " << Record.EHFrameHdrSize << "\n" ); |
| 223 | return Record; |
| 224 | } |
| 225 | |
| 226 | static PerfJITRecordBatch getRecords(ExecutionSession &ES, LinkGraph &G, |
| 227 | std::atomic<uint64_t> &CodeIndex, |
| 228 | bool EmitDebugInfo, bool EmitUnwindInfo) { |
| 229 | std::unique_ptr<DWARFContext> DC; |
| 230 | StringMap<std::unique_ptr<MemoryBuffer>> DCBacking; |
| 231 | if (EmitDebugInfo) { |
| 232 | auto EDC = createDWARFContext(G); |
| 233 | if (!EDC) { |
| 234 | ES.reportError(Err: EDC.takeError()); |
| 235 | EmitDebugInfo = false; |
| 236 | } else { |
| 237 | DC = std::move(EDC->first); |
| 238 | DCBacking = std::move(EDC->second); |
| 239 | } |
| 240 | } |
| 241 | PerfJITRecordBatch Batch; |
| 242 | for (auto Sym : G.defined_symbols()) { |
| 243 | if (!Sym->hasName() || !Sym->isCallable()) |
| 244 | continue; |
| 245 | if (EmitDebugInfo) { |
| 246 | auto DebugInfo = getDebugInfoRecord(Sym: *Sym, DC&: *DC); |
| 247 | if (DebugInfo) |
| 248 | Batch.DebugInfoRecords.push_back(x: std::move(*DebugInfo)); |
| 249 | } |
| 250 | Batch.CodeLoadRecords.push_back(x: getCodeLoadRecord(Sym: *Sym, CodeIndex)); |
| 251 | } |
| 252 | if (EmitUnwindInfo) { |
| 253 | auto UWR = getUnwindingRecord(G); |
| 254 | if (!UWR) { |
| 255 | ES.reportError(Err: UWR.takeError()); |
| 256 | } else { |
| 257 | Batch.UnwindingRecord = std::move(*UWR); |
| 258 | } |
| 259 | } else { |
| 260 | Batch.UnwindingRecord.Prefix.TotalSize = 0; |
| 261 | } |
| 262 | return Batch; |
| 263 | } |
| 264 | } // namespace |
| 265 | |
| 266 | PerfSupportPlugin::PerfSupportPlugin(ExecutorProcessControl &EPC, |
| 267 | ExecutorAddr RegisterPerfStartAddr, |
| 268 | ExecutorAddr RegisterPerfEndAddr, |
| 269 | ExecutorAddr RegisterPerfImplAddr, |
| 270 | bool EmitDebugInfo, bool EmitUnwindInfo) |
| 271 | : EPC(EPC), RegisterPerfStart( |
| 272 | sps::ProxySpec<Proxy<void()>, |
| 273 | perf_sps_ci::RegisterPerfStart>::dispatch, |
| 274 | RegisterPerfStartAddr), |
| 275 | RegisterPerfEnd( |
| 276 | sps::ProxySpec<Proxy<void()>, perf_sps_ci::RegisterPerfEnd>::dispatch, |
| 277 | RegisterPerfEndAddr), |
| 278 | RegisterPerfImplAddr(RegisterPerfImplAddr), CodeIndex(0), |
| 279 | EmitDebugInfo(EmitDebugInfo), EmitUnwindInfo(EmitUnwindInfo) { |
| 280 | cantFail(Err: RegisterPerfStart(EPC.getExecutionSession())); |
| 281 | } |
| 282 | PerfSupportPlugin::~PerfSupportPlugin() { |
| 283 | // FIXME: End message from destructor is unreliable. Executor-side perf |
| 284 | // support (currently JITLoaderPerf.cpp in OrcTargetProcess) should be |
| 285 | // reimplemented as a service in the new ORC runtime, where cleanup |
| 286 | // can be run as an on-shutdown event. |
| 287 | // |
| 288 | // For now, switch to async dispatch here so that a message failure doesn't |
| 289 | // load to a crash when we try to read from a std::future whose std::promise |
| 290 | // has been destoryed. |
| 291 | RegisterPerfEnd([](Error Err) { consumeError(Err: std::move(Err)); }, |
| 292 | EPC.getExecutionSession()); |
| 293 | } |
| 294 | |
| 295 | void PerfSupportPlugin::modifyPassConfig(MaterializationResponsibility &MR, |
| 296 | LinkGraph &G, |
| 297 | PassConfiguration &Config) { |
| 298 | Config.PostFixupPasses.push_back(x: [this](LinkGraph &G) { |
| 299 | auto Batch = getRecords(ES&: EPC.getExecutionSession(), G, CodeIndex, |
| 300 | EmitDebugInfo, EmitUnwindInfo); |
| 301 | G.allocActions().push_back( |
| 302 | x: {.Finalize: cantFail(ValOrErr: shared::WrapperFunctionCall::Create< |
| 303 | shared::SPSArgList<shared::SPSPerfJITRecordBatch>>( |
| 304 | FnAddr: RegisterPerfImplAddr, Args: Batch)), |
| 305 | .Dealloc: {}}); |
| 306 | return Error::success(); |
| 307 | }); |
| 308 | } |
| 309 | |
| 310 | Expected<std::unique_ptr<PerfSupportPlugin>> |
| 311 | PerfSupportPlugin::Create(ExecutorProcessControl &EPC, JITDylib &JD, |
| 312 | bool EmitDebugInfo, bool EmitUnwindInfo) { |
| 313 | if (!EPC.getTargetTriple().isOSBinFormatELF()) { |
| 314 | return make_error<StringError>( |
| 315 | Args: "Perf support only available for ELF LinkGraphs!" , |
| 316 | Args: inconvertibleErrorCode()); |
| 317 | } |
| 318 | ExecutorAddr StartAddr, EndAddr, ImplAddr; |
| 319 | if (auto Err = lookupAndApply( |
| 320 | JD, PrepareFns: {recordAddr(Name: perf_sps_ci::RegisterPerfStart::Name, A: &StartAddr), |
| 321 | recordAddr(Name: perf_sps_ci::RegisterPerfEnd::Name, A: &EndAddr), |
| 322 | recordAddr(Name: RegisterPerfImplSymbolName, A: &ImplAddr)})) |
| 323 | return std::move(Err); |
| 324 | return std::make_unique<PerfSupportPlugin>(args&: EPC, args&: StartAddr, args&: EndAddr, args&: ImplAddr, |
| 325 | args&: EmitDebugInfo, args&: EmitUnwindInfo); |
| 326 | } |
| 327 | |