Skip to content

Commit 55e3b6d

Browse files
authored
[lldb] Add count for errors of DWO files in statistics and combine DWO file count functions (#155023)
## Summary A new `totalDwoErrorCount` counter is available in statistics when calling `statistics dump` to track the number of DWO errors. Additionally, this PR refactors the DWO file statistics by consolidating the existing functionality for counting loaded and total DWO files together with the number of DWO errors into a single function that returns a new DWOStats struct. 1. A new struct, `DWOStats` is created to hold the number of loaded DWO files, the total number of DWO files and the number of DWO errors. 2. Replaced the previous `GetDwoFileCounts` function for loaded and total DWO file counts with a single `GetDwoStats()` function returning the struct `DWOStats`. An override is implemented for `SymbolFileDWARF` that computes the new DWO error count alongside existing counts in one pass. If the status of a DWO CU is `Fail`, which means there is error happened during the loading process, we increment the DWO error counter. _Note: The newly created function `GetDwoStats` will only be called when we try to get statistics. Other codepaths will not be affected._ 3. In Statistics, we sum up the total number of DWO file loading errors. This is done by getting `DWOStats` for each symbol file and adding up the results for each module, then adding to the total count among all modules. 4. In Statistics, we also updated call sites to use the new combined function and struct for loaded and total DWO file counts. As it is possible for one module to have several symbol files, the DWO file counts in a module's stats are updated to be calculated by adding up the counts from all symbol files. ## Expected Behavior - When binaries are compiled with split-dwarf and separate DWO files, `totalDwoLoadErrorCount` would be the number of dwo files with error occurs during the loading process, 0 if no error occurs during a loading process. - When not using split-dwarf, we expect `totalDwoLoadErrorCount` to be 0 since there no DWO file loading errors would be caused. - `totalLoadedDwoFileCount` and `totalDwoFileCount` should be correctly calculated after refactoring and updating. ## Testing ### Manual Testing We created some files to simulate the possible DWO errors manually and observed the results generated by statistics dump. For example, if we delete one of the DWO files generated after compiling, we would get: ``` (lldb) statistics dump { ... "totalDwoLoadErrorCount": 1, ... } ``` We also checked the time cost of `statistics dump` w/o the modification to make sure no significant time cost increase imported. ### Unit test Added two unit tests that build with new "dwo_error_foo.cpp" and "dwo_error_main.cpp" files. For tests with flags -gsplit-dwarf, this generates 2 DWO files. In one of the tests, we delete both DWO files and check the result to see if it reflects the number of DWO files with errors correctly. In another test we update one of the files but loading the outdated .dwo file of it, expecting it increments the error count by 1. To run the test: ``` $ bin/lldb-dotest -p TestStats.py ~/local/llvm-project/lldb/test/API/commands/statistics/basic/ -G "dwo" ---------------------------------------------------------------------- Ran 27 tests in 2.680s OK (skipped=21) $ bin/lldb-dotest -p TestStats.py ~/local/llvm-project/lldb/test/API/commands/statistics/basic/ ---------------------------------------------------------------------- Ran 27 tests in 370.131s OK (skipped=3) ```
1 parent c1cc9d2 commit 55e3b6d

File tree

8 files changed

+177
-27
lines changed

8 files changed

+177
-27
lines changed

lldb/include/lldb/Symbol/SymbolFile.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -488,13 +488,16 @@ class SymbolFile : public PluginInterface {
488488
return false;
489489
};
490490

491-
/// Get number of loaded/parsed DWO files. This is emitted in "statistics
492-
/// dump"
491+
/// Retrieves statistics about DWO files associated with this symbol file.
492+
/// This function returns a DWOStats struct containing:
493+
/// - The number of successfully loaded/parsed DWO files.
494+
/// - The total number of DWO files encountered.
495+
/// - The number of DWO CUs that failed to load due to errors.
496+
/// If this symbol file does not support DWO files, all counts will be zero.
493497
///
494498
/// \returns
495-
/// A pair containing (loaded_dwo_count, total_dwo_count). If this
496-
/// symbol file doesn't support DWO files, both counts will be 0.
497-
virtual std::pair<uint32_t, uint32_t> GetDwoFileCounts() { return {0, 0}; }
499+
/// A DWOStats struct with loaded, total, and error counts for DWO files.
500+
virtual DWOStats GetDwoStats() { return {}; }
498501

499502
virtual lldb::TypeSP
500503
MakeType(lldb::user_id_t uid, ConstString name,

lldb/include/lldb/Target/Statistics.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ struct StatsSuccessFail {
123123
uint32_t failures = 0;
124124
};
125125

126+
/// Holds statistics about DWO (Debug With Object) files.
127+
struct DWOStats {
128+
uint32_t loaded_dwo_file_count = 0;
129+
uint32_t dwo_file_count = 0;
130+
uint32_t dwo_error_count = 0;
131+
132+
DWOStats operator+(const DWOStats &rhs) const {
133+
return DWOStats{loaded_dwo_file_count + rhs.loaded_dwo_file_count,
134+
dwo_file_count + rhs.dwo_file_count,
135+
dwo_error_count + rhs.dwo_error_count};
136+
}
137+
};
138+
126139
/// A class that represents statistics for a since lldb_private::Module.
127140
struct ModuleStats {
128141
llvm::json::Value ToJSON() const;
@@ -153,8 +166,7 @@ struct ModuleStats {
153166
bool symtab_stripped = false;
154167
bool debug_info_had_variable_errors = false;
155168
bool debug_info_had_incomplete_types = false;
156-
uint32_t dwo_file_count = 0;
157-
uint32_t loaded_dwo_file_count = 0;
169+
DWOStats dwo_stats;
158170
};
159171

160172
struct ConstStringStats {

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4502,9 +4502,8 @@ void SymbolFileDWARF::GetCompileOptions(
45024502
}
45034503
}
45044504

4505-
std::pair<uint32_t, uint32_t> SymbolFileDWARF::GetDwoFileCounts() {
4506-
uint32_t total_dwo_count = 0;
4507-
uint32_t loaded_dwo_count = 0;
4505+
DWOStats SymbolFileDWARF::GetDwoStats() {
4506+
DWOStats stats;
45084507

45094508
DWARFDebugInfo &info = DebugInfo();
45104509
const size_t num_cus = info.GetNumUnits();
@@ -4517,16 +4516,21 @@ std::pair<uint32_t, uint32_t> SymbolFileDWARF::GetDwoFileCounts() {
45174516
if (!dwarf_cu->GetDWOId().has_value())
45184517
continue;
45194518

4520-
total_dwo_count++;
4519+
stats.dwo_file_count++;
45214520

45224521
// If we have a DWO symbol file, that means we were able to successfully
45234522
// load it.
45244523
SymbolFile *dwo_symfile =
45254524
dwarf_cu->GetDwoSymbolFile(/*load_all_debug_info=*/false);
45264525
if (dwo_symfile) {
4527-
loaded_dwo_count++;
4526+
stats.loaded_dwo_file_count++;
45284527
}
4528+
4529+
// Check if this unit has a DWO load error, false by default.
4530+
const Status &dwo_error = dwarf_cu->GetDwoError();
4531+
if (dwo_error.Fail())
4532+
stats.dwo_error_count++;
45294533
}
45304534

4531-
return {loaded_dwo_count, total_dwo_count};
4535+
return stats;
45324536
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,11 @@ class SymbolFileDWARF : public SymbolFileCommon {
283283
bool GetSeparateDebugInfo(StructuredData::Dictionary &d, bool errors_only,
284284
bool load_all_debug_info = false) override;
285285

286-
// Gets a pair of loaded and total dwo file counts.
287-
// For split-dwarf files, this reports the counts for successfully loaded DWO
288-
// CUs and total DWO CUs. For non-split-dwarf files, this reports 0 for both.
289-
std::pair<uint32_t, uint32_t> GetDwoFileCounts() override;
286+
/// Gets statistics about dwo files associated with this symbol file.
287+
/// For split-dwarf files, this reports the counts for successfully loaded DWO
288+
/// CUs, total DWO CUs, and the number of DWO CUs with loading errors.
289+
/// For non-split-dwarf files, this reports 0 for all.
290+
DWOStats GetDwoStats() override;
290291

291292
DWARFContext &GetDWARFContext() { return m_context; }
292293

lldb/source/Target/Statistics.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ 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);
76+
module.try_emplace("dwoFileCount", dwo_stats.dwo_file_count);
77+
module.try_emplace("loadedDwoFileCount", dwo_stats.loaded_dwo_file_count);
78+
module.try_emplace("dwoErrorCount", dwo_stats.dwo_error_count);
7879

7980
if (!symbol_locator_time.map.empty()) {
8081
json::Object obj;
@@ -324,8 +325,7 @@ llvm::json::Value DebuggerStats::ReportStatistics(
324325
uint32_t num_modules_with_incomplete_types = 0;
325326
uint32_t num_stripped_modules = 0;
326327
uint32_t symtab_symbol_count = 0;
327-
uint32_t total_loaded_dwo_file_count = 0;
328-
uint32_t total_dwo_file_count = 0;
328+
DWOStats total_dwo_stats;
329329
for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
330330
Module *module = target != nullptr
331331
? target->GetImages().GetModuleAtIndex(image_idx).get()
@@ -357,10 +357,9 @@ llvm::json::Value DebuggerStats::ReportStatistics(
357357
for (const auto &symbol_module : symbol_modules.Modules())
358358
module_stat.symfile_modules.push_back((intptr_t)symbol_module.get());
359359
}
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;
360+
DWOStats current_dwo_stats = sym_file->GetDwoStats();
361+
module_stat.dwo_stats = module_stat.dwo_stats + current_dwo_stats;
362+
total_dwo_stats = total_dwo_stats + current_dwo_stats;
364363
module_stat.debug_info_index_loaded_from_cache =
365364
sym_file->GetDebugInfoIndexWasLoadedFromCache();
366365
if (module_stat.debug_info_index_loaded_from_cache)
@@ -435,8 +434,9 @@ llvm::json::Value DebuggerStats::ReportStatistics(
435434
{"totalDebugInfoEnabled", num_debug_info_enabled_modules},
436435
{"totalSymbolTableStripped", num_stripped_modules},
437436
{"totalSymbolTableSymbolCount", symtab_symbol_count},
438-
{"totalLoadedDwoFileCount", total_loaded_dwo_file_count},
439-
{"totalDwoFileCount", total_dwo_file_count},
437+
{"totalLoadedDwoFileCount", total_dwo_stats.loaded_dwo_file_count},
438+
{"totalDwoFileCount", total_dwo_stats.dwo_file_count},
439+
{"totalDwoErrorCount", total_dwo_stats.dwo_error_count},
440440
};
441441

442442
if (include_targets) {

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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import glob
12
import json
23
import os
34
import re
5+
import shutil
46

57
import lldb
68
from lldbsuite.test.decorators import *
@@ -179,6 +181,7 @@ def test_default_no_run(self):
179181
"totalDebugInfoParseTime",
180182
"totalDwoFileCount",
181183
"totalLoadedDwoFileCount",
184+
"totalDwoErrorCount",
182185
]
183186
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
184187
if self.getPlatform() != "windows":
@@ -291,6 +294,7 @@ def test_default_with_run(self):
291294
"totalDebugInfoParseTime",
292295
"totalDwoFileCount",
293296
"totalLoadedDwoFileCount",
297+
"totalDwoErrorCount",
294298
]
295299
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
296300
stats = debug_stats["targets"][0]
@@ -331,6 +335,7 @@ def test_memory(self):
331335
"totalDebugInfoByteSize",
332336
"totalDwoFileCount",
333337
"totalLoadedDwoFileCount",
338+
"totalDwoErrorCount",
334339
]
335340
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
336341

@@ -385,6 +390,7 @@ def test_modules(self):
385390
"totalDebugInfoByteSize",
386391
"totalDwoFileCount",
387392
"totalLoadedDwoFileCount",
393+
"totalDwoErrorCount",
388394
]
389395
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
390396
stats = debug_stats["targets"][0]
@@ -407,6 +413,7 @@ def test_modules(self):
407413
"symbolTableSavedToCache",
408414
"dwoFileCount",
409415
"loadedDwoFileCount",
416+
"dwoErrorCount",
410417
"triple",
411418
"uuid",
412419
]
@@ -497,6 +504,7 @@ def test_breakpoints(self):
497504
"totalDebugInfoByteSize",
498505
"totalDwoFileCount",
499506
"totalLoadedDwoFileCount",
507+
"totalDwoErrorCount",
500508
]
501509
self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)
502510
target_stats = debug_stats["targets"][0]
@@ -655,6 +663,113 @@ def test_dwp_dwo_file_count(self):
655663
self.assertEqual(debug_stats["totalDwoFileCount"], 2)
656664
self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)
657665

666+
@add_test_categories(["dwo"])
667+
def test_dwo_missing_error_stats(self):
668+
"""
669+
Test that DWO missing errors are reported correctly in statistics.
670+
This test:
671+
1) Builds a program with split DWARF (.dwo files)
672+
2) Delete all two .dwo files
673+
3) Verify that 2 DWO errors are reported in statistics
674+
"""
675+
da = {
676+
"CXX_SOURCES": "dwo_error_main.cpp dwo_error_foo.cpp",
677+
"EXE": self.getBuildArtifact("a.out"),
678+
}
679+
# -gsplit-dwarf creates separate .dwo files,
680+
# Expected output: dwo_error_main.dwo (contains main) and dwo_error_foo.dwo (contains foo struct/function)
681+
self.build(dictionary=da, debug_info="dwo")
682+
self.addTearDownCleanup(dictionary=da)
683+
exe = self.getBuildArtifact("a.out")
684+
685+
# Remove the two .dwo files to trigger a DWO load error
686+
dwo_files = glob.glob(self.getBuildArtifact("*.dwo"))
687+
for dwo_file in dwo_files:
688+
os.rename(dwo_file, dwo_file + ".bak")
689+
690+
target = self.createTestTarget(file_path=exe)
691+
debug_stats = self.get_stats()
692+
693+
# Check DWO load error statistics are reported
694+
self.assertIn("totalDwoErrorCount", debug_stats)
695+
self.assertEqual(debug_stats["totalDwoErrorCount"], 2)
696+
697+
# Since there's only one module, module stats should have the same count as total count
698+
self.assertIn("dwoErrorCount", debug_stats["modules"][0])
699+
self.assertEqual(debug_stats["modules"][0]["dwoErrorCount"], 2)
700+
701+
# Restore the original .dwo file
702+
for dwo_file in dwo_files:
703+
os.rename(dwo_file + ".bak", dwo_file)
704+
705+
@add_test_categories(["dwo"])
706+
def test_dwo_id_mismatch_error_stats(self):
707+
"""
708+
Test that DWO ID mismatch errors are reported correctly in statistics.
709+
This test:
710+
1) Builds a program with split DWARF (.dwo files)
711+
2) Change one of the source file content and rebuild
712+
3) Replace the new .dwo file with the original one to create a DWO ID mismatch
713+
4) Verifies that a DWO error is reported in statistics
714+
5) Restores the original source file
715+
"""
716+
da = {
717+
"CXX_SOURCES": "dwo_error_main.cpp dwo_error_foo.cpp",
718+
"EXE": self.getBuildArtifact("a.out"),
719+
}
720+
# -gsplit-dwarf creates separate .dwo files,
721+
# Expected output: dwo_error_main.dwo (contains main) and dwo_error_foo.dwo (contains foo struct/function)
722+
self.build(dictionary=da, debug_info="dwo")
723+
self.addTearDownCleanup(dictionary=da)
724+
exe = self.getBuildArtifact("a.out")
725+
726+
# Find and make a backup of the original .dwo file
727+
dwo_files = glob.glob(self.getBuildArtifact("*.dwo"))
728+
729+
original_dwo_file = dwo_files[1]
730+
original_dwo_backup = original_dwo_file + ".bak"
731+
shutil.copy2(original_dwo_file, original_dwo_backup)
732+
733+
target = self.createTestTarget(file_path=exe)
734+
initial_stats = self.get_stats()
735+
self.assertIn("totalDwoErrorCount", initial_stats)
736+
self.assertEqual(initial_stats["totalDwoErrorCount"], 0)
737+
self.dbg.DeleteTarget(target)
738+
739+
# Get the original file size before modification
740+
source_file_path = self.getSourcePath("dwo_error_foo.cpp")
741+
original_size = os.path.getsize(source_file_path)
742+
743+
try:
744+
# Modify the source code and rebuild
745+
with open(source_file_path, "a") as f:
746+
f.write("\n void additional_foo(){}\n")
747+
748+
# Rebuild and replace the new .dwo file with the original one
749+
self.build(dictionary=da, debug_info="dwo")
750+
shutil.copy2(original_dwo_backup, original_dwo_file)
751+
752+
# Create a new target and run to a breakpoint to force DWO file loading
753+
target = self.createTestTarget(file_path=exe)
754+
debug_stats = self.get_stats()
755+
756+
# Check that DWO load error statistics are reported
757+
self.assertIn("totalDwoErrorCount", debug_stats)
758+
self.assertEqual(debug_stats["totalDwoErrorCount"], 1)
759+
760+
# Since there's only one module, module stats should have the same count as total count
761+
self.assertIn("dwoErrorCount", debug_stats["modules"][0])
762+
self.assertEqual(debug_stats["modules"][0]["dwoErrorCount"], 1)
763+
764+
finally:
765+
# Remove the appended content
766+
with open(source_file_path, "a") as f:
767+
f.truncate(original_size)
768+
769+
# Restore the original .dwo file
770+
if os.path.exists(original_dwo_backup):
771+
os.unlink(original_dwo_backup)
772+
658773
@skipUnlessDarwin
659774
@no_debug_info_test
660775
def test_dsym_binary_has_symfile_in_stats(self):
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
struct foo {
2+
int x;
3+
bool y;
4+
};
5+
6+
void dwo_error_foo() {
7+
foo f;
8+
f.x = 1;
9+
f.y = true;
10+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
void dwo_error_foo();
2+
int main() {
3+
dwo_error_foo();
4+
return 0;
5+
}

0 commit comments

Comments
 (0)