rust-lang / rust-lang/backtrace-rs

Self backtrace of deleted binaries

Open
#748 3 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
628
Forks
291
PR merge metrics
No merged PRs in 30d

Description

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"):
https://github.com/rust-lang/backtrace-rs/blob/28ec93b503bf0410745bc3d571bf3dc1caac3019/src/symbolize/gimli/libs_dl_iterate_phdr.rs#L75-L80

(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 (28ec93b503bf0410745bc3d571bf3dc1caac3019) 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 https://github.com/rust-lang/backtrace-rs/pull/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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/symbolize/gimli/libs_dl_iterate_phdr.rs, especially the callback's executable-name selection, and compare it with the behavior addressed by pull request 488. Reproduce the deleted-binary example from the issue, then verify that self-backtraces resolve deleted binaries while interpreter cases still use /proc/pid/maps when required.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
operating-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.