Skip to content

Commit 1d8f9ea

Browse files
committed
feat: add build-ID based debug file lookup
On Ubuntu/Debian, debug packages (e.g. libc6-dbg) install debug files at /usr/lib/debug/.build-id/XX/YYYYYY.debug — not at the paths checked by .gnu_debuglink. The binary has a .note.gnu.build-id section but the debuglink search paths are empty, so the previous implementation could never find the debug file. Now tries build-ID lookup first, then falls back to .gnu_debuglink. The search root is parameterized internally so both mechanisms are independently testable with a tempdir.
1 parent ad25e2a commit 1d8f9ea

2 files changed

Lines changed: 92 additions & 9 deletions

File tree

src/executor/wall_time/perf/elf_helper.rs

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,23 +191,63 @@ pub fn compute_base_avma(base_svma: u64, load_bias: u64) -> u64 {
191191
base_svma.wrapping_add(load_bias)
192192
}
193193

194-
/// Search for a separate debug info file using `.gnu_debuglink`.
195-
///
196-
/// Follows the standard GDB search order:
197-
/// 1. `<binary_dir>/<debuglink>`
198-
/// 2. `<binary_dir>/.debug/<debuglink>`
199-
/// 3. `/usr/lib/debug/<binary_dir>/<debuglink>`
194+
const DEFAULT_DEBUG_DIR: &str = "/usr/lib/debug";
195+
196+
/// Search for a separate debug info file.
200197
///
201-
/// Validates the CRC32 checksum to avoid using stale debug files.
198+
/// Tries two mechanisms in order:
199+
/// 1. **Build-ID path**: `<debug_dir>/.build-id/<XX>/<YYYYYY...>.debug`
200+
/// 2. **`.gnu_debuglink`** with GDB search order and CRC32 validation
202201
pub fn find_debug_file(object: &object::File, binary_path: &Path) -> Option<PathBuf> {
202+
find_debug_file_in(object, binary_path, Path::new(DEFAULT_DEBUG_DIR))
203+
}
204+
205+
fn find_debug_file_in(
206+
object: &object::File,
207+
binary_path: &Path,
208+
debug_dir: &Path,
209+
) -> Option<PathBuf> {
210+
if let Some(path) = find_debug_file_by_build_id(object, debug_dir) {
211+
return Some(path);
212+
}
213+
find_debug_file_by_debuglink(object, binary_path, debug_dir)
214+
}
215+
216+
fn find_debug_file_by_build_id(object: &object::File, debug_dir: &Path) -> Option<PathBuf> {
217+
let build_id = object.build_id().ok()??;
218+
if build_id.len() < 2 {
219+
return None;
220+
}
221+
222+
let hex = build_id
223+
.iter()
224+
.map(|b| format!("{b:02x}"))
225+
.collect::<String>();
226+
let path = debug_dir
227+
.join(".build-id")
228+
.join(&hex[..2])
229+
.join(format!("{}.debug", &hex[2..]));
230+
231+
if path.exists() {
232+
return Some(path);
233+
}
234+
235+
None
236+
}
237+
238+
fn find_debug_file_by_debuglink(
239+
object: &object::File,
240+
binary_path: &Path,
241+
debug_dir: &Path,
242+
) -> Option<PathBuf> {
203243
let (debuglink, expected_crc) = object.gnu_debuglink().ok()??;
204244
let debuglink = std::str::from_utf8(debuglink).ok()?;
205245
let dir = binary_path.parent()?;
206246

207247
let candidates = [
208248
dir.join(debuglink),
209249
dir.join(".debug").join(debuglink),
210-
Path::new("/usr/lib/debug")
250+
debug_dir
211251
.join(dir.strip_prefix("/").unwrap_or(dir))
212252
.join(debuglink),
213253
];
@@ -227,3 +267,46 @@ pub fn find_debug_file(object: &object::File, binary_path: &Path) -> Option<Path
227267
true
228268
})
229269
}
270+
271+
#[cfg(all(test, target_os = "linux"))]
272+
mod tests {
273+
use super::*;
274+
275+
#[test]
276+
fn test_find_debug_file_by_build_id() {
277+
// go_fib.bin has a build-id. Set up the build-id directory structure
278+
// in a tempdir and verify find_debug_file_in resolves it.
279+
let binary_path = Path::new("testdata/perf_map/go_fib.bin");
280+
let content = std::fs::read(binary_path).unwrap();
281+
let object = object::File::parse(&*content).unwrap();
282+
283+
let build_id = object.build_id().unwrap().unwrap();
284+
let hex: String = build_id.iter().map(|b| format!("{b:02x}")).collect();
285+
286+
let tmp = tempfile::tempdir().unwrap();
287+
let debug_file_dir = tmp.path().join(".build-id").join(&hex[..2]);
288+
std::fs::create_dir_all(&debug_file_dir).unwrap();
289+
290+
let debug_file_path = debug_file_dir.join(format!("{}.debug", &hex[2..]));
291+
// Copy the original binary as the "debug file" (it has DWARF)
292+
std::fs::copy(binary_path, &debug_file_path).unwrap();
293+
294+
let result = find_debug_file_in(&object, binary_path, tmp.path());
295+
assert_eq!(result, Some(debug_file_path));
296+
}
297+
298+
#[test]
299+
fn test_find_debug_file_by_debuglink() {
300+
// cpp_my_benchmark_stripped.bin has a .gnu_debuglink pointing to
301+
// cpp_my_benchmark.debug in the same directory.
302+
let binary_path = Path::new("testdata/perf_map/cpp_my_benchmark_stripped.bin");
303+
let content = std::fs::read(binary_path).unwrap();
304+
let object = object::File::parse(&*content).unwrap();
305+
306+
let result = find_debug_file_in(&object, binary_path, Path::new("/nonexistent"));
307+
assert_eq!(
308+
result,
309+
Some(PathBuf::from("testdata/perf_map/cpp_my_benchmark.debug"))
310+
);
311+
}
312+
}

0 commit comments

Comments
 (0)