Skip to content

Commit 9e3bb5b

Browse files
authored
Reland "[lldb] Add count for number of DWO files loaded in statistics #144424" (#145572)
This relands changes in #144424 for adding a count of DWO files parsed/loaded and the total number of DWO files. The previous PR was reverted in #145494 due to the newly added unit tests failing on Windows and MacOS CIs since these platforms don't support DWO. This change add an additional `@add_test_categories(["dwo"])` to the new tests to [skip](https://github.com/llvm/llvm-project/blob/cd46354dbd10820158edabe14dbd49d9f9010722/lldb/packages/Python/lldbsuite/test/test_categories.py#L56) these tests on Windows/MacOS. Original PR: #144424 ### Testing Ran unit tests ``` $ bin/lldb-dotest -p TestStats.py llvm-project/lldb/test/API/commands/statistics/basic/ ---------------------------------------------------------------------- Ran 24 tests in 211.391s OK (skipped=3) ```
1 parent 46d33b6 commit 9e3bb5b

File tree

10 files changed

+239
-8
lines changed

10 files changed

+239
-8
lines changed

lldb/include/lldb/Symbol/SymbolFile.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,14 @@ class SymbolFile : public PluginInterface {
472472
return false;
473473
};
474474

475+
/// Get number of loaded/parsed DWO files. This is emitted in "statistics
476+
/// dump"
477+
///
478+
/// \returns
479+
/// A pair containing (loaded_dwo_count, total_dwo_count). If this
480+
/// symbol file doesn't support DWO files, both counts will be 0.
481+
virtual std::pair<uint32_t, uint32_t> GetDwoFileCounts() { return {0, 0}; }
482+
475483
virtual lldb::TypeSP
476484
MakeType(lldb::user_id_t uid, ConstString name,
477485
std::optional<uint64_t> byte_size, SymbolContextScope *context,

lldb/include/lldb/Target/Statistics.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ struct ModuleStats {
153153
bool symtab_stripped = false;
154154
bool debug_info_had_variable_errors = false;
155155
bool debug_info_had_incomplete_types = false;
156+
uint32_t dwo_file_count = 0;
157+
uint32_t loaded_dwo_file_count = 0;
156158
};
157159

158160
struct ConstStringStats {

lldb/packages/Python/lldbsuite/test/builders/builder.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,25 @@ def getLLDBObjRoot(self):
247247
def _getDebugInfoArgs(self, debug_info):
248248
if debug_info is None:
249249
return []
250-
if debug_info == "dwarf":
251-
return ["MAKE_DSYM=NO"]
252-
if debug_info == "dwo":
253-
return ["MAKE_DSYM=NO", "MAKE_DWO=YES"]
254-
if debug_info == "gmodules":
255-
return ["MAKE_DSYM=NO", "MAKE_GMODULES=YES"]
256-
return None
250+
251+
debug_options = debug_info if isinstance(debug_info, list) else [debug_info]
252+
option_flags = {
253+
"dwarf": {"MAKE_DSYM": "NO"},
254+
"dwo": {"MAKE_DSYM": "NO", "MAKE_DWO": "YES"},
255+
"gmodules": {"MAKE_DSYM": "NO", "MAKE_GMODULES": "YES"},
256+
"debug_names": {"MAKE_DEBUG_NAMES": "YES"},
257+
"dwp": {"MAKE_DSYM": "NO", "MAKE_DWP": "YES"},
258+
}
259+
260+
# Collect all flags, with later options overriding earlier ones
261+
flags = {}
262+
263+
for option in debug_options:
264+
if not option or option not in option_flags:
265+
return None # Invalid options
266+
flags.update(option_flags[option])
267+
268+
return [f"{key}={value}" for key, value in flags.items()]
257269

258270
def getBuildCommand(
259271
self,

lldb/packages/Python/lldbsuite/test/make/Makefile.rules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,10 @@ ifeq "$(MAKE_DWO)" "YES"
276276
CFLAGS += -gsplit-dwarf
277277
endif
278278

279+
ifeq "$(MAKE_DEBUG_NAMES)" "YES"
280+
CFLAGS += -gpubnames
281+
endif
282+
279283
ifeq "$(USE_PRIVATE_MODULE_CACHE)" "YES"
280284
THE_CLANG_MODULE_CACHE_DIR := $(BUILDDIR)/private-module-cache
281285
else

lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4420,3 +4420,32 @@ void SymbolFileDWARF::GetCompileOptions(
44204420
args.insert({comp_unit, Args(flags)});
44214421
}
44224422
}
4423+
4424+
std::pair<uint32_t, uint32_t> SymbolFileDWARF::GetDwoFileCounts() {
4425+
uint32_t total_dwo_count = 0;
4426+
uint32_t loaded_dwo_count = 0;
4427+
4428+
DWARFDebugInfo &info = DebugInfo();
4429+
const size_t num_cus = info.GetNumUnits();
4430+
for (size_t cu_idx = 0; cu_idx < num_cus; cu_idx++) {
4431+
DWARFUnit *dwarf_cu = info.GetUnitAtIndex(cu_idx);
4432+
if (dwarf_cu == nullptr)
4433+
continue;
4434+
4435+
// Check if this is a DWO unit by checking if it has a DWO ID.
4436+
if (!dwarf_cu->GetDWOId().has_value())
4437+
continue;
4438+
4439+
total_dwo_count++;
4440+
4441+
// If we have a DWO symbol file, that means we were able to successfully
4442+
// load it.
4443+
SymbolFile *dwo_symfile =
4444+
dwarf_cu->GetDwoSymbolFile(/*load_all_debug_info=*/false);
4445+
if (dwo_symfile) {
4446+
loaded_dwo_count++;
4447+
}
4448+
}
4449+
4450+
return {loaded_dwo_count, total_dwo_count};
4451+
}

lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ class SymbolFileDWARF : public SymbolFileCommon {
282282
bool GetSeparateDebugInfo(StructuredData::Dictionary &d,
283283
bool errors_only) override;
284284

285+
// Gets a pair of loaded and total dwo file counts.
286+
// For split-dwarf files, this reports the counts for successfully loaded DWO
287+
// CUs and total DWO CUs. For non-split-dwarf files, this reports 0 for both.
288+
std::pair<uint32_t, uint32_t> GetDwoFileCounts() override;
289+
285290
DWARFContext &GetDWARFContext() { return m_context; }
286291

287292
const std::shared_ptr<SymbolFileDWARFDwo> &GetDwpSymbolFile();

lldb/source/Target/Statistics.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ json::Value ModuleStats::ToJSON() const {
7373
debug_info_had_incomplete_types);
7474
module.try_emplace("symbolTableStripped", symtab_stripped);
7575
module.try_emplace("symbolTableSymbolCount", symtab_symbol_count);
76+
module.try_emplace("dwoFileCount", dwo_file_count);
77+
module.try_emplace("loadedDwoFileCount", loaded_dwo_file_count);
7678

7779
if (!symbol_locator_time.map.empty()) {
7880
json::Object obj;
@@ -86,7 +88,7 @@ json::Value ModuleStats::ToJSON() const {
8688

8789
if (!symfile_modules.empty()) {
8890
json::Array symfile_ids;
89-
for (const auto symfile_id: symfile_modules)
91+
for (const auto symfile_id : symfile_modules)
9092
symfile_ids.emplace_back(symfile_id);
9193
module.try_emplace("symbolFileModuleIdentifiers", std::move(symfile_ids));
9294
}
@@ -322,6 +324,8 @@ llvm::json::Value DebuggerStats::ReportStatistics(
322324
uint32_t num_modules_with_incomplete_types = 0;
323325
uint32_t num_stripped_modules = 0;
324326
uint32_t symtab_symbol_count = 0;
327+
uint32_t total_loaded_dwo_file_count = 0;
328+
uint32_t total_dwo_file_count = 0;
325329
for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
326330
Module *module = target != nullptr
327331
? target->GetImages().GetModuleAtIndex(image_idx).get()
@@ -353,6 +357,10 @@ llvm::json::Value DebuggerStats::ReportStatistics(
353357
for (const auto &symbol_module : symbol_modules.Modules())
354358
module_stat.symfile_modules.push_back((intptr_t)symbol_module.get());
355359
}
360+
std::tie(module_stat.loaded_dwo_file_count, module_stat.dwo_file_count) =
361+
sym_file->GetDwoFileCounts();
362+
total_dwo_file_count += module_stat.dwo_file_count;
363+
total_loaded_dwo_file_count += module_stat.loaded_dwo_file_count;
356364
module_stat.debug_info_index_loaded_from_cache =
357365
sym_file->GetDebugInfoIndexWasLoadedFromCache();
358366
if (module_stat.debug_info_index_loaded_from_cache)
@@ -427,6 +435,8 @@ llvm::json::Value DebuggerStats::ReportStatistics(
427435
{"totalDebugInfoEnabled", num_debug_info_enabled_modules},
428436
{"totalSymbolTableStripped", num_stripped_modules},
429437
{"totalSymbolTableSymbolCount", symtab_symbol_count},
438+
{"totalLoadedDwoFileCount", total_loaded_dwo_file_count},
439+
{"totalDwoFileCount", total_dwo_file_count},
430440
};
431441

432442
if (include_targets) {

lldb/test/API/commands/statistics/basic/TestStats.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ def test_default_no_run(self):
177177
"totalDebugInfoIndexLoadedFromCache",
178178
"totalDebugInfoIndexSavedToCache",
179179
"totalDebugInfoParseTime",
180+
"totalDwoFileCount",
181+
"totalLoadedDwoFileCount",
180182
]
181183
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
182184
if self.getPlatform() != "windows":
@@ -287,6 +289,8 @@ def test_default_with_run(self):
287289
"totalDebugInfoIndexLoadedFromCache",
288290
"totalDebugInfoIndexSavedToCache",
289291
"totalDebugInfoParseTime",
292+
"totalDwoFileCount",
293+
"totalLoadedDwoFileCount",
290294
]
291295
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
292296
stats = debug_stats["targets"][0]
@@ -325,6 +329,8 @@ def test_memory(self):
325329
"totalDebugInfoIndexLoadedFromCache",
326330
"totalDebugInfoIndexSavedToCache",
327331
"totalDebugInfoByteSize",
332+
"totalDwoFileCount",
333+
"totalLoadedDwoFileCount",
328334
]
329335
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
330336

@@ -377,6 +383,8 @@ def test_modules(self):
377383
"totalDebugInfoIndexLoadedFromCache",
378384
"totalDebugInfoIndexSavedToCache",
379385
"totalDebugInfoByteSize",
386+
"totalDwoFileCount",
387+
"totalLoadedDwoFileCount",
380388
]
381389
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
382390
stats = debug_stats["targets"][0]
@@ -397,6 +405,8 @@ def test_modules(self):
397405
"symbolTableLoadedFromCache",
398406
"symbolTableParseTime",
399407
"symbolTableSavedToCache",
408+
"dwoFileCount",
409+
"loadedDwoFileCount",
400410
"triple",
401411
"uuid",
402412
]
@@ -485,6 +495,8 @@ def test_breakpoints(self):
485495
"totalDebugInfoIndexLoadedFromCache",
486496
"totalDebugInfoIndexSavedToCache",
487497
"totalDebugInfoByteSize",
498+
"totalDwoFileCount",
499+
"totalLoadedDwoFileCount",
488500
]
489501
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
490502
target_stats = debug_stats["targets"][0]
@@ -513,6 +525,136 @@ def test_breakpoints(self):
513525
breakpoint, 'target_stats["breakpoints"]', bp_keys_exist, None
514526
)
515527

528+
@add_test_categories(["dwo"])
529+
def test_non_split_dwarf_has_no_dwo_files(self):
530+
"""
531+
Test "statistics dump" and the dwo file count.
532+
Builds a binary without split-dwarf mode, and then
533+
verifies the dwo file count is zero after running "statistics dump"
534+
"""
535+
da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}
536+
self.build(dictionary=da, debug_info=["debug_names"])
537+
self.addTearDownCleanup(dictionary=da)
538+
exe = self.getBuildArtifact("a.out")
539+
target = self.createTestTarget(file_path=exe)
540+
debug_stats = self.get_stats()
541+
self.assertIn("totalDwoFileCount", debug_stats)
542+
self.assertIn("totalLoadedDwoFileCount", debug_stats)
543+
544+
# Verify that the dwo file count is zero
545+
self.assertEqual(debug_stats["totalDwoFileCount"], 0)
546+
self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 0)
547+
548+
@add_test_categories(["dwo"])
549+
def test_no_debug_names_eager_loads_dwo_files(self):
550+
"""
551+
Test the eager loading behavior of DWO files when debug_names is absent by
552+
building a split-dwarf binary without debug_names and then running "statistics dump".
553+
DWO file loading behavior:
554+
- With debug_names: DebugNamesDWARFIndex allows for lazy loading.
555+
DWO files are loaded on-demand when symbols are actually looked up
556+
- Without debug_names: ManualDWARFIndex uses eager loading.
557+
All DWO files are loaded upfront during the first symbol lookup to build a manual index.
558+
"""
559+
da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}
560+
self.build(dictionary=da, debug_info=["dwo"])
561+
self.addTearDownCleanup(dictionary=da)
562+
exe = self.getBuildArtifact("a.out")
563+
target = self.createTestTarget(file_path=exe)
564+
debug_stats = self.get_stats()
565+
self.assertIn("totalDwoFileCount", debug_stats)
566+
self.assertIn("totalLoadedDwoFileCount", debug_stats)
567+
568+
# Verify that all DWO files are loaded
569+
self.assertEqual(debug_stats["totalDwoFileCount"], 2)
570+
self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)
571+
572+
@add_test_categories(["dwo"])
573+
def test_split_dwarf_dwo_file_count(self):
574+
"""
575+
Test "statistics dump" and the dwo file count.
576+
Builds a binary w/ separate .dwo files and debug_names, and then
577+
verifies the loaded dwo file count is the expected count after running
578+
various commands
579+
"""
580+
da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}
581+
# -gsplit-dwarf creates separate .dwo files,
582+
# -gpubnames enables the debug_names accelerator tables for faster symbol lookup
583+
# and lazy loading of DWO files
584+
# Expected output: third.dwo (contains main) and baz.dwo (contains Baz struct/function)
585+
self.build(dictionary=da, debug_info=["dwo", "debug_names"])
586+
self.addTearDownCleanup(dictionary=da)
587+
exe = self.getBuildArtifact("a.out")
588+
target = self.createTestTarget(file_path=exe)
589+
debug_stats = self.get_stats()
590+
591+
# 1) 2 DWO files available but none loaded yet
592+
self.assertEqual(len(debug_stats["modules"]), 1)
593+
self.assertIn("totalLoadedDwoFileCount", debug_stats)
594+
self.assertIn("totalDwoFileCount", debug_stats)
595+
self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 0)
596+
self.assertEqual(debug_stats["totalDwoFileCount"], 2)
597+
598+
# Since there's only one module, module stats should have the same counts as total counts
599+
self.assertIn("dwoFileCount", debug_stats["modules"][0])
600+
self.assertIn("loadedDwoFileCount", debug_stats["modules"][0])
601+
self.assertEqual(debug_stats["modules"][0]["loadedDwoFileCount"], 0)
602+
self.assertEqual(debug_stats["modules"][0]["dwoFileCount"], 2)
603+
604+
# 2) Setting breakpoint in main triggers loading of third.dwo (contains main function)
605+
self.runCmd("b main")
606+
debug_stats = self.get_stats()
607+
self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 1)
608+
self.assertEqual(debug_stats["totalDwoFileCount"], 2)
609+
610+
self.assertEqual(debug_stats["modules"][0]["loadedDwoFileCount"], 1)
611+
self.assertEqual(debug_stats["modules"][0]["dwoFileCount"], 2)
612+
613+
# 3) Type lookup forces loading of baz.dwo (contains struct Baz definition)
614+
self.runCmd("type lookup Baz")
615+
debug_stats = self.get_stats()
616+
self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)
617+
self.assertEqual(debug_stats["totalDwoFileCount"], 2)
618+
619+
self.assertEqual(debug_stats["modules"][0]["loadedDwoFileCount"], 2)
620+
self.assertEqual(debug_stats["modules"][0]["dwoFileCount"], 2)
621+
622+
@add_test_categories(["dwo"])
623+
def test_dwp_dwo_file_count(self):
624+
"""
625+
Test "statistics dump" and the loaded dwo file count.
626+
Builds a binary w/ a separate .dwp file and debug_names, and then
627+
verifies the loaded dwo file count is the expected count after running
628+
various commands.
629+
630+
We expect the DWO file counters to reflect the number of compile units
631+
loaded from the DWP file (each representing what was originally a separate DWO file)
632+
"""
633+
da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}
634+
self.build(dictionary=da, debug_info=["dwp", "debug_names"])
635+
self.addTearDownCleanup(dictionary=da)
636+
exe = self.getBuildArtifact("a.out")
637+
target = self.createTestTarget(file_path=exe)
638+
debug_stats = self.get_stats()
639+
640+
# Initially: 2 DWO files available but none loaded yet
641+
self.assertIn("totalLoadedDwoFileCount", debug_stats)
642+
self.assertIn("totalDwoFileCount", debug_stats)
643+
self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 0)
644+
self.assertEqual(debug_stats["totalDwoFileCount"], 2)
645+
646+
# Setting breakpoint in main triggers parsing of the CU within a.dwp corresponding to third.dwo (contains main function)
647+
self.runCmd("b main")
648+
debug_stats = self.get_stats()
649+
self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 1)
650+
self.assertEqual(debug_stats["totalDwoFileCount"], 2)
651+
652+
# Type lookup forces parsing of the CU within a.dwp corresponding to baz.dwo (contains struct Baz definition)
653+
self.runCmd("type lookup Baz")
654+
debug_stats = self.get_stats()
655+
self.assertEqual(debug_stats["totalDwoFileCount"], 2)
656+
self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)
657+
516658
@skipUnlessDarwin
517659
@no_debug_info_test
518660
def test_dsym_binary_has_symfile_in_stats(self):
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Helper that the lldb command `statistics dump` works in split-dwarf mode.
2+
3+
struct Baz {
4+
int x;
5+
bool y;
6+
};
7+
8+
void baz() {
9+
Baz b;
10+
b.x = 1;
11+
b.y = true;
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Test that the lldb command `statistics dump` works.
2+
3+
void baz();
4+
int main(void) {
5+
baz();
6+
return 0;
7+
}

0 commit comments

Comments
 (0)