diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/x86_64.h b/llvm/include/llvm/ExecutionEngine/JITLink/x86_64.h --- a/llvm/include/llvm/ExecutionEngine/JITLink/x86_64.h +++ b/llvm/include/llvm/ExecutionEngine/JITLink/x86_64.h @@ -78,7 +78,7 @@ /// Delta from the fixup to the target. /// /// Fixup expression: - /// Fixup <- Target - Fixup + Addend : int64 + /// Fixup <- Target - Fixup + Addend : int32 /// /// Errors: /// - The result of the fixup expression must fit into an int32, otherwise diff --git a/llvm/include/llvm/ExecutionEngine/Orc/DebugInfoPlugins.h b/llvm/include/llvm/ExecutionEngine/Orc/DebugInfoPlugins.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/DebugInfoPlugins.h @@ -0,0 +1,81 @@ +//===--- DWARFContextPlugin.h -- Plugin to get DWARFContexts ---*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Reusable component to obtain DWARFContexts for LinkGraphs +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_DEBUGINFOPLUGINS_H +#define LLVM_EXECUTIONENGINE_ORC_DEBUGINFOPLUGINS_H + +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/DebugInfo/DWARF/DWARFContext.h" + +namespace llvm { +namespace orc { + +class PreserveDebugInfoSectionsPlugin : public ObjectLinkingLayer::Plugin { +public: + void modifyPassConfig(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, + jitlink::PassConfiguration &Config) override; + Error notifyFailed(MaterializationResponsibility &MR) override { + return Error::success(); + } + Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { + return Error::success(); + } + void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, + ResourceKey SrcKey) override {} + static Expected> Create(); +}; + +class DWARFContextPlugin : public ObjectLinkingLayer::Plugin { +public: + Expected getDWARFContext(MaterializationResponsibility &MR); + + void modifyPassConfig(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, + jitlink::PassConfiguration &Config) override; + + Error notifyEmitted(MaterializationResponsibility &MR) override; + Error notifyFailed(MaterializationResponsibility &MR) override; + + Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { + return Error::success(); + } + + void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, + ResourceKey SrcKey) override {} + + static Expected> Create(); +private: + + // Offset, Addend + typedef std::pair DWARFRelocation; + // Map from DWARF section name -> + // section data + // Map from relocation target section name -> + // Map from relocation target symbol name -> + // [DWARFRelocationEdge] + typedef StringMap, StringMap>>>> InFlightDWARFSections; + std::mutex Mutex; + // In-flight DWARFContexts, between pre-prune and pre-fixup + DenseMap InFlightDWARF; + // Finalized DWARFContexts, available after the plugin's pre-fixup pass has run + DenseMap>, std::unique_ptr>> DWARFContexts; +}; + +std::string createEHFrameHeader(jitlink::Section &EHFrame, support::endianness endianness, bool absolute); +Expected createEHFrameHeaderSection(jitlink::LinkGraph &G, jitlink::Section &EHFrameSection); + +} // namespace orc +} // namespace llvm + +#endif // LLVM_EXECUTIONENGINE_ORC_DEBUGINFOPLUGINS_H diff --git a/llvm/include/llvm/ExecutionEngine/Orc/PerfSupportPlugin.h b/llvm/include/llvm/ExecutionEngine/Orc/PerfSupportPlugin.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/PerfSupportPlugin.h @@ -0,0 +1,301 @@ +//===--- PerfSupportPlugin.h -- Utils for perf support ---*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Handles support for registering code with perf +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_PERFSUPPORTPLUGIN_H +#define LLVM_EXECUTIONENGINE_ORC_PERFSUPPORTPLUGIN_H + +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/Shared/SimplePackedSerialization.h" + +namespace llvm { +namespace orc { + +class DWARFContextPlugin; + +class PerfSupportPlugin : public ObjectLinkingLayer::Plugin { +public: + PerfSupportPlugin(ExecutorProcessControl &EPC, ExecutorAddr RegisterPerfStartAddr, + ExecutorAddr RegisterPerfEndAddr, ExecutorAddr RegisterPerfImplAddr, + DWARFContextPlugin *DwarfPlugin); + ~PerfSupportPlugin(); + + void modifyPassConfig(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, + jitlink::PassConfiguration &Config) override; + + Error notifyFailed(MaterializationResponsibility &MR) override { + return Error::success(); + } + + Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { + return Error::success(); + } + + void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, + ResourceKey SrcKey) override {} + + static Expected> Create(ExecutorProcessControl &EPC, JITDylib &JD, DWARFContextPlugin *DwarfPlugin = nullptr); +private: + ExecutorProcessControl &EPC; + ExecutorAddr RegisterPerfStartAddr; + ExecutorAddr RegisterPerfEndAddr; + ExecutorAddr RegisterPerfImplAddr; + DWARFContextPlugin *DWARFPlugin; + std::atomic CodeIndex; +}; + +// The following are POD struct definitions from the perf jit specification + +enum class PerfJITRecordType { + JIT_CODE_LOAD = 0, + JIT_CODE_MOVE = 1, // not emitted, code isn't moved + JIT_CODE_DEBUG_INFO = 2, + JIT_CODE_CLOSE = 3, // not emitted, unnecessary + JIT_CODE_UNWINDING_INFO = 4, // not emitted + + JIT_CODE_MAX +}; + +struct PerfJITRecordPrefix { + PerfJITRecordType Id; // record type identifier, uint32_t + uint32_t TotalSize; + uint64_t Timestamp; +}; +struct PerfJITCodeLoadRecord { + PerfJITRecordPrefix Prefix; + + uint32_t Pid; + uint32_t Tid; + uint64_t Vma; + uint64_t CodeAddr; + uint64_t CodeSize; + uint64_t CodeIndex; + std::string Name; +}; + +struct PerfJITDebugEntry { + uint64_t Addr; + uint32_t Lineno; // source line number starting at 1 + uint32_t Discrim; // column discriminator, 0 is default + std::string Name; +}; + +struct PerfJITDebugInfoRecord { + PerfJITRecordPrefix Prefix; + + uint64_t CodeAddr; + // this is technically redundant, but it serves as documentation of the actual record format + uint64_t NrEntry; + std::vector Entries; +}; + +struct PerfJITCodeUnwindingInfoRecord { + PerfJITRecordPrefix Prefix; + + uint64_t UnwindDataSize; + uint64_t EHFrameHdrSize; + uint64_t MappedSize; + // Union, one will always be 0/"", the other has data + uint64_t EHFrameHdrAddr; + std::string EHFrameHdr; + uint64_t EHFrameAddr; + // size is UnwindDataSize - EHFrameHdrSize +}; + +// Batch vehicle for minimizing RPC calls for perf jit records +struct PerfJITRecordBatch { + std::vector DebugInfoRecords; + std::vector CodeLoadRecords; + // only valid if record size > 0 + PerfJITCodeUnwindingInfoRecord UnwindingRecord; +}; + +// SPS traits for Records + +namespace shared { + +using SPSPerfJITRecordPrefix = SPSTuple; + +template<> class SPSSerializationTraits { +public: + using ImplTupleType = std::tuple; + static size_t size(const PerfJITRecordPrefix &Val) { + auto tup = std::make_tuple(static_cast(Val.Id), Val.TotalSize, Val.Timestamp); + return SPSSerializationTraits::size(tup); + } + static bool deserialize(SPSInputBuffer &IB, PerfJITRecordPrefix &Val) { + ImplTupleType Tup; + if (SPSSerializationTraits::deserialize(IB, Tup)) { + Val.Id = static_cast(std::move(std::get<0>(Tup))); + Val.TotalSize = std::move(std::get<1>(Tup)); + Val.Timestamp = std::move(std::get<2>(Tup)); + return true; + } else { + return false; + } + } + static bool serialize(SPSOutputBuffer &OB, const PerfJITRecordPrefix &Val) { + return SPSSerializationTraits::serialize( + OB, std::make_tuple(static_cast(Val.Id), Val.TotalSize, Val.Timestamp)); + } +}; + +using SPSPerfJITCodeLoadRecord = SPSTuple; + +template<> class SPSSerializationTraits { +public: + using ImplTupleType = std::tuple; + static size_t size(const PerfJITCodeLoadRecord &Val) { + auto tup = std::make_tuple(Val.Prefix, Val.Pid, Val.Tid, Val.Vma, Val.CodeAddr, Val.CodeSize, Val.CodeIndex, Val.Name); + return SPSSerializationTraits::size(tup); + } + + static bool deserialize(SPSInputBuffer &IB, PerfJITCodeLoadRecord &Val) { + ImplTupleType Tup; + if (SPSSerializationTraits::deserialize(IB, Tup)) { + Val.Prefix = std::move(std::get<0>(Tup)); + Val.Pid = std::move(std::get<1>(Tup)); + Val.Tid = std::move(std::get<2>(Tup)); + Val.Vma = std::move(std::get<3>(Tup)); + Val.CodeAddr = std::move(std::get<4>(Tup)); + Val.CodeSize = std::move(std::get<5>(Tup)); + Val.CodeIndex = std::move(std::get<6>(Tup)); + Val.Name = std::move(std::get<7>(Tup)); + return true; + } else { + return false; + } + } + + static bool serialize(SPSOutputBuffer &OB, const PerfJITCodeLoadRecord &Val) { + return SPSSerializationTraits::serialize( + OB, std::make_tuple(Val.Prefix, Val.Pid, Val.Tid, Val.Vma, Val.CodeAddr, Val.CodeSize, Val.CodeIndex, Val.Name)); + } +}; + +using SPSPerfJITDebugEntry = SPSTuple; + +template<> class SPSSerializationTraits { +public: + using ImplTupleType = std::tuple; + static size_t size(const PerfJITDebugEntry &Val) { + auto tup = std::make_tuple(Val.Addr, Val.Lineno, Val.Discrim, Val.Name); + return SPSSerializationTraits::size(tup); + } + + static bool deserialize(SPSInputBuffer &IB, PerfJITDebugEntry &Val) { + ImplTupleType Tup; + if (SPSSerializationTraits::deserialize(IB, Tup)) { + Val.Addr = std::move(std::get<0>(Tup)); + Val.Lineno = std::move(std::get<1>(Tup)); + Val.Discrim = std::move(std::get<2>(Tup)); + Val.Name = std::move(std::get<3>(Tup)); + return true; + } else { + return false; + } + } + + static bool serialize(SPSOutputBuffer &OB, const PerfJITDebugEntry &Val) { + return SPSSerializationTraits::serialize( + OB, std::make_tuple(Val.Addr, Val.Lineno, Val.Discrim, Val.Name)); + } +}; + +using SPSPerfJITDebugInfoRecord = SPSTuple>; + +template<> class SPSSerializationTraits { +public: + using ImplTupleType = std::tuple>; + static size_t size(const PerfJITDebugInfoRecord &Val) { + auto tup = std::make_tuple(Val.Prefix, Val.CodeAddr, Val.NrEntry, Val.Entries); + return SPSSerializationTraits::size(tup); + } + static bool deserialize(SPSInputBuffer &IB, PerfJITDebugInfoRecord &Val) { + ImplTupleType Tup; + if (SPSSerializationTraits::deserialize(IB, Tup)) { + Val.Prefix = std::move(std::get<0>(Tup)); + Val.CodeAddr = std::move(std::get<1>(Tup)); + Val.NrEntry = std::move(std::get<2>(Tup)); + Val.Entries = std::move(std::get<3>(Tup)); + return true; + } else { + return false; + } + } + static bool serialize(SPSOutputBuffer &OB, const PerfJITDebugInfoRecord &Val) { + return SPSSerializationTraits::serialize( + OB, std::make_tuple(Val.Prefix, Val.CodeAddr, Val.NrEntry, Val.Entries)); + } +}; + +using SPSPerfJITCodeUnwindingInfoRecord = SPSTuple; +template<> class SPSSerializationTraits { +public: + using ImplTupleType = std::tuple; + static size_t size(const PerfJITCodeUnwindingInfoRecord &Val) { + auto tup = std::make_tuple(Val.Prefix, Val.UnwindDataSize, Val.EHFrameHdrSize, Val.MappedSize, Val.EHFrameHdrAddr, Val.EHFrameHdr, Val.EHFrameAddr); + return SPSSerializationTraits::size(tup); + } + static bool deserialize(SPSInputBuffer &IB, PerfJITCodeUnwindingInfoRecord &Val) { + ImplTupleType Tup; + if (SPSSerializationTraits::deserialize(IB, Tup)) { + Val.Prefix = std::move(std::get<0>(Tup)); + Val.UnwindDataSize = std::move(std::get<1>(Tup)); + Val.EHFrameHdrSize = std::move(std::get<2>(Tup)); + Val.MappedSize = std::move(std::get<3>(Tup)); + Val.EHFrameHdrAddr = std::move(std::get<4>(Tup)); + Val.EHFrameHdr = std::move(std::get<5>(Tup)); + Val.EHFrameAddr = std::move(std::get<6>(Tup)); + return true; + } else { + return false; + } + } + static bool serialize(SPSOutputBuffer &OB, const PerfJITCodeUnwindingInfoRecord &Val) { + return SPSSerializationTraits::serialize( + OB, std::make_tuple(Val.Prefix, Val.UnwindDataSize, Val.EHFrameHdrSize, Val.MappedSize, Val.EHFrameHdrAddr, Val.EHFrameHdr, Val.EHFrameAddr)); + } +}; + +using SPSPerfJITRecordBatch = SPSTuple, SPSSequence, SPSPerfJITCodeUnwindingInfoRecord>; +template<> class SPSSerializationTraits { +public: + using ImplTupleType = std::tuple, std::vector, PerfJITCodeUnwindingInfoRecord>; + static size_t size(const PerfJITRecordBatch &Val) { + auto tup = std::make_tuple(Val.CodeLoadRecords, Val.DebugInfoRecords, Val.UnwindingRecord); + return SPSSerializationTraits::size(tup); + } + static bool deserialize(SPSInputBuffer &IB, PerfJITRecordBatch &Val) { + ImplTupleType Tup; + if (SPSSerializationTraits::deserialize(IB, Tup)) { + Val.CodeLoadRecords = std::move(std::get<0>(Tup)); + Val.DebugInfoRecords = std::move(std::get<1>(Tup)); + Val.UnwindingRecord = std::move(std::get<2>(Tup)); + return true; + } else { + return false; + } + } + static bool serialize(SPSOutputBuffer &OB, const PerfJITRecordBatch &Val) { + return SPSSerializationTraits::serialize( + OB, std::make_tuple(Val.CodeLoadRecords, Val.DebugInfoRecords, Val.UnwindingRecord)); + } +}; + +} + +} // namespace orc +} // namespace llvm + +#endif // LLVM_EXECUTIONENGINE_ORC_DEBUGGERSUPPORTPLUGIN_H diff --git a/llvm/include/llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h b/llvm/include/llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h @@ -0,0 +1,28 @@ +//===- JITLoaderPerf.h - Register objects via perf JIT interface -*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Register objects for access by profilers via the perf JIT interface. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_TARGETPROCESS_JITLOADERPERF_H +#define LLVM_EXECUTIONENGINE_ORC_TARGETPROCESS_JITLOADERPERF_H + +#include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h" +#include + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfImpl(const char *Data, uint64_t Size); + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfStart(const char *Data, uint64_t Size); + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfEnd(const char *Data, uint64_t Size); + +#endif // LLVM_EXECUTIONENGINE_ORC_TARGETPROCESS_JITLOADERPERF_H diff --git a/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h b/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h --- a/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h +++ b/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h @@ -308,26 +308,26 @@ if (!Name) return Name.takeError(); - // If the name indicates that it's a debug section then skip it: We don't - // support those yet. - if (isDwarfSection(*Name)) { - LLVM_DEBUG({ - dbgs() << " " << SecIndex << ": \"" << *Name - << "\" is a debug section: " - "No graph section will be created.\n"; - }); - continue; - } - - // Skip non-SHF_ALLOC sections - if (!(Sec.sh_flags & ELF::SHF_ALLOC)) { - LLVM_DEBUG({ - dbgs() << " " << SecIndex << ": \"" << *Name - << "\" is not an SHF_ALLOC section: " - "No graph section will be created.\n"; - }); - continue; - } + // // If the name indicates that it's a debug section then skip it: We don't + // // support those yet. + // if (isDwarfSection(*Name)) { + // LLVM_DEBUG({ + // dbgs() << " " << SecIndex << ": \"" << *Name + // << "\" is a debug section: " + // "No graph section will be created.\n"; + // }); + // continue; + // } + + // // Skip non-SHF_ALLOC sections + // if (!(Sec.sh_flags & ELF::SHF_ALLOC)) { + // LLVM_DEBUG({ + // dbgs() << " " << SecIndex << ": \"" << *Name + // << "\" is not an SHF_ALLOC section: " + // "No graph section will be created.\n"; + // }); + // continue; + // } LLVM_DEBUG({ dbgs() << " " << SecIndex << ": Creating section for \"" << *Name @@ -532,11 +532,11 @@ return Name.takeError(); LLVM_DEBUG(dbgs() << " " << *Name << ":\n"); - // Consider skipping these relocations. - if (!ProcessDebugSections && isDwarfSection(*Name)) { - LLVM_DEBUG(dbgs() << " skipped (dwarf section)\n\n"); - return Error::success(); - } + // // Consider skipping these relocations. + // if (!ProcessDebugSections && isDwarfSection(*Name)) { + // LLVM_DEBUG(dbgs() << " skipped (dwarf section)\n\n"); + // return Error::success(); + // } // Lookup the link-graph node corresponding to the target section name. auto *BlockToFix = getGraphBlock(RelSect.sh_info); @@ -579,11 +579,11 @@ return Name.takeError(); LLVM_DEBUG(dbgs() << " " << *Name << ":\n"); - // Consider skipping these relocations. - if (!ProcessDebugSections && isDwarfSection(*Name)) { - LLVM_DEBUG(dbgs() << " skipped (dwarf section)\n\n"); - return Error::success(); - } + // // Consider skipping these relocations. + // if (!ProcessDebugSections && isDwarfSection(*Name)) { + // LLVM_DEBUG(dbgs() << " skipped (dwarf section)\n\n"); + // return Error::success(); + // } // Lookup the link-graph node corresponding to the target section name. auto *BlockToFix = getGraphBlock(RelSect.sh_info); diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp --- a/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp @@ -108,6 +108,7 @@ enum ELFX86RelocationKind : Edge::Kind { Branch32 = Edge::FirstRelocation, + Pointer32, Pointer32Signed, Pointer64, PCRel32, @@ -123,6 +124,8 @@ static Expected getRelocationKind(const uint32_t Type) { switch (Type) { + case ELF::R_X86_64_32: + return ELFX86RelocationKind::Pointer32; case ELF::R_X86_64_32S: return ELFX86RelocationKind::Pointer32Signed; case ELF::R_X86_64_PC32: @@ -207,6 +210,9 @@ case Delta64: Kind = x86_64::Delta64; break; + case Pointer32: + Kind = x86_64::Pointer32; + break; case Pointer32Signed: Kind = x86_64::Pointer32Signed; break; diff --git a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt --- a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt @@ -11,6 +11,7 @@ DebugObjectManagerPlugin.cpp DebuggerSupportPlugin.cpp DebugUtils.cpp + DebugInfoPlugins.cpp EPCDynamicLibrarySearchGenerator.cpp EPCDebugObjectRegistrar.cpp EPCEHFrameRegistrar.cpp @@ -37,6 +38,7 @@ ObjectTransformLayer.cpp OrcABISupport.cpp OrcV2CBindings.cpp + PerfSupportPlugin.cpp RTDyldObjectLinkingLayer.cpp SimpleRemoteEPC.cpp Speculation.cpp diff --git a/llvm/lib/ExecutionEngine/Orc/DebugInfoPlugins.cpp b/llvm/lib/ExecutionEngine/Orc/DebugInfoPlugins.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/DebugInfoPlugins.cpp @@ -0,0 +1,373 @@ +#include "llvm/ExecutionEngine/Orc/DebugInfoPlugins.h" + +#include "llvm/ExecutionEngine/JITLink/x86_64.h" +#include "llvm/Support/SmallVectorMemoryBuffer.h" +#include "llvm/ADT/StringSet.h" + +#define DEBUG_TYPE "orc" + +using namespace llvm; +using namespace llvm::orc; +using namespace llvm::jitlink; + +namespace { + +constexpr StringRef JITLinkPreserveSymbol = "__JITLINK_PRESERVE"; +const StringSet<> DWARFSections = { +#define HANDLE_DWARF_SECTION(ENUM_NAME, ELF_NAME, CMDLINE_NAME, OPTION) \ + ELF_NAME, +#include "llvm/BinaryFormat/Dwarf.def" +#undef HANDLE_DWARF_SECTION +}; + +std::string serialize(LinkGraph &G) { + std::string out; + raw_string_ostream OS(out); + OS << "LinkGraph: \n"; + for (auto &S : G.sections()) { + OS << "Section: " << S.getName() << "\n"; + for (auto B : S.blocks()) { + OS << "Block: " << *B << "\n"; + for (auto &E : B->edges()) { + OS << " Edge: "; + OS << x86_64::getEdgeKindName(E.getKind()) << ": "; + OS << E.getTarget().getName() << " + " << E.getAddend() << " "; + OS << " @ " << E.getOffset() << "\n"; + } + } + for (auto Sym : S.symbols()) { + OS << "Symbol: " << *Sym << "\n"; + } + } + return out; +} + +void preserveSection(LinkGraph &G, Section &S) { + SmallPtrSet Blocks; + for (auto Sym : S.symbols()) { + if (Blocks.insert(&Sym->getBlock()).second) { + Sym->setLive(true); + } + } + for (auto Block : S.blocks()) { + if (!Blocks.count(Block)) { + // G.addAnonymousSymbol(*Block, 0, 0, false, true); + G.addDefinedSymbol(*Block, 0, JITLinkPreserveSymbol, 0, Linkage::Strong, Scope::Local, false, true); + } + } +} + +void stripPreservers(LinkGraph &G) { + SmallVector Symbols; + for (auto Sym : G.defined_symbols()) { + if (Sym->getName() == JITLinkPreserveSymbol) { + Symbols.push_back(Sym); + } + } + for (auto Sym : Symbols) { + G.removeDefinedSymbol(*Sym); + } +} + +void dumpDWARFContext(DWARFContext &DC) { + auto options = llvm::DIDumpOptions(); + options.DumpType &= ~DIDT_UUID; + options.DumpType &= ~(1 << DIDT_ID_DebugFrame); + DC.dump(dbgs(), options); +} + +std::unique_ptr makeDWARFContext(StringMap> &DebugSections, LinkGraph &G) { + for (auto &S : G.sections()) { + SmallVector Data; + for (auto B : S.blocks()) { + Data.append(B->getContent().begin(), B->getContent().end()); + } + auto Name = S.getName(); + // DWARFContext doesn't like it when our section names start with a '.'. + if (Name.startswith(".")) { + Name = Name.drop_front(); + } + DebugSections[Name] = std::make_unique(std::move(Data)); + } + auto DC = DWARFContext::create(DebugSections, G.getPointerSize(), G.getEndianness() == support::little); + return DC; +} +} + +void PreserveDebugInfoSectionsPlugin::modifyPassConfig( + MaterializationResponsibility &MR, jitlink::LinkGraph &G, + jitlink::PassConfiguration &Config) { + // Preserve debug info sections. + Config.PrePrunePasses.push_back( + [](LinkGraph &G) { + for (auto &S : G.sections()) { + // Known DWARF debug sections start with ".debug_" + if (DWARFSections.contains(S.getName())) { + preserveSection(G, S); + } + } + return Error::success(); + }); + // Strip preserved symbols. + Config.PostPrunePasses.push_back( + [](LinkGraph &G) { + stripPreservers(G); + return Error::success(); + }); +} + +Expected> PreserveDebugInfoSectionsPlugin::Create() { + return std::make_unique(); +} + +Expected DWARFContextPlugin::getDWARFContext( + MaterializationResponsibility &MR) { + std::lock_guard Lock(Mutex); + auto It = DWARFContexts.find(&MR); + if (It != DWARFContexts.end()) { + return It->second.second.get(); + } + if (InFlightDWARF.count(&MR)) { + return make_error("DWARFContext in flight for this MR", + inconvertibleErrorCode()); + } + return make_error("No DWARFContext for this MR", + inconvertibleErrorCode()); +} + +void DWARFContextPlugin::modifyPassConfig(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, + jitlink::PassConfiguration &Config) { + if (!G.getTargetTriple().isOSBinFormatELF()) return; + if (G.getTargetTriple().getArch() != Triple::x86_64) return; + Config.PrePrunePasses.push_back([this, MR = &MR](LinkGraph &G) { + InFlightDWARFSections InFlight; + dbgs() << serialize(G) << "\n"; + for (auto &Sec : G.sections()) { + if (!DWARFSections.contains(Sec.getName())) + continue; + // Handle DWARF relocations. + // DWARF relocations are either + // 32-bit or 64-bit absolute addresses + // DWARF->DWARF relocations are 32-bit + // addrs, which must be handled upfront. + // DWARF->alloced relocations are 64-bit + // addrs, which we leave for postalloc. + // We do still need to track those relocs, + // since we won't be preserving this section + // in the final object. + StringMap>> DWARFRelocs; + SmallVector SectionCopy; + for (auto Block : Sec.blocks()) { + SectionCopy.append(Block->getContent().begin(), Block->getContent().end()); + for (auto Edge_it = Block->edges().begin(); Edge_it != Block->edges().end();) { + auto &Edge = *Edge_it; + if (DWARFSections.contains(Edge.getTarget().getBlock().getSection().getName())) { + // DWARF->DWARF relocation + switch (Edge.getKind()) { + case x86_64::EdgeKind_x86_64::Pointer32: + case x86_64::EdgeKind_x86_64::Pointer32Signed: + // Fixup the edge now, since later JITLink can't fixup + // these DWARF edges + cantFail(x86_64::applyFixup(G, *Block, Edge, nullptr)); + dbgs() << "Applied DWARF->DWARF relocation: " << x86_64::getEdgeKindName(Edge.getKind()) << "\n"; + // We also remove the edge in case someone else + // preserved the section, so they won't run into + // this error + Edge_it = Block->removeEdge(Edge_it); + break; + default: + dbgs() << "ERROR: Unhandled DWARF->DWARF relocation: " << x86_64::getEdgeKindName(Edge.getKind()) << "\n"; + ++Edge_it; + break; + } + } else { + // DWARF->alloced relocation + switch (Edge.getKind()) { + case x86_64::EdgeKind_x86_64::Pointer64: { + auto &Target = Edge.getTarget(); + dbgs() << "Found DWARF->alloced relocation: " << x86_64::getEdgeKindName(Edge.getKind()) << "\n"; + dbgs() << "Target: " << Target.getBlock().getSection().getName() << " " << Target.getName() << "\n"; + dbgs() << "Offset: " << Target.getOffset() << "\n"; + // can't relocate anonymous symbols at nonzero offset, + // we won't be able to do the name + // lookup later + if (Target.getName().empty() && Target.getOffset() != 0) + break; + auto Offset = SectionCopy.size() - Block->getSize() + Edge.getOffset(); + auto Addend = Edge.getAddend(); + DWARFRelocs[Target.getBlock().getSection().getName()][Target.getName()].push_back(std::make_pair(Offset, Addend)); + dbgs() << "Added DWARF->alloced relocation: " << x86_64::getEdgeKindName(Edge.getKind()) << "\n"; + break; + } + default: + dbgs() << "ERROR: Unhandled DWARF->alloced relocation: " << x86_64::getEdgeKindName(Edge.getKind()) << "\n"; + break; + } + ++Edge_it; + } + } + } + InFlight[Sec.getName()] = std::make_pair(std::move(SectionCopy), std::move(DWARFRelocs)); + } + std::lock_guard Lock(Mutex); + InFlightDWARF[MR] = std::move(InFlight); + return Error::success(); + }); + Config.PreFixupPasses.push_back([this, MR = &MR](LinkGraph &G) { + // We need to fixup DWARF->alloced relocations + // now that we know the final addresses of + // all the alloced sections. + InFlightDWARFSections DS; + { + std::lock_guard Lock(Mutex); + auto It = InFlightDWARF.find(MR); + if (It == InFlightDWARF.end()) + // No DWARF sections to fixup + return Error::success(); + DS = std::move(It->getSecond()); + InFlightDWARF.erase(It); + } + StringMap> DWARFSections; + for (auto &Relocs : DS) { + auto &SecCopy = Relocs.getValue().first; + for (auto &TargetRelocs : Relocs.getValue().second) { + auto TargetSection = G.findSectionByName(TargetRelocs.getKey()); + // Missing relocation section, ignore it + if (!TargetSection) { + dbgs() << "WARNING: Missing DWARF relocation section: " << TargetRelocs.getKey() << "\n"; + continue; + } + auto &SymMap = TargetRelocs.getValue(); + // Apply relocations to all symbols found in this section + for (auto Sym : TargetSection->symbols()) { + dbgs() << "Relocating symbol: " << Sym->getName() << " in section " << TargetSection->getName() << "\n"; + if (!Sym->hasName()) { + dbgs() << "Skipping anonymous symbol\n"; + continue; + } + auto It = SymMap.find(Sym->getName()); + if (It == SymMap.end()) { + dbgs() << "Symbol not relocated\n"; + continue; + } + // Apply the relocations + // This assumes x86-64 and ELF, reassess this assumption if we extend to new archs + // We also assume only a X86_64_64 relocation gets to this point, reassess later + for (auto Reloc : It->getValue()) { + auto *Fixup = reinterpret_cast(SecCopy.data() + Reloc.first); + *Fixup = Sym->getAddress().getValue() + Reloc.second; + dbgs() << "Relocating " << Sym->getName() << " at offset " << Reloc.first << " with addend " << Reloc.second << " to " << *Fixup << "\n"; + } + } + auto Anon = SymMap.find(""); + if (Anon != SymMap.end()) { + auto Start = SectionRange(*TargetSection).getStart(); + // Apply the relocations + // This assumes x86-64 and ELF, reassess this assumption if we extend to new archs + // We also assume only a X86_64_64 relocation gets to this point, reassess later + for (auto Reloc : Anon->getValue()) { + auto *Fixup = reinterpret_cast(SecCopy.data() + Reloc.first); + *Fixup = Start.getValue() + Reloc.second; + dbgs() << "Relocating anonymous symbol at offset " << Reloc.first << " with addend " << Reloc.second << " to " << *Fixup << "\n"; + } + } + } + auto SecName = Relocs.getKey(); + if (SecName.starts_with(".")) { + // Remove the leading '.' from the section name + // DWARFContext doesn't like it + SecName = SecName.drop_front(); + } + DWARFSections[SecName] = std::make_unique(std::move(SecCopy)); + } + // Now that we've fixed up the DWARF sections, we can create the DWARFContext + auto DWARFCtx = DWARFContext::create(DWARFSections, G.getPointerSize(), G.getEndianness() == support::little); + dumpDWARFContext(*DWARFCtx); + { + std::lock_guard Lock(Mutex); + DWARFContexts[MR] = std::make_pair(std::move(DWARFSections), std::move(DWARFCtx)); + } + return Error::success(); + }); +} + +Error DWARFContextPlugin::notifyEmitted(MaterializationResponsibility &MR) { + std::lock_guard Lock(Mutex); + assert(InFlightDWARF.find(&MR) == InFlightDWARF.end() && "DWARF sections not fixed up before emission!"); + DWARFContexts.erase(&MR); + return Error::success(); +} + +Error DWARFContextPlugin::notifyFailed(MaterializationResponsibility &MR) { + std::lock_guard Lock(Mutex); + // Might have failed either before or after fixup + InFlightDWARF.erase(&MR); + DWARFContexts.erase(&MR); + return Error::success(); +} + +Expected> DWARFContextPlugin::Create() { + return std::make_unique(); +} + +// Creates an EH frame header prepared for a 32-bit relative relocation +// to the start of the .eh_frame section. Absolute injects a 64-bit absolute +// address space offset 4 bytes from the start +std::string llvm::orc::createEHFrameHeader(Section &EHFrame, support::endianness endianness, bool absolute) { + uint8_t Version = 1; + uint8_t EhFramePtrEnc = 0; + if (absolute) { + EhFramePtrEnc |= dwarf::DW_EH_PE_sdata8 | dwarf::DW_EH_PE_absptr; + } else { + EhFramePtrEnc |= dwarf::DW_EH_PE_sdata4 | dwarf::DW_EH_PE_datarel; + } + uint8_t FDECountEnc = dwarf::DW_EH_PE_omit; + uint8_t TableEnc = dwarf::DW_EH_PE_omit; + // X86_64_64 relocation to the start of the .eh_frame section + uint32_t EHFrameRelocation = 0; + // uint32_t FDECount = 0; + // Skip the FDE binary search table + // We'd have to reprocess the CIEs to get this information, + // which seems like more trouble than it's worth + // TODO consider implementing this. + // binary search table goes here + + size_t HeaderSize = ( + sizeof(Version) + + sizeof(EhFramePtrEnc) + + sizeof(FDECountEnc) + + sizeof(TableEnc) + + absolute ? sizeof(uint64_t) : sizeof(EHFrameRelocation) + ); + std::string HeaderContent(HeaderSize, '\0'); + HeaderContent[0] = Version; + HeaderContent[1] = EhFramePtrEnc; + HeaderContent[2] = FDECountEnc; + HeaderContent[3] = TableEnc; + if (absolute) { + uint64_t EHFrameAddr = support::endian::byte_swap(SectionRange(EHFrame).getStart().getValue(), endianness); + memcpy(HeaderContent.data() + 4, &EHFrameAddr, sizeof(EHFrameAddr)); + } else { + memcpy(HeaderContent.data() + 4, &EHFrameRelocation, sizeof(EHFrameRelocation)); + } + return HeaderContent; +} + +Expected
llvm::orc::createEHFrameHeaderSection(LinkGraph &G, Section &EHFrame) { + if (!G.getTargetTriple().isOSBinFormatELF()) + return make_error("EHFrame header only supported on ELF targets", + inconvertibleErrorCode()); + if (G.getTargetTriple().getArch() != Triple::x86_64) + return make_error("EHFrame header only supported on x86_64 targets", + inconvertibleErrorCode()); + auto &EHFrameHdr = G.createSection(".eh_frame_hdr", orc::MemProt::Read); + auto HeaderBytes = createEHFrameHeader(EHFrame, G.getEndianness(), false); + auto HeaderContent = G.allocateBuffer(HeaderBytes.size()); + memcpy(HeaderContent.data(), HeaderBytes.data(), HeaderBytes.size()); + auto &B = G.createMutableContentBlock(EHFrameHdr, HeaderContent, ExecutorAddr(), 4, 0); + auto &EHStart = G.addAnonymousSymbol(*SectionRange(EHFrame).getFirstBlock(), 0, 0, false, false); + // fixup 4 bytes in, want .eh_frame - .eh_frame_hdr = .eh_frame - (.eh_frame_hdr + 4) + 4 = Delta32, offset = 4, addend = 4 + B.addEdge(x86_64::EdgeKind_x86_64::Delta32, 4, EHStart, 4); + return &EHFrameHdr; +} diff --git a/llvm/lib/ExecutionEngine/Orc/PerfSupportPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/PerfSupportPlugin.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/PerfSupportPlugin.cpp @@ -0,0 +1,258 @@ +#include "llvm/ExecutionEngine/Orc/PerfSupportPlugin.h" + +#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h" + +#include "llvm/ExecutionEngine/Orc/Shared/SimplePackedSerialization.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/DebugInfoPlugins.h" +#include "llvm/ExecutionEngine/JITLink/x86_64.h" + +#include // mmap() +#include // clock_gettime(), time(), localtime_r() */ +#include // for read(), close() + +#define DEBUG_TYPE orc + +using namespace llvm; +using namespace llvm::orc; +using namespace llvm::jitlink; + +namespace { + +std::string serialize(LinkGraph &G) { + std::string out; + raw_string_ostream OS(out); + OS << "LinkGraph: \n"; + for (auto &S : G.sections()) { + OS << "Section: " << S.getName() << "\n"; + for (auto B : S.blocks()) { + OS << "Block: " << *B << "\n"; + for (auto &E : B->edges()) { + OS << " Edge: "; + OS << x86_64::getEdgeKindName(E.getKind()) << ": "; + OS << E.getTarget().getName() << " + " << E.getAddend() << " "; + OS << " @ " << E.getOffset() << "\n"; + } + } + for (auto Sym : S.symbols()) { + OS << "Symbol: " << *Sym << "\n"; + } + } + return out; +} + +constexpr StringRef RegisterPerfStartSymbolName = "llvm_orc_registerJITLoaderPerfStart"; +constexpr StringRef RegisterPerfEndSymbolName = "llvm_orc_registerJITLoaderPerfEnd"; +constexpr StringRef RegisterPerfImplSymbolName = "llvm_orc_registerJITLoaderPerfImpl"; + +static inline uint64_t timespec_to_ns(const struct timespec *ts) { + const uint64_t NanoSecPerSec = 1000000000; + return ((uint64_t)ts->tv_sec * NanoSecPerSec) + ts->tv_nsec; +} + +static inline uint64_t perf_get_timestamp() { + struct timespec ts; + int ret; + + ret = clock_gettime(CLOCK_MONOTONIC, &ts); + if (ret) + return 0; + + return timespec_to_ns(&ts); +} + +static PerfJITCodeLoadRecord getCodeLoadRecord(const Symbol &Sym, std::atomic &CodeIndex) { + PerfJITCodeLoadRecord Record; + auto Name = Sym.getName(); + auto Addr = Sym.getAddress(); + auto Size = Sym.getSize(); + Record.Prefix.Id = PerfJITRecordType::JIT_CODE_LOAD; + Record.Prefix.Timestamp = perf_get_timestamp(); + //Runtime sets PID + Record.Pid = 0; + //Runtime sets TID + Record.Tid = 0; + Record.Vma = Addr.getValue(); + Record.CodeAddr = Addr.getValue(); + Record.CodeSize = Size; + Record.CodeIndex = CodeIndex++; + Record.Name = Name.str(); + // Initialize last, once all the other fields are filled + Record.Prefix.TotalSize = ( + 2 * sizeof(uint32_t) // id, total_size + + sizeof(uint64_t) // timestamp + + 2 * sizeof(uint32_t) // pid, tid + + 4 * sizeof(uint64_t) // vma, code_addr, code_size, code_index + + Name.size() + 1 // symbol name + + Record.CodeSize // code + ); + return Record; +} + +static std::optional getDebugInfoRecord(const Symbol &Sym, DWARFContext *DC) { + // dbgs() << "Getting debug info for symbol " << Sym.getName() << "\n"; + if (!DC) { + // No debug info available + dbgs() << "No debug info available\n"; + return std::nullopt; + } + auto &Section = Sym.getBlock().getSection(); + auto Addr = Sym.getAddress(); + auto Size = Sym.getSize(); + auto SAddr = object::SectionedAddress{Addr.getValue(), Section.getOrdinal()}; + dbgs() << "Getting debug info for symbol " << Sym.getName() << " at address " << Addr.getValue() << " with size " << Size << "\n"; + dbgs() << "Section ordinal: " << Section.getOrdinal() << "\n"; + auto LInfo = DC->getLineInfoForAddressRange(SAddr, Size, DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath); + if (LInfo.empty()) { + // No line info available + dbgs() << "No line info available\n"; + return std::nullopt; + } + PerfJITDebugInfoRecord Record; + Record.Prefix.Id = PerfJITRecordType::JIT_CODE_DEBUG_INFO; + Record.Prefix.Timestamp = perf_get_timestamp(); + Record.CodeAddr = Addr.getValue(); + for (const auto &Entry : LInfo) { + auto Addr = Entry.first; + // The function re-created by perf is preceded by a elf + // header. Need to adjust for that, otherwise the results are + // wrong. + Addr += 0x40; + Record.Entries.push_back({Addr, Entry.second.Line, Entry.second.Discriminator, Entry.second.FileName}); + } + Record.NrEntry = Record.Entries.size(); + size_t EntriesBytes = ( + 2 // record header + + 2 // record fields + ) * sizeof(uint64_t); + for (const auto &Entry : Record.Entries) { + EntriesBytes += sizeof(uint64_t) + 2 * sizeof(uint32_t); // Addr, Line/Discrim + EntriesBytes += Entry.Name.size() + 1; // Name + } + Record.Prefix.TotalSize = EntriesBytes; + return Record; +} + +static PerfJITCodeUnwindingInfoRecord getUnwindingRecord(LinkGraph &G) { + PerfJITCodeUnwindingInfoRecord Record; + Record.Prefix.Id = PerfJITRecordType::JIT_CODE_UNWINDING_INFO; + Record.Prefix.TotalSize = 0; + Record.Prefix.Timestamp = perf_get_timestamp(); + auto Eh_frame = G.findSectionByName(".eh_frame"); + if (!Eh_frame) { + dbgs() << "No .eh_frame section found\n"; + return Record; + } + auto SR = SectionRange(*Eh_frame); + Record.EHFrameAddr = SR.getStart().getValue(); + auto EHFrameSize = SR.getSize(); + auto Eh_frame_hdr = G.findSectionByName(".eh_frame_hdr"); + if (!Eh_frame_hdr) { + Record.EHFrameHdr = createEHFrameHeader(*Eh_frame, G.getEndianness(), true); + Record.EHFrameHdrAddr = 0; + Record.EHFrameHdrSize = Record.EHFrameHdr.size(); + Record.UnwindDataSize = EHFrameSize + Record.EHFrameHdrSize; + Record.MappedSize = 0; // Because the EHFrame header was not mapped + } else { + auto SR = SectionRange(*Eh_frame_hdr); + Record.EHFrameHdrAddr = SR.getStart().getValue(); + Record.EHFrameHdrSize = SR.getSize(); + Record.UnwindDataSize = EHFrameSize + Record.EHFrameHdrSize; + Record.MappedSize = Record.UnwindDataSize; + } + Record.Prefix.TotalSize = ( + 2 * sizeof(uint32_t) // id, total_size + + sizeof(uint64_t) // timestamp + + 3 * sizeof(uint64_t) // unwind_data_size, eh_frame_hdr_size, mapped_size + + Record.UnwindDataSize // eh_frame_hdr, eh_frame + ); + dbgs() << "Created unwind record\n"; + dbgs() << "Total size: " << Record.Prefix.TotalSize << "\n"; + dbgs() << "Unwind size: " << Record.UnwindDataSize << "\n"; + dbgs() << "EHFrame size: " << EHFrameSize << "\n"; + dbgs() << "EHFrameHdr size: " << Record.EHFrameHdrSize << "\n"; + return Record; +} + +static PerfJITRecordBatch getRecords(LinkGraph &G, DWARFContext *DC, std::atomic &CodeIndex) { + PerfJITRecordBatch Batch; + for (auto Sym : G.defined_symbols()) { + if (!Sym->hasName()) + continue; + auto DebugInfo = getDebugInfoRecord(*Sym, DC); + if (DebugInfo) + Batch.DebugInfoRecords.push_back(std::move(*DebugInfo)); + Batch.CodeLoadRecords.push_back(getCodeLoadRecord(*Sym, CodeIndex)); + } + Batch.UnwindingRecord = getUnwindingRecord(G); + return Batch; +} +} // namespace + +PerfSupportPlugin::PerfSupportPlugin(ExecutorProcessControl &EPC, ExecutorAddr RegisterPerfStartAddr, + ExecutorAddr RegisterPerfEndAddr, ExecutorAddr RegisterPerfImplAddr, + DWARFContextPlugin *DWARFPlugin) + : EPC(EPC), RegisterPerfStartAddr(RegisterPerfStartAddr), + RegisterPerfEndAddr(RegisterPerfEndAddr), RegisterPerfImplAddr(RegisterPerfImplAddr), + DWARFPlugin(DWARFPlugin), CodeIndex(0) { + cantFail(EPC.callSPSWrapper(RegisterPerfStartAddr)); + dbgs() << "Created perf support plugin\n"; +} +PerfSupportPlugin::~PerfSupportPlugin() { + dbgs() << "Destroying perf support plugin\n"; + cantFail(EPC.callSPSWrapper(RegisterPerfEndAddr)); +} + +void PerfSupportPlugin::modifyPassConfig(MaterializationResponsibility &MR, + LinkGraph &G, + PassConfiguration &Config) { + dbgs() << "Modifying perf support plugin pass config\n"; + Config.PostFixupPasses.push_back([this, &MR](LinkGraph &G) { + dbgs() << "Running perf support plugin post-fixup pass\n"; + dbgs() << serialize(G) << "\n"; + DWARFContext *DWC = nullptr; + dbgs() << "DWARF plugin " << DWARFPlugin << "\n"; + if (DWARFPlugin) { + auto DC = DWARFPlugin->getDWARFContext(MR); + if (!DC) { + dbgs() << "No useful DWARF context found\n"; + consumeError(DC.takeError()); + } else { + dbgs() << "Got DWARF context " << DC.get() << "\n"; + DWC = DC.get(); + } + } + auto Batch = getRecords(G, DWC, CodeIndex); + G.allocActions().push_back( + { + cantFail(shared::WrapperFunctionCall::Create< + shared::SPSArgList< + shared::SPSPerfJITRecordBatch + > + >( + RegisterPerfImplAddr, + Batch + )), + {} + } + ); + dbgs() << "Registered alloc action\n"; + dbgs() << "Impl address: " << RegisterPerfImplAddr << "\n"; + return Error::success(); + }); +} + +Expected> PerfSupportPlugin::Create(ExecutorProcessControl &EPC, JITDylib &JD, DWARFContextPlugin *DWARFPlugin) { + auto &ES = EPC.getExecutionSession(); + auto StartName = ES.intern(RegisterPerfStartSymbolName); + auto EndName = ES.intern(RegisterPerfEndSymbolName); + auto ImplName = ES.intern(RegisterPerfImplSymbolName); + SymbolLookupSet SLS({StartName, EndName, ImplName}); + auto Res = ES.lookup(makeJITDylibSearchOrder({&JD}), std::move(SLS)); + if (!Res) + return Res.takeError(); + auto StartAddr = ExecutorAddr(Res->find(StartName)->second.getAddress()); + auto EndAddr = ExecutorAddr(Res->find(EndName)->second.getAddress()); + auto ImplAddr = ExecutorAddr(Res->find(ImplName)->second.getAddress()); + return std::make_unique(EPC, StartAddr, EndAddr, ImplAddr, DWARFPlugin); +} diff --git a/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt --- a/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt @@ -11,6 +11,7 @@ SimpleExecutorMemoryManager.cpp SimpleRemoteEPCServer.cpp TargetExecutionUtils.cpp + JITLoaderPerf.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/ExecutionEngine/Orc diff --git a/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.cpp b/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.cpp @@ -0,0 +1,392 @@ +#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h" + +#include "llvm/ExecutionEngine/Orc/PerfSupportPlugin.h" + +#include "llvm/Support/Process.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +#include // mmap() +#include // clock_gettime(), time(), localtime_r() */ +#include // for read(), close() +#include +#include + +// language identifier (XXX: should we generate something better from debug +// info?) +#define JIT_LANG "llvm-IR" +#define LLVM_PERF_JIT_MAGIC \ + ((uint32_t)'J' << 24 | (uint32_t)'i' << 16 | (uint32_t)'T' << 8 | \ + (uint32_t)'D') +#define LLVM_PERF_JIT_VERSION 1 + +using namespace llvm; +using namespace llvm::orc; + +struct PerfState { + // cache lookups + uint32_t Pid; + + // base directory for output data + std::string JitPath; + + // output data stream, closed via Dumpstream + int DumpFd = -1; + + // output data stream + std::unique_ptr Dumpstream; + + // perf mmap marker + void *MarkerAddr = NULL; +}; + + +// prevent concurrent dumps from messing up the output file +static std::mutex Mutex; +static std::optional state; + +struct RecHeader { + uint32_t Id; + uint32_t TotalSize; + uint64_t Timestamp; +}; + +struct DIR { + RecHeader Prefix; + uint64_t CodeAddr; + uint64_t NrEntry; +}; + +struct DIE { + uint64_t CodeAddr; + uint32_t Line; + uint32_t Discrim; +}; + +struct CLR { + RecHeader Prefix; + uint32_t Pid; + uint32_t Tid; + uint64_t Vma; + uint64_t CodeAddr; + uint64_t CodeSize; + uint64_t CodeIndex; +}; + +struct UWR { + RecHeader Prefix; + uint64_t UnwindDataSize; + uint64_t EhFrameHeaderSize; + uint64_t MappedSize; +}; + +static void writeDebugRecord(const PerfJITDebugInfoRecord &DebugRecord) { + assert(state && "PerfState not initialized"); + dbgs() << "Writing debug record with " << DebugRecord.Entries.size() << " entries\n"; + DIR dir{RecHeader{static_cast(DebugRecord.Prefix.Id), DebugRecord.Prefix.TotalSize, DebugRecord.Prefix.Timestamp}, DebugRecord.CodeAddr, DebugRecord.NrEntry}; + state->Dumpstream->write(reinterpret_cast(&dir), sizeof(dir)); + for (auto &die : DebugRecord.Entries) { + DIE d{die.Addr, die.Lineno, die.Discrim}; + state->Dumpstream->write(reinterpret_cast(&d), sizeof(d)); + state->Dumpstream->write(die.Name.data(), die.Name.size() + 1); + } +} + +static void writeCodeRecord(const PerfJITCodeLoadRecord &CodeRecord) { + assert(state && "PerfState not initialized"); + uint32_t Tid = get_threadid(); + dbgs() << "Writing code record with code size " << CodeRecord.CodeSize << " and code index " << CodeRecord.CodeIndex << "\n"; + CLR clr{RecHeader{static_cast(CodeRecord.Prefix.Id), CodeRecord.Prefix.TotalSize, CodeRecord.Prefix.Timestamp}, state->Pid, Tid, CodeRecord.Vma, CodeRecord.CodeAddr, CodeRecord.CodeSize, CodeRecord.CodeIndex}; + dbgs() << "wrote " << sizeof(clr) << " bytes of CLR, " << CodeRecord.Name.size() + 1 << " bytes of name, " << CodeRecord.CodeSize << " bytes of code\n"; + state->Dumpstream->write(reinterpret_cast(&clr), sizeof(clr)); + state->Dumpstream->write(CodeRecord.Name.data(), CodeRecord.Name.size() + 1); + state->Dumpstream->write((const char *)CodeRecord.CodeAddr, CodeRecord.CodeSize); +} + +static void writeUnwindRecord(const PerfJITCodeUnwindingInfoRecord &UnwindRecord) { + assert(state && "PerfState not initialized"); + dbgs() << "Writing unwind record with unwind data size " << UnwindRecord.UnwindDataSize << " and EH frame header size " << UnwindRecord.EHFrameHdrSize << " and mapped size " << UnwindRecord.MappedSize << "\n"; + UWR uwr{RecHeader{static_cast(UnwindRecord.Prefix.Id), UnwindRecord.Prefix.TotalSize, UnwindRecord.Prefix.Timestamp}, UnwindRecord.UnwindDataSize, UnwindRecord.EHFrameHdrSize, UnwindRecord.MappedSize}; + dbgs() << "wrote " << sizeof(uwr) << " bytes of UWR, " << UnwindRecord.EHFrameHdrSize << " bytes of EH frame header, " << UnwindRecord.UnwindDataSize - UnwindRecord.EHFrameHdrSize << " bytes of EH frame\n"; + state->Dumpstream->write(reinterpret_cast(&uwr), sizeof(uwr)); + if (UnwindRecord.EHFrameHdrAddr) { + state->Dumpstream->write((const char *)UnwindRecord.EHFrameHdrAddr, UnwindRecord.EHFrameHdrSize); + } else { + state->Dumpstream->write(UnwindRecord.EHFrameHdr.data(), UnwindRecord.EHFrameHdrSize); + } + state->Dumpstream->write((const char *)UnwindRecord.EHFrameAddr, UnwindRecord.UnwindDataSize - UnwindRecord.EHFrameHdrSize); +} + +static Error registerJITLoaderPerfImpl(const PerfJITRecordBatch &Batch) { + dbgs() << "registerJITLoaderPerfImpl called\n"; + if (!state) { + return make_error("PerfState not initialized", + inconvertibleErrorCode()); + } + + // Serialize the batch + std::lock_guard Lock(Mutex); + if (Batch.UnwindingRecord.Prefix.TotalSize > 0) { + writeUnwindRecord(Batch.UnwindingRecord); + } + for (const auto &DebugInfo : Batch.DebugInfoRecords) { + writeDebugRecord(DebugInfo); + } + for (const auto &CodeLoad : Batch.CodeLoadRecords) { + writeCodeRecord(CodeLoad); + } + + state->Dumpstream->flush(); + + return Error::success(); +} + +struct Header { + uint32_t Magic; // characters "JiTD" + uint32_t Version; // header version + uint32_t TotalSize; // total size of header + uint32_t ElfMach; // elf mach target + uint32_t Pad1; // reserved + uint32_t Pid; + uint64_t Timestamp; // timestamp + uint64_t Flags; // flags +}; + +static Error OpenMarker(PerfState &state) { + // We mmap the jitdump to create an MMAP RECORD in perf.data file. The mmap + // is captured either live (perf record running when we mmap) or in deferred + // mode, via /proc/PID/maps. The MMAP record is used as a marker of a jitdump + // file for more meta data info about the jitted code. Perf report/annotate + // detect this special filename and process the jitdump file. + // + // Mapping must be PROT_EXEC to ensure it is captured by perf record + // even when not using -d option. + state.MarkerAddr = ::mmap(NULL, sys::Process::getPageSizeEstimate(), + PROT_READ | PROT_EXEC, MAP_PRIVATE, state.DumpFd, 0); + + if (state.MarkerAddr == MAP_FAILED) { + return make_error("could not mmap JIT marker", + inconvertibleErrorCode()); + } + return Error::success(); +} + +void CloseMarker(PerfState &state) { + if (!state.MarkerAddr) + return; + + munmap(state.MarkerAddr, sys::Process::getPageSizeEstimate()); + state.MarkerAddr = nullptr; +} + +static inline uint64_t timespec_to_ns(const struct timespec *ts) { + const uint64_t NanoSecPerSec = 1000000000; + return ((uint64_t)ts->tv_sec * NanoSecPerSec) + ts->tv_nsec; +} + +static inline uint64_t perf_get_timestamp() { + struct timespec ts; + int ret; + + ret = clock_gettime(CLOCK_MONOTONIC, &ts); + if (ret) + return 0; + + return timespec_to_ns(&ts); +} + +static Expected
FillMachine(PerfState &state) { + Header hdr; + hdr.Magic = LLVM_PERF_JIT_MAGIC; + hdr.Version = LLVM_PERF_JIT_VERSION; + hdr.TotalSize = sizeof(hdr); + hdr.Pid = state.Pid; + hdr.Timestamp = perf_get_timestamp(); + + char id[16]; + struct { + uint16_t e_type; + uint16_t e_machine; + } info; + + size_t RequiredMemory = sizeof(id) + sizeof(info); + + ErrorOr> MB = + MemoryBuffer::getFileSlice("/proc/self/exe", + RequiredMemory, + 0); + + // This'll not guarantee that enough data was actually read from the + // underlying file. Instead the trailing part of the buffer would be + // zeroed. Given the ELF signature check below that seems ok though, + // it's unlikely that the file ends just after that, and the + // consequence would just be that perf wouldn't recognize the + // signature. + if (!MB) { + return make_error("could not open /proc/self/exe", + MB.getError()); + } + + memcpy(&id, (*MB)->getBufferStart(), sizeof(id)); + memcpy(&info, (*MB)->getBufferStart() + sizeof(id), sizeof(info)); + + // check ELF signature + if (id[0] != 0x7f || id[1] != 'E' || id[2] != 'L' || id[3] != 'F') { + return make_error("invalid ELF signature", + inconvertibleErrorCode()); + } + + hdr.ElfMach = info.e_machine; + + return hdr; +} + +static Error InitDebuggingDir(PerfState &state) { + time_t Time; + struct tm LocalTime; + char TimeBuffer[sizeof("YYYYMMDD")]; + SmallString<64> Path; + + // search for location to dump data to + if (const char *BaseDir = getenv("JITDUMPDIR")) + Path.append(BaseDir); + else if (!sys::path::home_directory(Path)) + Path = "."; + + // create debug directory + Path += "/.debug/jit/"; + if (auto EC = sys::fs::create_directories(Path)) { + std::string errstr; + raw_string_ostream errstream(errstr); + errstream << "could not create jit cache directory " << Path << ": " + << EC.message() << "\n"; + return make_error(std::move(errstr), + inconvertibleErrorCode()); + } + + // create unique directory for dump data related to this process + time(&Time); + localtime_r(&Time, &LocalTime); + strftime(TimeBuffer, sizeof(TimeBuffer), "%Y%m%d", &LocalTime); + Path += JIT_LANG "-jit-"; + Path += TimeBuffer; + + SmallString<128> UniqueDebugDir; + + using sys::fs::createUniqueDirectory; + if (auto EC = createUniqueDirectory(Path, UniqueDebugDir)) { + std::string errstr; + raw_string_ostream errstream(errstr); + errstream << "could not create unique jit cache directory " << UniqueDebugDir + << ": " << EC.message() << "\n"; + return make_error(std::move(errstr), + inconvertibleErrorCode()); + } + + state.JitPath = std::string(UniqueDebugDir.str()); + + return Error::success(); +} + +static Error registerJITLoaderPerfStartImpl() { + PerfState tentative; + tentative.Pid = sys::Process::getProcessId(); + // check if clock-source is supported + if (!perf_get_timestamp()) { + return make_error("kernel does not support CLOCK_MONOTONIC", + inconvertibleErrorCode()); + } + + if (auto err = InitDebuggingDir(tentative)) { + return std::move(err); + } + + std::string Filename; + raw_string_ostream FilenameBuf(Filename); + FilenameBuf << tentative.JitPath << "/jit-" << tentative.Pid << ".dump"; + + // Need to open ourselves, because we need to hand the FD to OpenMarker() and + // raw_fd_ostream doesn't expose the FD. + using sys::fs::openFileForWrite; + if (auto EC = + openFileForReadWrite(FilenameBuf.str(), tentative.DumpFd, + sys::fs::CD_CreateNew, sys::fs::OF_None)) { + std::string errstr; + raw_string_ostream errstream(errstr); + errstream << "could not open JIT dump file " << FilenameBuf.str() << ": " + << EC.message() << "\n"; + return make_error(std::move(errstr), + inconvertibleErrorCode()); + } + + tentative.Dumpstream = std::make_unique(tentative.DumpFd, true); + + auto header = FillMachine(tentative); + if (!header) { + return header.takeError(); + } + + // signal this process emits JIT information + if (auto err = OpenMarker(tentative)) { + return std::move(err); + } + + + tentative.Dumpstream->write(reinterpret_cast(&header.get()), sizeof(*header)); + + // Everything initialized, can do profiling now. + if (tentative.Dumpstream->has_error()) { + return make_error("could not write JIT dump header", + inconvertibleErrorCode()); + } + state = std::move(tentative); + return Error::success(); +} + +static Error registerJITLoaderPerfEndImpl() { + if (!state) { + return make_error("PerfState not initialized", + inconvertibleErrorCode()); + } + RecHeader close; + close.Id = static_cast(PerfJITRecordType::JIT_CODE_CLOSE); + close.TotalSize = sizeof(close); + close.Timestamp = perf_get_timestamp(); + state->Dumpstream->write(reinterpret_cast(&close), sizeof(close)); + if (state->MarkerAddr) { + CloseMarker(*state); + } + state.reset(); + return Error::success(); +} + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfImpl(const char *Data, uint64_t Size) { + using namespace orc::shared; + return WrapperFunction::handle( + Data, Size, + registerJITLoaderPerfImpl) + .release(); +} + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfStart(const char *Data, uint64_t Size) { + using namespace orc::shared; + return WrapperFunction::handle( + Data, Size, + registerJITLoaderPerfStartImpl) + .release(); +} + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfEnd(const char *Data, uint64_t Size) { + using namespace orc::shared; + return WrapperFunction::handle( + Data, Size, + registerJITLoaderPerfEndImpl) + .release(); +} \ No newline at end of file diff --git a/llvm/test/ExecutionEngine/JITLink/X86/ELF_x86-64_perf.s b/llvm/test/ExecutionEngine/JITLink/X86/ELF_x86-64_perf.s new file mode 100644 --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/X86/ELF_x86-64_perf.s @@ -0,0 +1,31 @@ +# RUN: rm -rf %t && mkdir -p %t +# RUN: llvm-mc -triple=x86_64-unknown-linux -position-independent \ +# RUN: -filetype=obj -o %t/ELF_x86-64_perf.o %s +# RUN: JITDUMPDIR="%t" llvm-jitlink -perf-support \ +# RUN: %t/ELF_x86-64_perf.o +# RUN: test -f %t/.debug/jit/llvm-IR-jit-*/jit-*.dump +# +# Test ELF perf support + .text + .file "example.c" + .globl main # -- Begin function main + .p2align 4, 0x90 + .type main,@function +main: # @main + .cfi_startproc + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + movq %rsp, %rbp + .cfi_def_cfa_register %rbp + movl $0, -4(%rbp) + movl $1, %eax + popq %rbp + .cfi_def_cfa %rsp, 8 + retq +.Lfunc_end0: + .size main, .Lfunc_end0-main + .cfi_endproc + .ident "clang version 15.0.0 (https://github.com/llvm/llvm-project.git 4ba6a9c9f65bbc8bd06e3652cb20fd4dfc846137)" + .section ".note.GNU-stack","",@progbits + .addrsig \ No newline at end of file diff --git a/llvm/test/ExecutionEngine/JITLink/X86/ELF_x86-64_perf_debuginfo.s b/llvm/test/ExecutionEngine/JITLink/X86/ELF_x86-64_perf_debuginfo.s new file mode 100644 --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/X86/ELF_x86-64_perf_debuginfo.s @@ -0,0 +1,202 @@ +# RUN: rm -rf %t && mkdir -p %t +# RUN: llvm-mc -triple=x86_64-unknown-linux -position-independent \ +# RUN: -filetype=obj -o %t/ELF_x86-64_perf.o %s +# RUN: llvm-objdump -s %t/ELF_x86-64_perf.o +# RUN: JITDUMPDIR="%t" llvm-jitlink -show-addrs -dwarf-support \ +# RUN: -perf-support %t/ELF_x86-64_perf.o +# RUN: test -f %t/.debug/jit/llvm-IR-jit-*/jit-*.dump +# +# Test ELF perf support for debuginfo + .text + .file "example.c" + .section .text.source,"ax",@progbits + .globl source # -- Begin function source + .p2align 4, 0x90 + .type source,@function +source: # @source +.Lfunc_begin0: + .file 1 "/app" "example.c" + .loc 1 1 0 # example.c:1:0 + .cfi_startproc +# %bb.0: + .loc 1 2 5 prologue_end # example.c:2:5 + movl $1, %eax + retq +.Ltmp0: +.Lfunc_end0: + .size source, .Lfunc_end0-source + .cfi_endproc + # -- End function + .section .text.passthrough,"ax",@progbits + .globl passthrough # -- Begin function passthrough + .p2align 4, 0x90 + .type passthrough,@function +passthrough: # @passthrough +.Lfunc_begin1: + .loc 1 5 0 # example.c:5:0 + .cfi_startproc +# %bb.0: + .loc 1 6 5 prologue_end # example.c:6:5 + movl $1, %eax + retq +.Ltmp1: +.Lfunc_end1: + .size passthrough, .Lfunc_end1-passthrough + .cfi_endproc + # -- End function + .section .text.main,"ax",@progbits + .globl main # -- Begin function main + .p2align 4, 0x90 + .type main,@function +main: # @main +.Lfunc_begin2: + .loc 1 9 0 # example.c:9:0 + .cfi_startproc +# %bb.0: + .loc 1 10 5 prologue_end # example.c:10:5 + movl $1, %eax + retq +.Ltmp2: +.Lfunc_end2: + .size main, .Lfunc_end2-main + .cfi_endproc + # -- End function + .section .debug_abbrev,"",@progbits + .byte 1 # Abbreviation Code + .byte 17 # DW_TAG_compile_unit + .byte 1 # DW_CHILDREN_yes + .byte 37 # DW_AT_producer + .byte 14 # DW_FORM_strp + .byte 19 # DW_AT_language + .byte 5 # DW_FORM_data2 + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 16 # DW_AT_stmt_list + .byte 23 # DW_FORM_sec_offset + .byte 27 # DW_AT_comp_dir + .byte 14 # DW_FORM_strp + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 85 # DW_AT_ranges + .byte 23 # DW_FORM_sec_offset + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 2 # Abbreviation Code + .byte 46 # DW_TAG_subprogram + .byte 0 # DW_CHILDREN_no + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 64 # DW_AT_frame_base + .byte 24 # DW_FORM_exprloc + .ascii "\227B" # DW_AT_GNU_all_call_sites + .byte 25 # DW_FORM_flag_present + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 63 # DW_AT_external + .byte 25 # DW_FORM_flag_present + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 3 # Abbreviation Code + .byte 36 # DW_TAG_base_type + .byte 0 # DW_CHILDREN_no + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 62 # DW_AT_encoding + .byte 11 # DW_FORM_data1 + .byte 11 # DW_AT_byte_size + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 0 # EOM(3) + .section .debug_info,"",@progbits +.Lcu_begin0: + .long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit +.Ldebug_info_start0: + .short 4 # DWARF version number + .long .debug_abbrev # Offset Into Abbrev. Section + .byte 8 # Address Size (in bytes) + .byte 1 # Abbrev [1] 0xb:0x72 DW_TAG_compile_unit + .long .Linfo_string0 # DW_AT_producer + .short 12 # DW_AT_language + .long .Linfo_string1 # DW_AT_name + .long .Lline_table_start0 # DW_AT_stmt_list + .long .Linfo_string2 # DW_AT_comp_dir + .quad 0 # DW_AT_low_pc + .long .Ldebug_ranges0 # DW_AT_ranges + .byte 2 # Abbrev [2] 0x2a:0x19 DW_TAG_subprogram + .quad .Lfunc_begin0 # DW_AT_low_pc + .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 87 + # DW_AT_GNU_all_call_sites + .long .Linfo_string3 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 1 # DW_AT_decl_line + .long 117 # DW_AT_type + # DW_AT_external + .byte 2 # Abbrev [2] 0x43:0x19 DW_TAG_subprogram + .quad .Lfunc_begin1 # DW_AT_low_pc + .long .Lfunc_end1-.Lfunc_begin1 # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 87 + # DW_AT_GNU_all_call_sites + .long .Linfo_string5 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 5 # DW_AT_decl_line + .long 117 # DW_AT_type + # DW_AT_external + .byte 2 # Abbrev [2] 0x5c:0x19 DW_TAG_subprogram + .quad .Lfunc_begin2 # DW_AT_low_pc + .long .Lfunc_end2-.Lfunc_begin2 # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 87 + # DW_AT_GNU_all_call_sites + .long .Linfo_string6 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 9 # DW_AT_decl_line + .long 117 # DW_AT_type + # DW_AT_external + .byte 3 # Abbrev [3] 0x75:0x7 DW_TAG_base_type + .long .Linfo_string4 # DW_AT_name + .byte 5 # DW_AT_encoding + .byte 4 # DW_AT_byte_size + .byte 0 # End Of Children Mark +.Ldebug_info_end0: + .section .debug_ranges,"",@progbits +.Ldebug_ranges0: + .quad .Lfunc_begin0 + .quad .Lfunc_end0 + .quad .Lfunc_begin1 + .quad .Lfunc_end1 + .quad .Lfunc_begin2 + .quad .Lfunc_end2 + .quad 0 + .quad 0 + .section .debug_str,"MS",@progbits,1 +.Linfo_string0: + .asciz "clang version 15.0.0 (https://github.com/llvm/llvm-project.git 4ba6a9c9f65bbc8bd06e3652cb20fd4dfc846137)" # string offset=0 +.Linfo_string1: + .asciz "/app/example.c" # string offset=105 +.Linfo_string2: + .asciz "/app" # string offset=120 +.Linfo_string3: + .asciz "source" # string offset=125 +.Linfo_string4: + .asciz "int" # string offset=132 +.Linfo_string5: + .asciz "passthrough" # string offset=136 +.Linfo_string6: + .asciz "main" # string offset=148 + .ident "clang version 15.0.0 (https://github.com/llvm/llvm-project.git 4ba6a9c9f65bbc8bd06e3652cb20fd4dfc846137)" + .section ".note.GNU-stack","",@progbits + .addrsig + .section .debug_line,"",@progbits +.Lline_table_start0: \ No newline at end of file diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp --- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp +++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp @@ -28,8 +28,11 @@ #include "llvm/ExecutionEngine/Orc/MachOPlatform.h" #include "llvm/ExecutionEngine/Orc/MapperJITLinkMemoryManager.h" #include "llvm/ExecutionEngine/Orc/ObjectFileInterface.h" +#include "llvm/ExecutionEngine/Orc/PerfSupportPlugin.h" +#include "llvm/ExecutionEngine/Orc/DebugInfoPlugins.h" #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" #include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" +#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h" #include "llvm/ExecutionEngine/Orc/TargetProcess/RegisterEHFrames.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCContext.h" @@ -140,6 +143,16 @@ cl::desc("Enable debugger suppport (default = !-noexec)"), cl::init(true), cl::Hidden, cl::cat(JITLinkCategory)); +static cl::opt + DWARFSupport("dwarf-support", + cl::desc("Enable DWARF suppport (default = !-noexec)"), + cl::init(false), cl::Hidden, cl::cat(JITLinkCategory)); + +static cl::opt + PerfSupport("perf-support", + cl::desc("Enable perf profiling suppport (default = !-noexec)"), + cl::init(false), cl::Hidden, cl::cat(JITLinkCategory)); + static cl::opt NoProcessSymbols("no-process-syms", cl::desc("Do not resolve to llvm-jitlink process symbols"), @@ -249,7 +262,10 @@ errs() << (void *)&llvm_orc_registerEHFrameSectionWrapper << (void *)&llvm_orc_deregisterEHFrameSectionWrapper << (void *)&llvm_orc_registerJITLoaderGDBWrapper - << (void *)&llvm_orc_registerJITLoaderGDBAllocAction; + << (void *)&llvm_orc_registerJITLoaderGDBAllocAction + << (void *)&llvm_orc_registerJITLoaderPerfImpl + << (void *)&llvm_orc_registerJITLoaderPerfStart + << (void *)&llvm_orc_registerJITLoaderPerfEnd; } static bool UseTestResultOverride = false; @@ -987,6 +1003,20 @@ if (DebuggerSupport && TT.isOSBinFormatMachO()) ObjLayer.addPlugin(ExitOnErr( GDBJITDebugInfoRegistrationPlugin::Create(this->ES, *MainJD, TT))); + + DWARFContextPlugin *DWARFPlugin = nullptr; + if (DWARFSupport && TT.isOSBinFormatELF()) { + ObjLayer.addPlugin(ExitOnErr( + PreserveDebugInfoSectionsPlugin::Create())); + auto DWARFContextPlugin = ExitOnErr( + DWARFContextPlugin::Create()); + DWARFPlugin = DWARFContextPlugin.get(); + ObjLayer.addPlugin(std::move(DWARFContextPlugin)); + } + + if (PerfSupport && TT.isOSBinFormatELF()) + ObjLayer.addPlugin(ExitOnErr( + PerfSupportPlugin::Create(this->ES.getExecutorProcessControl(), *MainJD, DWARFPlugin))); // Set up the platform. if (TT.isOSBinFormatMachO() && !OrcRuntime.empty()) { @@ -2046,6 +2076,8 @@ if (ShowAddrs) S->dumpSessionInfo(outs()); + + linkComponents(); dumpSessionStats(*S); @@ -2053,6 +2085,7 @@ if (Timers) Timers->JITLinkTG.printAll(errs()); reportLLVMJITLinkError(EntryPoint.takeError()); + dbgs() << "Failed to find entry point \"" << EntryPointName << "\".\n"; exit(1); }