Skip to content

Conversation

@Jlalond
Copy link
Contributor

@Jlalond Jlalond commented Jun 13, 2025

Today we can run target modules dump separate-debug-info --json to get a json blob of all the separate debug info, but it has a few shortcomings when developing some scripting against it. Namely, the caller has to know the structure of the JSON per architecture that will be returned.

I've been working on a Minidump packing utility where we enumerate symbols and source and put them in a package so we can debug with symbols portably, and it's been difficult to maintain multiple architectures due to the above shortcomings. To address this for myself, I've exposed a simple iterator for the SBModule to get all the separate-debug-info as list of filespecs with no need for the caller to have context on what kind of data it is.

I also extened the swig interfaces to make writing my test easier and as a nice to have.

@Jlalond Jlalond requested review from clayborg and jasonmolenda June 13, 2025 16:55
@Jlalond Jlalond requested a review from JDevlieghere as a code owner June 13, 2025 16:55
@llvmbot llvmbot added the lldb label Jun 13, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 13, 2025

@llvm/pr-subscribers-lldb

Author: Jacob Lalonde (Jlalond)

Changes

Today we can run target modules dump separate-debug-info --json to get a json blob of all the separate debug info, but it has a few shortcomings when developing some scripting against it. Namely, the caller has to know the structure of the JSON per architecture that will be returned.

I've been working on a Minidump packing utility where we enumerate symbols and source and put them in a package so we can debug with symbols portably, and it's been difficult to maintain multiple architectures due to the above shortcomings. To address this for myself, I've exposed a simple iterator for the SBModule to get all the separate-debug-info as list of filespecs with no need for the caller to have context on what kind of data it is.

I also extened the swig interfaces to make writing my test easier and as a nice to have.


Full diff: https://github.com/llvm/llvm-project/pull/144119.diff

15 Files Affected:

  • (modified) lldb/bindings/interface/SBFileSpecListExtensions.i (+4)
  • (modified) lldb/include/lldb/API/SBModule.h (+8)
  • (modified) lldb/include/lldb/Core/Module.h (+2)
  • (modified) lldb/include/lldb/Symbol/SymbolFile.h (+14)
  • (modified) lldb/include/lldb/Symbol/SymbolFileOnDemand.h (+4)
  • (modified) lldb/source/API/SBModule.cpp (+14)
  • (modified) lldb/source/Core/Module.cpp (+9)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp (+26)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h (+2)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp (+15)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h (+2)
  • (modified) lldb/source/Symbol/SymbolFile.cpp (-1)
  • (added) lldb/test/API/python_api/sbmodule/SeperateDebugInfo/Makefile (+3)
  • (added) lldb/test/API/python_api/sbmodule/SeperateDebugInfo/TestSBModuleSeparateDebugInfo.py (+44)
  • (added) lldb/test/API/python_api/sbmodule/SeperateDebugInfo/main.cpp (+5)
diff --git a/lldb/bindings/interface/SBFileSpecListExtensions.i b/lldb/bindings/interface/SBFileSpecListExtensions.i
index 1e7b897a08d95..f8504e10d4063 100644
--- a/lldb/bindings/interface/SBFileSpecListExtensions.i
+++ b/lldb/bindings/interface/SBFileSpecListExtensions.i
@@ -10,6 +10,10 @@ STRING_EXTENSION_OUTSIDE(SBFileSpecList)
     def __iter__(self):
       '''Iterate over all FileSpecs in a lldb.SBFileSpecList object.'''
       return lldb_iter(self, 'GetSize', 'GetFileSpecAtIndex')
+
+    def __getitem__(self, index):
+      '''Get an lldb.SBFileSpec at a given index, an invalid SBFileSpec will be returned if the index is invalid.'''
+      return self.GetFileSpecAtIndex(index)
     %}
 #endif
 }
diff --git a/lldb/include/lldb/API/SBModule.h b/lldb/include/lldb/API/SBModule.h
index 85332066ee687..4091fe1c3cb9e 100644
--- a/lldb/include/lldb/API/SBModule.h
+++ b/lldb/include/lldb/API/SBModule.h
@@ -287,6 +287,14 @@ class LLDB_API SBModule {
   ///     A const reference to the file specification object.
   lldb::SBFileSpec GetSymbolFileSpec() const;
 
+  /// Get a list of filespecs associated with all the separate symbol files
+  /// associated with this module.
+  ///
+  /// \return
+  ///     A list of filespecs associated with all the separate symbol files
+  ///     associated with this module.
+  lldb::SBFileSpecList GetSeparateDebugInfoFiles();
+
   lldb::SBAddress GetObjectFileHeaderAddress() const;
   lldb::SBAddress GetObjectFileEntryPointAddress() const;
 
diff --git a/lldb/include/lldb/Core/Module.h b/lldb/include/lldb/Core/Module.h
index 8bb55c95773bc..9a7d4b2c73205 100644
--- a/lldb/include/lldb/Core/Module.h
+++ b/lldb/include/lldb/Core/Module.h
@@ -482,6 +482,8 @@ class Module : public std::enable_shared_from_this<Module>,
 
   const FileSpec &GetSymbolFileFileSpec() const { return m_symfile_spec; }
 
+  const llvm::StringMap<FileSpec> GetSeparateDebugInfoFiles();
+
   void PreloadSymbols();
 
   void SetSymbolFileFileSpec(const FileSpec &file);
diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h
index 75c7f230ddf3d..1020f1a21541a 100644
--- a/lldb/include/lldb/Symbol/SymbolFile.h
+++ b/lldb/include/lldb/Symbol/SymbolFile.h
@@ -27,6 +27,7 @@
 #include "lldb/lldb-private.h"
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/StringMap.h"
 #include "llvm/Support/Errc.h"
 
 #include <mutex>
@@ -472,6 +473,19 @@ class SymbolFile : public PluginInterface {
     return false;
   };
 
+  /// Return a map of separate debug info files that are loaded.
+  ///
+  /// Unlike GetSeparateDebugInfo(), this function will only return the list of
+  /// files, if there are errors they are simply ignored. This function will
+  /// always return a valid list, even if it is empty.
+  ///
+  /// \return
+  ///     A unique map of all the filespecs, dwos in a dwps would be joined to
+  ///     the dwp path for example.
+  virtual llvm::StringMap<lldb_private::FileSpec> GetSeparateDebugInfoFiles() {
+    return {};
+  }
+
   virtual lldb::TypeSP
   MakeType(lldb::user_id_t uid, ConstString name,
            std::optional<uint64_t> byte_size, SymbolContextScope *context,
diff --git a/lldb/include/lldb/Symbol/SymbolFileOnDemand.h b/lldb/include/lldb/Symbol/SymbolFileOnDemand.h
index ba4a7f09afeaa..7e08414725d63 100644
--- a/lldb/include/lldb/Symbol/SymbolFileOnDemand.h
+++ b/lldb/include/lldb/Symbol/SymbolFileOnDemand.h
@@ -223,6 +223,10 @@ class SymbolFileOnDemand : public lldb_private::SymbolFile {
     return m_sym_file_impl->SetDebugInfoHadFrameVariableErrors();
   }
 
+  llvm::StringMap<lldb_private::FileSpec> GetSeparateDebugInfoFiles() override {
+    return m_sym_file_impl->GetSeparateDebugInfoFiles();
+  }
+
   bool GetSeparateDebugInfo(StructuredData::Dictionary &d,
                             bool errors_only) override {
     return m_sym_file_impl->GetSeparateDebugInfo(d, errors_only);
diff --git a/lldb/source/API/SBModule.cpp b/lldb/source/API/SBModule.cpp
index 985107ec68efd..63deb1446f713 100644
--- a/lldb/source/API/SBModule.cpp
+++ b/lldb/source/API/SBModule.cpp
@@ -633,6 +633,20 @@ lldb::SBFileSpec SBModule::GetSymbolFileSpec() const {
   return sb_file_spec;
 }
 
+lldb::SBFileSpecList SBModule::GetSeparateDebugInfoFiles() {
+  lldb::SBFileSpecList sb_filespeclist;
+  ModuleSP module_sp(GetSP());
+  if (module_sp) {
+    llvm::StringMap<lldb_private::FileSpec> debug_info_files =
+        module_sp->GetSeparateDebugInfoFiles();
+    for (auto &&[_, debug_info_file] : debug_info_files) {
+      sb_filespeclist.Append(debug_info_file);
+    }
+  }
+
+  return sb_filespeclist;
+}
+
 lldb::SBAddress SBModule::GetObjectFileHeaderAddress() const {
   LLDB_INSTRUMENT_VA(this);
 
diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp
index 90997dada3666..5fd21dca8fbf3 100644
--- a/lldb/source/Core/Module.cpp
+++ b/lldb/source/Core/Module.cpp
@@ -1644,3 +1644,12 @@ DataFileCache *Module::GetIndexCache() {
                             .GetPath());
   return g_data_file_cache;
 }
+
+const llvm::StringMap<lldb_private::FileSpec>
+Module::GetSeparateDebugInfoFiles() {
+  SymbolFile *symfile = GetSymbolFile(false);
+  if (!symfile)
+    return {};
+
+  return symfile->GetSeparateDebugInfoFiles();
+}
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 71f204c03a42a..34bf05690e9e8 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -4138,6 +4138,32 @@ void SymbolFileDWARF::DumpClangAST(Stream &s, llvm::StringRef filter) {
   clang->Dump(s.AsRawOstream(), filter);
 }
 
+llvm::StringMap<lldb_private::FileSpec>
+SymbolFileDWARF::GetSeparateDebugInfoFiles() {
+  DWARFDebugInfo &info = DebugInfo();
+  const size_t num_cus = info.GetNumUnits();
+  llvm::StringMap<lldb_private::FileSpec> symbolfile_map;
+  for (uint32_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 == nullptr)
+      continue;
+
+    if (!dwarf_cu->GetDWOId().has_value())
+      continue;
+
+    SymbolFile *dwo_symfile = dwarf_cu->GetDwoSymbolFile();
+    if (!dwo_symfile)
+      continue;
+
+    lldb_private::FileSpec symfile_spec =
+        dwo_symfile->GetObjectFile()->GetFileSpec();
+    if (symbolfile_map.find(symfile_spec.GetPath()) == symbolfile_map.end())
+      symbolfile_map[symfile_spec.GetPath()] = symfile_spec;
+  }
+  return symbolfile_map;
+}
+
 bool SymbolFileDWARF::GetSeparateDebugInfo(StructuredData::Dictionary &d,
                                            bool errors_only) {
   StructuredData::Array separate_debug_info_files;
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
index d2d30d7decb16..efc78cbb177e6 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -278,6 +278,8 @@ class SymbolFileDWARF : public SymbolFileCommon {
 
   void DumpClangAST(Stream &s, llvm::StringRef filter) override;
 
+  llvm::StringMap<lldb_private::FileSpec> GetSeparateDebugInfoFiles() override;
+
   /// List separate dwo files.
   bool GetSeparateDebugInfo(StructuredData::Dictionary &d,
                             bool errors_only) override;
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
index f3a940b2ee396..c66d3cd548707 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
@@ -1277,6 +1277,21 @@ void SymbolFileDWARFDebugMap::DumpClangAST(Stream &s, llvm::StringRef filter) {
   });
 }
 
+llvm::StringMap<lldb_private::FileSpec>
+SymbolFileDWARFDebugMap::GetSeparateDebugInfoFiles() {
+  const uint32_t cu_count = GetNumCompileUnits();
+  llvm::StringMap<lldb_private::FileSpec> cu_map;
+  for (uint32_t cu_idx = 0; cu_idx < cu_count; ++cu_idx) {
+    const auto &info = m_compile_unit_infos[cu_idx];
+    if (info.so_file.GetPath().empty())
+      continue;
+
+    if (cu_map.find(info.oso_path) == cu_map.end())
+      cu_map[info.oso_path] = lldb_private::FileSpec(info.oso_path);
+  }
+  return cu_map;
+}
+
 bool SymbolFileDWARFDebugMap::GetSeparateDebugInfo(
     lldb_private::StructuredData::Dictionary &d, bool errors_only) {
   StructuredData::Array separate_debug_info_files;
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
index 35cbdbbb1692f..f46a17dd469bd 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
@@ -131,6 +131,8 @@ class SymbolFileDWARFDebugMap : public SymbolFileCommon {
 
   void DumpClangAST(Stream &s, llvm::StringRef filter) override;
 
+  llvm::StringMap<lldb_private::FileSpec> GetSeparateDebugInfoFiles() override;
+
   /// List separate oso files.
   bool GetSeparateDebugInfo(StructuredData::Dictionary &d,
                             bool errors_only) override;
diff --git a/lldb/source/Symbol/SymbolFile.cpp b/lldb/source/Symbol/SymbolFile.cpp
index 870d778dca740..9cc2ea7df3fbc 100644
--- a/lldb/source/Symbol/SymbolFile.cpp
+++ b/lldb/source/Symbol/SymbolFile.cpp
@@ -20,7 +20,6 @@
 #include "lldb/Utility/StreamString.h"
 #include "lldb/Utility/StructuredData.h"
 #include "lldb/lldb-private.h"
-
 #include <future>
 
 using namespace lldb_private;
diff --git a/lldb/test/API/python_api/sbmodule/SeperateDebugInfo/Makefile b/lldb/test/API/python_api/sbmodule/SeperateDebugInfo/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/python_api/sbmodule/SeperateDebugInfo/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/python_api/sbmodule/SeperateDebugInfo/TestSBModuleSeparateDebugInfo.py b/lldb/test/API/python_api/sbmodule/SeperateDebugInfo/TestSBModuleSeparateDebugInfo.py
new file mode 100644
index 0000000000000..011928cd0f330
--- /dev/null
+++ b/lldb/test/API/python_api/sbmodule/SeperateDebugInfo/TestSBModuleSeparateDebugInfo.py
@@ -0,0 +1,44 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import os, signal, subprocess
+
+from lldbsuite.test import lldbutil
+
+
+class SBModuleSeparateDebugInfoCase(TestBase):
+    def setUp(self):
+        TestBase.setUp(self)
+        self.background_pid = None
+
+    def tearDown(self):
+        TestBase.tearDown(self)
+        if self.background_pid:
+            os.kill(self.background_pid, signal.SIGKILL)
+
+    @skipIf(debug_info=no_match("dwo"))
+    def test_get_separate_debug_info_files_dwo(self):
+        """Test the SBModule::GetSeparateDebugInfoFiles"""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        target = self.dbg.CreateTarget(exe)
+
+        # Target should have a DWO
+        main_module = target.GetModuleAtIndex(0)
+        file_specs = main_module.GetSeparateDebugInfoFiles()
+        self.assertEqual(len(file_specs), 1)
+        self.assertTrue(file_specs[0].GetFilename().endswith(".dwo"))
+
+    @skipUnlessDarwin
+    @skipIf(debug_info=no_match("dwarf"))
+    def test_get_separate_debug_info_files_darwin_dwarf(self):
+        """Test the SBModule::GetSeparateDebugInfoFiles"""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        target = self.dbg.CreateTarget(exe)
+
+        # Target should have a DWO
+        main_module = target.GetModuleAtIndex(0)
+        file_specs = main_module.GetSeparateDebugInfoFiles()
+        self.assertEqual(len(file_specs), 1)
+        self.assertTrue(file_specs[0].GetFilename().endswith(".o"))
diff --git a/lldb/test/API/python_api/sbmodule/SeperateDebugInfo/main.cpp b/lldb/test/API/python_api/sbmodule/SeperateDebugInfo/main.cpp
new file mode 100644
index 0000000000000..f54a8087e1dc4
--- /dev/null
+++ b/lldb/test/API/python_api/sbmodule/SeperateDebugInfo/main.cpp
@@ -0,0 +1,5 @@
+int main() {
+  int x = 40;
+  x += 2; // break here
+  return x;
+}

@Jlalond
Copy link
Contributor Author

Jlalond commented Jun 13, 2025

@jasonmolenda tagged you because I think you're the relevant SME, but feel free to reassign otherwise.

@Jlalond Jlalond requested a review from royitaqi June 13, 2025 16:56
@jimingham
Copy link
Collaborator

From what I can tell, you have the lldb_private API's you are adding return a StringMap because it facilitates building a list where each unique file spec appears only once. Other than that, I can't see you make any use of the StringMap-ness of the data you return.

That's awkward because then none of the consumers actually want that StringMap, whose map nature after all doesn't provide any new information since the keys are also in the values.

However, FileSpecList already has an AppendIfUnique method. So you could (with less code than the current implementation) have all the new lldb_private API's return what consumers actually want: an FileSpecList, and then that would be trivially convertible to the SBFileSpecList, which is in the end what you wanted to expose.

@Jlalond
Copy link
Contributor Author

Jlalond commented Jun 16, 2025

However, FileSpecList already has an AppendIfUnique method. So you could (with less code than the current implementation) have all the new lldb_private API's return what consumers actually want: an FileSpecList, and then that would be trivially convertible to the SBFileSpecList, which is in the end what you wanted to expose.

Good suggestion @jimingham, I also added a constructor to move a FileSpecList into the SBFileSpecList. The code is much cleaner now as you expected.

@Jlalond Jlalond force-pushed the sb-module-debuginfo-enumerable branch from f7c437a to 3ac3b45 Compare September 8, 2025 21:40
@github-actions
Copy link

github-actions bot commented Sep 8, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@Jlalond Jlalond force-pushed the sb-module-debuginfo-enumerable branch from 3ac3b45 to 6b1618d Compare September 8, 2025 21:51
@Jlalond Jlalond requested a review from dmpots September 11, 2025 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants