Skip to content

Commit c5548fc

Browse files
authored
Merge pull request #175 from alexcrichton/cleanup2
Pair SymCleanup with calls to SymInitializeW
2 parents 67e0cac + b92a131 commit c5548fc

File tree

6 files changed

+233
-22
lines changed

6 files changed

+233
-22
lines changed

benches/benchmarks.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#![feature(test)]
2+
3+
extern crate test;
4+
5+
extern crate backtrace;
6+
7+
#[cfg(feature = "std")]
8+
use backtrace::Backtrace;
9+
10+
#[bench]
11+
#[cfg(feature = "std")]
12+
fn trace(b: &mut test::Bencher) {
13+
#[inline(never)]
14+
fn the_function() {
15+
backtrace::trace(|frame| {
16+
let ip = frame.ip();
17+
test::black_box(ip);
18+
true
19+
});
20+
}
21+
b.iter(the_function);
22+
}
23+
24+
#[bench]
25+
#[cfg(feature = "std")]
26+
fn trace_and_resolve_callback(b: &mut test::Bencher) {
27+
#[inline(never)]
28+
fn the_function() {
29+
backtrace::trace(|frame| {
30+
backtrace::resolve(frame.ip(), |symbol| {
31+
let addr = symbol.addr();
32+
test::black_box(addr);
33+
});
34+
true
35+
});
36+
}
37+
b.iter(the_function);
38+
}
39+
40+
41+
42+
#[bench]
43+
#[cfg(feature = "std")]
44+
fn trace_and_resolve_separate(b: &mut test::Bencher) {
45+
#[inline(never)]
46+
fn the_function(frames: &mut Vec<*mut std::ffi::c_void>) {
47+
backtrace::trace(|frame| {
48+
frames.push(frame.ip());
49+
true
50+
});
51+
frames.iter().for_each(|frame_ip| {
52+
backtrace::resolve(*frame_ip, |symbol| {
53+
test::black_box(symbol);
54+
});
55+
});
56+
}
57+
let mut frames = Vec::with_capacity(1024);
58+
b.iter(|| {
59+
the_function(&mut frames);
60+
frames.clear();
61+
});
62+
}
63+
64+
#[bench]
65+
#[cfg(feature = "std")]
66+
fn new_unresolved(b: &mut test::Bencher) {
67+
#[inline(never)]
68+
fn the_function() {
69+
let bt = Backtrace::new_unresolved();
70+
test::black_box(bt);
71+
}
72+
b.iter(the_function);
73+
}
74+
75+
#[bench]
76+
#[cfg(feature = "std")]
77+
fn new(b: &mut test::Bencher) {
78+
#[inline(never)]
79+
fn the_function() {
80+
let bt = Backtrace::new();
81+
test::black_box(bt);
82+
}
83+
b.iter(the_function);
84+
}
85+
86+
#[bench]
87+
#[cfg(feature = "std")]
88+
fn new_unresolved_and_resolve_separate(b: &mut test::Bencher) {
89+
#[inline(never)]
90+
fn the_function() {
91+
let mut bt = Backtrace::new_unresolved();
92+
bt.resolve();
93+
test::black_box(bt);
94+
}
95+
b.iter(the_function);
96+
}

src/backtrace/dbghelp.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ pub unsafe fn trace(cb: &mut FnMut(&super::Frame) -> bool) {
5151
};
5252
let image = init_frame(&mut frame.inner.inner, &context.0);
5353

54-
// Initialize this process's symbols
55-
let _c = ::dbghelp_init();
54+
// Ensure this process's symbols are initialized
55+
let _cleanup = match ::dbghelp::init() {
56+
Ok(cleanup) => cleanup,
57+
Err(()) => return, // oh well...
58+
};
5659

5760
// And now that we're done with all the setup, do the stack walking!
5861
while dbghelp::StackWalk64(image as DWORD,

src/capture.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ impl Backtrace {
6868
/// ```
6969
#[inline(never)] // want to make sure there's a frame here to remove
7070
pub fn new() -> Backtrace {
71+
let _guard = lock_and_platform_init();
7172
let mut bt = Self::create(Self::new as usize);
7273
bt.resolve();
7374
bt
@@ -93,6 +94,7 @@ impl Backtrace {
9394
/// ```
9495
#[inline(never)] // want to make sure there's a frame here to remove
9596
pub fn new_unresolved() -> Backtrace {
97+
let _guard = lock_and_platform_init();
9698
Self::create(Self::new_unresolved as usize)
9799
}
98100

@@ -141,6 +143,7 @@ impl Backtrace {
141143
/// If this backtrace has been previously resolved or was created through
142144
/// `new`, this function does nothing.
143145
pub fn resolve(&mut self) {
146+
let _guard = lock_and_platform_init();
144147
for frame in self.frames.iter_mut().filter(|f| f.symbols.is_none()) {
145148
let mut symbols = Vec::new();
146149
resolve(frame.ip as *mut _, |symbol| {
@@ -289,3 +292,48 @@ impl Default for Backtrace {
289292
Backtrace::new()
290293
}
291294
}
295+
296+
// When using `dbghelp` on Windows this is a performance optimization. If
297+
// we don't do this then `SymInitializeW` is called once per trace and once per
298+
// frame during resolution. That function, however, takes quite some time! To
299+
// help speed it up this function can amortize the calls necessary by ensuring
300+
// that the scope this is called in only initializes when this is called and
301+
// doesn't reinitialize for the rest of the scope.
302+
#[cfg(all(windows, feature = "dbghelp"))]
303+
fn lock_and_platform_init() -> impl Drop {
304+
use std::mem::ManuallyDrop;
305+
306+
struct Cleanup {
307+
_lock: crate::lock::LockGuard,
308+
309+
// Need to make sure this is cleaned up before `_lock`
310+
dbghelp_cleanup: Option<ManuallyDrop<crate::dbghelp::Cleanup>>,
311+
}
312+
313+
impl Drop for Cleanup {
314+
fn drop(&mut self) {
315+
if let Some(cleanup) = self.dbghelp_cleanup.as_mut() {
316+
// Unsafety here should be ok since we're only dropping this in
317+
// `Drop` to ensure it's dropped before the lock, and `Drop`
318+
// should only be called once.
319+
unsafe {
320+
ManuallyDrop::drop(cleanup);
321+
}
322+
}
323+
}
324+
}
325+
326+
// Unsafety here should be ok because we only acquire the `dbghelp`
327+
// initialization (the unsafe part) after acquiring the global lock for this
328+
// crate. Note that we're also careful to drop it before the lock is
329+
// dropped.
330+
unsafe {
331+
Cleanup {
332+
_lock: crate::lock::lock(),
333+
dbghelp_cleanup: crate::dbghelp::init().ok().map(ManuallyDrop::new),
334+
}
335+
}
336+
}
337+
338+
#[cfg(not(all(windows, feature = "dbghelp")))]
339+
fn lock_and_platform_init() {}

src/lib.rs

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -148,47 +148,108 @@ mod lock {
148148
use std::boxed::Box;
149149
use std::sync::{Once, Mutex, MutexGuard, ONCE_INIT};
150150

151-
pub struct LockGuard(MutexGuard<'static, ()>);
151+
pub struct LockGuard(Option<MutexGuard<'static, ()>>);
152152

153153
static mut LOCK: *mut Mutex<()> = 0 as *mut _;
154154
static INIT: Once = ONCE_INIT;
155155
thread_local!(static LOCK_HELD: Cell<bool> = Cell::new(false));
156156

157157
impl Drop for LockGuard {
158158
fn drop(&mut self) {
159-
LOCK_HELD.with(|slot| {
160-
assert!(slot.get());
161-
slot.set(false);
162-
});
159+
if self.0.is_some() {
160+
LOCK_HELD.with(|slot| {
161+
assert!(slot.get());
162+
slot.set(false);
163+
});
164+
}
163165
}
164166
}
165167

166-
pub fn lock() -> Option<LockGuard> {
168+
pub fn lock() -> LockGuard {
167169
if LOCK_HELD.with(|l| l.get()) {
168-
return None
170+
return LockGuard(None)
169171
}
170172
LOCK_HELD.with(|s| s.set(true));
171173
unsafe {
172174
INIT.call_once(|| {
173175
LOCK = Box::into_raw(Box::new(Mutex::new(())));
174176
});
175-
Some(LockGuard((*LOCK).lock().unwrap()))
177+
LockGuard(Some((*LOCK).lock().unwrap()))
176178
}
177179
}
178180
}
179181

180-
// requires external synchronization
181182
#[cfg(all(windows, feature = "dbghelp"))]
182-
unsafe fn dbghelp_init() {
183-
use winapi::shared::minwindef;
184-
use winapi::um::{dbghelp, processthreadsapi};
183+
mod dbghelp {
184+
use core::ptr;
185+
use winapi::shared::minwindef::{DWORD, TRUE};
186+
use winapi::um::processthreadsapi::GetCurrentProcess;
187+
use winapi::um::dbghelp;
188+
189+
pub struct Cleanup;
190+
191+
static mut COUNT: usize = 0;
192+
static mut OPTS_ORIG: DWORD = 0;
185193

186-
static mut INITIALIZED: bool = false;
194+
const SYMOPT_DEFERRED_LOADS: DWORD = 0x00000004;
195+
extern "system" {
196+
fn SymGetOptions() -> DWORD;
197+
fn SymSetOptions(options: DWORD);
198+
}
199+
200+
/// Unsafe because this requires external synchronization, must be done
201+
/// inside of the same lock as all other backtrace operations.
202+
///
203+
/// Note that the `Cleanup` returned must also be dropped within the same
204+
/// lock.
205+
#[cfg(all(windows, feature = "dbghelp"))]
206+
pub unsafe fn init() -> Result<Cleanup, ()> {
207+
// Initializing symbols has significant overhead, but initializing only
208+
// once without cleanup causes problems for external sources. For
209+
// example, the standard library checks the result of SymInitializeW
210+
// (which returns an error if attempting to initialize twice) and in
211+
// the event of an error, will not print a backtrace on panic.
212+
// Presumably, external debuggers may have similar issues.
213+
//
214+
// As a compromise, we'll keep track of the number of internal
215+
// initialization requests within a single API call in order to
216+
// minimize the number of init/cleanup cycles.
217+
218+
if COUNT > 0 {
219+
COUNT += 1;
220+
return Ok(Cleanup);
221+
}
187222

188-
if !INITIALIZED {
189-
dbghelp::SymInitializeW(processthreadsapi::GetCurrentProcess(),
190-
0 as *mut _,
191-
minwindef::TRUE);
192-
INITIALIZED = true;
223+
OPTS_ORIG = SymGetOptions();
224+
225+
// Ensure that the `SYMOPT_DEFERRED_LOADS` flag is set, because
226+
// according to MSVC's own docs about this: "This is the fastest, most
227+
// efficient way to use the symbol handler.", so let's do that!
228+
SymSetOptions(OPTS_ORIG | SYMOPT_DEFERRED_LOADS);
229+
230+
let ret = dbghelp::SymInitializeW(GetCurrentProcess(), ptr::null_mut(), TRUE);
231+
if ret != TRUE {
232+
// Symbols may have been initialized by another library or an
233+
// external debugger
234+
SymSetOptions(OPTS_ORIG);
235+
Err(())
236+
} else {
237+
COUNT += 1;
238+
Ok(Cleanup)
239+
}
240+
}
241+
242+
impl Drop for Cleanup {
243+
fn drop(&mut self) {
244+
unsafe {
245+
COUNT -= 1;
246+
if COUNT != 0 {
247+
return;
248+
}
249+
250+
dbghelp::SymCleanup(GetCurrentProcess());
251+
SymSetOptions(OPTS_ORIG);
252+
}
253+
}
193254
}
194255
}

src/symbolize/dbghelp.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ pub unsafe fn resolve(addr: *mut c_void, cb: &mut FnMut(&super::Symbol)) {
8686
// due to struct alignment.
8787
info.SizeOfStruct = 88;
8888

89-
let _c = ::dbghelp_init();
89+
// Ensure this process's symbols are initialized
90+
let _cleanup = match ::dbghelp::init() {
91+
Ok(cleanup) => cleanup,
92+
Err(()) => return, // oh well...
93+
};
9094

9195
let mut displacement = 0u64;
9296
let ret = dbghelp::SymFromAddrW(processthreadsapi::GetCurrentProcess(),

tests/long_fn_name.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ mod _234567890_234567890_234567890_234567890_234567890 {
2323
#[test]
2424
#[cfg(all(windows, feature = "dbghelp", target_env = "msvc"))]
2525
fn test_long_fn_name() {
26-
use winapi::um::dbghelp;
2726
use _234567890_234567890_234567890_234567890_234567890::
2827
_234567890_234567890_234567890_234567890_234567890 as S;
2928

0 commit comments

Comments
 (0)