Skip to content

Commit ac8c6d2

Browse files
committed
Implement windows native unwinding
[ci skip]
1 parent 66a533d commit ac8c6d2

File tree

8 files changed

+259
-23
lines changed

8 files changed

+259
-23
lines changed

Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ A library to acquire a stack trace (backtrace) at runtime in a Rust program.
1515
[dependencies]
1616
libc = "0.1"
1717
backtrace-sys = { path = "backtrace-sys", version = "0.1", optional = true }
18+
kernel32-sys = { version = "0.1", optional = true }
19+
winapi = { version = "0.1", optional = true }
20+
dbghelp-sys = { version = "0.0.1", optional = true }
1821

1922
# Each feature controls the two phases of finding a backtrace: getting a
2023
# backtrace and then resolving instruction pointers to symbols. The default
@@ -25,7 +28,7 @@ backtrace-sys = { path = "backtrace-sys", version = "0.1", optional = true }
2528
# Note that not all features are available on all platforms, so even though a
2629
# feature is enabled some other feature may be used instead.
2730
[features]
28-
default = ["libunwind", "libbacktrace", "dladdr"]
31+
default = ["libunwind", "libbacktrace", "dladdr", "dbghelp"]
2932

3033
#=======================================
3134
# Methods of acquiring a backtrace
@@ -36,8 +39,14 @@ default = ["libunwind", "libbacktrace", "dladdr"]
3639
# - unix-backtrace: this uses the backtrace(3) function to acquire a
3740
# backtrace, but is not as reliable as libunwind. It is, however,
3841
# generally found in more locations.
42+
# - dbghelp: on windows this enables usage of dbghelp.dll to find a
43+
# backtrace at runtime
44+
# - kernel32: on windows this enables using RtlCaptureStackBackTrace as the
45+
# function to acquire a backtrace
3946
libunwind = []
4047
unix-backtrace = []
48+
dbghelp = ["kernel32-sys", "winapi", "dbghelp-sys"]
49+
kernel32 = []
4150

4251
#=======================================
4352
# Methods of resolving symbols

backtrace-sys/build.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,13 @@ fn main() {
99

1010
// libbacktrace doesn't currently support Mach-O files
1111
if target.contains("darwin") { return }
12-
// libbacktrace currently doesn't build on MSVC
13-
if target.contains("msvc") { return }
12+
// libbacktrace isn't used on windows
13+
if target.contains("windows") { return }
1414
// libbacktrace is already included in the linux libstd for rust
1515
if target.contains("linux") { return }
1616

17-
let configure = src.join("src/libbacktrace/configure").display().to_string();
18-
let configure = if cfg!(windows) {
19-
configure.replace("\\", "/")
20-
} else {
21-
configure
22-
};
23-
run(Command::new("sh")
17+
run(Command::new(src.join("src/libbacktrace/configure"))
2418
.current_dir(&dst)
25-
.arg(configure)
2619
.arg("--with-pic")
2720
.arg("--disable-multilib")
2821
.arg("--disable-shared")

src/backtrace/dbghelp.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![allow(bad_style)]
12+
13+
use std::mem;
14+
use std::ptr;
15+
use winapi::*;
16+
use kernel32;
17+
18+
use Frame;
19+
20+
struct Cleanup {
21+
handle: HANDLE,
22+
}
23+
24+
impl Drop for Cleanup {
25+
fn drop(&mut self) {
26+
unsafe {
27+
::dbghelp::SymCleanup(self.handle);
28+
}
29+
}
30+
}
31+
32+
impl Frame for STACKFRAME64 {
33+
fn ip(&self) -> *mut c_void { self.AddrPC.Offset as *mut _ }
34+
fn symbol_address(&self) -> *mut c_void { self.ip() }
35+
}
36+
37+
pub fn trace(cb: &mut FnMut(&Frame) -> bool) {
38+
// According to windows documentation, all dbghelp functions are
39+
// single-threaded.
40+
let _g = ::lock::lock();
41+
42+
unsafe {
43+
// Allocate necessary structures for doing the stack walk
44+
let process = kernel32::GetCurrentProcess();
45+
let thread = kernel32::GetCurrentThread();
46+
47+
// FIXME(retep998/winapi-rs#110): currently the structure is too small
48+
// so allocate a large block of data which is big enough to fit the
49+
// context for now.
50+
let mut context = [0u8; 2048];
51+
let context = &mut *(context.as_mut_ptr() as *mut CONTEXT);
52+
kernel32::RtlCaptureContext(context);
53+
let mut frame: STACKFRAME64 = mem::zeroed();
54+
let image = init_frame(&mut frame, context);
55+
56+
// Initialize this process's symbols
57+
let ret = ::dbghelp::SymInitializeW(process, ptr::null_mut(), TRUE);
58+
if ret != TRUE { return }
59+
let _c = Cleanup { handle: process };
60+
61+
// And now that we're done with all the setup, do the stack walking!
62+
while ::dbghelp::StackWalk64(image, process, thread, &mut frame,
63+
context as *mut _ as *mut _,
64+
None,
65+
Some(::dbghelp::SymFunctionTableAccess64),
66+
Some(::dbghelp::SymGetModuleBase64),
67+
None) == TRUE {
68+
if frame.AddrPC.Offset == frame.AddrReturn.Offset ||
69+
frame.AddrPC.Offset == 0 ||
70+
frame.AddrReturn.Offset == 0 {
71+
break
72+
}
73+
74+
if !cb(&frame) {
75+
break
76+
}
77+
}
78+
}
79+
}
80+
81+
#[cfg(target_arch = "x86_64")]
82+
fn init_frame(frame: &mut STACKFRAME64, ctx: &CONTEXT) -> DWORD {
83+
frame.AddrPC.Offset = ctx.Rip as u64;
84+
frame.AddrPC.Mode = ADDRESS_MODE::AddrModeFlat;
85+
frame.AddrStack.Offset = ctx.Rsp as u64;
86+
frame.AddrStack.Mode = ADDRESS_MODE::AddrModeFlat;
87+
frame.AddrFrame.Offset = ctx.Rbp as u64;
88+
frame.AddrFrame.Mode = ADDRESS_MODE::AddrModeFlat;
89+
IMAGE_FILE_MACHINE_AMD64
90+
}
91+
92+
#[cfg(target_arch = "x86")]
93+
fn init_frame(frame: &mut STACKFRAME64, ctx: &CONTEXT) -> libc::DWORD {
94+
frame.AddrPC.Offset = ctx.Eip as u64;
95+
frame.AddrPC.Mode = ADDRESS_MODE::AddrModeFlat;
96+
frame.AddrStack.Offset = ctx.Esp as u64;
97+
frame.AddrStack.Mode = ADDRESS_MODE::AddrModeFlat;
98+
frame.AddrFrame.Offset = ctx.Ebp as u64;
99+
frame.AddrFrame.Mode = ADDRESS_MODE::AddrModeFlat;
100+
IMAGE_FILE_MACHINE_I386
101+
}

src/backtrace/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ cascade! {
7171
} else if #[cfg(all(unix, feature = "unix-backtrace"))] {
7272
mod unix_backtrace;
7373
use self::unix_backtrace::trace as trace_imp;
74+
} else if #[cfg(all(windows, feature = "dbghelp"))] {
75+
mod dbghelp;
76+
use self::dbghelp::trace as trace_imp;
7477
} else {
7578
mod noop;
7679
use self::noop::trace as trace_imp;

src/lib.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@
6969
#![cfg_attr(test, deny(warnings))]
7070

7171
extern crate libc;
72+
#[cfg(feature = "kernel32-sys")] extern crate kernel32;
73+
#[cfg(feature = "winapi")] extern crate winapi;
74+
#[cfg(feature = "dbghelp")] extern crate dbghelp;
7275

7376
#[macro_use]
7477
mod macros;
@@ -95,3 +98,37 @@ impl Drop for Bomb {
9598
}
9699
}
97100
}
101+
102+
mod lock {
103+
use std::cell::Cell;
104+
use std::mem;
105+
use std::sync::{Once, Mutex, MutexGuard, ONCE_INIT};
106+
107+
pub struct LockGuard(MutexGuard<'static, ()>);
108+
109+
static mut LOCK: *mut Mutex<()> = 0 as *mut _;
110+
static INIT: Once = ONCE_INIT;
111+
thread_local!(static LOCK_HELD: Cell<bool> = Cell::new(false));
112+
113+
impl Drop for LockGuard {
114+
fn drop(&mut self) {
115+
LOCK_HELD.with(|slot| {
116+
assert!(slot.get());
117+
slot.set(false);
118+
});
119+
}
120+
}
121+
122+
pub fn lock() -> Option<LockGuard> {
123+
if LOCK_HELD.with(|l| l.get()) {
124+
return None
125+
}
126+
LOCK_HELD.with(|s| s.set(true));
127+
unsafe {
128+
INIT.call_once(|| {
129+
LOCK = mem::transmute(Box::new(Mutex::new(())));
130+
});
131+
Some(LockGuard((*LOCK).lock().unwrap()))
132+
}
133+
}
134+
}

src/symbolize/dbghelp.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![allow(bad_style)]
12+
13+
use std::ffi::OsString;
14+
use std::mem;
15+
use std::os::windows::prelude::*;
16+
use std::slice;
17+
use kernel32;
18+
use winapi::*;
19+
20+
use Symbol;
21+
22+
struct SymbolInfo<'a> {
23+
info: &'a SYMBOL_INFOW,
24+
data: Option<&'a [u8]>,
25+
line: Option<&'a IMAGEHLP_LINEW64>,
26+
line_data: Option<&'a [u8]>,
27+
}
28+
29+
impl<'a> Symbol for SymbolInfo<'a> {
30+
fn name(&self) -> Option<&[u8]> { self.data }
31+
fn addr(&self) -> Option<*mut c_void> { Some(self.info.Address as *mut _) }
32+
fn filename(&self) -> Option<&[u8]> { self.line_data }
33+
fn lineno(&self) -> Option<u32> { self.line.map(|l| l.LineNumber as u32) }
34+
}
35+
36+
pub fn resolve(addr: *mut c_void, cb: &mut FnMut(&Symbol)) {
37+
// According to windows documentation, all dbghelp functions are
38+
// single-threaded.
39+
let _g = ::lock::lock();
40+
41+
unsafe {
42+
let size = 2 * MAX_SYM_NAME + mem::size_of::<SYMBOL_INFOW>();
43+
let mut data = vec![0u8; size];
44+
let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFOW);
45+
info.MaxNameLen = MAX_SYM_NAME as ULONG;
46+
// the struct size in C. the value is different to
47+
// `size_of::<SYMBOL_INFOW>() - MAX_SYM_NAME + 1` (== 81)
48+
// due to struct alignment.
49+
info.SizeOfStruct = 88;
50+
51+
let mut displacement = 0u64;
52+
let ret = ::dbghelp::SymFromAddrW(kernel32::GetCurrentProcess(),
53+
addr as DWORD64,
54+
&mut displacement,
55+
info);
56+
if ret != TRUE {
57+
return
58+
}
59+
let name = slice::from_raw_parts(info.Name.as_ptr() as *const u16,
60+
info.NameLen as usize);
61+
let name = OsString::from_wide(name);
62+
let name = name.to_str().map(|s| s.as_bytes());
63+
64+
let mut line = mem::zeroed::<IMAGEHLP_LINEW64>();
65+
line.SizeOfStruct = mem::size_of::<IMAGEHLP_LINEW64>() as DWORD;
66+
let mut displacement = 0;
67+
let ret = ::dbghelp::SymGetLineFromAddrW64(kernel32::GetCurrentProcess(),
68+
addr as DWORD64,
69+
&mut displacement,
70+
&mut line);
71+
let line_data;
72+
let (line, line_data) = if ret == TRUE {
73+
let base = line.FileName;
74+
let mut len = 0;
75+
while *base.offset(len) != 0 {
76+
len += 1;
77+
}
78+
let name = slice::from_raw_parts(base, len as usize);
79+
line_data = OsString::from_wide(name);
80+
(Some(&line), line_data.to_str().map(|s| s.as_bytes()))
81+
} else {
82+
(None, None)
83+
};
84+
85+
cb(&SymbolInfo {
86+
info: info,
87+
data: name,
88+
line: line,
89+
line_data: line_data,
90+
})
91+
}
92+
}

src/symbolize/dladdr.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ struct Dl_info {
2222
dli_saddr: *mut c_void,
2323
}
2424

25-
impl<'a> Symbol for Option<&'a Dl_info> {
25+
impl Symbol for Dl_info {
2626
fn name(&self) -> Option<&[u8]> {
27-
self.and_then(|p| {
28-
if p.dli_sname.is_null() {None} else {Some(p.dli_sname)}
29-
}).map(|p| unsafe {
30-
CStr::from_ptr(p).to_bytes()
31-
})
27+
if self.dli_sname.is_null() {
28+
None
29+
} else {
30+
Some(unsafe { CStr::from_ptr(self.dli_sname).to_bytes() })
31+
}
3232
}
3333
fn addr(&self) -> Option<*mut c_void> {
34-
self.map(|p| p.dli_saddr)
34+
Some(self.dli_saddr)
3535
}
3636
}
3737

@@ -41,9 +41,7 @@ extern {
4141

4242
pub fn resolve(addr: *mut c_void, cb: &mut FnMut(&Symbol)) {
4343
let mut info: Dl_info = unsafe { mem::zeroed() };
44-
if unsafe { dladdr(addr, &mut info) == 0 } {
45-
cb(&None::<&Dl_info>)
46-
} else {
47-
cb(&Some(&info))
44+
if unsafe { dladdr(addr, &mut info) != 0 } {
45+
cb(&info)
4846
}
4947
}

src/symbolize/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ pub fn resolve(addr: *mut c_void, cb: &mut FnMut(&Symbol)) {
6767
}
6868

6969
cascade! {
70-
if #[cfg(all(feature = "libbacktrace", not(target_os = "macos")))] {
70+
if #[cfg(all(windows, feature = "dbghelp"))] {
71+
mod dbghelp;
72+
use self::dbghelp::resolve as resolve_imp;
73+
} else if #[cfg(all(feature = "libbacktrace", not(target_os = "macos")))] {
7174
mod libbacktrace;
7275
use self::libbacktrace::resolve as resolve_imp;
7376
} else if #[cfg(all(unix, feature = "dladdr"))] {

0 commit comments

Comments
 (0)