]> git.lizzy.rs Git - rust.git/blobdiff - src/libstd/sys/unix/backtrace.rs
Auto merge of #22839 - lifthrasiir:better-backtrace, r=alexcrichton
[rust.git] / src / libstd / sys / unix / backtrace.rs
index 6267792ba745e5ffa82bcca807c959d8ed4dd2a5..3695b615f62b8fe17237684d631f6f9b733bf700 100644 (file)
@@ -128,7 +128,7 @@ fn backtrace(buf: *mut *mut libc::c_void,
 
     // 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, (), |_, _| ())
 }
@@ -172,7 +172,16 @@ struct Context<'a> {
     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
@@ -183,7 +192,7 @@ struct Context<'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) }
@@ -204,7 +213,7 @@ struct Context<'a> {
         // 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); }
         }
@@ -215,7 +224,8 @@ struct Context<'a> {
 }
 
 #[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 {
@@ -240,7 +250,8 @@ fn dladdr(addr: *const libc::c_void,
 }
 
 #[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;
 
@@ -253,6 +264,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,
@@ -273,12 +290,19 @@ fn backtrace_syminfo(state: *mut backtrace_state,
                              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
@@ -291,6 +315,25 @@ fn backtrace_syminfo(state: *mut backtrace_state,
         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
@@ -359,15 +402,42 @@ unsafe fn init_state() -> *mut backtrace_state {
     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.
@@ -381,6 +451,18 @@ fn output(w: &mut Writer, idx: int, addr: *mut libc::c_void,
     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
@@ -421,9 +503,12 @@ pub fn _Unwind_Backtrace(trace: _Unwind_Trace_Fn,
                                  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"))))]
@@ -479,6 +564,18 @@ fn _Unwind_VRS_Get(ctx: *mut _Unwind_Context,
         (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",