Skip to content

Commit bdd31b3

Browse files
committed
std: Include line numbers in backtraces.
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.
1 parent dccdde4 commit bdd31b3

File tree

3 files changed

+268
-10
lines changed

3 files changed

+268
-10
lines changed

src/libstd/sys/unix/backtrace.rs

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ pub fn write(w: &mut Writer) -> IoResult<()> {
127127

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

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

216225
#[cfg(any(target_os = "macos", target_os = "ios"))]
217-
fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
226+
fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void,
227+
symaddr: *mut libc::c_void) -> IoResult<()> {
218228
use intrinsics;
219229
#[repr(C)]
220230
struct Dl_info {
@@ -239,7 +249,8 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
239249
}
240250

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

@@ -252,6 +263,12 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
252263
symname: *const libc::c_char,
253264
symval: libc::uintptr_t,
254265
symsize: libc::uintptr_t);
266+
type backtrace_full_callback =
267+
extern "C" fn(data: *mut libc::c_void,
268+
pc: libc::uintptr_t,
269+
filename: *const libc::c_char,
270+
lineno: libc::c_int,
271+
function: *const libc::c_char) -> libc::c_int;
255272
type backtrace_error_callback =
256273
extern "C" fn(data: *mut libc::c_void,
257274
msg: *const libc::c_char,
@@ -272,12 +289,19 @@ fn print(w: &mut Writer, idx: int, addr: *mut libc::c_void) -> IoResult<()> {
272289
cb: backtrace_syminfo_callback,
273290
error: backtrace_error_callback,
274291
data: *mut libc::c_void) -> libc::c_int;
292+
fn backtrace_pcinfo(state: *mut backtrace_state,
293+
addr: libc::uintptr_t,
294+
cb: backtrace_full_callback,
295+
error: backtrace_error_callback,
296+
data: *mut libc::c_void) -> libc::c_int;
275297
}
276298

277299
////////////////////////////////////////////////////////////////////////
278300
// helper callbacks
279301
////////////////////////////////////////////////////////////////////////
280302

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

294337
// The libbacktrace API supports creating a state, but it does not
295338
// 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<()> {
358401
let mut data = ptr::null();
359402
let data_addr = &mut data as *mut *const libc::c_char;
360403
let ret = unsafe {
361-
backtrace_syminfo(state, addr as libc::uintptr_t,
404+
backtrace_syminfo(state, symaddr as libc::uintptr_t,
362405
syminfo_cb, error_cb,
363406
data_addr as *mut libc::c_void)
364407
};
365408
if ret == 0 || data.is_null() {
366-
output(w, idx, addr, None)
409+
try!(output(w, idx, addr, None));
367410
} else {
368-
output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() }))
411+
try!(output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() })));
412+
}
413+
414+
// pcinfo may return an arbitrary number of file:line pairs,
415+
// in the order of stack trace (i.e. inlined calls first).
416+
// in order to avoid allocation, we stack-allocate a fixed size of entries.
417+
const FILELINE_SIZE: usize = 32;
418+
let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE];
419+
let ret;
420+
let fileline_count;
421+
{
422+
let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
423+
let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
424+
ret = unsafe {
425+
backtrace_pcinfo(state, addr as libc::uintptr_t,
426+
pcinfo_cb, error_cb,
427+
fileline_addr as *mut libc::c_void)
428+
};
429+
fileline_count = FILELINE_SIZE - fileline_win.len();
430+
}
431+
if ret == 0 {
432+
for (i, &(file, line)) in fileline_buf[..fileline_count].iter().enumerate() {
433+
if file.is_null() { continue; } // just to be sure
434+
let file = unsafe { CStr::from_ptr(file).to_bytes() };
435+
try!(output_fileline(w, file, line, i == FILELINE_SIZE - 1));
436+
}
369437
}
438+
439+
Ok(())
370440
}
371441

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

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

504+
// available since GCC 4.2.0, should be fine for our purpose
423505
#[cfg(all(not(all(target_os = "android", target_arch = "arm")),
424506
not(all(target_os = "linux", target_arch = "arm"))))]
425-
pub fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t;
507+
pub fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context,
508+
ip_before_insn: *mut libc::c_int)
509+
-> libc::uintptr_t;
426510

427511
#[cfg(all(not(target_os = "android"),
428512
not(all(target_os = "linux", target_arch = "arm"))))]
@@ -478,6 +562,18 @@ mod uw {
478562
(val & !1) as libc::uintptr_t
479563
}
480564

565+
// This function doesn't exist on Android or ARM/Linux, so make it same
566+
// to _Unwind_GetIP
567+
#[cfg(any(target_os = "android",
568+
all(target_os = "linux", target_arch = "arm")))]
569+
pub unsafe fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context,
570+
ip_before_insn: *mut libc::c_int)
571+
-> libc::uintptr_t
572+
{
573+
*ip_before_insn = 0;
574+
_Unwind_GetIP(ctx)
575+
}
576+
481577
// This function also doesn't exist on Android or ARM/Linux, so make it
482578
// a no-op
483579
#[cfg(any(target_os = "android",
Lines changed: 22 additions & 0 deletions
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+
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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+
// compile-flags:-g
12+
13+
#![feature(asm)]
14+
15+
use std::old_io::stderr;
16+
use std::env;
17+
18+
#[path = "backtrace-debuginfo-aux.rs"] mod aux;
19+
20+
macro_rules! pos {
21+
() => ((file!(), line!()))
22+
}
23+
24+
// we can't use a function as it will alter the backtrace
25+
macro_rules! check {
26+
($counter:expr; $($pos:expr),*) => ({
27+
if *$counter == 0 {
28+
// XXX we cannot include the current position because
29+
// the macro span takes over the last frame's file/line.
30+
dump_filelines(&[$($pos),*]);
31+
panic!();
32+
} else {
33+
*$counter -= 1;
34+
}
35+
})
36+
}
37+
38+
type Pos = (&'static str, u32);
39+
40+
// this goes to stdout and each line has to be occurred
41+
// in the following backtrace to stderr with a correct order.
42+
fn dump_filelines(filelines: &[Pos]) {
43+
for &(file, line) in filelines.iter().rev() {
44+
// extract a basename
45+
let basename = file.split(&['/', '\\'][..]).last().unwrap();
46+
println!("{}:{}", basename, line);
47+
}
48+
}
49+
50+
#[inline(never)]
51+
fn inner(counter: &mut u32, main_pos: Pos, outer_pos: Pos) {
52+
check!(counter; main_pos, outer_pos);
53+
check!(counter; main_pos, outer_pos);
54+
let inner_pos = pos!(); aux::callback(|aux_pos| {
55+
check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
56+
});
57+
let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
58+
check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
59+
});
60+
}
61+
62+
#[inline(always)]
63+
fn inner_inlined(counter: &mut u32, main_pos: Pos, outer_pos: Pos) {
64+
check!(counter; main_pos, outer_pos);
65+
check!(counter; main_pos, outer_pos);
66+
67+
#[inline(always)]
68+
fn inner_further_inlined(counter: &mut u32, main_pos: Pos, outer_pos: Pos, inner_pos: Pos) {
69+
check!(counter; main_pos, outer_pos, inner_pos);
70+
}
71+
inner_further_inlined(counter, main_pos, outer_pos, pos!());
72+
73+
let inner_pos = pos!(); aux::callback(|aux_pos| {
74+
check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
75+
});
76+
let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
77+
check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
78+
});
79+
80+
// this tests a distinction between two independent calls to the inlined function.
81+
// (un)fortunately, LLVM somehow merges two consecutive such calls into one node.
82+
inner_further_inlined(counter, main_pos, outer_pos, pos!());
83+
}
84+
85+
#[inline(never)]
86+
fn outer(mut counter: u32, main_pos: Pos) {
87+
inner(&mut counter, main_pos, pos!());
88+
inner_inlined(&mut counter, main_pos, pos!());
89+
}
90+
91+
fn check_trace(output: &str, error: &str) {
92+
// reverse the position list so we can start with the last item (which was the first line)
93+
let mut remaining: Vec<&str> = output.lines().map(|s| s.trim()).rev().collect();
94+
95+
assert!(error.contains("stack backtrace"), "no backtrace in the error: {}", error);
96+
for line in error.lines() {
97+
if !remaining.is_empty() && line.contains(remaining.last().unwrap()) {
98+
remaining.pop();
99+
}
100+
}
101+
assert!(remaining.is_empty(),
102+
"trace does not match position list: {}\n---\n{}", error, output);
103+
}
104+
105+
fn run_test(me: &str) {
106+
use std::str;
107+
use std::old_io::process::Command;
108+
109+
let mut template = Command::new(me);
110+
template.env("RUST_BACKTRACE", "1");
111+
112+
let mut i = 0;
113+
loop {
114+
let p = template.clone().arg(i.to_string()).spawn().unwrap();
115+
let out = p.wait_with_output().unwrap();
116+
let output = str::from_utf8(&out.output).unwrap();
117+
let error = str::from_utf8(&out.error).unwrap();
118+
if out.status.success() {
119+
assert!(output.contains("done."), "bad output for successful run: {}", output);
120+
break;
121+
} else {
122+
check_trace(output, error);
123+
}
124+
i += 1;
125+
}
126+
}
127+
128+
#[inline(never)]
129+
fn main() {
130+
let args: Vec<String> = env::args().collect();
131+
if args.len() >= 2 {
132+
let case = args[1].parse().unwrap();
133+
writeln!(&mut stderr(), "test case {}", case).unwrap();
134+
outer(case, pos!());
135+
println!("done.");
136+
} else {
137+
run_test(&args[0]);
138+
}
139+
}
140+

0 commit comments

Comments
 (0)