diff --git a/src/libstd/sys/unix/backtrace.rs b/src/libstd/sys/unix/backtrace.rs index 6f07dea5279..8bc3ffd6ed1 100644 --- a/src/libstd/sys/unix/backtrace.rs +++ b/src/libstd/sys/unix/backtrace.rs @@ -127,7 +127,7 @@ pub fn write(w: &mut Writer) -> IoResult<()> { // skipping the first one as it is write itself let iter = (1..cnt).map(|i| { - print(w, i as int, buf[i]) + print(w, i as int, buf[i], buf[i]) }); result::fold(iter, (), |_, _| ()) } @@ -171,7 +171,16 @@ pub fn write(w: &mut Writer) -> IoResult<()> { extern fn trace_fn(ctx: *mut uw::_Unwind_Context, arg: *mut libc::c_void) -> uw::_Unwind_Reason_Code { let cx: &mut Context = unsafe { mem::transmute(arg) }; - let ip = unsafe { uw::_Unwind_GetIP(ctx) as *mut libc::c_void }; + let mut ip_before_insn = 0; + let mut ip = unsafe { + uw::_Unwind_GetIPInfo(ctx, &mut ip_before_insn) as *mut libc::c_void + }; + if ip_before_insn == 0 { + // this is a non-signaling frame, so `ip` refers to the address + // after the calling instruction. account for that. + ip = (ip as usize - 1) as *mut _; + } + // dladdr() on osx gets whiny when we use FindEnclosingFunction, and // it appears to work fine without it, so we only use // FindEnclosingFunction on non-osx platforms. In doing so, we get a @@ -182,7 +191,7 @@ pub fn write(w: &mut Writer) -> IoResult<()> { // instructions after it. This means that the return instruction // pointer points *outside* of the calling function, and by // unwinding it we go back to the original function. - let ip = if cfg!(target_os = "macos") || cfg!(target_os = "ios") { + let symaddr = if cfg!(target_os = "macos") || cfg!(target_os = "ios") { ip } else { unsafe { uw::_Unwind_FindEnclosingFunction(ip) } @@ -203,7 +212,7 @@ pub fn write(w: &mut Writer) -> IoResult<()> { // Once we hit an error, stop trying to print more frames if cx.last_error.is_some() { return uw::_URC_FAILURE } - match print(cx.writer, cx.idx, ip) { + match print(cx.writer, cx.idx, ip, symaddr) { Ok(()) => {} Err(e) => { cx.last_error = Some(e); } } @@ -214,7 +223,8 @@ pub fn write(w: &mut Writer) -> IoResult<()> { } #[cfg(any(target_os = "macos", target_os = "ios"))] -fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> { +fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void, + symaddr: *mut libc::c_void) -> IoResult<()> { use intrinsics; #[repr(C)] struct Dl_info { @@ -239,7 +249,8 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> { } #[cfg(not(any(target_os = "macos", target_os = "ios")))] -fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> { +fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void, + symaddr: *mut libc::c_void) -> IoResult<()> { use env; use ptr; @@ -252,6 +263,12 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> { symname: *const libc::c_char, symval: libc::uintptr_t, symsize: libc::uintptr_t); + type backtrace_full_callback = + extern "C" fn(data: *mut libc::c_void, + pc: libc::uintptr_t, + filename: *const libc::c_char, + lineno: libc::c_int, + function: *const libc::c_char) -> libc::c_int; type backtrace_error_callback = extern "C" fn(data: *mut libc::c_void, msg: *const libc::c_char, @@ -272,12 +289,19 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> { cb: backtrace_syminfo_callback, error: backtrace_error_callback, data: *mut libc::c_void) -> libc::c_int; + fn backtrace_pcinfo(state: *mut backtrace_state, + addr: libc::uintptr_t, + cb: backtrace_full_callback, + error: backtrace_error_callback, + data: *mut libc::c_void) -> libc::c_int; } //////////////////////////////////////////////////////////////////////// // helper callbacks //////////////////////////////////////////////////////////////////////// + type FileLine = (*const libc::c_char, libc::c_int); + extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char, _errnum: libc::c_int) { // do nothing for now @@ -290,6 +314,25 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> { let slot = data as *mut *const libc::c_char; unsafe { *slot = symname; } } + extern fn pcinfo_cb(data: *mut libc::c_void, + _pc: libc::uintptr_t, + filename: *const libc::c_char, + lineno: libc::c_int, + _function: *const libc::c_char) -> libc::c_int { + if !filename.is_null() { + let slot = data as *mut &mut [FileLine]; + let buffer = unsafe {ptr::read(slot)}; + + // if the buffer is not full, add file:line to the buffer + // and adjust the buffer for next possible calls to pcinfo_cb. + if !buffer.is_empty() { + buffer[0] = (filename, lineno); + unsafe { ptr::write(slot, &mut buffer[1..]); } + } + } + + 0 + } // The libbacktrace API supports creating a state, but it does not // support destroying a state. I personally take this to mean that a @@ -358,15 +401,42 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> { let mut data = ptr::null(); let data_addr = &mut data as *mut *const libc::c_char; let ret = unsafe { - backtrace_syminfo(state, addr as libc::uintptr_t, + backtrace_syminfo(state, symaddr as libc::uintptr_t, syminfo_cb, error_cb, data_addr as *mut libc::c_void) }; if ret == 0 || data.is_null() { - output(w, idx, addr, None) + try!(output(w, idx, addr, None)); } else { - output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() })) + try!(output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() }))); } + + // pcinfo may return an arbitrary number of file:line pairs, + // in the order of stack trace (i.e. inlined calls first). + // in order to avoid allocation, we stack-allocate a fixed size of entries. + const FILELINE_SIZE: usize = 32; + let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE]; + let ret; + let fileline_count; + { + let mut fileline_win: &mut [FileLine] = &mut fileline_buf; + let fileline_addr = &mut fileline_win as *mut &mut [FileLine]; + ret = unsafe { + backtrace_pcinfo(state, addr as libc::uintptr_t, + pcinfo_cb, error_cb, + fileline_addr as *mut libc::c_void) + }; + fileline_count = FILELINE_SIZE - fileline_win.len(); + } + if ret == 0 { + for (i, &(file, line)) in fileline_buf[..fileline_count].iter().enumerate() { + if file.is_null() { continue; } // just to be sure + let file = unsafe { CStr::from_ptr(file).to_bytes() }; + try!(output_fileline(w, file, line, i == FILELINE_SIZE - 1)); + } + } + + Ok(()) } // Finally, after all that work above, we can emit a symbol. @@ -380,6 +450,17 @@ fn output(w: &mut Writer, idx: int, addr: *mut libc::c_void, w.write_all(&['\n' as u8]) } +fn output_fileline(w: &mut Writer, file: &[u8], line: libc::c_int, + more: bool) -> IoResult<()> { + let file = str::from_utf8(file).ok().unwrap_or(""); + // prior line: " ##: {:2$} - func" + try!(write!(w, " {:3$}at {}:{}", "", file, line, HEX_WIDTH)); + if more { + try!(write!(w, " <... and possibly more>")); + } + w.write_all(&['\n' as u8]) +} + /// Unwind library interface used for backtraces /// /// Note that dead code is allowed as here are just bindings @@ -420,9 +501,12 @@ mod uw { trace_argument: *mut libc::c_void) -> _Unwind_Reason_Code; + // available since GCC 4.2.0, should be fine for our purpose #[cfg(all(not(all(target_os = "android", target_arch = "arm")), not(all(target_os = "linux", target_arch = "arm"))))] - pub fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t; + pub fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context, + ip_before_insn: *mut libc::c_int) + -> libc::uintptr_t; #[cfg(all(not(target_os = "android"), not(all(target_os = "linux", target_arch = "arm"))))] @@ -478,6 +562,18 @@ mod uw { (val & !1) as libc::uintptr_t } + // This function doesn't exist on Android or ARM/Linux, so make it same + // to _Unwind_GetIP + #[cfg(any(target_os = "android", + all(target_os = "linux", target_arch = "arm")))] + pub unsafe fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context, + ip_before_insn: *mut libc::c_int) + -> libc::uintptr_t + { + *ip_before_insn = 0; + _Unwind_GetIP(ctx) + } + // This function also doesn't exist on Android or ARM/Linux, so make it // a no-op #[cfg(any(target_os = "android", diff --git a/src/test/run-pass/backtrace-debuginfo-aux.rs b/src/test/run-pass/backtrace-debuginfo-aux.rs new file mode 100644 index 00000000000..074ee97c37a --- /dev/null +++ b/src/test/run-pass/backtrace-debuginfo-aux.rs @@ -0,0 +1,22 @@ +// Copyright 2015 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. + +// ignore-test: not a test, used by backtrace-debuginfo.rs to test file!() + +#[inline(never)] +pub fn callback(f: F) where F: FnOnce((&'static str, u32)) { + f((file!(), line!())) +} + +#[inline(always)] +pub fn callback_inlined(f: F) where F: FnOnce((&'static str, u32)) { + f((file!(), line!())) +} + diff --git a/src/test/run-pass/backtrace-debuginfo.rs b/src/test/run-pass/backtrace-debuginfo.rs new file mode 100644 index 00000000000..618a6b6e309 --- /dev/null +++ b/src/test/run-pass/backtrace-debuginfo.rs @@ -0,0 +1,140 @@ +// Copyright 2015 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. + +// compile-flags:-g + +#![feature(asm)] + +use std::old_io::stderr; +use std::env; + +#[path = "backtrace-debuginfo-aux.rs"] mod aux; + +macro_rules! pos { + () => ((file!(), line!())) +} + +// we can't use a function as it will alter the backtrace +macro_rules! check { + ($counter:expr; $($pos:expr),*) => ({ + if *$counter == 0 { + // XXX we cannot include the current position because + // the macro span takes over the last frame's file/line. + dump_filelines(&[$($pos),*]); + panic!(); + } else { + *$counter -= 1; + } + }) +} + +type Pos = (&'static str, u32); + +// this goes to stdout and each line has to be occurred +// in the following backtrace to stderr with a correct order. +fn dump_filelines(filelines: &[Pos]) { + for &(file, line) in filelines.iter().rev() { + // extract a basename + let basename = file.split(&['/', '\\'][..]).last().unwrap(); + println!("{}:{}", basename, line); + } +} + +#[inline(never)] +fn inner(counter: &mut u32, main_pos: Pos, outer_pos: Pos) { + check!(counter; main_pos, outer_pos); + check!(counter; main_pos, outer_pos); + let inner_pos = pos!(); aux::callback(|aux_pos| { + check!(counter; main_pos, outer_pos, inner_pos, aux_pos); + }); + let inner_pos = pos!(); aux::callback_inlined(|aux_pos| { + check!(counter; main_pos, outer_pos, inner_pos, aux_pos); + }); +} + +#[inline(always)] +fn inner_inlined(counter: &mut u32, main_pos: Pos, outer_pos: Pos) { + check!(counter; main_pos, outer_pos); + check!(counter; main_pos, outer_pos); + + #[inline(always)] + fn inner_further_inlined(counter: &mut u32, main_pos: Pos, outer_pos: Pos, inner_pos: Pos) { + check!(counter; main_pos, outer_pos, inner_pos); + } + inner_further_inlined(counter, main_pos, outer_pos, pos!()); + + let inner_pos = pos!(); aux::callback(|aux_pos| { + check!(counter; main_pos, outer_pos, inner_pos, aux_pos); + }); + let inner_pos = pos!(); aux::callback_inlined(|aux_pos| { + check!(counter; main_pos, outer_pos, inner_pos, aux_pos); + }); + + // this tests a distinction between two independent calls to the inlined function. + // (un)fortunately, LLVM somehow merges two consecutive such calls into one node. + inner_further_inlined(counter, main_pos, outer_pos, pos!()); +} + +#[inline(never)] +fn outer(mut counter: u32, main_pos: Pos) { + inner(&mut counter, main_pos, pos!()); + inner_inlined(&mut counter, main_pos, pos!()); +} + +fn check_trace(output: &str, error: &str) { + // reverse the position list so we can start with the last item (which was the first line) + let mut remaining: Vec<&str> = output.lines().map(|s| s.trim()).rev().collect(); + + assert!(error.contains("stack backtrace"), "no backtrace in the error: {}", error); + for line in error.lines() { + if !remaining.is_empty() && line.contains(remaining.last().unwrap()) { + remaining.pop(); + } + } + assert!(remaining.is_empty(), + "trace does not match position list: {}\n---\n{}", error, output); +} + +fn run_test(me: &str) { + use std::str; + use std::old_io::process::Command; + + let mut template = Command::new(me); + template.env("RUST_BACKTRACE", "1"); + + let mut i = 0; + loop { + let p = template.clone().arg(i.to_string()).spawn().unwrap(); + let out = p.wait_with_output().unwrap(); + let output = str::from_utf8(&out.output).unwrap(); + let error = str::from_utf8(&out.error).unwrap(); + if out.status.success() { + assert!(output.contains("done."), "bad output for successful run: {}", output); + break; + } else { + check_trace(output, error); + } + i += 1; + } +} + +#[inline(never)] +fn main() { + let args: Vec = env::args().collect(); + if args.len() >= 2 { + let case = args[1].parse().unwrap(); + writeln!(&mut stderr(), "test case {}", case).unwrap(); + outer(case, pos!()); + println!("done."); + } else { + run_test(&args[0]); + } +} +