I want to change the Rust standard library’s short backtrace printing to be extensible. I opened Generate `DW_AT_RUST_short_backtrace` attributes for `DISubprogram` nodes by jyn514 · Pull Request #123683 · llvm/llvm-project · GitHub and @dblaikie suggested that I talked about the design and goals before we got bogged down in the implementation. So! Here goes.
The Rust standard library has two styles for printing backtraces at runtime:
- Full backtraces. These work in the obvious way.
- Short backtraces. These filter out “unimportant” frames that are likely not related to the developer’s bug. For example, frames like
__libc_start_main
,_Unwind_Resume
, and rust runtime internals likestd::rt::lang_start
are filtered out.
Currently, the Rust runtime determines “unimportant” frames by looking directly at un-mangled symbol names of generated functions. This is not extensible, and involves a state machine that requires the frames to be present at runtime; in particular the frames must be marked noinline
, impeding optimizations.
I want to allow individual frames to be marked as “unimportant” for the purpose of backtraces, using a new DWARF vendor extension attribute. PDB doesn’t appear to be extensible and so I haven’t tried to implement this there; @wesleywiser suggested I used llvm.codeview.annotation
for PDB, but I’m leaving that for future work.
Ideally, this would would be extensible to other languages and codegen backends; I would love to see llvm-symbolizer
have a mode for printing short backtraces instead of the full backtrace.
I added the following enum API:
enum class ShortBacktraceAttr {
SkipFrame = 0,
StartShortBacktrace = 1,
EndShortBacktrace = 2,
SkipFrame
indicates only the current frame should be skipped. StartShortBacktrace
and EndShortBacktrace
control this state machine in the rust runtime: rust/library/std/src/sys/backtrace.rs at master · rust-lang/rust · GitHub. I don’t think they can be replicated only with SkipFrame
; in particular, StartShortBacktrace
is necessary so that we can hide frames before main, for which we don’t control the debuginfo. If this is to be extensible to other languages, we also want EndShortBacktrace
so that we can have a start/end pair that lets this work across shared object libraries, or if part of the code was written in different language.
@dblaikie you suggested that this could instead be an enum { Skip, Print, Inherit }
enum; presumably you intended for Inherit
to be the default if there’s no debuginfo present at runtime. But I don’t think this is a general enough mechanism. Consider a program like this one:
// lib.rs
#[rustc_skip_short_backtrace] // Skip
pub fn foo() { panic!(); }
// library/std/src/panic.rs
pub fn catch_unwind(f: fn()) { f(); }
// main.rs
fn main() {
std::panic::catch_unwind(foo);
}
First, we generate foo
with a Skip
backtrace annotation. To keep the same behavior as my proposal, we need some way to print the frame for catch_unwind
without printing the frame for foo. So we can’t use Inherit
. But we also need to not print catch_unwind when it’s used before main in the runtime startup (see the worked example below). So we can’t use Print
. So I don’t think your idea works.
Here are some worked examples of the new attribute, taken from the Rustc test suite:
A trivial program which immediately panics: fn main() { panic!() }
With short backtraces:
thread 'main' panicked at src/main.rs:11:5:
explicit panic
stack backtrace:
[... omitted 17 frames ...]
18: 0x573969c95d9d - example::main::h0cbc0be966554fbd
at /home/jyn/src/example/src/main.rs:11:5
[... omitted 18 frames ...]
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
With full backtraces:
thread 'main' panicked at src/main.rs:11:5:
explicit panic
stack backtrace:
0: 0x573969cb557a - std::backtrace_rs::backtrace::libunwind::trace::h5248f59125b65dcb
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/../../backtrace/src/backtrace/libunwind.rs:116:5
1: 0x573969cb557a - std::backtrace_rs::backtrace::trace_unsynchronized::h51f8c2f0c1f665a8
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
2: 0x573969cb557a - std::sys::backtrace::_print_fmt::h394536ef105dc1ee
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/sys/backtrace.rs:66:9
3: 0x573969cb557a - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::hdad3ec861e1bc3c2
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/sys/backtrace.rs:39:26
4: 0x573969cd2553 - core::fmt::rt::Argument::fmt::h1fff0e041375e022
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/core/src/fmt/rt.rs:177:76
5: 0x573969cd2553 - core::fmt::write::hd3d2ae2bd7022d6c
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/core/src/fmt/mod.rs:1440:21
6: 0x573969cb2e43 - std::io::Write::write_fmt::h6f807cd45fe0ec3f
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/io/mod.rs:1888:15
7: 0x573969cb53c2 - std::sys::backtrace::BacktraceLock::print::hd89057abd6064d03
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/sys/backtrace.rs:42:9
8: 0x573969cb630f - std::panicking::default_hook::{{closure}}::ha5006fae9f6b3890
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panicking.rs:298:22
9: 0x573969cb617a - std::panicking::default_hook::hb2297b08dc8057bb
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panicking.rs:325:9
10: 0x573969cb6be2 - std::panicking::rust_panic_with_hook::hc90599a27179187c
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panicking.rs:831:13
11: 0x573969cb6a7a - std::panicking::begin_panic_handler::{{closure}}::hef9dccd4fc0fa6b6
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panicking.rs:704:13
12: 0x573969cb5a79 - std::sys::backtrace::__rust_end_short_backtrace::h34e3b56edd49a65f
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/sys/backtrace.rs:168:18
13: 0x573969cb670d - rust_begin_unwind
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panicking.rs:695:5
14: 0x573969cd1960 - core::panicking::panic_fmt::hab3db7cb7603f25e
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/core/src/panicking.rs:75:14
15: 0x573969cd1ae6 - core::panicking::panic_display::h06ed683e585343e8
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/core/src/panicking.rs:261:5
16: 0x573969cd1ae6 - core::panicking::panic_explicit::hfe0b9a0df85a8a12
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/core/src/panicking.rs:234:5
17: 0x573969c95daa - example::main::panic_cold_explicit::h59bb81719ac049b2
at /home/jyn/.local/lib/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic.rs:88:13
18: 0x573969c95d9d - example::main::h0cbc0be966554fbd
at /home/jyn/src/example/src/main.rs:11:5
19: 0x573969c95d4b - core::ops::function::FnOnce::call_once::h7cdd469612e13c58
at /home/jyn/.local/lib/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
20: 0x573969c95d0e - std::sys::backtrace::__rust_begin_short_backtrace::hd598eb18a7a773b1
at /home/jyn/.local/lib/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:152:18
21: 0x573969c95ce1 - std::rt::lang_start::{{closure}}::he0cb2971e4611cae
at /home/jyn/.local/lib/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:194:18
22: 0x573969cb0c70 - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::h1662fe2ef888d630
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/core/src/ops/function.rs:284:13
23: 0x573969cb0c70 - std::panicking::try::do_call::h3d18bcf005343ff3
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panicking.rs:587:40
24: 0x573969cb0c70 - std::panicking::try::h9d6374bf9286b4b5
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panicking.rs:550:19
25: 0x573969cb0c70 - std::panic::catch_unwind::h8bed5993af4f99ba
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panic.rs:358:14
26: 0x573969cb0c70 - std::rt::lang_start_internal::{{closure}}::hb5f804e6afeba7e4
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/rt.rs:163:24
27: 0x573969cb0c70 - std::panicking::try::do_call::h8a13ba7f8d7b0ead
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panicking.rs:587:40
28: 0x573969cb0c70 - std::panicking::try::h370a7fcea6779d1d
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panicking.rs:550:19
29: 0x573969cb0c70 - std::panic::catch_unwind::h3e6c1755441ed33e
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/panic.rs:358:14
30: 0x573969cb0c70 - std::rt::lang_start_internal::h481232870f10ba8e
at /rustc/049355708383ab1b9a1046559b9d4230bdb3a5bc/library/std/src/rt.rs:159:5
31: 0x573969c95cc7 - std::rt::lang_start::he943d34afc9204bb
at /home/jyn/.local/lib/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:193:5
32: 0x573969c95dce - main
33: 0x71ddee229d90 - __libc_start_call_main
at ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
34: 0x71ddee229e40 - __libc_start_main_impl
at ./csu/../csu/libc-start.c:392:3
35: 0x573969c95bc5 - _start
36: 0x0 - <unknown>
A program that uses these attributes to control the printing of its own backtrace
// Has no effect, since we already have a inner function with #[rust_end_short_backtrace]
#[rustc_end_short_backtrace]
fn first() {
second();
}
#[rustc_end_short_backtrace]
fn second() {
third(); // won't show up in backtrace
}
fn third() {
fourth(); // won't show up in backtrace
}
fn fourth() {
fifth(); // won't show up in backtrace
}
#[rustc_start_short_backtrace]
fn fifth() {
sixth();
}
fn sixth() {
seven();
}
fn seven() {
panic!("debug!!!");
}
fn main() {
first();
}
With short printing:
stack backtrace:
[... omitted 14 frames ...]
0: short_ice_remove_middle_frames::seven
at $DIR/short-ice-remove-middle-frames.rs:44:5
1: short_ice_remove_middle_frames::sixth
at $DIR/short-ice-remove-middle-frames.rs:40:5
[... omitted 3 frames ...]
2: second
at $DIR/short-ice-remove-middle-frames.rs:23:5
3: first
at $DIR/short-ice-remove-middle-frames.rs:18:5
4: short_ice_remove_middle_frames::main
at $DIR/short-ice-remove-middle-frames.rs:48:5
[... omitted 16 frames ...]
Without short printing:
[ ... ]
1: short_ice_remove_middle_frames::seven
at ./tests/ui/panics/short-ice-remove-middle-frames.rs:44:5
2: short_ice_remove_middle_frames::sixth
at ./tests/ui/panics/short-ice-remove-middle-frames.rs:40:5
3: short_ice_remove_middle_frames::fifth
at ./tests/ui/panics/short-ice-remove-middle-frames.rs:36:5
4: short_ice_remove_middle_frames::fourth
at ./tests/ui/panics/short-ice-remove-middle-frames.rs:31:5
5: short_ice_remove_middle_frames::third
at ./tests/ui/panics/short-ice-remove-middle-frames.rs:27:5
6: short_ice_remove_middle_frames::second
at ./tests/ui/panics/short-ice-remove-middle-frames.rs:23:5
7: short_ice_remove_middle_frames::first
at ./tests/ui/panics/short-ice-remove-middle-frames.rs:18:5
8: short_ice_remove_middle_frames::main
at ./tests/ui/panics/short-ice-remove-middle-frames.rs:48:5
[ ... ]
In this particular case, the program could use SkipFrame
three times instead of a Start/EndShortBacktrace
pair, or the compiler could do such a transformation internally; but this is not true in the general case.