Skip to content

Commit cc2c4f8

Browse files
committed
Cleanup dbghelp so that stdlib can print backtraces on panic again
Fixes #165
1 parent d8ea02e commit cc2c4f8

File tree

5 files changed

+175
-11
lines changed

5 files changed

+175
-11
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ std = []
7373
# function to acquire a backtrace
7474
libunwind = []
7575
unix-backtrace = []
76-
dbghelp = []
76+
dbghelp = ["std"]
7777
kernel32 = []
7878

7979
#=======================================

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/capture.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ impl Backtrace {
6868
/// ```
6969
#[inline(never)] // want to make sure there's a frame here to remove
7070
pub fn new() -> Backtrace {
71+
// initialize dbghelp only once for both the trace and the resolve
72+
#[cfg(all(windows, feature = "dbghelp"))]
73+
let _c = unsafe { ::dbghelp_init() };
74+
7175
let mut bt = Self::create(Self::new as usize);
7276
bt.resolve();
7377
bt
@@ -141,6 +145,10 @@ impl Backtrace {
141145
/// If this backtrace has been previously resolved or was created through
142146
/// `new`, this function does nothing.
143147
pub fn resolve(&mut self) {
148+
// initialize dbghelp only once for all frames
149+
#[cfg(all(windows, feature = "dbghelp"))]
150+
let _c = unsafe { ::dbghelp_init() };
151+
144152
for frame in self.frames.iter_mut().filter(|f| f.symbols.is_none()) {
145153
let mut symbols = Vec::new();
146154
resolve(frame.ip as *mut _, |symbol| {

src/lib.rs

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,79 @@ mod lock {
179179
}
180180
}
181181

182-
// requires external synchronization
183182
#[cfg(all(windows, feature = "dbghelp"))]
184-
unsafe fn dbghelp_init() {
183+
struct Cleanup {
184+
handle: winapi::um::winnt::HANDLE,
185+
opts: winapi::shared::minwindef::DWORD,
186+
}
187+
188+
#[cfg(all(windows, feature = "dbghelp"))]
189+
unsafe fn dbghelp_init() -> Option<Cleanup> {
185190
use winapi::shared::minwindef;
186191
use winapi::um::{dbghelp, processthreadsapi};
187192

188-
static mut INITIALIZED: bool = false;
193+
use std::sync::{Mutex, Once, ONCE_INIT};
194+
use std::boxed::Box;
195+
196+
// Initializing symbols has significant overhead, but initializing only once
197+
// without cleanup causes problems for external sources. For example, the
198+
// standard library checks the result of SymInitializeW (which returns an
199+
// error if attempting to initialize twice) and in the event of an error,
200+
// will not print a backtrace on panic. Presumably, external debuggers may
201+
// have similar issues.
202+
//
203+
// As a compromise, we'll keep track of the number of internal initialization
204+
// requests within a single API call in order to minimize the number of
205+
// init/cleanup cycles.
206+
static mut REF_COUNT: *mut Mutex<usize> = 0 as *mut _;
207+
static mut INIT: Once = ONCE_INIT;
189208

190-
if !INITIALIZED {
191-
dbghelp::SymInitializeW(processthreadsapi::GetCurrentProcess(),
192-
0 as *mut _,
193-
minwindef::TRUE);
194-
INITIALIZED = true;
209+
INIT.call_once(|| {
210+
REF_COUNT = Box::into_raw(Box::new(Mutex::new(0)));
211+
});
212+
213+
// Not sure why these are missing in winapi
214+
const SYMOPT_DEFERRED_LOADS: minwindef::DWORD = 0x00000004;
215+
extern "system" {
216+
fn SymGetOptions() -> minwindef::DWORD;
217+
fn SymSetOptions(options: minwindef::DWORD);
195218
}
196-
}
219+
220+
impl Drop for Cleanup {
221+
fn drop(&mut self) {
222+
unsafe {
223+
let mut ref_count_guard = (&*REF_COUNT).lock().unwrap();
224+
*ref_count_guard -= 1;
225+
226+
if *ref_count_guard == 0 {
227+
dbghelp::SymCleanup(self.handle);
228+
SymSetOptions(self.opts);
229+
}
230+
}
231+
}
232+
}
233+
234+
let opts = SymGetOptions();
235+
let handle = processthreadsapi::GetCurrentProcess();
236+
237+
let mut ref_count_guard = (&*REF_COUNT).lock().unwrap();
238+
239+
if *ref_count_guard > 0 {
240+
*ref_count_guard += 1;
241+
return Some(Cleanup { handle, opts });
242+
}
243+
244+
SymSetOptions(opts | SYMOPT_DEFERRED_LOADS);
245+
246+
let ret = dbghelp::SymInitializeW(handle,
247+
0 as *mut _,
248+
minwindef::TRUE);
249+
250+
if ret != minwindef::TRUE {
251+
// Symbols may have been initialized by another library or an external debugger
252+
None
253+
} else {
254+
*ref_count_guard += 1;
255+
Some(Cleanup { handle, opts })
256+
}
257+
}

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)