Skip to content

Commit 8519e78

Browse files
committed
Auto merge of #22839 - lifthrasiir:better-backtrace, r=alexcrichton
Fixes #20978 for supported platforms (i.e. non-Android POSIX). This uses `backtrace_pcinfo` to inspect the DWARF debug info and list the file and line pairs for given stack frame. Such pair is not unique due to the presence of inlined functions and the updated routine correctly handles this case. The code is modelled after libbacktrace's `backtrace_full` routine. There is one known issue with this approach. Macros, when invoked, take over the current frame and shadows the file and line pair which has invoked a macro. In particular, this makes many panicking macros a bit harder to inspect. This really is a debuginfo problem, and the backtrace routine should print them correctly with a correct debuginfo. Some example trace: ``` thread '<main>' panicked at 'explicit panic', /home/arachneng/Works/git/rust/src/test/run-pass/backtrace-debuginfo.rs:74 stack backtrace: 1: 0xd964702f - sys::backtrace::write::h32d93fffb64131b2yxC 2: 0xd9670202 - panicking::on_panic::h3a4fcb37b873aefeooM 3: 0xd95b396a - rt::unwind::begin_unwind_inner::h576b3df5f626902dJ2L 4: 0xd9eb88df - rt::unwind::begin_unwind::h16852273847167740350 5: 0xd9eb8afb - aux::callback::h15056955655605709172 at /home/arachneng/Works/git/rust/<std macros>:3 at src/test/run-pass/backtrace-debuginfo-aux.rs:15 6: 0xd9eb8caa - outer::h2cf96412459fceb6ema at src/test/run-pass/backtrace-debuginfo.rs:73 at src/test/run-pass/backtrace-debuginfo.rs:88 7: 0xd9ebab24 - main::h3f701287441442edasa at src/test/run-pass/backtrace-debuginfo.rs:134 8: 0xd96daba8 - rust_try_inner 9: 0xd96dab95 - rust_try 10: 0xd9671af4 - rt::lang_start::h7da0de9529b4c394liM 11: 0xd8f3aec4 - __libc_start_main 12: 0xd9eb8148 - <unknown> 13: 0xffffffff - <unknown> ```
2 parents 48aeaba + ff678ea commit 8519e78

File tree

3 files changed

+289
-10
lines changed

3 files changed

+289
-10
lines changed

src/libstd/sys/unix/backtrace.rs

+107-10
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub fn write(w: &mut Writer) -> IoResult<()> {
128128

129129
// skipping the first one as it is write itself
130130
let iter = (1..cnt).map(|i| {
131-
print(w, i as int, buf[i])
131+
print(w, i as int, buf[i], buf[i])
132132
});
133133
result::fold(iter, (), |_, _| ())
134134
}
@@ -172,7 +172,16 @@ pub fn write(w: &mut Writer) -> IoResult<()> {
172172
extern fn trace_fn(ctx: *mut uw::_Unwind_Context,
173173
arg: *mut libc::c_void) -> uw::_Unwind_Reason_Code {
174174
let cx: &mut Context = unsafe { mem::transmute(arg) };
175-
let ip = unsafe { uw::_Unwind_GetIP(ctx) as *mut libc::c_void };
175+
let mut ip_before_insn = 0;
176+
let mut ip = unsafe {
177+
uw::_Unwind_GetIPInfo(ctx, &mut ip_before_insn) as *mut libc::c_void
178+
};
179+
if ip_before_insn == 0 {
180+
// this is a non-signaling frame, so `ip` refers to the address
181+
// after the calling instruction. account for that.
182+
ip = (ip as usize - 1) as *mut _;
183+
}
184+
176185
// dladdr() on osx gets whiny when we use FindEnclosingFunction, and
177186
// it appears to work fine without it, so we only use
178187
// FindEnclosingFunction on non-osx platforms. In doing so, we get a
@@ -183,7 +192,7 @@ pub fn write(w: &mut Writer) -> IoResult<()> {
183192
// instructions after it. This means that the return instruction
184193
// pointer points *outside* of the calling function, and by
185194
// unwinding it we go back to the original function.
186-
let ip = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
195+
let symaddr = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
187196
ip
188197
} else {
189198
unsafe { uw::_Unwind_FindEnclosingFunction(ip) }
@@ -204,7 +213,7 @@ pub fn write(w: &mut Writer) -> IoResult<()> {
204213
// Once we hit an error, stop trying to print more frames
205214
if cx.last_error.is_some() { return uw::_URC_FAILURE }
206215

207-
match print(cx.writer, cx.idx, ip) {
216+
match print(cx.writer, cx.idx, ip, symaddr) {
208217
Ok(()) => {}
209218
Err(e) => { cx.last_error = Some(e); }
210219
}
@@ -215,7 +224,8 @@ pub fn write(w: &mut Writer) -> IoResult<()> {
215224
}
216225

217226
#[cfg(any(target_os = "macos", target_os = "ios"))]
218-
fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
227+
fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void,
228+
_symaddr: *mut libc::c_void) -> IoResult<()> {
219229
use intrinsics;
220230
#[repr(C)]
221231
struct Dl_info {
@@ -240,7 +250,8 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
240250
}
241251

242252
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
243-
fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
253+
fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void,
254+
symaddr: *mut libc::c_void) -> IoResult<()> {
244255
use env;
245256
use ptr;
246257

@@ -253,6 +264,12 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
253264
symname: *const libc::c_char,
254265
symval: libc::uintptr_t,
255266
symsize: libc::uintptr_t);
267+
type backtrace_full_callback =
268+
extern "C" fn(data: *mut libc::c_void,
269+
pc: libc::uintptr_t,
270+
filename: *const libc::c_char,
271+
lineno: libc::c_int,
272+
function: *const libc::c_char) -> libc::c_int;
256273
type backtrace_error_callback =
257274
extern "C" fn(data: *mut libc::c_void,
258275
msg: *const libc::c_char,
@@ -273,12 +290,19 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
273290
cb: backtrace_syminfo_callback,
274291
error: backtrace_error_callback,
275292
data: *mut libc::c_void) -> libc::c_int;
293+
fn backtrace_pcinfo(state: *mut backtrace_state,
294+
addr: libc::uintptr_t,
295+
cb: backtrace_full_callback,
296+
error: backtrace_error_callback,
297+
data: *mut libc::c_void) -> libc::c_int;
276298
}
277299

278300
////////////////////////////////////////////////////////////////////////
279301
// helper callbacks
280302
////////////////////////////////////////////////////////////////////////
281303

304+
type FileLine = (*const libc::c_char, libc::c_int);
305+
282306
extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char,
283307
_errnum: libc::c_int) {
284308
// do nothing for now
@@ -291,6 +315,25 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
291315
let slot = data as *mut *const libc::c_char;
292316
unsafe { *slot = symname; }
293317
}
318+
extern fn pcinfo_cb(data: *mut libc::c_void,
319+
_pc: libc::uintptr_t,
320+
filename: *const libc::c_char,
321+
lineno: libc::c_int,
322+
_function: *const libc::c_char) -> libc::c_int {
323+
if !filename.is_null() {
324+
let slot = data as *mut &mut [FileLine];
325+
let buffer = unsafe {ptr::read(slot)};
326+
327+
// if the buffer is not full, add file:line to the buffer
328+
// and adjust the buffer for next possible calls to pcinfo_cb.
329+
if !buffer.is_empty() {
330+
buffer[0] = (filename, lineno);
331+
unsafe { ptr::write(slot, &mut buffer[1..]); }
332+
}
333+
}
334+
335+
0
336+
}
294337

295338
// The libbacktrace API supports creating a state, but it does not
296339
// support destroying a state. I personally take this to mean that a
@@ -359,15 +402,42 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
359402
let mut data = ptr::null();
360403
let data_addr = &mut data as *mut *const libc::c_char;
361404
let ret = unsafe {
362-
backtrace_syminfo(state, addr as libc::uintptr_t,
405+
backtrace_syminfo(state, symaddr as libc::uintptr_t,
363406
syminfo_cb, error_cb,
364407
data_addr as *mut libc::c_void)
365408
};
366409
if ret == 0 || data.is_null() {
367-
output(w, idx, addr, None)
410+
try!(output(w, idx, addr, None));
368411
} else {
369-
output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() }))
412+
try!(output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() })));
413+
}
414+
415+
// pcinfo may return an arbitrary number of file:line pairs,
416+
// in the order of stack trace (i.e. inlined calls first).
417+
// in order to avoid allocation, we stack-allocate a fixed size of entries.
418+
const FILELINE_SIZE: usize = 32;
419+
let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE];
420+
let ret;
421+
let fileline_count;
422+
{
423+
let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
424+
let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
425+
ret = unsafe {
426+
backtrace_pcinfo(state, addr as libc::uintptr_t,
427+
pcinfo_cb, error_cb,
428+
fileline_addr as *mut libc::c_void)
429+
};
430+
fileline_count = FILELINE_SIZE - fileline_win.len();
370431
}
432+
if ret == 0 {
433+
for (i, &(file, line)) in fileline_buf[..fileline_count].iter().enumerate() {
434+
if file.is_null() { continue; } // just to be sure
435+
let file = unsafe { CStr::from_ptr(file).to_bytes() };
436+
try!(output_fileline(w, file, line, i == FILELINE_SIZE - 1));
437+
}
438+
}
439+
440+
Ok(())
371441
}
372442

373443
// 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,
381451
w.write_all(&['\n' as u8])
382452
}
383453

454+
#[allow(dead_code)]
455+
fn output_fileline(w: &mut Writer, file: &[u8], line: libc::c_int,
456+
more: bool) -> IoResult<()> {
457+
let file = str::from_utf8(file).ok().unwrap_or("<unknown>");
458+
// prior line: " ##: {:2$} - func"
459+
try!(write!(w, " {:3$}at {}:{}", "", file, line, HEX_WIDTH));
460+
if more {
461+
try!(write!(w, " <... and possibly more>"));
462+
}
463+
w.write_all(&['\n' as u8])
464+
}
465+
384466
/// Unwind library interface used for backtraces
385467
///
386468
/// Note that dead code is allowed as here are just bindings
@@ -421,9 +503,12 @@ mod uw {
421503
trace_argument: *mut libc::c_void)
422504
-> _Unwind_Reason_Code;
423505

506+
// available since GCC 4.2.0, should be fine for our purpose
424507
#[cfg(all(not(all(target_os = "android", target_arch = "arm")),
425508
not(all(target_os = "linux", target_arch = "arm"))))]
426-
pub fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t;
509+
pub fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context,
510+
ip_before_insn: *mut libc::c_int)
511+
-> libc::uintptr_t;
427512

428513
#[cfg(all(not(target_os = "android"),
429514
not(all(target_os = "linux", target_arch = "arm"))))]
@@ -479,6 +564,18 @@ mod uw {
479564
(val & !1) as libc::uintptr_t
480565
}
481566

567+
// This function doesn't exist on Android or ARM/Linux, so make it same
568+
// to _Unwind_GetIP
569+
#[cfg(any(target_os = "android",
570+
all(target_os = "linux", target_arch = "arm")))]
571+
pub unsafe fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context,
572+
ip_before_insn: *mut libc::c_int)
573+
-> libc::uintptr_t
574+
{
575+
*ip_before_insn = 0;
576+
_Unwind_GetIP(ctx)
577+
}
578+
482579
// This function also doesn't exist on Android or ARM/Linux, so make it
483580
// a no-op
484581
#[cfg(any(target_os = "android",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// ignore-test: not a test, used by backtrace-debuginfo.rs to test file!()
12+
13+
#[inline(never)]
14+
pub fn callback<F>(f: F) where F: FnOnce((&'static str, u32)) {
15+
f((file!(), line!()))
16+
}
17+
18+
#[inline(always)]
19+
pub fn callback_inlined<F>(f: F) where F: FnOnce((&'static str, u32)) {
20+
f((file!(), line!()))
21+
}
22+

0 commit comments

Comments
 (0)