// 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, (), |_, _| ())
}
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
// 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) }
// 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); }
}
}
#[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 {
}
#[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;
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,
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
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
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.
w.write_all(&['\n' as u8])
}
+#[allow(dead_code)]
+fn output_fileline(w: &mut Writer, file: &[u8], line: libc::c_int,
+ more: bool) -> IoResult<()> {
+ let file = str::from_utf8(file).ok().unwrap_or("<unknown>");
+ // 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
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"))))]
(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",
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// compile-flags:-g
+// ignore-pretty as this critically relies on line numbers
+
+use std::old_io::stderr;
+use std::env;
+
+#[path = "backtrace-debuginfo-aux.rs"] mod aux;
+
+macro_rules! pos {
+ () => ((file!(), line!()))
+}
+
+#[cfg(all(unix,
+ not(target_os = "macos"),
+ not(target_os = "ios"),
+ not(target_os = "android"),
+ not(all(target_os = "linux", target_arch = "arm"))))]
+macro_rules! dump_and_die {
+ ($($pos:expr),*) => ({
+ // FIXME(#18285): we cannot include the current position because
+ // the macro span takes over the last frame's file/line.
+ dump_filelines(&[$($pos),*]);
+ panic!();
+ })
+}
+
+// this does not work on Windows, Android, OSX or iOS
+#[cfg(any(not(unix),
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "android",
+ all(target_os = "linux", target_arch = "arm")))]
+macro_rules! dump_and_die {
+ ($($pos:expr),*) => ({ let _ = [$($pos),*]; })
+}
+
+// we can't use a function as it will alter the backtrace
+macro_rules! check {
+ ($counter:expr; $($pos:expr),*) => ({
+ if *$counter == 0 {
+ dump_and_die!($($pos),*)
+ } 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<String> = 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]);
+ }
+}
+