[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