[Lldb-commits] [lldb] Add a public LLDB API for .dwp/.dwo discovery (PR #197051)

via lldb-commits lldb-commits at lists.llvm.org
Mon May 11 15:49:53 PDT 2026


https://github.com/ionuthristodorescu created https://github.com/llvm/llvm-project/pull/197051

Added a public lldb::SBFileSpecList lldb::SBModule::GetSeparateDebugInfoFiles LLDB API for .dwp/.dwo discovery (wrapping around the existing const std::shared_ptr<SymbolFileDWARFDwo> &SymbolFileDWARF::GetDwpSymbolFile() LLDB private API); returns a SBFileSpecList with either the .dwp file or .dwo (if no .dwp) or empty if no split debug info.

>From f91836f7fb4d2a895fad60f91cd719893c7fbccc Mon Sep 17 00:00:00 2001
From: Ionut Hristodorescu <ionuth at meta.com>
Date: Mon, 11 May 2026 15:45:21 -0700
Subject: [PATCH] Added a public lldb::SBModuleSpecList
 lldb::SBModule::GetSeparateDebugInfoFiles LLDB API for .dwp/.dwo discovery
 (wrapping around the existing const std::shared_ptr<SymbolFileDWARFDwo>
 &SymbolFileDWARF::GetDwpSymbolFile() LLDB private API); returns a
 SBFileSpecList with either the .dwp file or .dwo (if no .dwp) or empty if no
 split debug info.

---
 lldb/include/lldb/API/SBModule.h              | 23 +++++++++
 lldb/include/lldb/API/SBModuleSpec.h          |  2 +
 lldb/include/lldb/Symbol/SymbolFile.h         | 22 +++++++++
 lldb/source/API/SBModule.cpp                  | 16 +++++++
 lldb/source/API/SBModuleSpec.cpp              |  2 +
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp      | 41 ++++++++++++++++
 .../SymbolFile/DWARF/SymbolFileDWARF.h        |  2 +
 .../DWARF/SymbolFileDWARFDebugMap.cpp         | 26 ++++++++++
 .../DWARF/SymbolFileDWARFDebugMap.h           |  2 +
 .../get-separate-debug-info-files-darwin.cpp  | 38 +++++++++++++++
 .../DWARF/get-separate-debug-info-files.cpp   | 48 +++++++++++++++++++
 11 files changed, 222 insertions(+)
 create mode 100644 lldb/test/Shell/SymbolFile/DWARF/get-separate-debug-info-files-darwin.cpp
 create mode 100644 lldb/test/Shell/SymbolFile/DWARF/get-separate-debug-info-files.cpp

diff --git a/lldb/include/lldb/API/SBModule.h b/lldb/include/lldb/API/SBModule.h
index 4009ca1461e51..b5106b0ede3c5 100644
--- a/lldb/include/lldb/API/SBModule.h
+++ b/lldb/include/lldb/API/SBModule.h
@@ -290,6 +290,29 @@ class LLDB_API SBModule {
   lldb::SBAddress GetObjectFileHeaderAddress() const;
   lldb::SBAddress GetObjectFileEntryPointAddress() const;
 
+  /// Get the separate debug info files for this module.
+  ///
+  /// Returns a list of file paths for the separate debug info files
+  /// associated with this module. Separate debug info files are
+  /// considered any files that are referenced from debug info but
+  /// aren't the actual object file that the symbol file parses.
+  ///
+  /// If this module uses split DWARF it will return a DWARF package
+  /// (.dwp) if it exists, otherwise it will return a list of all
+  /// .dwo files.
+  ///
+  /// If this module uses DWARF in .o files (Darwin), it will return
+  /// a list of all .o files if there is no dSYM file. If a dSYM file
+  /// is present, no specifications will be returned since the debug
+  /// info is self-contained in the dSYM bundle.
+  ///
+  /// An empty list will be returned if there are no separate debug
+  /// info files for this module.
+  ///
+  /// \return
+  ///     A list of module specifications for the separate debug info files.
+  lldb::SBModuleSpecList GetSeparateDebugInfoFiles();
+
   /// Get the number of global modules.
   static uint32_t GetNumberAllocatedModules();
 
diff --git a/lldb/include/lldb/API/SBModuleSpec.h b/lldb/include/lldb/API/SBModuleSpec.h
index 0e7f0f3489596..8f26dea4e2a4d 100644
--- a/lldb/include/lldb/API/SBModuleSpec.h
+++ b/lldb/include/lldb/API/SBModuleSpec.h
@@ -135,6 +135,8 @@ class SBModuleSpecList {
   bool GetDescription(lldb::SBStream &description);
 
 private:
+  friend class SBModule;
+  lldb_private::ModuleSpecList &ref();
   std::unique_ptr<lldb_private::ModuleSpecList> m_opaque_up;
 };
 
diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h
index 6b53ba8b6acce..8c0095185aad3 100644
--- a/lldb/include/lldb/Symbol/SymbolFile.h
+++ b/lldb/include/lldb/Symbol/SymbolFile.h
@@ -135,6 +135,28 @@ class SymbolFile : public PluginInterface {
   /// It will be true for most implementations except SymbolFileOnDemand.
   virtual bool GetLoadDebugInfoEnabled() { return true; }
 
+  /// Get the separate debug info files for this module.
+  ///
+  /// Returns a list of module specs for the separate debug info files
+  /// associated with this module. Separate debug info files are
+  /// considered any files that are referenced from debug info but
+  /// aren't the actual object file that the symbol file parses.
+  ///
+  /// If this module uses split DWARF it will return a DWARF package
+  /// (.dwp) if it exists, otherwise it will return a list of all
+  /// .dwo files.
+  ///
+  /// If this module uses DWARF in .o files (Darwin), it will return
+  /// a list of all .o files if there is no dSYM file. If a dSYM file
+  /// is present, no specifications will be returned since the debug
+  /// info is self-contained in the dSYM bundle.
+  ///
+  /// An empty list will be returned if there are no separate debug
+  /// info files for this module.
+  virtual ModuleSpecList GetSeparateDebugInfoModuleSpecs() {
+    return {};
+  }
+
   /// Specify debug info should be loaded.
   ///
   /// It will be no-op for most implementations except SymbolFileOnDemand.
diff --git a/lldb/source/API/SBModule.cpp b/lldb/source/API/SBModule.cpp
index ea0cb2ae356b8..8483d9fd5bdd4 100644
--- a/lldb/source/API/SBModule.cpp
+++ b/lldb/source/API/SBModule.cpp
@@ -666,6 +666,22 @@ lldb::SBAddress SBModule::GetObjectFileEntryPointAddress() const {
   return sb_addr;
 }
 
+lldb::SBModuleSpecList SBModule::GetSeparateDebugInfoFiles() {
+  LLDB_INSTRUMENT_VA(this);
+
+  SBModuleSpecList sb_mspec_list;
+  ModuleSP module_sp(GetSP());
+  if (!module_sp)
+    return sb_mspec_list;
+
+  SymbolFile *sym_file = module_sp->GetSymbolFile();
+  if (!sym_file)
+    return sb_mspec_list;
+  
+  sb_mspec_list.ref() = sym_file->GetSeparateDebugInfoModuleSpecs();
+  return sb_mspec_list;
+}
+
 uint32_t SBModule::GetNumberAllocatedModules() {
   LLDB_INSTRUMENT();
 
diff --git a/lldb/source/API/SBModuleSpec.cpp b/lldb/source/API/SBModuleSpec.cpp
index 7b59538f70161..6249302757de2 100644
--- a/lldb/source/API/SBModuleSpec.cpp
+++ b/lldb/source/API/SBModuleSpec.cpp
@@ -269,3 +269,5 @@ bool SBModuleSpecList::GetDescription(lldb::SBStream &description) {
   m_opaque_up->Dump(description.ref());
   return true;
 }
+
+ModuleSpecList &SBModuleSpecList::ref() { return *m_opaque_up; }
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index aca36b45d69ca..4c8d8f8a87273 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -4560,6 +4560,47 @@ StatsDuration::Duration SymbolFileDWARF::GetDebugInfoIndexTime() {
   return {};
 }
 
+ModuleSpecList SymbolFileDWARF::GetSeparateDebugInfoModuleSpecs() {
+  ModuleSpecList specs;
+
+  // Check if a .dwp file exists using LLDB's built-in DWP discovery.
+  if (const auto &dwp_sp = GetDwpSymbolFile()) {
+    if (ObjectFile *dwp_obj = dwp_sp->GetObjectFile()) {
+      specs.Append(ModuleSpec(dwp_obj->GetFileSpec()));
+      return specs;
+    }
+  }
+
+  // No DWP — collect individual DWO file paths from the skeleton CUs.
+  DWARFDebugInfo &info = DebugInfo();
+  const size_t num_cus = info.GetNumUnits();
+  for (size_t cu_idx = 0; cu_idx < num_cus; cu_idx++) {
+    DWARFUnit *unit = info.GetUnitAtIndex(cu_idx);
+    DWARFCompileUnit *dwarf_cu = llvm::dyn_cast<DWARFCompileUnit>(unit);
+    if (!dwarf_cu || !dwarf_cu->GetDWOId().has_value())
+      continue;
+
+    const DWARFBaseDIE die = dwarf_cu->GetUnitDIEOnly();
+    if (!die)
+      continue;
+
+    const char *dwo_name = GetDWOName(*dwarf_cu, *die.GetDIE());
+    if (!dwo_name)
+      continue;
+
+    FileSpec dwo_file(dwo_name);
+    if (!dwo_file.IsAbsolute()) {
+      const char *comp_dir = 
+        die.GetDIE()->GetAttributeValueAsString(dwarf_cu, 
+          DW_AT_comp_dir, nullptr);
+      if (comp_dir)
+        dwo_file.PrependPathComponent(comp_dir);
+    }
+    specs.Append(ModuleSpec(dwo_file));
+  }
+  return specs;
+}
+
 void SymbolFileDWARF::ResetStatistics() {
   m_parse_time.reset();
   if (m_index)
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
index 09dc8da9b7260..142297a6f512c 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -321,6 +321,8 @@ class SymbolFileDWARF : public SymbolFileCommon {
 
   StatsDuration &GetDebugInfoParseTimeRef() { return m_parse_time; }
 
+  ModuleSpecList GetSeparateDebugInfoModuleSpecs() override;
+
   void ResetStatistics() override;
 
   virtual lldb::offset_t
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
index eb80b7ed45d7b..2db17557070cb 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
@@ -13,6 +13,7 @@
 
 #include "lldb/Core/Module.h"
 #include "lldb/Core/ModuleList.h"
+#include "lldb/Core/ModuleSpec.h"
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Core/Progress.h"
 #include "lldb/Core/Section.h"
@@ -1327,6 +1328,31 @@ bool SymbolFileDWARFDebugMap::GetSeparateDebugInfo(
   return true;
 }
 
+ModuleSpecList SymbolFileDWARFDebugMap::GetSeparateDebugInfoModuleSpecs() {
+  ModuleSpecList specs;
+  const uint32_t cu_count = GetNumCompileUnits();
+  for (uint32_t cu_idx = 0; cu_idx < cu_count; ++cu_idx) {
+    const auto &info = m_compile_unit_infos[cu_idx];
+    if (!info.oso_path)
+      continue;
+
+    ModuleSpec spec;
+    FileSpec oso_file;
+    ConstString oso_object;
+    if (ObjectFile::SplitArchivePathWithObject(
+            info.oso_path.GetStringRef(), oso_file, oso_object,
+            /*must_exist=*/false)) {
+      spec.GetFileSpec() = oso_file;
+      spec.GetObjectName() = oso_object;
+    } else {
+      spec.GetFileSpec() = FileSpec(info.oso_path.GetStringRef());
+    }
+    spec.GetObjectModificationTime() = info.oso_mod_time;
+    specs.Append(spec);
+  }
+  return specs;
+}
+
 lldb::CompUnitSP
 SymbolFileDWARFDebugMap::GetCompileUnit(SymbolFileDWARF *oso_dwarf,
                                         DWARFCompileUnit &dwarf_cu) {
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
index 74b97f610f29c..dcd685edbf78e 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
@@ -136,6 +136,8 @@ class SymbolFileDWARFDebugMap : public SymbolFileCommon {
   bool GetSeparateDebugInfo(StructuredData::Dictionary &d, bool errors_only,
                             bool load_all_debug_info = false) override;
 
+  ModuleSpecList GetSeparateDebugInfoModuleSpecs() override;
+
   // PluginInterface protocol
   llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
 
diff --git a/lldb/test/Shell/SymbolFile/DWARF/get-separate-debug-info-files-darwin.cpp b/lldb/test/Shell/SymbolFile/DWARF/get-separate-debug-info-files-darwin.cpp
new file mode 100644
index 0000000000000..4558bd5425e15
--- /dev/null
+++ b/lldb/test/Shell/SymbolFile/DWARF/get-separate-debug-info-files-darwin.cpp
@@ -0,0 +1,38 @@
+// REQUIRES: system-darwin
+​​​
+// Test SBModule::GetSeparateDebugInfoFiles() for Darwin scenarios:
+//   1. DWARF in .o files (no dSYM) — returns .o file paths
+//   2. dSYM present — returns empty (debug info is self-contained)
+​​​
+struct A {
+  int x = 47;
+};
+A a;
+int main() {}
+​​​
+// ============================================================================
+// TEST 1: DWARF in .o files (no dSYM) — returns .o paths
+// ============================================================================
+// Compile to .o with debug info, then link without generating a dSYM.
+// RUN: %clang_host -g -c %s -o %t.main.o
+// RUN: %clang_host %t.main.o -o %t.oso -Wl,-no_uuid
+// RUN: %lldb -b \
+// RUN:   -o "script m = lldb.target.modules[0]; files = m.GetSeparateDebugInfoFiles(); print('OSO_COUNT=' + str(files.GetSize())); [print('OSO_FILE=' + files.GetSpecAtIndex(i).GetFileSpec().fullpath) for i in range(files.GetSize())]" \
+// RUN:   %t.oso 2>&1 | FileCheck %s --check-prefix=OSO
+//
+// With DWARF in .o files, should list the .o file.
+// OSO: OSO_COUNT=1
+// OSO: OSO_FILE={{.*}}.main.o
+​​​
+// ============================================================================
+// TEST 2: dSYM present — returns empty list
+// ============================================================================
+// Build with debug info and generate a dSYM bundle.
+// RUN: %clang_host -g %s -o %t.dsym_exe
+// RUN: dsymutil %t.dsym_exe
+// RUN: %lldb -b \
+// RUN:   -o "script m = lldb.target.modules[0]; files = m.GetSeparateDebugInfoFiles(); print('DSYM_COUNT=' + str(files.GetSize()))" \
+// RUN:   %t.dsym_exe 2>&1 | FileCheck %s --check-prefix=DSYM
+//
+// With a dSYM, debug info is self-contained — no separate files.
+// DSYM: DSYM_COUNT=0
diff --git a/lldb/test/Shell/SymbolFile/DWARF/get-separate-debug-info-files.cpp b/lldb/test/Shell/SymbolFile/DWARF/get-separate-debug-info-files.cpp
new file mode 100644
index 0000000000000..a816f3cdd46fa
--- /dev/null
+++ b/lldb/test/Shell/SymbolFile/DWARF/get-separate-debug-info-files.cpp
@@ -0,0 +1,48 @@
+// REQUIRES: system-linux
+​​​
+// Test SBModule::GetSeparateDebugInfoFiles() for three scenarios:
+//   1. No split DWARF — returns empty list
+//   2. Split DWARF with .dwo files — returns DWO file paths
+//   3. Split DWARF with .dwp file — returns DWP file path
+​​​
+struct A {
+  int x = 47;
+};
+A a;
+int main() {}
+​​​
+// ============================================================================
+// TEST 1: No split DWARF — GetSeparateDebugInfoFiles returns empty
+// ============================================================================
+// RUN: %clang_host -g -c %s -o %t.nosplit.o
+// RUN: %clang_host %t.nosplit.o -o %t.nosplit
+// RUN: %lldb -b \
+// RUN:   -o "script m = lldb.target.modules[0]; files = m.GetSeparateDebugInfoFiles(); print('NOSPLIT_COUNT=' + str(files.GetSize()))" \
+// RUN:   %t.nosplit 2>&1 | FileCheck %s --check-prefix=NOSPLIT
+//
+// NOSPLIT: NOSPLIT_COUNT=0
+​​​
+// ============================================================================
+// TEST 2: Split DWARF with .dwo files — returns DWO paths
+// ============================================================================
+// RUN: %clang_host -gsplit-dwarf -gdwarf-5 -c %s -o %t.dwo.o
+// RUN: %clang_host %t.dwo.o -o %t.dwo
+// RUN: rm -f %t.dwo.dwp
+// RUN: %lldb -b \
+// RUN:   -o "script m = lldb.target.modules[0]; files = m.GetSeparateDebugInfoFiles(); print('DWO_COUNT=' + str(files.GetSize())); [print('DWO_FILE=' + files.GetSpecAtIndex(i).GetFileSpec().fullpath) for i in range(files.GetSize())]" \
+// RUN:   %t.dwo 2>&1 | FileCheck %s --check-prefix=DWO
+//
+// DWO: DWO_COUNT=1
+// DWO: DWO_FILE={{.*}}.dwo
+​​​
+// ============================================================================
+// TEST 3: Split DWARF with .dwp file — returns DWP path
+// ============================================================================
+// RUN: llvm-dwp %t.dwo.dwo -o %t.dwo.dwp
+// RUN: rm %t.dwo.dwo
+// RUN: %lldb -b \
+// RUN:   -o "script m = lldb.target.modules[0]; files = m.GetSeparateDebugInfoFiles(); print('DWP_COUNT=' + str(files.GetSize())); [print('DWP_FILE=' + files.GetSpecAtIndex(i).GetFileSpec().fullpath) for i in range(files.GetSize())]" \
+// RUN:   %t.dwo 2>&1 | FileCheck %s --check-prefix=DWP
+//
+// DWP: DWP_COUNT=1
+// DWP: DWP_FILE={{.*}}.dwp



More information about the lldb-commits mailing list