Skip to content

Commit d978cb5

Browse files
committed
[lldb] Add count for errors of DWO files in statistics
1 parent e6e874c commit d978cb5

File tree

8 files changed

+161
-0
lines changed

8 files changed

+161
-0
lines changed

lldb/include/lldb/Symbol/SymbolFile.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,10 @@ class SymbolFile : public PluginInterface {
496496
/// symbol file doesn't support DWO files, both counts will be 0.
497497
virtual std::pair<uint32_t, uint32_t> GetDwoFileCounts() { return {0, 0}; }
498498

499+
/// Calculates the count of dwo load error, return the number of dwo file with
500+
/// errors, 0 by default.
501+
virtual uint32_t CountDwoLoadErrors() { return 0; }
502+
499503
virtual lldb::TypeSP
500504
MakeType(lldb::user_id_t uid, ConstString name,
501505
std::optional<uint64_t> byte_size, SymbolContextScope *context,

lldb/include/lldb/Target/Statistics.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ struct ModuleStats {
155155
bool debug_info_had_incomplete_types = false;
156156
uint32_t dwo_file_count = 0;
157157
uint32_t loaded_dwo_file_count = 0;
158+
uint32_t dwo_load_error_count = 0;
158159
};
159160

160161
struct ConstStringStats {

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4529,3 +4529,21 @@ std::pair<uint32_t, uint32_t> SymbolFileDWARF::GetDwoFileCounts() {
45294529

45304530
return {loaded_dwo_count, total_dwo_count};
45314531
}
4532+
4533+
uint32_t SymbolFileDWARF::CountDwoLoadErrors() {
4534+
uint32_t dwo_load_error_count = 0;
4535+
4536+
DWARFDebugInfo &info = DebugInfo();
4537+
const size_t num_cus = info.GetNumUnits();
4538+
for (size_t cu_idx = 0; cu_idx < num_cus; cu_idx++) {
4539+
DWARFUnit *dwarf_cu = info.GetUnitAtIndex(cu_idx);
4540+
if (dwarf_cu == nullptr)
4541+
continue;
4542+
4543+
// Check if this unit has dwo error (False by default).
4544+
const Status &dwo_error = dwarf_cu->GetDwoError();
4545+
if (dwo_error.Fail())
4546+
dwo_load_error_count++;
4547+
}
4548+
return dwo_load_error_count;
4549+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ class SymbolFileDWARF : public SymbolFileCommon {
288288
// CUs and total DWO CUs. For non-split-dwarf files, this reports 0 for both.
289289
std::pair<uint32_t, uint32_t> GetDwoFileCounts() override;
290290

291+
/// Count the number of dwo load errors happened.
292+
uint32_t CountDwoLoadErrors() override;
293+
291294
DWARFContext &GetDWARFContext() { return m_context; }
292295

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

lldb/source/Target/Statistics.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ json::Value ModuleStats::ToJSON() const {
7575
module.try_emplace("symbolTableSymbolCount", symtab_symbol_count);
7676
module.try_emplace("dwoFileCount", dwo_file_count);
7777
module.try_emplace("loadedDwoFileCount", loaded_dwo_file_count);
78+
module.try_emplace("dwoLoadErrorCount", dwo_load_error_count);
7879

7980
if (!symbol_locator_time.map.empty()) {
8081
json::Object obj;
@@ -326,6 +327,7 @@ llvm::json::Value DebuggerStats::ReportStatistics(
326327
uint32_t symtab_symbol_count = 0;
327328
uint32_t total_loaded_dwo_file_count = 0;
328329
uint32_t total_dwo_file_count = 0;
330+
uint32_t total_dwo_load_error_count = 0;
329331
for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
330332
Module *module = target != nullptr
331333
? target->GetImages().GetModuleAtIndex(image_idx).get()
@@ -361,6 +363,8 @@ llvm::json::Value DebuggerStats::ReportStatistics(
361363
sym_file->GetDwoFileCounts();
362364
total_dwo_file_count += module_stat.dwo_file_count;
363365
total_loaded_dwo_file_count += module_stat.loaded_dwo_file_count;
366+
module_stat.dwo_load_error_count += current_dwo_errors;
367+
total_dwo_load_error_count += current_dwo_errors;
364368
module_stat.debug_info_index_loaded_from_cache =
365369
sym_file->GetDebugInfoIndexWasLoadedFromCache();
366370
if (module_stat.debug_info_index_loaded_from_cache)
@@ -437,6 +441,7 @@ llvm::json::Value DebuggerStats::ReportStatistics(
437441
{"totalSymbolTableSymbolCount", symtab_symbol_count},
438442
{"totalLoadedDwoFileCount", total_loaded_dwo_file_count},
439443
{"totalDwoFileCount", total_dwo_file_count},
444+
{"totalDwoLoadErrorCount", total_dwo_load_error_count},
440445
};
441446

442447
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+
"totalDwoLoadErrorCount",
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+
"totalDwoLoadErrorCount",
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+
"totalDwoLoadErrorCount",
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+
"totalDwoLoadErrorCount",
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+
"dwoLoadErrorCount",
410417
"triple",
411418
"uuid",
412419
]
@@ -497,6 +504,7 @@ def test_breakpoints(self):
497504
"totalDebugInfoByteSize",
498505
"totalDwoFileCount",
499506
"totalLoadedDwoFileCount",
507+
"totalDwoLoadErrorCount",
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 load 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("totalDwoLoadErrorCount", debug_stats)
695+
self.assertEqual(debug_stats["totalDwoLoadErrorCount"], 2)
696+
697+
# Since there's only one module, module stats should have the same count as total count
698+
self.assertIn("dwoLoadErrorCount", debug_stats["modules"][0])
699+
self.assertEqual(debug_stats["modules"][0]["dwoLoadErrorCount"], 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 load 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("totalDwoLoadErrorCount", initial_stats)
736+
self.assertEqual(initial_stats["totalDwoLoadErrorCount"], 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("totalDwoLoadErrorCount", debug_stats)
758+
self.assertEqual(debug_stats["totalDwoLoadErrorCount"], 1)
759+
760+
# Since there's only one module, module stats should have the same count as total count
761+
self.assertIn("dwoLoadErrorCount", debug_stats["modules"][0])
762+
self.assertEqual(debug_stats["modules"][0]["dwoLoadErrorCount"], 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)