From cf79540a6d4b8e0fb9c1597d1ae953a381341aea Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Feb 2014 15:19:40 -0800 Subject: [PATCH] Add basic backtrace functionality Whenever a failure happens, if a program is run with `RUST_LOG=std::rt::backtrace` a backtrace will be printed to the task's stderr handle. Currently this is not an exported interface because it's only supported on osx/linux. I found that windows/freebsd/android all required extra libraries which are not necessarily present by default. cc #10128 --- src/libstd/rt/backtrace.rs | 174 +++++++++++++++++++++++++++++++++++++ src/libstd/rt/mod.rs | 3 + src/libstd/rt/unwind.rs | 8 ++ src/libstd/rt/util.rs | 23 ++--- 4 files changed, 197 insertions(+), 11 deletions(-) create mode 100644 src/libstd/rt/backtrace.rs diff --git a/src/libstd/rt/backtrace.rs b/src/libstd/rt/backtrace.rs new file mode 100644 index 0000000000000..2db87bb480602 --- /dev/null +++ b/src/libstd/rt/backtrace.rs @@ -0,0 +1,174 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[allow(non_camel_case_types)]; + +use c_str::CString; +use container::Container; +use io::{Writer, IoResult}; +use iter::{range, Iterator}; +use libc; +use option::{Some, None}; +use result::{Ok, Err}; +use unstable::raw::{Repr, Slice}; +use vec::ImmutableVector; + +static MAX_BACKTRACE: uint = 128; + +pub fn log_enabled() -> bool { + log_enabled!(::logging::DEBUG) +} + +#[cfg(target_os = "macos")] +#[cfg(target_os = "linux")] +pub fn write(w: &mut Writer) -> IoResult<()> { + use ptr::RawPtr; + use ptr; + + extern { + fn backtrace(array: *mut *mut libc::c_void, + size: libc::c_int) -> libc::c_int; + fn backtrace_symbols(array: *mut *libc::c_void, + size: libc::c_int) -> **libc::c_char; + } + + let array = [0u, ..MAX_BACKTRACE]; + let Slice { data, len } = array.repr(); + let len = unsafe { + backtrace(data as *mut *mut libc::c_void, len as libc::c_int) + }; + if len == 0 { return Ok(()) } + + let arr = unsafe { + backtrace_symbols(data as *mut *libc::c_void, len) + }; + if arr.is_null() { return Ok(()) } + + if_ok!(write!(w, "stack backtrace:\n")); + for i in range(0, len) { + let c_str = unsafe { CString::new(*ptr::offset(arr, i as int), false) }; + if_ok!(w.write_str(" ")); + let bytes = c_str.as_bytes(); + if_ok!(w.write(bytes.slice_to(bytes.len() - 1))); + if_ok!(w.write_str("\n")); + } + + Ok(()) +} + +// Windows uses functions from DbgHelp.dll which looks like it's not installed +// on mingw by default. Right now we don't have a great way of conditionally +// linking to something based on is presence, so for now we just ignore +// windows. +// +// The commented out code below is an untested implementation (but compiles) as +// I could never find DbgHelp.dll... +#[cfg(target_os = "win32")] +fn write(_: &mut Writer) -> IoResult<()> { Ok(()) } +/* +#[cfg(target_os = "win32")] +pub fn write(w: &mut Writer) -> IoResult<()> { + use mem; + use unstable::intrinsics; + + struct SYMBOL_INFO { + SizeOfStruct: libc::c_ulong, + TypeIndex: libc::c_ulong, + Reserved: [u64, ..2], + Index: libc::c_ulong, + Size: libc::c_ulong, + ModBase: u64, + Flags: libc::c_ulong, + Value: u64, + Address: u64, + Register: libc::c_ulong, + Scope: libc::c_ulong, + Tag: libc::c_ulong, + NameLen: libc::c_ulong, + MaxNameLen: libc::c_ulong, + // note that windows has this as 1, but it basically just means that the + // name is inline at the end of the struct. For us, we just bump the + // struct size up to 256. + Name: [libc::c_char, ..256], + } + + #[link(name = "DbgHelp")] + extern "system" { + fn CaptureStackBackTrace( + FramesToSkip: libc::c_ulong, + FramesToCapture: libc::c_ulong, + BackTrace: *mut *libc::c_void, + BackTraceHash: *libc::c_ulong, + ) -> libc::c_short; + + fn SymFromAddr( + hProcess: libc::HANDLE, + Address: i64, + Displacement: *i64, + Symbol: *mut SYMBOL_INFO, + ) -> libc::BOOL; + + fn GetCurrentProcess() -> libc::HANDLE; + fn SymInitialize( + hProcess: libc::HANDLE, + UserSearchPath: *libc::c_void, + fInvadeProcess: libc::BOOL, + ) -> libc::BOOL; + } + + let process = unsafe { GetCurrentProcess() }; + let ret = unsafe { SymInitialize(process, 0 as *libc::c_void, libc::TRUE) }; + if ret != libc::TRUE { return Ok(()) } + + let array = [0u, ..MAX_BACKTRACE]; + let Slice { data, len } = array.repr(); + let len = unsafe { + CaptureStackBackTrace(0, len as libc::c_ulong, + data as *mut *libc::c_void, 0 as *libc::c_ulong) + }; + if len == 0 { return Ok(()) } + + if_ok!(write!(w, "stack backtrace:\n")); + let mut info: SYMBOL_INFO = unsafe { intrinsics::init() }; + for i in range(0, len) { + info.MaxNameLen = (info.Name.len() - 1) as libc::c_ulong; + info.SizeOfStruct = (mem::size_of::() - + info.Name.len() + 1) as libc::c_ulong; + + let ret = unsafe { + SymFromAddr(process, array[i] as i64, 0 as *i64, &mut info) + }; + + if ret == libc::TRUE { + let cstr = unsafe { CString::new(info.Name.as_ptr(), false) }; + if_ok!(match cstr.as_str() { + Some(s) => writeln!(w, "{}: {} - {:#10x}", i, s, info.Address), + None => writeln!(w, "{}: - {:#10x}", i, info.Address) + }) + } else { + if_ok!(w.write_str(" \n")); + } + } + + Ok(()) +} +*/ + + +// Apparently freebsd has its backtrace functionality through an external +// libexecinfo library (not on the system by default). For now, just ignore +// writing backtraces on freebsd +#[cfg(target_os = "freebsd")] +fn write(_: &mut Writer) -> IoResult<()> { Ok(()) } + +// Android, like freebsd, I believe also uses libexecinfo as a separate library. +// For now this is just a stub. +#[cfg(target_os = "android")] +fn write(_: &mut Writer) -> IoResult<()> { Ok(()) } diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 55425eb2e7226..b0b9637968899 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -119,6 +119,9 @@ mod thread_local_storage; /// Stack unwinding pub mod unwind; +/// Simple backtrace functionality (to print on failure) +mod backtrace; + /// Just stuff mod util; diff --git a/src/libstd/rt/unwind.rs b/src/libstd/rt/unwind.rs index 9aece13b84ca1..7e6cf306dee6a 100644 --- a/src/libstd/rt/unwind.rs +++ b/src/libstd/rt/unwind.rs @@ -64,6 +64,7 @@ use option::{Some, None, Option}; use prelude::drop; use ptr::RawPtr; use result::{Err, Ok}; +use rt::backtrace; use rt::local::Local; use rt::task::Task; use str::Str; @@ -468,6 +469,9 @@ fn begin_unwind_inner(msg: ~Any, file: &'static str, line: uint) -> ! { let _err = format_args!(|args| ::fmt::writeln(stderr, args), "task '{}' failed at '{}', {}:{}", n, msg_s, file, line); + if backtrace::log_enabled() { + let _err = backtrace::write(stderr); + } task = Local::take(); match util::replace(&mut task.stderr, Some(stderr)) { @@ -482,6 +486,10 @@ fn begin_unwind_inner(msg: ~Any, file: &'static str, line: uint) -> ! { None => { rterrln!("task '{}' failed at '{}', {}:{}", n, msg_s, file, line); + if backtrace::log_enabled() { + let mut err = ::rt::util::Stderr; + let _err = backtrace::write(&mut err); + } } } } diff --git a/src/libstd/rt/util.rs b/src/libstd/rt/util.rs index 69e240f30bcf0..202449e9b9f8f 100644 --- a/src/libstd/rt/util.rs +++ b/src/libstd/rt/util.rs @@ -12,6 +12,7 @@ use container::Container; use fmt; use from_str::FromStr; use io::IoResult; +use io; use iter::Iterator; use libc; use option::{Some, None, Option}; @@ -70,20 +71,20 @@ pub fn default_sched_threads() -> uint { } } -pub fn dumb_println(args: &fmt::Arguments) { - use io; +pub struct Stderr; - struct Stderr; - impl io::Writer for Stderr { - fn write(&mut self, data: &[u8]) -> IoResult<()> { - unsafe { - libc::write(libc::STDERR_FILENO, - data.as_ptr() as *libc::c_void, - data.len() as libc::size_t); - } - Ok(()) // yes, we're lying +impl io::Writer for Stderr { + fn write(&mut self, data: &[u8]) -> IoResult<()> { + unsafe { + libc::write(libc::STDERR_FILENO, + data.as_ptr() as *libc::c_void, + data.len() as libc::size_t); } + Ok(()) // yes, we're lying } +} + +pub fn dumb_println(args: &fmt::Arguments) { let mut w = Stderr; let _ = fmt::writeln(&mut w as &mut io::Writer, args); }