[llvm] dcf8ae8 - Reapply "[ORC] Add utilities for limited symbolication of JIT backtra… (#175469)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jan 11 17:23:11 PST 2026


Author: Lang Hames
Date: 2026-01-12T12:23:07+11:00
New Revision: dcf8ae80289fd5e343b6ca172073388b6d227b62

URL: https://github.com/llvm/llvm-project/commit/dcf8ae80289fd5e343b6ca172073388b6d227b62
DIFF: https://github.com/llvm/llvm-project/commit/dcf8ae80289fd5e343b6ca172073388b6d227b62.diff

LOG: Reapply "[ORC] Add utilities for limited symbolication of JIT backtra… (#175469)

…ces"

This reapplies 906b48616c03948a4df62a5a144f7108f3c455e8, which was
reverted in c11df52f9b847170b766fb71defd2a9222d95a8d due to bot
failures.

The testcase has been dropped from this recommit as it failed on several
bots (possbly due to differing backtrace formats or failure modes). I'll
re-introduce the testcase in a follow-up commit so that it cane be
iterated on (and re-reverted if necessary) without affecting the options
introduced by this commit. (Since these options are best-effort
debugging tools it's ok if they live in-tree without a test for now).

Added: 
    llvm/include/llvm/ExecutionEngine/Orc/BacktraceTools.h
    llvm/lib/ExecutionEngine/Orc/BacktraceTools.cpp

Modified: 
    llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
    llvm/tools/llvm-jitlink/llvm-jitlink.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/ExecutionEngine/Orc/BacktraceTools.h b/llvm/include/llvm/ExecutionEngine/Orc/BacktraceTools.h
new file mode 100644
index 0000000000000..454d7654b66bf
--- /dev/null
+++ b/llvm/include/llvm/ExecutionEngine/Orc/BacktraceTools.h
@@ -0,0 +1,99 @@
+//===-- BacktraceTools.h - Backtrace symbolication tools -------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Tools for dumping symbol tables and symbolicating backtraces.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_ORC_BACKTRACETOOLS_H
+#define LLVM_EXECUTIONENGINE_ORC_BACKTRACETOOLS_H
+
+#include "llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <memory>
+#include <mutex>
+#include <string>
+
+namespace llvm::orc {
+
+/// Dumps symbol tables from LinkGraphs to enable backtrace symbolication.
+///
+/// This plugin appends symbol information to a file in the following format:
+///   "<link graph name>"
+///   <address> <symbol name>
+///   <address> <symbol name>
+///   ...
+///
+/// Where addresses are in hexadecimal and symbol names are for defined symbols.
+class LLVM_ABI SymbolTableDumpPlugin : public LinkGraphLinkingLayer::Plugin {
+public:
+  /// Create a SymbolTableDumpPlugin that will append symbol information
+  /// to the file at the given path.
+  static Expected<std::shared_ptr<SymbolTableDumpPlugin>>
+  Create(StringRef Path);
+
+  /// Create a SymbolTableDumpPlugin. The resulting object is in an invalid
+  /// state if, upon return, EC != std::error_code().
+  /// Prefer SymbolTableDumpPlugin::Create.
+  SymbolTableDumpPlugin(StringRef Path, std::error_code &EC);
+
+  SymbolTableDumpPlugin(const SymbolTableDumpPlugin &) = delete;
+  SymbolTableDumpPlugin &operator=(const SymbolTableDumpPlugin &) = delete;
+  SymbolTableDumpPlugin(SymbolTableDumpPlugin &&) = delete;
+  SymbolTableDumpPlugin &operator=(SymbolTableDumpPlugin &&) = delete;
+
+  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 {}
+
+private:
+  raw_fd_ostream OutputStream;
+  std::mutex DumpMutex;
+};
+
+/// A class for symbolicating backtraces using a previously dumped symbol table.
+class LLVM_ABI DumpedSymbolTable {
+public:
+  /// Create a DumpedSymbolTable from the given path.
+  static Expected<DumpedSymbolTable> Create(StringRef Path);
+
+  /// Given a backtrace, try to symbolicate any unsymbolicated lines using the
+  /// symbol addresses in the dumped symbol table.
+  LLVM_ABI std::string symbolicate(StringRef Backtrace);
+
+private:
+  DumpedSymbolTable(std::unique_ptr<MemoryBuffer> SymtabBuffer);
+
+  void parseBuffer();
+
+  struct SymbolInfo {
+    StringRef SymName;
+    StringRef GraphName;
+  };
+
+  std::map<uint64_t, SymbolInfo> SymbolInfos;
+  std::unique_ptr<MemoryBuffer> SymtabBuffer;
+};
+
+} // namespace llvm::orc
+
+#endif // LLVM_EXECUTIONENGINE_ORC_BACKTRACETOOLS_H

diff  --git a/llvm/lib/ExecutionEngine/Orc/BacktraceTools.cpp b/llvm/lib/ExecutionEngine/Orc/BacktraceTools.cpp
new file mode 100644
index 0000000000000..3955469dfc0e6
--- /dev/null
+++ b/llvm/lib/ExecutionEngine/Orc/BacktraceTools.cpp
@@ -0,0 +1,150 @@
+//===------- BacktraceTools.cpp - Backtrace symbolication tools ----------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ExecutionEngine/Orc/BacktraceTools.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ExecutionEngine/JITLink/JITLink.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+namespace llvm::orc {
+
+Expected<std::shared_ptr<SymbolTableDumpPlugin>>
+SymbolTableDumpPlugin::Create(StringRef Path) {
+  std::error_code EC;
+  auto P = std::make_shared<SymbolTableDumpPlugin>(Path, EC);
+  if (EC)
+    return createFileError(Path, EC);
+  return P;
+}
+
+SymbolTableDumpPlugin::SymbolTableDumpPlugin(StringRef Path,
+                                             std::error_code &EC)
+    : OutputStream(Path, EC) {}
+
+void SymbolTableDumpPlugin::modifyPassConfig(
+    MaterializationResponsibility &MR, jitlink::LinkGraph &G,
+    jitlink::PassConfiguration &Config) {
+
+  Config.PostAllocationPasses.push_back([this](jitlink::LinkGraph &G) -> Error {
+    std::scoped_lock<std::mutex> Lock(DumpMutex);
+
+    OutputStream << "\"" << G.getName() << "\"\n";
+    for (auto &Sec : G.sections()) {
+      // NoAlloc symbols don't exist in the executing process, so can't
+      // contribute to symbolication. (Note: We leave Finalize-liftime symbols
+      // in for now in case of crashes during finalization, but we should
+      // probably make this optional).
+      if (Sec.getMemLifetime() == MemLifetime::NoAlloc)
+        continue;
+
+      // Write out named symbols. Anonymous symbols are skipped, since they
+      // don't add any information for symbolication purposes.
+      for (auto *Sym : Sec.symbols()) {
+        if (Sym->hasName())
+          OutputStream << formatv("{0:x}", Sym->getAddress().getValue()) << " "
+                       << Sym->getName() << "\n";
+      }
+    }
+
+    OutputStream.flush();
+    return Error::success();
+  });
+}
+
+Expected<DumpedSymbolTable> DumpedSymbolTable::Create(StringRef Path) {
+  auto MB = MemoryBuffer::getFile(Path);
+  if (!MB)
+    return createFileError(Path, MB.getError());
+
+  return DumpedSymbolTable(std::move(*MB));
+}
+
+DumpedSymbolTable::DumpedSymbolTable(std::unique_ptr<MemoryBuffer> SymtabBuffer)
+    : SymtabBuffer(std::move(SymtabBuffer)) {
+  parseBuffer();
+}
+
+void DumpedSymbolTable::parseBuffer() {
+  // Read the symbol table file
+  SmallVector<StringRef, 0> Rows;
+  SymtabBuffer->getBuffer().split(Rows, '\n');
+
+  StringRef CurGraph = "<unidentified>";
+  for (auto Row : Rows) {
+    Row = Row.trim();
+    if (Row.empty())
+      continue;
+
+    // Check for graph name line (enclosed in quotes)
+    if (Row.starts_with("\"") && Row.ends_with("\"")) {
+      CurGraph = Row.trim('"');
+      continue;
+    }
+
+    // Parse "address symbol_name" lines, ignoring malformed lines.
+    size_t SpacePos = Row.find(' ');
+    if (SpacePos == StringRef::npos)
+      continue;
+
+    StringRef AddrStr = Row.substr(0, SpacePos);
+    StringRef SymName = Row.substr(SpacePos + 1);
+
+    uint64_t Addr;
+    if (AddrStr.starts_with("0x"))
+      AddrStr = AddrStr.drop_front(2);
+    if (AddrStr.getAsInteger(16, Addr))
+      continue; // Skip malformed lines
+
+    SymbolInfos[Addr] = {SymName, CurGraph};
+  }
+}
+
+std::string DumpedSymbolTable::symbolicate(StringRef Backtrace) {
+  // Symbolicate the backtrace by replacing rows with empty symbol names
+  SmallVector<StringRef, 0> BacktraceRows;
+  Backtrace.split(BacktraceRows, '\n');
+
+  std::string Result;
+  raw_string_ostream Out(Result);
+  for (auto Row : BacktraceRows) {
+    // Look for a row ending with a hex number. If there's only one column, or
+    // if the last column is not a hex number, then just reproduce the input
+    // row.
+    auto [RowStart, AddrCol] = Row.rtrim().rsplit(' ');
+    auto AddrStr = AddrCol.starts_with("0x") ? AddrCol.drop_front(2) : AddrCol;
+
+    uint64_t Addr;
+    if (AddrStr.empty() || AddrStr.getAsInteger(16, Addr)) {
+      Out << Row << "\n";
+      continue;
+    }
+
+    // Search for the address
+    auto I = SymbolInfos.upper_bound(Addr);
+
+    // If no JIT symbol entry within 2Gb then skip.
+    if (I == SymbolInfos.begin() || (Addr - std::prev(I)->first >= 1U << 31)) {
+      Out << Row << "\n";
+      continue;
+    }
+
+    // Found a symbol. Output modified line.
+    auto &[SymAddr, SymInfo] = *std::prev(I);
+    Out << RowStart << " " << AddrCol << " " << SymInfo.SymName;
+    if (auto Delta = Addr - SymAddr)
+      Out << " + " << formatv("{0}", Delta);
+    Out << " (" << SymInfo.GraphName << ")\n";
+  }
+
+  return Result;
+}
+
+} // namespace llvm::orc

diff  --git a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
index 422b20c649c70..8c78c0250cfb8 100644
--- a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
+++ b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
@@ -8,6 +8,7 @@ endif()
 
 add_llvm_component_library(LLVMOrcJIT
   AbsoluteSymbols.cpp
+  BacktraceTools.cpp
   COFF.cpp
   COFFVCRuntimeSupport.cpp
   COFFPlatform.cpp

diff  --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
index b8de817ec16c8..0cf4a175622f8 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
@@ -16,6 +16,7 @@
 #include "llvm/BinaryFormat/Magic.h"
 #include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX, LLVM_ENABLE_THREADS
 #include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h"
+#include "llvm/ExecutionEngine/Orc/BacktraceTools.h"
 #include "llvm/ExecutionEngine/Orc/COFFPlatform.h"
 #include "llvm/ExecutionEngine/Orc/Debugging/DebugInfoSupport.h"
 #include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.h"
@@ -140,6 +141,18 @@ static cl::list<std::string>
                cl::desc("Link against library X with hidden visibility"),
                cl::cat(JITLinkCategory));
 
+static cl::opt<std::string>
+    WriteSymbolTableTo("write-symtab",
+                       cl::desc("Write the symbol table for the JIT'd program "
+                                "to the specified file"),
+                       cl::cat(JITLinkCategory));
+
+static cl::opt<std::string> SymbolicateWith(
+    "symbolicate-with",
+    cl::desc("Given a path to a symbol table file, symbolicate the given "
+             "backtrace(s)"),
+    cl::cat(JITLinkCategory));
+
 static cl::list<std::string>
     LibrariesWeak("weak-l",
                   cl::desc("Emulate weak link against library X. Must resolve "
@@ -1203,6 +1216,15 @@ Session::Session(std::unique_ptr<ExecutorProcessControl> EPC, Error &Err)
 
   auto &TT = ES.getTargetTriple();
 
+  if (!WriteSymbolTableTo.empty()) {
+    if (auto STDump = SymbolTableDumpPlugin::Create(WriteSymbolTableTo))
+      ObjLayer.addPlugin(std::move(*STDump));
+    else {
+      Err = STDump.takeError();
+      return;
+    }
+  }
+
   if (DebuggerSupport && TT.isOSBinFormatMachO()) {
     if (!ProcessSymsJD) {
       Err = make_error<StringError>("MachO debugging requires process symbols",
@@ -1666,6 +1688,12 @@ Session::findSymbolInfo(const orc::SymbolStringPtr &SymbolName,
 } // end namespace llvm
 
 static std::pair<Triple, SubtargetFeatures> getFirstFileTripleAndFeatures() {
+
+  // If we're running in symbolicate mode then just use the process triple.
+  if (!SymbolicateWith.empty())
+    return std::make_pair(Triple(sys::getProcessTriple()), SubtargetFeatures());
+
+  // Otherwise we need to inspect the input files.
   static std::pair<Triple, SubtargetFeatures> FirstTTAndFeatures = []() {
     assert(!InputFiles.empty() && "InputFiles can not be empty");
 
@@ -1878,6 +1906,14 @@ static Error sanitizeArguments(const Triple &TT, const char *ArgV0) {
     }
   }
 
+  if (!SymbolicateWith.empty()) {
+    if (!WriteSymbolTableTo.empty())
+      errs() << WriteSymbolTableTo.ArgStr << " specified with "
+             << SymbolicateWith.ArgStr << ", ignoring.";
+    if (InputFiles.empty())
+      InputFiles.push_back("-");
+  }
+
   return Error::success();
 }
 
@@ -2817,6 +2853,22 @@ static Expected<int> runWithoutRuntime(Session &S,
   return S.ES.getExecutorProcessControl().runAsMain(EntryPointAddr, InputArgv);
 }
 
+static Error symbolicateBacktraces() {
+  auto Symtab = DumpedSymbolTable::Create(SymbolicateWith);
+  if (!Symtab)
+    return Symtab.takeError();
+
+  for (auto InputFile : InputFiles) {
+    auto BacktraceBuffer = MemoryBuffer::getFileOrSTDIN(InputFile);
+    if (!BacktraceBuffer)
+      return createFileError(InputFile, BacktraceBuffer.getError());
+
+    outs() << Symtab->symbolicate((*BacktraceBuffer)->getBuffer());
+  }
+
+  return Error::success();
+}
+
 namespace {
 struct JITLinkTimers {
   TimerGroup JITLinkTG{"llvm-jitlink timers", "timers for llvm-jitlink phases"};
@@ -2844,6 +2896,11 @@ int main(int argc, char *argv[]) {
   auto [TT, Features] = getFirstFileTripleAndFeatures();
   ExitOnErr(sanitizeArguments(TT, argv[0]));
 
+  if (!SymbolicateWith.empty()) {
+    ExitOnErr(symbolicateBacktraces());
+    return 0;
+  }
+
   auto S = ExitOnErr(Session::Create(TT, Features));
 
   enableStatistics(*S, !OrcRuntime.empty());


        


More information about the llvm-commits mailing list