-
Notifications
You must be signed in to change notification settings - Fork 253
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
Capturing a backtrace prevents any future panic from printing a backtrace on Windows #165
Comments
The default panic unwind hook attempts to print the backtrace by first initializing DbgHelp with `SymInitializeW`. The documentation states that this can't be called again until after `SymCleanup` is called first. If called prior to cleanup, it returns a non-success result. The default unwind hook detects this error and never tries to print the backtrace. This PR installs a new panic handler that first calls `SymCleanup` prior to calling the previously installed (or default) panic hook. Fixes rust-lang#165
The default panic unwind hook attempts to print the backtrace by first initializing DbgHelp with `SymInitializeW`. The documentation states that this can't be called again until after `SymCleanup` is called first. If called prior to cleanup, it returns a non-success result. The default unwind hook detects this error and never tries to print the backtrace. This PR installs a new panic handler that first calls `SymCleanup` prior to calling the previously installed (or default) panic hook. Fixes rust-lang#165
The default panic unwind hook attempts to print the backtrace by first initializing DbgHelp with `SymInitializeW`. The documentation states that this can't be called again until after `SymCleanup` is called first. If called prior to cleanup, it returns a non-success result. The default unwind hook detects this error and never tries to print the backtrace. This PR installs a new panic handler that first calls `SymCleanup` prior to calling the previously installed (or default) panic hook. Fixes rust-lang#165
Thanks for the report! This is definitely a bug and something that should be fixed! I honestly am not sure how to best fix it and I'm not 100% clear on how this works in Windows. I think it's already technically UB beacuse we're not synchronizing with the backtrace mechanism in the standard library, but that's not necessarily the end of the world. I think though that registering a panic hook may not be the best approach here, is there perhaps something we can do to update the standard library and/or this crate and have the two work together? |
Me neither. Everything I know about backtrace in windows was discovered yesterday evening :)
I agree! Although I do think it's better than leaving the current behavior (even if temporary).
Having the standard library preemptively call I think that coordinating with the standard library is the right approach. Is this something that would require an RFC? And/or could it be something exposed in Do you know of any examples of libraries coordinating platform specifics with the standard library? |
There's been some coordination with libstd and other librararies before, but it just doesn't really come up that often. Ironically this library has had to coordinate the most probably... In any case after reading the code it looks like libstd already has initialize/cleanup pairings, so maybe this library should just have that as well? I believe that would fix the issue, right? |
I think having the initialize/cleanup pairings is a correct approach, but it does have a non-trivial performance penalty. I added the cleanup back and ran I also tried with the master:
master with cleanup code added back:
Master with cleanup and
// requires external synchronization
#[cfg(all(windows, feature = "dbghelp"))]
unsafe fn dbghelp_init() -> std::boxed::Box<dyn std::any::Any> {
use winapi::shared::minwindef::{self, DWORD};
use winapi::um::{dbghelp, processthreadsapi};
use winapi::um::winnt::HANDLE;
struct Cleanup { handle: HANDLE }
impl Drop for Cleanup {
fn drop(&mut self) {
unsafe {
dbghelp::SymCleanup(self.handle);
}
}
}
let handle = processthreadsapi::GetCurrentProcess();
const SYMOPT_DEFERRED_LOADS: DWORD = 0x00000004;
// not sure why these are missing in winapi
extern "system" {
fn SymGetOptions() -> DWORD;
fn SymSetOptions(options: DWORD);
}
let opts = SymGetOptions();
SymSetOptions(opts | SYMOPT_DEFERRED_LOADS);
let ret = dbghelp::SymInitializeW(handle,
0 as *mut _,
minwindef::TRUE);
if ret != minwindef::TRUE {
std::boxed::Box::new(())
} else {
std::boxed::Box::new(Cleanup { handle })
}
} |
It's still pretty slow to with the cleanup pairings.. #[bench]
fn new(b: &mut test::Bencher) {
b.iter(|| {
let bt = Backtrace::new();
test::black_box(bt);
});
} #[bench]
fn new_unresolved(b: &mut test::Bencher) {
b.iter(|| {
let bt = Backtrace::new_unresolved();
test::black_box(bt);
});
} Master:
Master with cleanup:
Master with cleanup and
|
I think we've had bad performance problems with libbacktrace on other platforms as well before, so taking quite a long time during symbolication I think is expected. It's a bummer it's that slow but shouldn't be the end of the world in most cases in theory. The backtracing portion may not actually need to call SymInitialize, I may have just written that in at some point in error! |
This commit updates the behavior of backtraces on Windows to execute `SymInitializeW` globally once-per-process and ignore the return value as to whether it succeeded or not. This undoes previous work in this crate to specifically check the return value, and this is a behavior update for the standard library when this goes into the standard library. The `SymInitializeW` function is required to be called on MSVC before any backtraces can be captured or before any addresses can be symbolized. This function is quite slow. It can only be called once-per-process and there's a corresponding `SymCleanup` to undo the work of `SymInitializeW`. The behavior of what to do with `SymInitializeW` has changed a lot over time in this crate. The very first implementation for Windows paired with `SymCleanup`. Then reports of slowness at rust-lang/rustup#591 led to ac8c6d2 where `SymCleanup` was removed. This led to #165 where because the standard library still checked `SymInitializeW`'s return value and called `SymCleanup` generating a backtrace with this crate would break `RUST_BACKTRACE=1`. Finally (and expectedly) the performance report has come back as #229 for this crate. I'm proposing that the final nail is put in this coffin at this point where this crate will ignore the return value of `SymInitializeW`, initializing symbols once per process globally. This update will go into the standard library and get updated on the stable channel eventually, meaning both libstd and this crate will be able to work with one another and only initialize the process's symbols once globally. This does mean that there will be a period of "breakage" where we will continue to make `RUST_BACKTRACE=1` not useful if this crate is used on MSVC, but that's sort of the extension of the status quo as of a month or so ago. This will eventually be fixed once the stable channel of Rust is updated.
As the title states, backtraces are not printed if the program panics after a backtrace was captured with the library.
With
RUST_BACKTRACE=1
I tracked it down to this commit: 3cfb76a
The cleanup code was removed.
Changing the example to the following allows for both backtraces to print:
The runtime panic-unwind code is checking the result of
SymInitialize
which is probably an error due to double initialization.https://github.com/rust-lang/rust/blob/9ebf47851a357faa4cd97f4b1dc7835f6376e639/src/libstd/sys/windows/backtrace/mod.rs#L74
https://docs.microsoft.com/en-us/windows/desktop/api/dbghelp/nf-dbghelp-syminitializew
Perhaps the old behavior could be restored with deferred symbol loading:
https://docs.microsoft.com/en-us/windows/desktop/api/Dbghelp/nf-dbghelp-symsetoptions
Alternatively, the runtime panic-unwind code could perhaps always call
SymCleanup
first (and ignore the error) prior to callingSymInitialize
. As far as I can tell, callingSymCleanup
without first initializing returns an error code but is otherwise harmless.The text was updated successfully, but these errors were encountered: