[Lldb-commits] [lldb] 3095d3a - [lldb] Add count for number of DWO files loaded in statistics (#144424)
via lldb-commits
lldb-commits at lists.llvm.org
Mon Jun 23 11:51:12 PDT 2025
Author: qxy11
Date: 2025-06-23T11:51:08-07:00
New Revision: 3095d3a47d624b573d0748ee37f8f201d5702b63
URL: https://github.com/llvm/llvm-project/commit/3095d3a47d624b573d0748ee37f8f201d5702b63
DIFF: https://github.com/llvm/llvm-project/commit/3095d3a47d624b573d0748ee37f8f201d5702b63.diff
LOG: [lldb] Add count for number of DWO files loaded in statistics (#144424)
## Summary
A new `totalLoadedDwoFileCount` and `totalDwoFileCount` counters to
available statisctics when calling "statistics dump".
1. `GetDwoFileCounts ` is created, and returns a pair of ints
representing the number of loaded DWO files and the total number of DWO
files, respectively. An override is implemented for `SymbolFileDWARF`
that loops through each compile unit, and adds to a counter if it's a
DWO unit, and then uses `GetDwoSymbolFile(false)` to check whether the
DWO file was already loaded/parsed.
3. In `Statistics`, use `GetSeparateDebugInfo` to sum up the total
number of loaded/parsed DWO files along with the total number of DWO
files. This is done by checking whether the DWO file was already
successfully `loaded` in the collected DWO data, anding adding to the
`totalLoadedDwoFileCount`, and adding to `totalDwoFileCount` for all CU
units.
## Expected Behavior
- When binaries are compiled with split-dwarf and separate DWO files,
`totalLoadedDwoFileCount` would be the number of loaded DWO files and
`totalDwoFileCount` would be the total count of DWO files.
- When using a DWP file instead of separate DWO files,
`totalLoadedDwoFileCount` would be the number of parsed compile units,
while `totalDwoFileCount` would be the total number of CUs in the DWP
file. This should be similar to the counts we get from loading separate
DWO files rather than only counting whether a single DWP file was
loaded.
- When not using split-dwarf, we expect both `totalDwoFileCount` and
`totalLoadedDwoFileCount` to be 0 since no separate debug info is
loaded.
## Testing
**Manual Testing**
On an internal script that has many DWO files, `statistics dump` was
called before and after a `type lookup` command. The
`totalLoadedDwoFileCount` increased as expected after the `type lookup`.
```
(lldb) statistics dump
{
...
"totalLoadedDwoFileCount": 29,
}
(lldb) type lookup folly::Optional<unsigned int>::Storage
typedef std::conditional<true, folly::Optional<unsigned int>::StorageTriviallyDestructible, folly::Optional<unsigned int>::StorageNonTriviallyDestructible>::type
typedef std::conditional<true, folly::Optional<unsigned int>::StorageTriviallyDestructible, folly::Optional<unsigned int>::StorageNonTriviallyDestructible>::type
...
(lldb) statistics dump
{
...
"totalLoadedDwoFileCount": 2160,
}
```
**Unit test**
Added three unit tests that build with new "third.cpp" and "baz.cpp"
files. For tests with w/ flags `-gsplit-dwarf -gpubnames`, this
generates 2 DWO files. Then, the test incrementally adds breakpoints,
and does a type lookup, and the count should increase for each of these
as new DWO files get loaded to support these.
```
$ bin/lldb-dotest -p TestStats.py ~/llvm-sand/external/llvm-project/lldb/test/API/commands/statistics/basic/
----------------------------------------------------------------------
Ran 20 tests in 211.738s
OK (skipped=3)
```
Added:
lldb/test/API/commands/statistics/basic/baz.cpp
lldb/test/API/commands/statistics/basic/third.cpp
Modified:
lldb/include/lldb/Symbol/SymbolFile.h
lldb/include/lldb/Target/Statistics.h
lldb/packages/Python/lldbsuite/test/builders/builder.py
lldb/packages/Python/lldbsuite/test/make/Makefile.rules
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
lldb/source/Target/Statistics.cpp
lldb/test/API/commands/statistics/basic/TestStats.py
Removed:
################################################################################
diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h
index 75c7f230ddf3d..b0b608d0a5e79 100644
--- a/lldb/include/lldb/Symbol/SymbolFile.h
+++ b/lldb/include/lldb/Symbol/SymbolFile.h
@@ -472,6 +472,14 @@ class SymbolFile : public PluginInterface {
return false;
};
+ /// Get number of loaded/parsed DWO files. This is emitted in "statistics
+ /// dump"
+ ///
+ /// \returns
+ /// A pair containing (loaded_dwo_count, total_dwo_count). If this
+ /// symbol file doesn't support DWO files, both counts will be 0.
+ virtual std::pair<uint32_t, uint32_t> GetDwoFileCounts() { return {0, 0}; }
+
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/Target/Statistics.h b/lldb/include/lldb/Target/Statistics.h
index 2d0d25cd3c753..42f03798c219e 100644
--- a/lldb/include/lldb/Target/Statistics.h
+++ b/lldb/include/lldb/Target/Statistics.h
@@ -153,6 +153,8 @@ struct ModuleStats {
bool symtab_stripped = false;
bool debug_info_had_variable_errors = false;
bool debug_info_had_incomplete_types = false;
+ uint32_t dwo_file_count = 0;
+ uint32_t loaded_dwo_file_count = 0;
};
struct ConstStringStats {
diff --git a/lldb/packages/Python/lldbsuite/test/builders/builder.py b/lldb/packages/Python/lldbsuite/test/builders/builder.py
index de05732469448..efb1ba568e3e6 100644
--- a/lldb/packages/Python/lldbsuite/test/builders/builder.py
+++ b/lldb/packages/Python/lldbsuite/test/builders/builder.py
@@ -247,13 +247,25 @@ def getLLDBObjRoot(self):
def _getDebugInfoArgs(self, debug_info):
if debug_info is None:
return []
- if debug_info == "dwarf":
- return ["MAKE_DSYM=NO"]
- if debug_info == "dwo":
- return ["MAKE_DSYM=NO", "MAKE_DWO=YES"]
- if debug_info == "gmodules":
- return ["MAKE_DSYM=NO", "MAKE_GMODULES=YES"]
- return None
+
+ debug_options = debug_info if isinstance(debug_info, list) else [debug_info]
+ option_flags = {
+ "dwarf": {"MAKE_DSYM": "NO"},
+ "dwo": {"MAKE_DSYM": "NO", "MAKE_DWO": "YES"},
+ "gmodules": {"MAKE_DSYM": "NO", "MAKE_GMODULES": "YES"},
+ "debug_names": {"MAKE_DEBUG_NAMES": "YES"},
+ "dwp": {"MAKE_DSYM": "NO", "MAKE_DWP": "YES"},
+ }
+
+ # Collect all flags, with later options overriding earlier ones
+ flags = {}
+
+ for option in debug_options:
+ if not option or option not in option_flags:
+ return None # Invalid options
+ flags.update(option_flags[option])
+
+ return [f"{key}={value}" for key, value in flags.items()]
def getBuildCommand(
self,
diff --git a/lldb/packages/Python/lldbsuite/test/make/Makefile.rules b/lldb/packages/Python/lldbsuite/test/make/Makefile.rules
index 06959f226066a..58833e1b0cc78 100644
--- a/lldb/packages/Python/lldbsuite/test/make/Makefile.rules
+++ b/lldb/packages/Python/lldbsuite/test/make/Makefile.rules
@@ -276,6 +276,10 @@ ifeq "$(MAKE_DWO)" "YES"
CFLAGS += -gsplit-dwarf
endif
+ifeq "$(MAKE_DEBUG_NAMES)" "YES"
+ CFLAGS += -gpubnames
+endif
+
ifeq "$(USE_PRIVATE_MODULE_CACHE)" "YES"
THE_CLANG_MODULE_CACHE_DIR := $(BUILDDIR)/private-module-cache
else
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 71f204c03a42a..c83779c40a05b 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -4420,3 +4420,32 @@ void SymbolFileDWARF::GetCompileOptions(
args.insert({comp_unit, Args(flags)});
}
}
+
+std::pair<uint32_t, uint32_t> SymbolFileDWARF::GetDwoFileCounts() {
+ uint32_t total_dwo_count = 0;
+ uint32_t loaded_dwo_count = 0;
+
+ DWARFDebugInfo &info = DebugInfo();
+ const size_t num_cus = info.GetNumUnits();
+ for (size_t cu_idx = 0; cu_idx < num_cus; cu_idx++) {
+ DWARFUnit *dwarf_cu = info.GetUnitAtIndex(cu_idx);
+ if (dwarf_cu == nullptr)
+ continue;
+
+ // Check if this is a DWO unit by checking if it has a DWO ID.
+ if (!dwarf_cu->GetDWOId().has_value())
+ continue;
+
+ total_dwo_count++;
+
+ // If we have a DWO symbol file, that means we were able to successfully
+ // load it.
+ SymbolFile *dwo_symfile =
+ dwarf_cu->GetDwoSymbolFile(/*load_all_debug_info=*/false);
+ if (dwo_symfile) {
+ loaded_dwo_count++;
+ }
+ }
+
+ return {loaded_dwo_count, total_dwo_count};
+}
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
index d2d30d7decb16..8335f11712872 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -282,6 +282,11 @@ class SymbolFileDWARF : public SymbolFileCommon {
bool GetSeparateDebugInfo(StructuredData::Dictionary &d,
bool errors_only) override;
+ // Gets a pair of loaded and total dwo file counts.
+ // For split-dwarf files, this reports the counts for successfully loaded DWO
+ // CUs and total DWO CUs. For non-split-dwarf files, this reports 0 for both.
+ std::pair<uint32_t, uint32_t> GetDwoFileCounts() override;
+
DWARFContext &GetDWARFContext() { return m_context; }
const std::shared_ptr<SymbolFileDWARFDwo> &GetDwpSymbolFile();
diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp
index 6ec8f8963baf9..909f335687b21 100644
--- a/lldb/source/Target/Statistics.cpp
+++ b/lldb/source/Target/Statistics.cpp
@@ -73,6 +73,8 @@ json::Value ModuleStats::ToJSON() const {
debug_info_had_incomplete_types);
module.try_emplace("symbolTableStripped", symtab_stripped);
module.try_emplace("symbolTableSymbolCount", symtab_symbol_count);
+ module.try_emplace("dwoFileCount", dwo_file_count);
+ module.try_emplace("loadedDwoFileCount", loaded_dwo_file_count);
if (!symbol_locator_time.map.empty()) {
json::Object obj;
@@ -86,7 +88,7 @@ json::Value ModuleStats::ToJSON() const {
if (!symfile_modules.empty()) {
json::Array symfile_ids;
- for (const auto symfile_id: symfile_modules)
+ for (const auto symfile_id : symfile_modules)
symfile_ids.emplace_back(symfile_id);
module.try_emplace("symbolFileModuleIdentifiers", std::move(symfile_ids));
}
@@ -322,6 +324,8 @@ llvm::json::Value DebuggerStats::ReportStatistics(
uint32_t num_modules_with_incomplete_types = 0;
uint32_t num_stripped_modules = 0;
uint32_t symtab_symbol_count = 0;
+ uint32_t total_loaded_dwo_file_count = 0;
+ uint32_t total_dwo_file_count = 0;
for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
Module *module = target != nullptr
? target->GetImages().GetModuleAtIndex(image_idx).get()
@@ -353,6 +357,10 @@ llvm::json::Value DebuggerStats::ReportStatistics(
for (const auto &symbol_module : symbol_modules.Modules())
module_stat.symfile_modules.push_back((intptr_t)symbol_module.get());
}
+ std::tie(module_stat.loaded_dwo_file_count, module_stat.dwo_file_count) =
+ sym_file->GetDwoFileCounts();
+ total_dwo_file_count += module_stat.dwo_file_count;
+ total_loaded_dwo_file_count += module_stat.loaded_dwo_file_count;
module_stat.debug_info_index_loaded_from_cache =
sym_file->GetDebugInfoIndexWasLoadedFromCache();
if (module_stat.debug_info_index_loaded_from_cache)
@@ -427,6 +435,8 @@ llvm::json::Value DebuggerStats::ReportStatistics(
{"totalDebugInfoEnabled", num_debug_info_enabled_modules},
{"totalSymbolTableStripped", num_stripped_modules},
{"totalSymbolTableSymbolCount", symtab_symbol_count},
+ {"totalLoadedDwoFileCount", total_loaded_dwo_file_count},
+ {"totalDwoFileCount", total_dwo_file_count},
};
if (include_targets) {
diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py
index 83132b40d85db..5281bde4c6479 100644
--- a/lldb/test/API/commands/statistics/basic/TestStats.py
+++ b/lldb/test/API/commands/statistics/basic/TestStats.py
@@ -177,6 +177,8 @@ def test_default_no_run(self):
"totalDebugInfoIndexLoadedFromCache",
"totalDebugInfoIndexSavedToCache",
"totalDebugInfoParseTime",
+ "totalDwoFileCount",
+ "totalLoadedDwoFileCount",
]
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
if self.getPlatform() != "windows":
@@ -287,6 +289,8 @@ def test_default_with_run(self):
"totalDebugInfoIndexLoadedFromCache",
"totalDebugInfoIndexSavedToCache",
"totalDebugInfoParseTime",
+ "totalDwoFileCount",
+ "totalLoadedDwoFileCount",
]
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
stats = debug_stats["targets"][0]
@@ -325,6 +329,8 @@ def test_memory(self):
"totalDebugInfoIndexLoadedFromCache",
"totalDebugInfoIndexSavedToCache",
"totalDebugInfoByteSize",
+ "totalDwoFileCount",
+ "totalLoadedDwoFileCount",
]
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
@@ -377,6 +383,8 @@ def test_modules(self):
"totalDebugInfoIndexLoadedFromCache",
"totalDebugInfoIndexSavedToCache",
"totalDebugInfoByteSize",
+ "totalDwoFileCount",
+ "totalLoadedDwoFileCount",
]
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
stats = debug_stats["targets"][0]
@@ -397,6 +405,8 @@ def test_modules(self):
"symbolTableLoadedFromCache",
"symbolTableParseTime",
"symbolTableSavedToCache",
+ "dwoFileCount",
+ "loadedDwoFileCount",
"triple",
"uuid",
]
@@ -485,6 +495,8 @@ def test_breakpoints(self):
"totalDebugInfoIndexLoadedFromCache",
"totalDebugInfoIndexSavedToCache",
"totalDebugInfoByteSize",
+ "totalDwoFileCount",
+ "totalLoadedDwoFileCount",
]
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
target_stats = debug_stats["targets"][0]
@@ -512,6 +524,132 @@ def test_breakpoints(self):
self.verify_keys(
breakpoint, 'target_stats["breakpoints"]', bp_keys_exist, None
)
+ def test_non_split_dwarf_has_no_dwo_files(self):
+ """
+ Test "statistics dump" and the dwo file count.
+ Builds a binary without split-dwarf mode, and then
+ verifies the dwo file count is zero after running "statistics dump"
+ """
+ da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}
+ self.build(dictionary=da, debug_info=["debug_names"])
+ self.addTearDownCleanup(dictionary=da)
+ exe = self.getBuildArtifact("a.out")
+ target = self.createTestTarget(file_path=exe)
+ debug_stats = self.get_stats()
+ self.assertIn("totalDwoFileCount", debug_stats)
+ self.assertIn("totalLoadedDwoFileCount", debug_stats)
+
+ # Verify that the dwo file count is zero
+ self.assertEqual(debug_stats["totalDwoFileCount"], 0)
+ self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 0)
+
+ def test_no_debug_names_eager_loads_dwo_files(self):
+ """
+ Test the eager loading behavior of DWO files when debug_names is absent by
+ building a split-dwarf binary without debug_names and then running "statistics dump".
+ DWO file loading behavior:
+ - With debug_names: DebugNamesDWARFIndex allows for lazy loading.
+ DWO files are loaded on-demand when symbols are actually looked up
+ - Without debug_names: ManualDWARFIndex uses eager loading.
+ All DWO files are loaded upfront during the first symbol lookup to build a manual index.
+ """
+ da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}
+ self.build(dictionary=da, debug_info=["dwo"])
+ self.addTearDownCleanup(dictionary=da)
+ exe = self.getBuildArtifact("a.out")
+ target = self.createTestTarget(file_path=exe)
+ debug_stats = self.get_stats()
+ self.assertIn("totalDwoFileCount", debug_stats)
+ self.assertIn("totalLoadedDwoFileCount", debug_stats)
+
+ # Verify that all DWO files are loaded
+ self.assertEqual(debug_stats["totalDwoFileCount"], 2)
+ self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)
+
+ def test_split_dwarf_dwo_file_count(self):
+ """
+ Test "statistics dump" and the dwo file count.
+ Builds a binary w/ separate .dwo files and debug_names, and then
+ verifies the loaded dwo file count is the expected count after running
+ various commands
+ """
+ da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}
+ # -gsplit-dwarf creates separate .dwo files,
+ # -gpubnames enables the debug_names accelerator tables for faster symbol lookup
+ # and lazy loading of DWO files
+ # Expected output: third.dwo (contains main) and baz.dwo (contains Baz struct/function)
+ self.build(dictionary=da, debug_info=["dwo", "debug_names"])
+ self.addTearDownCleanup(dictionary=da)
+ exe = self.getBuildArtifact("a.out")
+ target = self.createTestTarget(file_path=exe)
+ debug_stats = self.get_stats()
+
+ # 1) 2 DWO files available but none loaded yet
+ self.assertEqual(len(debug_stats["modules"]), 1)
+ self.assertIn("totalLoadedDwoFileCount", debug_stats)
+ self.assertIn("totalDwoFileCount", debug_stats)
+ self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 0)
+ self.assertEqual(debug_stats["totalDwoFileCount"], 2)
+
+ # Since there's only one module, module stats should have the same counts as total counts
+ self.assertIn("dwoFileCount", debug_stats["modules"][0])
+ self.assertIn("loadedDwoFileCount", debug_stats["modules"][0])
+ self.assertEqual(debug_stats["modules"][0]["loadedDwoFileCount"], 0)
+ self.assertEqual(debug_stats["modules"][0]["dwoFileCount"], 2)
+
+ # 2) Setting breakpoint in main triggers loading of third.dwo (contains main function)
+ self.runCmd("b main")
+ debug_stats = self.get_stats()
+ self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 1)
+ self.assertEqual(debug_stats["totalDwoFileCount"], 2)
+
+ self.assertEqual(debug_stats["modules"][0]["loadedDwoFileCount"], 1)
+ self.assertEqual(debug_stats["modules"][0]["dwoFileCount"], 2)
+
+ # 3) Type lookup forces loading of baz.dwo (contains struct Baz definition)
+ self.runCmd("type lookup Baz")
+ debug_stats = self.get_stats()
+ self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)
+ self.assertEqual(debug_stats["totalDwoFileCount"], 2)
+
+ self.assertEqual(debug_stats["modules"][0]["loadedDwoFileCount"], 2)
+ self.assertEqual(debug_stats["modules"][0]["dwoFileCount"], 2)
+
+ def test_dwp_dwo_file_count(self):
+ """
+ Test "statistics dump" and the loaded dwo file count.
+ Builds a binary w/ a separate .dwp file and debug_names, and then
+ verifies the loaded dwo file count is the expected count after running
+ various commands.
+
+ We expect the DWO file counters to reflect the number of compile units
+ loaded from the DWP file (each representing what was originally a separate DWO file)
+ """
+ da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}
+ self.build(dictionary=da, debug_info=["dwp", "debug_names"])
+ self.addTearDownCleanup(dictionary=da)
+ exe = self.getBuildArtifact("a.out")
+ target = self.createTestTarget(file_path=exe)
+ debug_stats = self.get_stats()
+
+ # Initially: 2 DWO files available but none loaded yet
+ self.assertIn("totalLoadedDwoFileCount", debug_stats)
+ self.assertIn("totalDwoFileCount", debug_stats)
+ self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 0)
+ self.assertEqual(debug_stats["totalDwoFileCount"], 2)
+
+ # Setting breakpoint in main triggers parsing of the CU within a.dwp corresponding to third.dwo (contains main function)
+ self.runCmd("b main")
+ debug_stats = self.get_stats()
+ self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 1)
+ self.assertEqual(debug_stats["totalDwoFileCount"], 2)
+
+ # Type lookup forces parsing of the CU within a.dwp corresponding to baz.dwo (contains struct Baz definition)
+ self.runCmd("type lookup Baz")
+ debug_stats = self.get_stats()
+ self.assertEqual(debug_stats["totalDwoFileCount"], 2)
+ self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)
+
@skipUnlessDarwin
@no_debug_info_test
diff --git a/lldb/test/API/commands/statistics/basic/baz.cpp b/lldb/test/API/commands/statistics/basic/baz.cpp
new file mode 100644
index 0000000000000..536758b17d839
--- /dev/null
+++ b/lldb/test/API/commands/statistics/basic/baz.cpp
@@ -0,0 +1,12 @@
+// Helper that the lldb command `statistics dump` works in split-dwarf mode.
+
+struct Baz {
+ int x;
+ bool y;
+};
+
+void baz() {
+ Baz b;
+ b.x = 1;
+ b.y = true;
+}
diff --git a/lldb/test/API/commands/statistics/basic/third.cpp b/lldb/test/API/commands/statistics/basic/third.cpp
new file mode 100644
index 0000000000000..3943b9c2faafe
--- /dev/null
+++ b/lldb/test/API/commands/statistics/basic/third.cpp
@@ -0,0 +1,7 @@
+// Test that the lldb command `statistics dump` works.
+
+void baz();
+int main(void) {
+ baz();
+ return 0;
+}
More information about the lldb-commits
mailing list