@@ -11,6 +11,41 @@ use std::path::PathBuf;
1111
1212type EndianRcSlice = gimli:: EndianRcSlice < gimli:: RunTimeEndian > ;
1313
14+ /// Search for a separate debug info file using `.gnu_debuglink`.
15+ ///
16+ /// Follows the standard GDB search order:
17+ /// 1. `<binary_dir>/<debuglink>`
18+ /// 2. `<binary_dir>/.debug/<debuglink>`
19+ /// 3. `/usr/lib/debug/<binary_dir>/<debuglink>`
20+ fn find_debug_file ( object : & object:: File , binary_path : & Path ) -> Option < PathBuf > {
21+ let ( debuglink, expected_crc) = object. gnu_debuglink ( ) . ok ( ) ??;
22+ let debuglink = std:: str:: from_utf8 ( debuglink) . ok ( ) ?;
23+ let dir = binary_path. parent ( ) ?;
24+
25+ let candidates = [
26+ dir. join ( debuglink) ,
27+ dir. join ( ".debug" ) . join ( debuglink) ,
28+ Path :: new ( "/usr/lib/debug" )
29+ . join ( dir. strip_prefix ( "/" ) . unwrap_or ( dir) )
30+ . join ( debuglink) ,
31+ ] ;
32+
33+ candidates. into_iter ( ) . find ( |p| {
34+ let Ok ( content) = std:: fs:: read ( p) else {
35+ return false ;
36+ } ;
37+ let actual_crc = crc32fast:: hash ( & content) ;
38+ if actual_crc != expected_crc {
39+ trace ! (
40+ "CRC mismatch for {}: expected {expected_crc:#x}, got {actual_crc:#x}" ,
41+ p. display( )
42+ ) ;
43+ return false ;
44+ }
45+ true
46+ } )
47+ }
48+
1449pub trait ModuleDebugInfoExt {
1550 fn from_symbols < P : AsRef < Path > > (
1651 path : P ,
@@ -43,7 +78,10 @@ pub trait ModuleDebugInfoExt {
4378}
4479
4580impl ModuleDebugInfoExt for ModuleDebugInfo {
46- /// Create debug info from existing symbols by looking up file/line in DWARF
81+ /// Create debug info from existing symbols by looking up file/line in DWARF.
82+ ///
83+ /// If the binary has no DWARF sections, tries to find a separate debug file
84+ /// via `.gnu_debuglink` (e.g. installed by `libc6-dbg`).
4785 fn from_symbols < P : AsRef < Path > > (
4886 path : P ,
4987 symbols : & ModuleSymbols ,
@@ -52,7 +90,25 @@ impl ModuleDebugInfoExt for ModuleDebugInfo {
5290 let content = std:: fs:: read ( path. as_ref ( ) ) ?;
5391 let object = object:: File :: parse ( & * content) ?;
5492
55- let ctx = Self :: create_dwarf_context ( & object) . context ( "Failed to create DWARF context" ) ?;
93+ // If the binary has no DWARF, try a separate debug file via .gnu_debuglink
94+ let ctx = if object. section_by_name ( ".debug_info" ) . is_some ( ) {
95+ Self :: create_dwarf_context ( & object) . context ( "Failed to create DWARF context" ) ?
96+ } else {
97+ let debug_path = find_debug_file ( & object, path. as_ref ( ) ) . with_context ( || {
98+ format ! (
99+ "No DWARF in {:?} and no separate debug file found" ,
100+ path. as_ref( )
101+ )
102+ } ) ?;
103+ trace ! (
104+ "Using separate debug file {debug_path:?} for {:?}" ,
105+ path. as_ref( )
106+ ) ;
107+ let debug_content = std:: fs:: read ( & debug_path) ?;
108+ let debug_object = object:: File :: parse ( & * debug_content) ?;
109+ Self :: create_dwarf_context ( & debug_object)
110+ . context ( "Failed to create DWARF context from debug file" ) ?
111+ } ;
56112 let ( mut min_addr, mut max_addr) = ( None , None ) ;
57113 let debug_infos = symbols
58114 . symbols ( )
@@ -213,6 +269,29 @@ mod tests {
213269 insta:: assert_debug_snapshot!( module_debug_info. debug_infos) ;
214270 }
215271
272+ #[ test]
273+ fn test_stripped_binary_with_debuglink_resolves_debug_info ( ) {
274+ // Mimics the libc + libc6-dbg scenario:
275+ // - cpp_my_benchmark_stripped.bin has symbols but no DWARF,
276+ // with a .gnu_debuglink pointing to cpp_my_benchmark.debug
277+ // - cpp_my_benchmark.debug has the DWARF sections
278+ const MODULE_PATH : & str = "testdata/perf_map/cpp_my_benchmark_stripped.bin" ;
279+
280+ let module_symbols = ModuleSymbols :: from_elf ( MODULE_PATH ) . unwrap ( ) ;
281+ assert ! (
282+ !module_symbols. symbols( ) . is_empty( ) ,
283+ "symbols should load from .symtab"
284+ ) ;
285+
286+ // Should succeed by following .gnu_debuglink to the .debug file
287+ let module_debug_info =
288+ ModuleDebugInfo :: from_symbols ( MODULE_PATH , & module_symbols, 0 ) . unwrap ( ) ;
289+ assert ! (
290+ !module_debug_info. debug_infos. is_empty( ) ,
291+ "should have resolved debug info from separate .debug file"
292+ ) ;
293+ }
294+
216295 #[ test]
217296 fn test_ruff_debug_info ( ) {
218297 const MODULE_PATH : & str = "testdata/perf_map/ty_walltime" ;
0 commit comments