Skip to content

Commit 09ca043

Browse files
committed
Fix concurrent panics and backtraces
On Windows `dbghelp.dll` is required to be used from only one thread at a time, and while this crate provides that guarantee this crate does not synchronize with itself in the standard library. This commit solves this issue by using a Windows-specific trick by creating a named mutex for synchronization. When this crate is updated in the standard library it means that the named mutex here will be shared with the standard library, so the standard library and this crate should be sharing the same synchronization primitive and should be able to coordinate calls to `dbghelp.dll`. More details are included in the commit itself, but this should... Closes #230
1 parent 61ba508 commit 09ca043

File tree

3 files changed

+130
-34
lines changed

3 files changed

+130
-34
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ verify-winapi = [
115115
'winapi/libloaderapi',
116116
'winapi/minwindef',
117117
'winapi/processthreadsapi',
118+
'winapi/synchapi',
118119
'winapi/winbase',
119120
'winapi/winnt',
120121
]

src/dbghelp.rs

Lines changed: 116 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -244,45 +244,127 @@ dbghelp! {
244244
}
245245
}
246246

247-
pub struct Init;
247+
pub struct Init {
248+
lock: HANDLE,
249+
}
248250

249-
/// Unsafe because this requires external synchronization, must be done
250-
/// inside of the same lock as all other backtrace operations.
251+
/// Initialize all support necessary to access `dbghelp` API functions from this
252+
/// crate.
251253
///
252-
/// Note that the `Dbghelp` returned must also be dropped within the same
253-
/// lock.
254+
/// Note that this function is **safe**, it internally has its own
255+
/// synchronization. Also note that it is safe to call this function multiple
256+
/// times recursively.
254257
#[cfg(all(windows, feature = "dbghelp"))]
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);
261-
}
258+
pub fn init() -> Result<Init, ()> {
259+
use core::sync::atomic::{AtomicUsize, Ordering::SeqCst};
262260

263-
// Actually load `dbghelp.dll` into the process here, returning an error if
264-
// that fails.
265-
DBGHELP.ensure_open()?;
261+
unsafe {
262+
// First thing we need to do is to synchronize this function. This can
263+
// be called concurrently from other threads or recursively within one
264+
// thread. Note that it's trickier than that though because what we're
265+
// using here, `dbghelp`, *also* needs to be synchronized with all other
266+
// callers to `dbghelp` in this process.
267+
//
268+
// Typically there aren't really that many calls to `dbghelp` within the
269+
// same process and we can probably safely assume that we're the only
270+
// ones accessing it. There is, however, one primary other user we have
271+
// to worry about which is ironically ourselves, but in the standard
272+
// library. The Rust standard library depends on this crate for
273+
// backtrace support, and this crate also exists on crates.io. This
274+
// means that if the standard library is printing a panic backtrace it
275+
// may race with this crate coming from crates.io, causing segfaults.
276+
//
277+
// To help solve this synchronization problem we employ a
278+
// Windows-specific trick here (it is, after all, a Windows-specific
279+
// restriction about synchronization). We create a *session-local* named
280+
// mutex to protect this call. The intention here is that the standard
281+
// library and this crate don't have to share Rust-level APIs to
282+
// synchronize here but can instead work behind the scenes to make sure
283+
// they're synchronizing with one another. That way when this function
284+
// is called through the standard library or through crates.io we can be
285+
// sure that the same mutex is being acquired.
286+
//
287+
// So all of that is to say that the first thing we do here is we
288+
// atomically create a `HANDLE` which is a named mutex on Windows. We
289+
// synchronize a bit with other threads sharing this function
290+
// specifically and ensure that only one handle is created per instance
291+
// of this function. Note that the handle is never closed once it's
292+
// stored in the global.
293+
//
294+
// After we've actually go the lock we simply acquire it, and our `Init`
295+
// handle we hand out will be responsible for dropping it eventually.
296+
static LOCK: AtomicUsize = AtomicUsize::new(0);
297+
let mut lock = LOCK.load(SeqCst);
298+
if lock == 0 {
299+
lock = CreateMutexA(
300+
ptr::null_mut(),
301+
0,
302+
"Local\\RustBacktraceMutex\0".as_ptr() as _,
303+
) as usize;
304+
if lock == 0 {
305+
return Err(());
306+
}
307+
if let Err(other) = LOCK.compare_exchange(0, lock, SeqCst, SeqCst) {
308+
debug_assert!(other != 0);
309+
CloseHandle(lock as HANDLE);
310+
lock = other;
311+
}
312+
}
313+
debug_assert!(lock != 0);
314+
let lock = lock as HANDLE;
315+
let r = WaitForSingleObjectEx(lock, INFINITE, FALSE);
316+
debug_assert_eq!(r, 0);
317+
let ret = Init { lock };
318+
319+
// Ok, phew! Now that we're all safely synchronized, let's actually
320+
// start processing everything. First up we need to ensure that
321+
// `dbghelp.dll` is actually loaded in this process. We do this
322+
// dynamically to avoid a static dependency. This has historically been
323+
// done to work around weird linking issues and is intended at making
324+
// binaries a bit more portable since this is largely just a debugging
325+
// utility.
326+
//
327+
// Once we've opened `dbghelp.dll` we need to call some initialization
328+
// functions in it, and that's detailed more below. We only do this
329+
// once, though, so we've got a global boolean indicating whether we're
330+
// done yet or not.
331+
DBGHELP.ensure_open()?;
266332

267-
let orig = DBGHELP.SymGetOptions().unwrap()();
333+
static mut INITIALIZED: bool = false;
334+
if INITIALIZED {
335+
return Ok(ret);
336+
}
268337

269-
// Ensure that the `SYMOPT_DEFERRED_LOADS` flag is set, because
270-
// according to MSVC's own docs about this: "This is the fastest, most
271-
// efficient way to use the symbol handler.", so let's do that!
272-
DBGHELP.SymSetOptions().unwrap()(orig | SYMOPT_DEFERRED_LOADS);
338+
let orig = DBGHELP.SymGetOptions().unwrap()();
273339

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)
340+
// Ensure that the `SYMOPT_DEFERRED_LOADS` flag is set, because
341+
// according to MSVC's own docs about this: "This is the fastest, most
342+
// efficient way to use the symbol handler.", so let's do that!
343+
DBGHELP.SymSetOptions().unwrap()(orig | SYMOPT_DEFERRED_LOADS);
344+
345+
// Actually initialize symbols with MSVC. Note that this can fail, but we
346+
// ignore it. There's not a ton of prior art for this per se, but LLVM
347+
// internally seems to ignore the return value here and one of the
348+
// sanitizer libraries in LLVM prints a scary warning if this fails but
349+
// basically ignores it in the long run.
350+
//
351+
// One case this comes up a lot for Rust is that the standard library and
352+
// this crate on crates.io both want to compete for `SymInitializeW`. The
353+
// standard library historically wanted to initialize then cleanup most of
354+
// the time, but now that it's using this crate it means that someone will
355+
// get to initialization first and the other will pick up that
356+
// initialization.
357+
DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE);
358+
INITIALIZED = true;
359+
Ok(ret)
360+
}
361+
}
362+
363+
impl Drop for Init {
364+
fn drop(&mut self) {
365+
unsafe {
366+
let r = ReleaseMutex(self.lock);
367+
debug_assert!(r != 0);
368+
}
369+
}
288370
}

src/windows.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ cfg_if::cfg_if! {
2828
pub use winapi::um::winnt::*;
2929
pub use winapi::um::fileapi::*;
3030
pub use winapi::um::minwinbase::*;
31+
pub use winapi::um::synchapi::*;
3132
}
3233
} else {
3334
pub use core::ffi::c_void;
@@ -314,6 +315,7 @@ ffi! {
314315
pub const FILE_SHARE_WRITE: DWORD = 0x2;
315316
pub const OPEN_EXISTING: DWORD = 0x3;
316317
pub const GENERIC_READ: DWORD = 0x80000000;
318+
pub const INFINITE: DWORD = !0;
317319

318320
pub type DWORD = u32;
319321
pub type PDWORD = *mut u32;
@@ -363,6 +365,17 @@ ffi! {
363365
dwFlagsAndAttributes: DWORD,
364366
hTemplateFile: HANDLE,
365367
) -> HANDLE;
368+
pub fn CreateMutexA(
369+
attrs: LPSECURITY_ATTRIBUTES,
370+
initial: BOOL,
371+
name: LPCSTR,
372+
) -> HANDLE;
373+
pub fn ReleaseMutex(hMutex: HANDLE) -> BOOL;
374+
pub fn WaitForSingleObjectEx(
375+
hHandle: HANDLE,
376+
dwMilliseconds: DWORD,
377+
bAlertable: BOOL,
378+
) -> DWORD;
366379
}
367380
}
368381

0 commit comments

Comments
 (0)