Skip to content

Self backtrace of deleted binaries #748

Description

@Jongy

In case the main binary is deleted, getting a backtrace from it fails, because the path we try to work against is the result of readlink("/proc/self/exe"):

let name = if is_static {
// don't try to look up our name from /proc/self/maps, it'll get silly
env::current_exe().unwrap_or_default().into_os_string()
} else if is_main && no_given_name {
infer_current_exe(&maps, dlpi_addr as usize)
} else {

(which is with (deleted) suffix, and in any case, the path no longer exists).

If we just use /proc/self/exe directly - things work. This example demonstrates that:

$ cat src/main.rs
fn inner() {
    // Delete our own binary from disk.
    let exe = std::env::current_exe().unwrap();
    std::fs::remove_file(&exe).unwrap();

    // Capture and print the backtrace.
    let bt = backtrace::Backtrace::new();
    eprintln!("\n{bt:?}");
}

fn main() {
    inner();
}
$ cat Cargo.toml
[package]
name = "bt-deleted-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
backtrace = { path = "/home/jong/src/rust/backtrace-rs" }

[profile.release]
debug = true            # keep debug info (DWARF)
strip = "debuginfo"     # then strip DWARF, keeping .symtab (like `strip --strip-debug`)

When run against master (28ec93b) I get:

   0: <unknown>
   1: <unknown>
   2: <unknown>
   3: <unknown>
   4: <unknown>
   5: __libc_start_call_main
             at ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
   6: __libc_start_main_impl
             at ./csu/../csu/libc-start.c:360:3
   7: <unknown>

I apply this patch:

diff --git i/src/symbolize/gimli/libs_dl_iterate_phdr.rs w/src/symbolize/gimli/libs_dl_iterate_phdr.rs
index 2d1da7c..e5c21e2 100644
--- i/src/symbolize/gimli/libs_dl_iterate_phdr.rs
+++ w/src/symbolize/gimli/libs_dl_iterate_phdr.rs
@@ -72,11 +72,8 @@ unsafe extern "C" fn callback(
     let no_given_name = dlpi_name.is_null()
         // SAFETY: we just checked for null
         || unsafe { *dlpi_name == 0 };
-    let name = if is_static {
-        // don't try to look up our name from /proc/self/maps, it'll get silly
-        env::current_exe().unwrap_or_default().into_os_string()
-    } else if is_main && no_given_name {
-        infer_current_exe(&maps, dlpi_addr as usize)
+    let name = if is_static || (is_main && no_given_name) {
+        OsString::from("/proc/self/exe")
     } else {
         // this fallback works even if we are main, because some platforms give the name anyways
         if dlpi_name.is_null() {

and get:

   0: bt_deleted_demo::main
   1: std::sys::backtrace::__rust_begin_short_backtrace
   2: std::rt::lang_start::{{closure}}
   3: std::rt::lang_start_internal
   4: main
   5: __libc_start_call_main
             at ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
   6: __libc_start_main_impl
             at ./csu/../csu/libc-start.c:360:3
   7: _start

This is relevant in one of our apps, where the binary might be deleted in some cases but we want the self-backtrace to keep working as expected.
Do you think it's a valid change? I'm happy to submit it. We need some handling around what #488 fixed (when /proc/self/exe is the interpreter we should still use /proc/pid/maps and not /proc/self/exe) but other than that I believe this fix is okay.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions