Skip to content

Commit e6ac926

Browse files
committed
Ignore return of SymInitializeW on Windows
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.
1 parent 31a002a commit e6ac926

File tree

3 files changed

+26
-104
lines changed

3 files changed

+26
-104
lines changed

src/capture.rs

-48
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ impl Backtrace {
124124
/// enabled, and the `std` feature is enabled by default.
125125
#[inline(never)] // want to make sure there's a frame here to remove
126126
pub fn new() -> Backtrace {
127-
let _guard = lock_and_platform_init();
128127
let mut bt = Self::create(Self::new as usize);
129128
bt.resolve();
130129
bt
@@ -155,7 +154,6 @@ impl Backtrace {
155154
/// enabled, and the `std` feature is enabled by default.
156155
#[inline(never)] // want to make sure there's a frame here to remove
157156
pub fn new_unresolved() -> Backtrace {
158-
let _guard = lock_and_platform_init();
159157
Self::create(Self::new_unresolved as usize)
160158
}
161159

@@ -205,7 +203,6 @@ impl Backtrace {
205203
/// This function requires the `std` feature of the `backtrace` crate to be
206204
/// enabled, and the `std` feature is enabled by default.
207205
pub fn resolve(&mut self) {
208-
let _guard = lock_and_platform_init();
209206
for frame in self.frames.iter_mut().filter(|f| f.symbols.is_none()) {
210207
let mut symbols = Vec::new();
211208
{
@@ -418,51 +415,6 @@ impl fmt::Debug for BacktraceSymbol {
418415
}
419416
}
420417

421-
// When using `dbghelp` on Windows this is a performance optimization. If
422-
// we don't do this then `SymInitializeW` is called once per trace and once per
423-
// frame during resolution. That function, however, takes quite some time! To
424-
// help speed it up this function can amortize the calls necessary by ensuring
425-
// that the scope this is called in only initializes when this is called and
426-
// doesn't reinitialize for the rest of the scope.
427-
#[cfg(all(windows, feature = "dbghelp"))]
428-
fn lock_and_platform_init() -> impl Drop {
429-
use std::mem::ManuallyDrop;
430-
431-
struct Cleanup {
432-
_lock: crate::lock::LockGuard,
433-
434-
// Need to make sure this is cleaned up before `_lock`
435-
dbghelp_cleanup: Option<ManuallyDrop<crate::dbghelp::Cleanup>>,
436-
}
437-
438-
impl Drop for Cleanup {
439-
fn drop(&mut self) {
440-
if let Some(cleanup) = self.dbghelp_cleanup.as_mut() {
441-
// Unsafety here should be ok since we're only dropping this in
442-
// `Drop` to ensure it's dropped before the lock, and `Drop`
443-
// should only be called once.
444-
unsafe {
445-
ManuallyDrop::drop(cleanup);
446-
}
447-
}
448-
}
449-
}
450-
451-
// Unsafety here should be ok because we only acquire the `dbghelp`
452-
// initialization (the unsafe part) after acquiring the global lock for this
453-
// crate. Note that we're also careful to drop it before the lock is
454-
// dropped.
455-
unsafe {
456-
Cleanup {
457-
_lock: crate::lock::lock(),
458-
dbghelp_cleanup: crate::dbghelp::init().ok().map(ManuallyDrop::new),
459-
}
460-
}
461-
}
462-
463-
#[cfg(not(all(windows, feature = "dbghelp")))]
464-
fn lock_and_platform_init() {}
465-
466418
#[cfg(feature = "serialize-rustc")]
467419
mod rustc_serialize_impls {
468420
use super::*;

src/dbghelp.rs

+24-54
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ macro_rules! dbghelp {
154154
// Convenience proxy to use the cleanup locks to reference dbghelp
155155
// functions.
156156
#[allow(dead_code)]
157-
impl Cleanup {
157+
impl Init {
158158
$(pub fn $name(&self) -> $name {
159159
unsafe {
160160
DBGHELP.$name().unwrap()
@@ -244,75 +244,45 @@ dbghelp! {
244244
}
245245
}
246246

247-
pub struct Cleanup;
248-
249-
// Number of times `init` has been called on this thread. This is externally
250-
// synchronized and doesn't use internal synchronization on our behalf.
251-
static mut COUNT: usize = 0;
252-
253-
// Used to restore `SymSetOptions` and `SymGetOptions` values.
254-
static mut OPTS_ORIG: DWORD = 0;
247+
pub struct Init;
255248

256249
/// Unsafe because this requires external synchronization, must be done
257250
/// inside of the same lock as all other backtrace operations.
258251
///
259252
/// Note that the `Dbghelp` returned must also be dropped within the same
260253
/// lock.
261254
#[cfg(all(windows, feature = "dbghelp"))]
262-
pub unsafe fn init() -> Result<Cleanup, ()> {
263-
// Initializing symbols has significant overhead, but initializing only
264-
// once without cleanup causes problems for external sources. For
265-
// example, the standard library checks the result of SymInitializeW
266-
// (which returns an error if attempting to initialize twice) and in
267-
// the event of an error, will not print a backtrace on panic.
268-
// Presumably, external debuggers may have similar issues.
269-
//
270-
// As a compromise, we'll keep track of the number of internal
271-
// initialization requests within a single API call in order to
272-
// minimize the number of init/cleanup cycles.
273-
if COUNT > 0 {
274-
COUNT += 1;
275-
return Ok(Cleanup);
255+
pub unsafe fn init() -> Result<Init, ()> {
256+
// Calling `SymInitializeW` is quite expensive, so we only do so once per
257+
// process.
258+
static mut INITIALIZED: bool = false;
259+
if INITIALIZED {
260+
return Ok(Init);
276261
}
277262

278263
// Actually load `dbghelp.dll` into the process here, returning an error if
279264
// that fails.
280265
DBGHELP.ensure_open()?;
281266

282-
OPTS_ORIG = DBGHELP.SymGetOptions().unwrap()();
267+
let orig = DBGHELP.SymGetOptions().unwrap()();
283268

284269
// Ensure that the `SYMOPT_DEFERRED_LOADS` flag is set, because
285270
// according to MSVC's own docs about this: "This is the fastest, most
286271
// efficient way to use the symbol handler.", so let's do that!
287-
DBGHELP.SymSetOptions().unwrap()(OPTS_ORIG | SYMOPT_DEFERRED_LOADS);
288-
289-
let ret = DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE);
290-
if ret != TRUE {
291-
// Symbols may have been initialized by another library or an
292-
// external debugger
293-
DBGHELP.SymSetOptions().unwrap()(OPTS_ORIG);
294-
Err(())
295-
} else {
296-
COUNT += 1;
297-
Ok(Cleanup)
298-
}
299-
}
272+
DBGHELP.SymSetOptions().unwrap()(orig | SYMOPT_DEFERRED_LOADS);
300273

301-
impl Drop for Cleanup {
302-
fn drop(&mut self) {
303-
unsafe {
304-
COUNT -= 1;
305-
if COUNT != 0 {
306-
return;
307-
}
308-
309-
// Clean up after ourselves by cleaning up symbols and restoring the
310-
// symbol options to their original value. This is currently
311-
// required to cooperate with libstd as libstd's backtracing will
312-
// assert symbol initialization succeeds and will clean up after the
313-
// backtrace is finished.
314-
DBGHELP.SymCleanup().unwrap()(GetCurrentProcess());
315-
DBGHELP.SymSetOptions().unwrap()(OPTS_ORIG);
316-
}
317-
}
274+
// Actually initialize symbols with MSVC. Note that this can fail, but we
275+
// ignore it. There's not a ton of prior art for this per se, but LLVM
276+
// internally seems to ignore the return value here and one of the
277+
// sanitizer libraries in LLVM prints a scary warning if this fails but
278+
// basically ignores it in the long run.
279+
//
280+
// One case this comes up a lot for Rust is that the standard library and
281+
// this crate on crates.io both want to compete for `SymInitializeW`. The
282+
// standard library historically wanted to initialize then cleanup most of
283+
// the time, but now that it's using this crate it means that someone will
284+
// get to initialization first and the other will pick up that
285+
// initialization.
286+
DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE);
287+
Ok(Init)
318288
}

src/symbolize/dbghelp.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ pub unsafe fn resolve(what: ResolveWhat, cb: &mut FnMut(&super::Symbol)) {
9898
}
9999

100100
unsafe fn resolve_with_inline(
101-
dbghelp: &dbghelp::Cleanup,
101+
dbghelp: &dbghelp::Init,
102102
frame: &STACKFRAME_EX,
103103
cb: &mut FnMut(&super::Symbol),
104104
) {
@@ -127,7 +127,7 @@ unsafe fn resolve_with_inline(
127127
}
128128

129129
unsafe fn resolve_without_inline(
130-
dbghelp: &dbghelp::Cleanup,
130+
dbghelp: &dbghelp::Init,
131131
addr: *mut c_void,
132132
cb: &mut FnMut(&super::Symbol),
133133
) {

0 commit comments

Comments
 (0)