diff --git a/.travis.yml b/.travis.yml index 27b3b20f3..7e0080261 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,11 +27,11 @@ matrix: - cargo test --no-default-features --features 'unix-backtrace' - cargo test --no-default-features --features 'unix-backtrace dladdr' - cargo test --no-default-features --features 'unix-backtrace libbacktrace' - - cargo test --no-default-features --features 'serialize-serde' - - cargo test --no-default-features --features 'serialize-rustc' - - cargo test --no-default-features --features 'serialize-rustc serialize-serde' - - cargo test --no-default-features --features 'cpp_demangle' - - cargo test --no-default-features --features 'gimli-symbolize' + - cargo test --no-default-features --features 'serialize-serde std' + - cargo test --no-default-features --features 'serialize-rustc std' + - cargo test --no-default-features --features 'serialize-rustc serialize-serde std' + - cargo test --no-default-features --features 'cpp_demangle std' + - cargo test --no-default-features --features 'gimli-symbolize std' - cd ./cpp_smoke_test && cargo test && cd .. - cargo clean && cargo build diff --git a/Cargo.toml b/Cargo.toml index 45c737f62..55bba6c62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ documentation = "https://docs.rs/backtrace" description = """ A library to acquire a stack trace (backtrace) at runtime in a Rust program. """ +autoexamples = true +autotests = true + [dependencies] cfg-if = "0.1" rustc-demangle = "0.1.4" @@ -30,10 +33,10 @@ memmap = { version = "0.7.0", optional = true } object = { version = "0.9.0", optional = true } [target.'cfg(unix)'.dependencies] -libc = "0.2" +libc = { version = "0.2", default-features = false } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.3", optional = true, features = ["std", "dbghelp", "processthreadsapi", "winnt", "minwindef"] } +winapi = { version = "0.3.3", features = ["dbghelp", "processthreadsapi", "winnt", "minwindef"] } [target.'cfg(all(unix, not(target_os = "fuchsia"), not(target_os = "emscripten"), not(target_os = "macos"), not(target_os = "ios")))'.dependencies] backtrace-sys = { path = "backtrace-sys", version = "0.1.17", optional = true } @@ -47,7 +50,10 @@ backtrace-sys = { path = "backtrace-sys", version = "0.1.17", optional = true } # Note that not all features are available on all platforms, so even though a # feature is enabled some other feature may be used instead. [features] -default = ["libunwind", "libbacktrace", "coresymbolication", "dladdr", "dbghelp"] +default = ["std", "libunwind", "libbacktrace", "coresymbolication", "dladdr", "dbghelp"] + +# Include std support. +std = [] #======================================= # Methods of acquiring a backtrace @@ -64,7 +70,7 @@ default = ["libunwind", "libbacktrace", "coresymbolication", "dladdr", "dbghelp" # function to acquire a backtrace libunwind = [] unix-backtrace = [] -dbghelp = ["winapi"] +dbghelp = [] kernel32 = [] #======================================= @@ -86,7 +92,7 @@ kernel32 = [] # the moment, this is only possible when targetting Linux, since macOS # splits DWARF out into a separate object file. Enabling this feature # means one less C dependency. -libbacktrace = ["backtrace-sys"] +libbacktrace = ["backtrace-sys", "std"] dladdr = [] coresymbolication = [] gimli-symbolize = ["addr2line", "findshlibs", "gimli", "memmap", "object" ] @@ -97,3 +103,23 @@ gimli-symbolize = ["addr2line", "findshlibs", "gimli", "memmap", "object" ] # Various features used for enabling rustc-serialize or syntex codegen. serialize-rustc = ["rustc-serialize"] serialize-serde = ["serde", "serde_derive"] + +[[example]] +name = "backtrace" +required-features = ["std"] + +[[example]] +name = "raw" +required-features = ["std"] + +[[test]] +name = "skip_inner_frames" +required-features = ["std"] + +[[test]] +name = "long_fn_name" +required-features = ["std"] + +[[test]] +name = "smoke" +required-features = ["std"] diff --git a/appveyor.yml b/appveyor.yml index 816d4de6f..33e8aa3f5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,6 +18,8 @@ build: false test_script: - cargo test --target %TARGET% + - cargo build --target %TARGET% --no-default-features + - cargo build --target %TARGET% --no-default-features --features dbghelp branches: only: diff --git a/backtrace-sys/Cargo.toml b/backtrace-sys/Cargo.toml index fba6a0761..831576ac7 100644 --- a/backtrace-sys/Cargo.toml +++ b/backtrace-sys/Cargo.toml @@ -12,7 +12,7 @@ Bindings to the libbacktrace gcc library """ [dependencies] -libc = "0.2" +libc = { version = "0.2", default-features = false } [build-dependencies] cc = "1.0" diff --git a/backtrace-sys/src/lib.rs b/backtrace-sys/src/lib.rs index 0edc2674c..961db2cba 100644 --- a/backtrace-sys/src/lib.rs +++ b/backtrace-sys/src/lib.rs @@ -1,9 +1,9 @@ #![allow(bad_style)] +#![no_std] extern crate libc; -use libc::uintptr_t; -use std::os::raw::{c_void, c_char, c_int}; +use libc::{c_void, c_char, c_int, uintptr_t}; pub type backtrace_syminfo_callback = extern fn(data: *mut c_void, diff --git a/src/backtrace/dbghelp.rs b/src/backtrace/dbghelp.rs index 26b395a69..dc1aab17f 100644 --- a/src/backtrace/dbghelp.rs +++ b/src/backtrace/dbghelp.rs @@ -10,14 +10,17 @@ #![allow(bad_style)] -use std::mem; -use winapi::ctypes::*; +use core::mem; +use core::prelude::v1::*; + use winapi::shared::minwindef::*; use winapi::um::processthreadsapi; use winapi::um::winnt::{self, CONTEXT}; use winapi::um::dbghelp; use winapi::um::dbghelp::*; +use types::c_void; + pub struct Frame { inner: STACKFRAME64, } @@ -32,50 +35,43 @@ impl Frame { } } -#[inline(always)] -pub fn trace(cb: &mut FnMut(&super::Frame) -> bool) { - // According to windows documentation, all dbghelp functions are - // single-threaded. - let _g = ::lock::lock(); +#[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now +struct MyContext(CONTEXT); - unsafe { - // Allocate necessary structures for doing the stack walk - let process = processthreadsapi::GetCurrentProcess(); - let thread = processthreadsapi::GetCurrentThread(); +#[inline(always)] +pub unsafe fn trace(cb: &mut FnMut(&super::Frame) -> bool) { + // Allocate necessary structures for doing the stack walk + let process = processthreadsapi::GetCurrentProcess(); + let thread = processthreadsapi::GetCurrentThread(); - // The CONTEXT structure needs to be aligned on a 16-byte boundary for - // 64-bit Windows, but currently we don't have a way to express that in - // Rust. Allocations are generally aligned to 16-bytes, though, so we - // box this up. - let mut context = Box::new(mem::zeroed::()); - winnt::RtlCaptureContext(&mut *context); - let mut frame = super::Frame { - inner: Frame { inner: mem::zeroed() }, - }; - let image = init_frame(&mut frame.inner.inner, &context); + let mut context = mem::zeroed::(); + winnt::RtlCaptureContext(&mut context.0); + let mut frame = super::Frame { + inner: Frame { inner: mem::zeroed() }, + }; + let image = init_frame(&mut frame.inner.inner, &context.0); - // Initialize this process's symbols - let _c = ::dbghelp_init(); + // Initialize this process's symbols + let _c = ::dbghelp_init(); - // And now that we're done with all the setup, do the stack walking! - while dbghelp::StackWalk64(image as DWORD, - process, - thread, - &mut frame.inner.inner, - &mut *context as *mut _ as *mut _, - None, - Some(dbghelp::SymFunctionTableAccess64), - Some(dbghelp::SymGetModuleBase64), - None) == TRUE { - if frame.inner.inner.AddrPC.Offset == frame.inner.inner.AddrReturn.Offset || - frame.inner.inner.AddrPC.Offset == 0 || - frame.inner.inner.AddrReturn.Offset == 0 { - break - } + // And now that we're done with all the setup, do the stack walking! + while dbghelp::StackWalk64(image as DWORD, + process, + thread, + &mut frame.inner.inner, + &mut context.0 as *mut CONTEXT as *mut _, + None, + Some(dbghelp::SymFunctionTableAccess64), + Some(dbghelp::SymGetModuleBase64), + None) == TRUE { + if frame.inner.inner.AddrPC.Offset == frame.inner.inner.AddrReturn.Offset || + frame.inner.inner.AddrPC.Offset == 0 || + frame.inner.inner.AddrReturn.Offset == 0 { + break + } - if !cb(&frame) { - break - } + if !cb(&frame) { + break } } } diff --git a/src/backtrace/libunwind.rs b/src/backtrace/libunwind.rs index 4d7aef248..b2d6fa6d3 100644 --- a/src/backtrace/libunwind.rs +++ b/src/backtrace/libunwind.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::os::raw::c_void; +use types::c_void; pub struct Frame { ctx: *mut uw::_Unwind_Context, @@ -48,10 +48,8 @@ impl Frame { } #[inline(always)] -pub fn trace(mut cb: &mut FnMut(&super::Frame) -> bool) { - unsafe { - uw::_Unwind_Backtrace(trace_fn, &mut cb as *mut _ as *mut _); - } +pub unsafe fn trace(mut cb: &mut FnMut(&super::Frame) -> bool) { + uw::_Unwind_Backtrace(trace_fn, &mut cb as *mut _ as *mut _); extern fn trace_fn(ctx: *mut uw::_Unwind_Context, arg: *mut c_void) -> uw::_Unwind_Reason_Code { @@ -83,8 +81,8 @@ pub fn trace(mut cb: &mut FnMut(&super::Frame) -> bool) { mod uw { pub use self::_Unwind_Reason_Code::*; - use libc; - use std::os::raw::{c_int, c_void}; + use libc::{self, c_int}; + use types::c_void; #[repr(C)] pub enum _Unwind_Reason_Code { diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index b36809676..da7af7454 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -1,6 +1,5 @@ -use std::fmt; - -use std::os::raw::c_void; +use core::fmt; +use types::c_void; /// Inspects the current call-stack, passing all active frames into the closure /// provided to calculate a stack trace. @@ -23,6 +22,11 @@ use std::os::raw::c_void; /// example, capture a backtrace to be inspected later, then the `Backtrace` /// type may be more appropriate. /// +/// # Required features +/// +/// This function requires the `std` feature of the `backtrace` crate to be +/// enabled, and the `std` feature is enabled by default. +/// /// # Example /// /// ``` @@ -36,11 +40,24 @@ use std::os::raw::c_void; /// }); /// } /// ``` +#[inline(always)] +#[cfg(feature = "std")] +pub fn trace bool>(cb: F) { + let _guard = ::lock::lock(); + unsafe { trace_unsynchronized(cb) } +} + +/// Same as `trace`, only unsafe as it's unsynchronized. +/// +/// This function does not have synchronization guarentees but is available +/// when the `std` feature of this crate isn't compiled in. See the `trace` +/// function for more documentation and examples. #[inline(never)] -pub fn trace bool>(mut cb: F) { +pub unsafe fn trace_unsynchronized bool>(mut cb: F) { trace_imp(&mut cb) } + /// A trait representing one frame of a backtrace, yielded to the `trace` /// function of this crate. /// diff --git a/src/backtrace/noop.rs b/src/backtrace/noop.rs index 8b8f8766e..4ad79ccee 100644 --- a/src/backtrace/noop.rs +++ b/src/backtrace/noop.rs @@ -1,4 +1,4 @@ -use std::os::raw::c_void; +use types::c_void; #[inline(always)] pub fn trace(_cb: &mut FnMut(&super::Frame) -> bool) {} diff --git a/src/backtrace/unix_backtrace.rs b/src/backtrace/unix_backtrace.rs index 061bba9ef..a64d71d45 100644 --- a/src/backtrace/unix_backtrace.rs +++ b/src/backtrace/unix_backtrace.rs @@ -8,8 +8,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::mem; -use std::os::raw::{c_void, c_int}; +use core::mem; +use libc::c_int; + +use types::c_void; pub struct Frame { addr: *mut c_void, @@ -25,15 +27,14 @@ extern { } #[inline(always)] -pub fn trace(cb: &mut FnMut(&super::Frame) -> bool) { +pub unsafe fn trace(cb: &mut FnMut(&super::Frame) -> bool) { const SIZE: usize = 100; let mut buf: [*mut c_void; SIZE]; let cnt; - unsafe { - buf = mem::zeroed(); - cnt = backtrace(buf.as_mut_ptr(), SIZE as c_int); - } + + buf = mem::zeroed(); + cnt = backtrace(buf.as_mut_ptr(), SIZE as c_int); for addr in buf[..cnt as usize].iter() { let cx = super::Frame { diff --git a/src/capture.rs b/src/capture.rs index 2b52b40e6..55e18ddb9 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -1,9 +1,10 @@ +use std::prelude::v1::*; use std::fmt; use std::mem; -use std::os::raw::c_void; use std::path::{Path, PathBuf}; use {trace, resolve, SymbolName}; +use types::c_void; /// Representation of an owned and self-contained backtrace. /// @@ -144,7 +145,7 @@ impl Backtrace { symbols.push(BacktraceSymbol { name: symbol.name().map(|m| m.as_bytes().to_vec()), addr: symbol.addr().map(|a| a as usize), - filename: symbol.filename().map(|m| m.to_path_buf()), + filename: symbol.filename().map(|m| m.to_owned()), lineno: symbol.lineno(), }); }); diff --git a/src/lib.rs b/src/lib.rs index f0b6e655c..6510c25c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,8 @@ //! extern crate backtrace; //! //! fn main() { +//! # // Unsafe here so test passes on no_std. +//! # #[cfg(feature = "std")] { //! backtrace::trace(|frame| { //! let ip = frame.ip(); //! let symbol_address = frame.symbol_address(); @@ -63,14 +65,20 @@ //! true // keep going to the next frame //! }); //! } +//! # } //! ``` #![doc(html_root_url = "https://docs.rs/backtrace")] #![deny(missing_docs)] +#![no_std] + +#[cfg(feature = "std")] +#[macro_use] extern crate std; #[cfg(unix)] extern crate libc; -#[cfg(all(windows, feature = "winapi"))] extern crate winapi; +#[cfg(windows)] +extern crate winapi; #[cfg(feature = "serde_derive")] #[cfg_attr(feature = "serde_derive", macro_use)] @@ -98,18 +106,27 @@ cfg_if! { } #[allow(dead_code)] // not used everywhere -#[cfg(unix)] +#[cfg(all(unix, feature = "std"))] #[macro_use] mod dylib; -pub use backtrace::{trace, Frame}; +pub use backtrace::{trace_unsynchronized, Frame}; mod backtrace; -pub use symbolize::{resolve, Symbol, SymbolName}; +pub use symbolize::{resolve_unsynchronized, Symbol, SymbolName}; mod symbolize; -pub use capture::{Backtrace, BacktraceFrame, BacktraceSymbol}; -mod capture; +pub use types::BytesOrWideString; +mod types; + +cfg_if! { + if #[cfg(feature = "std")] { + pub use backtrace::trace; + pub use symbolize::resolve; + pub use capture::{Backtrace, BacktraceFrame, BacktraceSymbol}; + mod capture; + } +} #[allow(dead_code)] struct Bomb { @@ -126,8 +143,10 @@ impl Drop for Bomb { } #[allow(dead_code)] +#[cfg(feature = "std")] mod lock { use std::cell::Cell; + use std::boxed::Box; use std::sync::{Once, Mutex, MutexGuard, ONCE_INIT}; pub struct LockGuard(MutexGuard<'static, ()>); diff --git a/src/symbolize/coresymbolication.rs b/src/symbolize/coresymbolication.rs index e8337d555..4db2d8dbd 100644 --- a/src/symbolize/coresymbolication.rs +++ b/src/symbolize/coresymbolication.rs @@ -10,11 +10,8 @@ #![allow(bad_style)] -use std::ffi::{CStr, OsStr}; +use std::ffi::CStr; use std::mem; -use std::os::raw::{c_void, c_char, c_int}; -use std::os::unix::prelude::*; -use std::path::Path; use std::ptr; use std::sync::atomic::ATOMIC_USIZE_INIT; @@ -23,6 +20,7 @@ use libc::{self, Dl_info}; use SymbolName; use dylib::Dylib; use dylib::Symbol as DylibSymbol; +use types::{BytesOrWideString, c_void, c_char, c_int}; #[repr(C)] #[derive(Copy, Clone, PartialEq)] @@ -69,15 +67,15 @@ impl Symbol { } } - pub fn filename(&self) -> Option<&Path> { + pub fn filename_raw(&self) -> Option { match *self { Symbol::Core { path, .. } => { if path.is_null() { None } else { - Some(Path::new(OsStr::from_bytes(unsafe { + Some(BytesOrWideString::Bytes(unsafe { CStr::from_ptr(path).to_bytes() - }))) + })) } } Symbol::Dladdr(_) => None, @@ -177,16 +175,14 @@ unsafe fn try_resolve(addr: *mut c_void, cb: &mut FnMut(&super::Symbol)) -> bool rv } -pub fn resolve(addr: *mut c_void, cb: &mut FnMut(&super::Symbol)) { - unsafe { - if try_resolve(addr, cb) { - return - } - let mut info: Dl_info = mem::zeroed(); - if libc::dladdr(addr as *mut _, &mut info) != 0 { - cb(&super::Symbol { - inner: Symbol::Dladdr(info), - }); - } +pub unsafe fn resolve(addr: *mut c_void, cb: &mut FnMut(&super::Symbol)) { + if try_resolve(addr, cb) { + return + } + let mut info: Dl_info = mem::zeroed(); + if libc::dladdr(addr as *mut _, &mut info) != 0 { + cb(&super::Symbol { + inner: Symbol::Dladdr(info), + }); } } diff --git a/src/symbolize/dbghelp.rs b/src/symbolize/dbghelp.rs index a2c990612..e6324bbb7 100644 --- a/src/symbolize/dbghelp.rs +++ b/src/symbolize/dbghelp.rs @@ -10,11 +10,10 @@ #![allow(bad_style)] -use std::ffi::OsString; -use std::mem; -use std::path::Path; -use std::os::windows::prelude::*; -use std::slice; +use core::mem; +use core::slice; +use core::char; + use winapi::ctypes::*; use winapi::shared::basetsd::*; use winapi::shared::minwindef::*; @@ -23,96 +22,145 @@ use winapi::um::dbghelp; use winapi::um::dbghelp::*; use SymbolName; +use types::BytesOrWideString; +// Store an OsString on std so we can provide the symbol name and filename. pub struct Symbol { - name: OsString, + name: *const [u8], addr: *mut c_void, line: Option, - filename: Option, + filename: Option<*const [u16]>, + #[cfg(feature = "std")] + _filename_cache: Option<::std::ffi::OsString>, + #[cfg(not(feature = "std"))] + _filename_cache: (), } impl Symbol { pub fn name(&self) -> Option { - self.name.to_str().map(|s| SymbolName::new(s.as_bytes())) + Some(SymbolName::new(unsafe { &*self.name })) } pub fn addr(&self) -> Option<*mut c_void> { Some(self.addr as *mut _) } - pub fn filename(&self) -> Option<&Path> { - self.filename.as_ref().map(Path::new) + pub fn filename_raw(&self) -> Option { + self.filename.map(|slice| { + unsafe { + BytesOrWideString::Wide(&*slice) + } + }) } pub fn lineno(&self) -> Option { self.line } + + #[cfg(feature = "std")] + pub fn filename(&self) -> Option<&::std::ffi::OsString> { + self._filename_cache.as_ref() + } } -pub fn resolve(addr: *mut c_void, cb: &mut FnMut(&super::Symbol)) { - // According to windows documentation, all dbghelp functions are - // single-threaded. - let _g = ::lock::lock(); - - unsafe { - let size = 2 * MAX_SYM_NAME + mem::size_of::(); - let mut data = vec![0u8; size]; - let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFOW); - info.MaxNameLen = MAX_SYM_NAME as ULONG; - // the struct size in C. the value is different to - // `size_of::() - MAX_SYM_NAME + 1` (== 81) - // due to struct alignment. - info.SizeOfStruct = 88; - - let _c = ::dbghelp_init(); - - let mut displacement = 0u64; - let ret = dbghelp::SymFromAddrW(processthreadsapi::GetCurrentProcess(), - addr as DWORD64, - &mut displacement, - info); - if ret != TRUE { - return - } +#[repr(C, align(8))] +struct Aligned8(T); + +pub unsafe fn resolve(addr: *mut c_void, cb: &mut FnMut(&super::Symbol)) { + const SIZE: usize = 2 * MAX_SYM_NAME + mem::size_of::(); + let mut data = Aligned8([0u8; SIZE]); + let data = &mut data.0; + let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFOW); + info.MaxNameLen = MAX_SYM_NAME as ULONG; + // the struct size in C. the value is different to + // `size_of::() - MAX_SYM_NAME + 1` (== 81) + // due to struct alignment. + info.SizeOfStruct = 88; + + let _c = ::dbghelp_init(); + + let mut displacement = 0u64; + let ret = dbghelp::SymFromAddrW(processthreadsapi::GetCurrentProcess(), + addr as DWORD64, + &mut displacement, + info); + if ret != TRUE { + return + } - // If the symbol name is greater than MaxNameLen, SymFromAddrW will - // give a buffer of (MaxNameLen - 1) characters and set NameLen to - // the real value. - let name_len = ::std::cmp::min(info.NameLen as usize, - info.MaxNameLen as usize - 1); - - let name = slice::from_raw_parts(info.Name.as_ptr() as *const u16, - name_len); - let name = OsString::from_wide(name); - - let mut line = mem::zeroed::(); - line.SizeOfStruct = mem::size_of::() as DWORD; - let mut displacement = 0; - let ret = dbghelp::SymGetLineFromAddrW64(processthreadsapi::GetCurrentProcess(), - addr as DWORD64, - &mut displacement, - &mut line); - let mut filename = None; - let mut lineno = None; - if ret == TRUE { - lineno = Some(line.LineNumber as u32); - - let base = line.FileName; - let mut len = 0; - while *base.offset(len) != 0 { - len += 1; + // If the symbol name is greater than MaxNameLen, SymFromAddrW will + // give a buffer of (MaxNameLen - 1) characters and set NameLen to + // the real value. + let name_len = ::core::cmp::min(info.NameLen as usize, + info.MaxNameLen as usize - 1); + let name_ptr = info.Name.as_ptr() as *const u16; + let name = slice::from_raw_parts(name_ptr, name_len); + + // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like + // all other platforms + let mut name_len = 0; + let mut name_buffer = [0; 256]; + { + let mut remaining = &mut name_buffer[..]; + for c in char::decode_utf16(name.iter().cloned()) { + let c = c.unwrap_or(char::REPLACEMENT_CHARACTER); + let len = c.len_utf8(); + if len < remaining.len() { + c.encode_utf8(remaining); + let tmp = remaining; + remaining = &mut tmp[len..]; + name_len += len; + } else { + break } - let name = slice::from_raw_parts(base, len as usize); - filename = Some(OsString::from_wide(name)); + } + } + let name = &name_buffer[..name_len] as *const [u8]; + + let mut line = mem::zeroed::(); + line.SizeOfStruct = mem::size_of::() as DWORD; + let mut displacement = 0; + let ret = dbghelp::SymGetLineFromAddrW64(processthreadsapi::GetCurrentProcess(), + addr as DWORD64, + &mut displacement, + &mut line); + + let mut filename = None; + let mut lineno = None; + if ret == TRUE { + lineno = Some(line.LineNumber as u32); + + let base = line.FileName; + let mut len = 0; + while *base.offset(len) != 0 { + len += 1; } - cb(&super::Symbol { - inner: Symbol { - name: name, - addr: info.Address as *mut _, - line: lineno, - filename: filename, - }, - }) + let len = len as usize; + + filename = Some(slice::from_raw_parts(base, len) as *const [u16]); } + + + cb(&super::Symbol { + inner: Symbol { + name, + addr: info.Address as *mut _, + line: lineno, + filename, + _filename_cache: cache(filename), + }, + }) +} + +#[cfg(feature = "std")] +unsafe fn cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString> { + use std::os::windows::ffi::OsStringExt; + filename.map(|f| { + ::std::ffi::OsString::from_wide(&*f) + }) +} + +#[cfg(not(feature = "std"))] +unsafe fn cache(_filename: Option<*const [u16]>) { } diff --git a/src/symbolize/dladdr.rs b/src/symbolize/dladdr.rs index d7cdfbb8b..470b8fbd3 100644 --- a/src/symbolize/dladdr.rs +++ b/src/symbolize/dladdr.rs @@ -8,11 +8,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::ffi::CStr; -use std::mem; -use std::os::raw::c_void; -use std::path::Path; +use core::{mem, slice}; +use types::{BytesOrWideString, c_void}; use libc::{self, Dl_info}; use SymbolName; @@ -26,9 +24,11 @@ impl Symbol { if self.inner.dli_sname.is_null() { None } else { - Some(SymbolName::new(unsafe { - CStr::from_ptr(self.inner.dli_sname).to_bytes() - })) + let ptr = self.inner.dli_sname as *const u8; + unsafe { + let len = libc::strlen(self.inner.dli_sname); + Some(SymbolName::new(slice::from_raw_parts(ptr, len))) + } } } @@ -36,7 +36,7 @@ impl Symbol { Some(self.inner.dli_saddr as *mut _) } - pub fn filename(&self) -> Option<&Path> { + pub fn filename_raw(&self) -> Option { None } @@ -45,15 +45,13 @@ impl Symbol { } } -pub fn resolve(addr: *mut c_void, cb: &mut FnMut(&super::Symbol)) { - unsafe { - let mut info: super::Symbol = super::Symbol { - inner: Symbol { - inner: mem::zeroed(), - }, - }; - if libc::dladdr(addr as *mut _, &mut info.inner.inner) != 0 { - cb(&info) - } +pub unsafe fn resolve(addr: *mut c_void, cb: &mut FnMut(&super::Symbol)) { + let mut info: super::Symbol = super::Symbol { + inner: Symbol { + inner: mem::zeroed(), + }, + }; + if libc::dladdr(addr as *mut _, &mut info.inner.inner) != 0 { + cb(&info) } } diff --git a/src/symbolize/gimli.rs b/src/symbolize/gimli.rs index ed5d66917..464bd99ec 100644 --- a/src/symbolize/gimli.rs +++ b/src/symbolize/gimli.rs @@ -7,11 +7,13 @@ use std::cell::RefCell; use std::env; use std::fs::File; use std::mem; -use std::os::raw::c_void; -use std::path::{Path, PathBuf}; +use libc::c_void; +use std::path::PathBuf; use std::u32; +use std::prelude::v1::*; use SymbolName; +use types::BytesOrWideString; const MAPPINGS_CACHE_SIZE: usize = 4; @@ -209,8 +211,8 @@ impl Symbol { Some(self.addr as *mut c_void) } - pub fn filename(&self) -> Option<&Path> { - self.file.as_ref().map(|f| f.as_ref()) + pub fn filename_raw(&self) -> Option { + self.file.as_ref().map(|f| BytesOrWideString::Bytes(f.as_bytes())) } pub fn lineno(&self) -> Option { diff --git a/src/symbolize/libbacktrace.rs b/src/symbolize/libbacktrace.rs index 865f69181..395395fba 100644 --- a/src/symbolize/libbacktrace.rs +++ b/src/symbolize/libbacktrace.rs @@ -12,16 +12,16 @@ extern crate backtrace_sys as bt; -use libc::uintptr_t; -use std::ffi::{CStr, OsStr}; -use std::os::raw::{c_void, c_char, c_int}; -use std::os::unix::prelude::*; -use std::path::Path; -use std::ptr; +use std::ffi::CStr; +use std::{ptr, slice}; use std::sync::{ONCE_INIT, Once}; +use libc::{self, c_char, c_int, c_void, uintptr_t}; + use SymbolName; +use types::BytesOrWideString; + pub enum Symbol { Syminfo { pc: uintptr_t, @@ -56,13 +56,15 @@ impl Symbol { if pc == 0 {None} else {Some(pc as *mut _)} } - pub fn filename(&self) -> Option<&Path> { + pub fn filename_raw(&self) -> Option { match *self { Symbol::Syminfo { .. } => None, Symbol::Pcinfo { filename, .. } => { - Some(Path::new(OsStr::from_bytes(unsafe { - CStr::from_ptr(filename).to_bytes() - }))) + let ptr = filename as *const u8; + unsafe { + let len = libc::strlen(filename); + Some(BytesOrWideString::Bytes(slice::from_raw_parts(ptr, len))) + } } } } @@ -158,23 +160,20 @@ unsafe fn init_state() -> *mut bt::backtrace_state { STATE } -pub fn resolve(symaddr: *mut c_void, mut cb: &mut FnMut(&super::Symbol)) { - let _guard = ::lock::lock(); - +pub unsafe fn resolve(symaddr: *mut c_void, mut cb: &mut FnMut(&super::Symbol)) +{ // backtrace errors are currently swept under the rug - unsafe { - let state = init_state(); - if state.is_null() { - return - } + let state = init_state(); + if state.is_null() { + return + } - let ret = bt::backtrace_pcinfo(state, symaddr as uintptr_t, - pcinfo_cb, error_cb, - &mut cb as *mut _ as *mut _); - if ret != 0 { - bt::backtrace_syminfo(state, symaddr as uintptr_t, - syminfo_cb, error_cb, - &mut cb as *mut _ as *mut _); - } + let ret = bt::backtrace_pcinfo(state, symaddr as uintptr_t, + pcinfo_cb, error_cb, + &mut cb as *mut _ as *mut _); + if ret != 0 { + bt::backtrace_syminfo(state, symaddr as uintptr_t, + syminfo_cb, error_cb, + &mut cb as *mut _ as *mut _); } } diff --git a/src/symbolize/mod.rs b/src/symbolize/mod.rs index cb5bb45f6..922e0ef4a 100644 --- a/src/symbolize/mod.rs +++ b/src/symbolize/mod.rs @@ -1,9 +1,13 @@ -use std::fmt; -#[cfg(not(feature = "cpp_demangle"))] -use std::marker::PhantomData; -use std::os::raw::c_void; -use std::path::Path; -use std::str; +use core::{fmt, str}; + +cfg_if! { + if #[cfg(feature = "std")] { + use std::path::Path; + use std::prelude::v1::*; + } +} + +use types::{BytesOrWideString, c_void}; use rustc_demangle::{try_demangle, Demangle}; /// Resolve an address to a symbol, passing the symbol to the specified @@ -19,6 +23,11 @@ use rustc_demangle::{try_demangle, Demangle}; /// Symbols yielded represent the execution at the specified `addr`, returning /// file/line pairs for that address (if available). /// +/// # Required features +/// +/// This function requires the `std` feature of the `backtrace` crate to be +/// enabled, and the `std` feature is enabled by default. +/// /// # Example /// /// ``` @@ -36,10 +45,24 @@ use rustc_demangle::{try_demangle, Demangle}; /// }); /// } /// ``` -pub fn resolve(addr: *mut c_void, mut cb: F) { - resolve_imp(addr, &mut cb) +#[cfg(feature = "std")] +pub fn resolve(addr: *mut c_void, cb: F) { + let _guard = ::lock::lock(); + unsafe { resolve_unsynchronized(addr, cb) } +} + +/// Same as `resolve`, only unsafe as it's unsynchronized. +/// +/// This function does not have synchronization guarentees but is available when +/// the `std` feature of this crate isn't compiled in. See the `resolve` +/// function for more documentation and examples. +pub unsafe fn resolve_unsynchronized(addr: *mut c_void, mut cb: F) + where F: FnMut(&Symbol) +{ + resolve_imp(addr as *mut _, &mut cb) } + /// A trait representing the resolution of a symbol in a file. /// /// This trait is yielded as a trait object to the closure given to the @@ -69,19 +92,16 @@ impl Symbol { /// Returns the starting address of this function. pub fn addr(&self) -> Option<*mut c_void> { - self.inner.addr() + self.inner.addr().map(|p| p as *mut _) } - /// Returns the file name where this function was defined. - /// - /// This is currently only available when libbacktrace is being used (e.g. - /// unix platforms other than OSX) and when a binary is compiled with - /// debuginfo. If neither of these conditions is met then this will likely - /// return `None`. - pub fn filename(&self) -> Option<&Path> { - self.inner.filename() + /// Returns the raw filename as a slice. This is mainly useful for `no_std` + /// environments. + pub fn filename_raw(&self) -> Option { + self.inner.filename_raw() } + /// Returns the line number for where this symbol is currently executing. /// /// This return value is typically `Some` if `filename` returns `Some`, and @@ -89,6 +109,35 @@ impl Symbol { pub fn lineno(&self) -> Option { self.inner.lineno() } + + /// Returns the file name where this function was defined. + /// + /// This is currently only available when libbacktrace is being used (e.g. + /// unix platforms other than OSX) and when a binary is compiled with + /// debuginfo. If neither of these conditions is met then this will likely + /// return `None`. + /// + /// This function requires the `std` feature to be enabled for this crate. + #[cfg(feature = "std")] + pub fn filename(&self) -> Option<&Path> { + #[cfg(not(windows))] + { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + match self.filename_raw() { + Some(BytesOrWideString::Bytes(slice)) => { + Some(Path::new(OsStr::from_bytes(slice))) + } + None => None, + _ => unreachable!(), + } + } + #[cfg(windows)] + { + self.inner.filename().map(Path::new) + } + } } impl fmt::Debug for Symbol { @@ -100,9 +149,13 @@ impl fmt::Debug for Symbol { if let Some(addr) = self.addr() { d.field("addr", &addr); } - if let Some(filename) = self.filename() { - d.field("filename", &filename); + + #[cfg(feature = "std")] { + if let Some(filename) = self.filename() { + d.field("filename", &filename); + } } + if let Some(lineno) = self.lineno() { d.field("lineno", &lineno); } @@ -110,7 +163,6 @@ impl fmt::Debug for Symbol { } } - cfg_if! { if #[cfg(feature = "cpp_demangle")] { // Maybe a parsed C++ symbol, if parsing the mangled symbol as Rust @@ -127,6 +179,8 @@ cfg_if! { } } } else { + use core::marker::PhantomData; + // Make sure to keep this zero-sized, so that the `cpp_demangle` feature // has no cost when disabled. struct OptionCppSymbol<'a>(PhantomData<&'a ()>); @@ -188,6 +242,30 @@ impl<'a> SymbolName<'a> { } } +fn format_symbol_name(fmt: fn(&str, &mut fmt::Formatter) -> fmt::Result, + mut bytes: &[u8], + f: &mut fmt::Formatter) + -> fmt::Result +{ + while bytes.len() > 0 { + match str::from_utf8(bytes) { + Ok(name) => { + fmt(name, f)?; + break + } + Err(err) => { + fmt("\u{FFFD}", f)?; + + match err.error_len() { + Some(len) => bytes = &bytes[err.valid_up_to() + len..], + None => break, + } + } + } + } + Ok(()) +} + cfg_if! { if #[cfg(feature = "cpp_demangle")] { impl<'a> fmt::Display for SymbolName<'a> { @@ -197,7 +275,7 @@ cfg_if! { } else if let Some(ref cpp) = self.cpp_demangled.0 { cpp.fmt(f) } else { - String::from_utf8_lossy(self.bytes).fmt(f) + format_symbol_name(fmt::Display::fmt, self.bytes, f) } } } @@ -207,7 +285,7 @@ cfg_if! { if let Some(ref s) = self.demangled { s.fmt(f) } else { - String::from_utf8_lossy(self.bytes).fmt(f) + format_symbol_name(fmt::Display::fmt, self.bytes, f) } } } @@ -215,7 +293,7 @@ cfg_if! { } cfg_if! { - if #[cfg(feature = "cpp_demangle")] { + if #[cfg(all(feature = "std", feature = "cpp_demangle"))] { impl<'a> fmt::Debug for SymbolName<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use std::fmt::Write; @@ -234,7 +312,7 @@ cfg_if! { } } - String::from_utf8_lossy(self.bytes).fmt(f) + format_symbol_name(fmt::Debug::fmt, self.bytes, f) } } } else { @@ -243,7 +321,7 @@ cfg_if! { if let Some(ref s) = self.demangled { s.fmt(f) } else { - String::from_utf8_lossy(self.bytes).fmt(f) + format_symbol_name(fmt::Debug::fmt, self.bytes, f) } } } @@ -255,7 +333,8 @@ cfg_if! { mod dbghelp; use self::dbghelp::resolve as resolve_imp; use self::dbghelp::Symbol as SymbolImp; - } else if #[cfg(all(feature = "gimli-symbolize", + } else if #[cfg(all(feature = "std", + feature = "gimli-symbolize", unix, target_os = "linux"))] { mod gimli; diff --git a/src/symbolize/noop.rs b/src/symbolize/noop.rs index 78b2a63f8..1730d57dc 100644 --- a/src/symbolize/noop.rs +++ b/src/symbolize/noop.rs @@ -1,9 +1,7 @@ -use std::path::Path; -use std::os::raw::c_void; - +use types::{BytesOrWideString, c_void}; use SymbolName; -pub fn resolve(_addr: *mut c_void, _cb: &mut FnMut(&super::Symbol)) { +pub unsafe fn resolve(_addr: *mut c_void, _cb: &mut FnMut(&super::Symbol)) { } pub struct Symbol; @@ -17,7 +15,7 @@ impl Symbol { None } - pub fn filename(&self) -> Option<&Path> { + pub fn filename_raw(&self) -> Option { None } diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 000000000..eefcf426e --- /dev/null +++ b/src/types.rs @@ -0,0 +1,71 @@ +//! Platform dependent types. + +cfg_if! { + if #[cfg(feature = "std")] { + pub use std::os::raw::c_void; + use std::borrow::Cow; + use std::fmt; + use std::path::PathBuf; + use std::prelude::v1::*; + } else { + pub use core::ffi::c_void; + } +} + +/// A platform independent representation of a string. When working with `std` +/// enabled it is recommended to the convenience methods for providing +/// conversions to `std` types. +#[derive(Debug)] +pub enum BytesOrWideString<'a> { + /// A slice, typically provided on Unix platforms. + Bytes(&'a [u8]), + /// Wide strings typically from Windows. + Wide(&'a [u16]), +} + +#[cfg(feature = "std")] +impl<'a> BytesOrWideString<'a> { + /// Lossy converts to a `Cow`, will allocate if `Bytes` is not valid + /// UTF-8 or if `BytesOrWideString` is `Wide`. + pub fn to_str_lossy(&self) -> Cow<'a, str> { + use self::BytesOrWideString::*; + + match self { + Bytes(slice) => String::from_utf8_lossy(slice), + Wide(wide) => Cow::Owned(String::from_utf16_lossy(wide)), + } + } + + /// Provides a `Path` representation of `BytesOrWideString`. + #[cfg(not(windows))] + pub fn into_path_buf(self) -> PathBuf { + use self::BytesOrWideString::*; + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + match self { + Bytes(slice) => PathBuf::from(OsStr::from_bytes(slice)), + _ => unreachable!(), + } + } + + /// Provides a `Path` representation of `BytesOrWideString`. + #[cfg(windows)] + pub fn into_path_buf(self) -> PathBuf { + use self::BytesOrWideString::*; + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + + match self { + Wide(slice) => PathBuf::from(OsString::from_wide(slice)), + _ => unreachable!(), + } + } +} + +#[cfg(feature = "std")] +impl<'a> fmt::Display for BytesOrWideString<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_str_lossy().fmt(f) + } +} diff --git a/tests/long_fn_name.rs b/tests/long_fn_name.rs index ce93671a6..862b62e70 100644 --- a/tests/long_fn_name.rs +++ b/tests/long_fn_name.rs @@ -48,7 +48,7 @@ fn test_long_fn_name() { "::_234567890_234567890_234567890_234567890_234567890") { found_long_name_frame = true; - assert_eq!(function_name.len(), dbghelp::MAX_SYM_NAME - 1); + assert!(function_name.len() > 200); } } }