Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore more frames on backtrace unwinding. #40264

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/libstd/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ pub use panicking::{begin_panic, begin_panic_fmt, update_panic_count};

#[cfg(not(test))]
#[lang = "start"]
fn lang_start(main: *const u8, argc: isize, argv: *const *const u8) -> isize {
use mem;
fn lang_start(main: fn(), argc: isize, argv: *const *const u8) -> isize {
use panic;
use sys;
use sys_common;
Expand All @@ -54,7 +53,9 @@ fn lang_start(main: *const u8, argc: isize, argv: *const *const u8) -> isize {
sys::args::init(argc, argv);

// Let's run some code!
let res = panic::catch_unwind(mem::transmute::<_, fn()>(main));
let res = panic::catch_unwind(|| {
__rust_begin_short_backtrace_binary(main)
});
sys_common::cleanup();
res.is_err()
};
Expand All @@ -65,3 +66,9 @@ fn lang_start(main: *const u8, argc: isize, argv: *const *const u8) -> isize {
0
}
}

/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
#[inline(never)]
fn __rust_begin_short_backtrace_binary(f: fn()) {
f()
}
77 changes: 33 additions & 44 deletions src/libstd/sys_common/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ fn _print(w: &mut Write, format: PrintFormat) -> io::Result<()> {
Ok(())
}

/// Returns a number of frames to remove at the beginning and at the end of the
/// backtrace, according to the backtrace format.
fn filter_frames(frames: &[Frame],
format: PrintFormat,
context: &BacktraceContext) -> (usize, usize)
Expand All @@ -101,59 +103,35 @@ fn filter_frames(frames: &[Frame],
return (0, 0);
}

// We want to filter out frames with some prefixes
// from both top and bottom of the call stack.
// Frames to remove from the top of the backtrace.
//
// The raw form is used so that we don't have to demangle the symbol names.
// The `a::b::c` form can show up on Windows/MSVC.
static BAD_PREFIXES_TOP: &'static [&'static str] = &[
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this array entirely? Currently rust_panic is the "canonical frame name" of the panic, and if there's a lower frame we should start from then we should just give that a canonical name instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What to you think about removing the frames from the core::panicking::panic* functions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to strive to eliminate them, but we shouldn't have such a long list of what to filter. There should be one frame that we look for and if we find it maybe do a few calculations based off that. Substring searching is basically guarantee to go wrong in the future.

"_ZN3std3sys3imp9backtrace",
"ZN3std3sys3imp9backtrace",
"std::sys::imp::backtrace",
"_ZN3std10sys_common9backtrace",
"ZN3std10sys_common9backtrace",
"ZN3std3sys3imp9backtrace",

"std::sys_common::backtrace",
"_ZN3std9panicking",
"ZN3std9panicking",
"ZN3std10sys_common9backtrace",

"std::panicking",
"_ZN4core9panicking",
"ZN4core9panicking",
"ZN3std9panicking",

"core::panicking",
"_ZN4core6result13unwrap_failed",
"ZN4core6result13unwrap_failed",
"ZN4core9panicking",

"core::result::unwrap_failed",
"ZN4core6result13unwrap_failed",

"rust_begin_unwind",
"_ZN4drop",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Er was this added in the previous PR? This definitely seems like something that we shouldn't be dropping, this is just a normal function?

"mingw_set_invalid_parameter_handler",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and drop above should be removed

];
static BAD_PREFIXES_BOTTOM: &'static [&'static str] = &[
"_ZN3std9panicking",
"ZN3std9panicking",
"std::panicking",
"_ZN3std5panic",
"ZN3std5panic",
"std::panic",
"_ZN4core9panicking",
"ZN4core9panicking",
"core::panicking",
"_ZN3std2rt10lang_start",
"ZN3std2rt10lang_start",
"std::rt::lang_start",
"panic_unwind::__rust_maybe_catch_panic",
"__rust_maybe_catch_panic",
"_rust_maybe_catch_panic",
"__libc_start_main",
"__rust_try",
"_start",
"main",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__libc_start_main, _start and main appear at the bottom of the stack and need to be filtered away.
They are removed from the list in this PR, but they may be required again if the "filtering point" is moved from rust_maybe_catch_panic below (e.g. to lang_start), I don't remember exactly what's their relative order.

"BaseThreadInitThunk",
"RtlInitializeExceptionChain",
"__scrt_common_main_seh",
"_ZN4drop",
"mingw_set_invalid_parameter_handler",
];

let is_good_frame = |frame: Frame, bad_prefixes: &[&str]| {
resolve_symname(frame, |symname| {
if let Some(mangled_symbol_name) = symname {
if !bad_prefixes.iter().any(|s| mangled_symbol_name.starts_with(s)) {
if !bad_prefixes.iter().any(|s| mangled_symbol_name.contains(s)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use no_mangle functions as boundaries, it should be possible to check that the symbol name is exactly equal to the expected one, instead of just a prefix or substring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, some platforms seems to have different behaviours, like __rust_try vs _rust_try if I remember correctly, so we contains it should cover all cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, but that should be the only cases (no ::h49fe0d471b73c20e trailers).
I do not expect anybody to define functions which contain the patterns the code is matching for, but a strict comparison would help keeping us on the safe side, just in case ;)

return Ok(())
}
}
Expand All @@ -164,11 +142,22 @@ fn filter_frames(frames: &[Frame],
let skipped_before = frames.iter().position(|frame| {
is_good_frame(*frame, BAD_PREFIXES_TOP)
}).unwrap_or(frames.len());
let skipped_after = frames[skipped_before..].iter().rev().position(|frame| {
is_good_frame(*frame, BAD_PREFIXES_BOTTOM)
}).unwrap_or(frames.len() - skipped_before);

if skipped_before + skipped_after == frames.len() {
let skipped_after = frames.len() - frames.iter().position(|frame| {
let mut is_marker = false;
let _ = resolve_symname(*frame, |symname| {
if let Some(mangled_symbol_name) = symname {
// Use grep to find the concerned functions
if mangled_symbol_name.contains("__rust_begin_short_backtrace_") {
is_marker = true;
}
}
Ok(())
}, context);
is_marker
}).unwrap_or(frames.len());

if skipped_before + skipped_after >= frames.len() {
// Avoid showing completely empty backtraces
return (0, 0);
}
Expand Down Expand Up @@ -198,7 +187,7 @@ pub fn log_enabled() -> Option<PrintFormat> {
}

let val = match env::var_os("RUST_BACKTRACE") {
Some(x) => if &x == "0" {
Some(x) => if &x == "0" || &x == "" {
None
} else if &x == "full" {
Some(PrintFormat::Full)
Expand Down
12 changes: 11 additions & 1 deletion src/libstd/thread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,9 @@ impl Builder {
}
unsafe {
thread_info::set(imp::guard::current(), their_thread);
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(f));
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
__rust_begin_short_backtrace_thread(f)
}));
*their_packet.get() = Some(try_result);
}
};
Expand All @@ -372,6 +374,14 @@ impl Builder {
}
}

/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
#[inline(never)]
fn __rust_begin_short_backtrace_thread<F, T>(f: F) -> T
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be consolidated with the main entry point?

where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static
{
f()
}

////////////////////////////////////////////////////////////////////////////////
// Free functions
////////////////////////////////////////////////////////////////////////////////
Expand Down
41 changes: 36 additions & 5 deletions src/libtest/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1252,12 +1252,16 @@ pub fn convert_benchmarks_to_tests(tests: Vec<TestDescAndFn>) -> Vec<TestDescAnd
let testfn = match x.testfn {
DynBenchFn(bench) => {
DynTestFn(Box::new(move |()| {
bench::run_once(|b| bench.run(b))
bench::run_once(|b| {
__rust_begin_short_backtrace_bench_dyn(&bench, b)
})
}))
}
StaticBenchFn(benchfn) => {
DynTestFn(Box::new(move |()| {
bench::run_once(|b| benchfn(b))
bench::run_once(|b| {
__rust_begin_short_backtrace_bench_static(benchfn, b)
})
}))
}
f => f,
Expand Down Expand Up @@ -1363,12 +1367,39 @@ pub fn run_test(opts: &TestOpts,
monitor_ch.send((desc, TrMetrics(mm), Vec::new())).unwrap();
return;
}
DynTestFn(f) => run_test_inner(desc, monitor_ch, opts.nocapture, f),
StaticTestFn(f) => run_test_inner(desc, monitor_ch, opts.nocapture,
Box::new(move |()| f())),
DynTestFn(f) =>
run_test_inner(desc, monitor_ch, opts.nocapture,
Box::new(move |()| __rust_begin_short_backtrace_test_boxfn(f))),
StaticTestFn(f) =>
run_test_inner(desc, monitor_ch, opts.nocapture,
Box::new(move |()| __rust_begin_short_backtrace_test(f))),
}
}

/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
#[inline(never)]
fn __rust_begin_short_backtrace_test_boxfn(f: Box<FnBox<()>>) {
f.call_box(())
}

/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
#[inline(never)]
fn __rust_begin_short_backtrace_test(f: fn()) {
f()
}

/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
#[inline(never)]
fn __rust_begin_short_backtrace_bench_static(f: fn(&mut Bencher), b: &mut Bencher) {
f(b)
}

/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
#[inline(never)]
fn __rust_begin_short_backtrace_bench_dyn(bench: &Box<TDynBenchFn>, b: &mut Bencher) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these be consolidated? There probably only needs to be one implementation here:

#[inline(never)
fn name<F: FnOnce()>(f: F) { f() }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Box<TDynBenchFn> doesn't implement FnOnce.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use a generic closure here, the signatures don't have to literally all be the same

bench.run(b)
}

fn calc_result(desc: &TestDesc, task_result: Result<(), Box<Any + Send>>) -> TestResult {
match (&desc.should_panic, task_result) {
(&ShouldPanic::No, Ok(())) |
Expand Down