Skip to content

Commit 7925c79

Browse files
Refactor unix backtracing. NFC.
1 parent aca2057 commit 7925c79

File tree

8 files changed

+681
-586
lines changed

8 files changed

+681
-586
lines changed

src/libstd/sys/unix/backtrace.rs

-586
This file was deleted.

src/libstd/sys/unix/backtrace/mod.rs

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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+
/// Backtrace support built on libgcc with some extra OS-specific support
12+
///
13+
/// Some methods of getting a backtrace:
14+
///
15+
/// * The backtrace() functions on unix. It turns out this doesn't work very
16+
/// well for green threads on OSX, and the address to symbol portion of it
17+
/// suffers problems that are described below.
18+
///
19+
/// * Using libunwind. This is more difficult than it sounds because libunwind
20+
/// isn't installed everywhere by default. It's also a bit of a hefty library,
21+
/// so possibly not the best option. When testing, libunwind was excellent at
22+
/// getting both accurate backtraces and accurate symbols across platforms.
23+
/// This route was not chosen in favor of the next option, however.
24+
///
25+
/// * We're already using libgcc_s for exceptions in rust (triggering thread
26+
/// unwinding and running destructors on the stack), and it turns out that it
27+
/// conveniently comes with a function that also gives us a backtrace. All of
28+
/// these functions look like _Unwind_*, but it's not quite the full
29+
/// repertoire of the libunwind API. Due to it already being in use, this was
30+
/// the chosen route of getting a backtrace.
31+
///
32+
/// After choosing libgcc_s for backtraces, the sad part is that it will only
33+
/// give us a stack trace of instruction pointers. Thankfully these instruction
34+
/// pointers are accurate (they work for green and native threads), but it's
35+
/// then up to us again to figure out how to translate these addresses to
36+
/// symbols. As with before, we have a few options. Before, that, a little bit
37+
/// of an interlude about symbols. This is my very limited knowledge about
38+
/// symbol tables, and this information is likely slightly wrong, but the
39+
/// general idea should be correct.
40+
///
41+
/// When talking about symbols, it's helpful to know a few things about where
42+
/// symbols are located. Some symbols are located in the dynamic symbol table
43+
/// of the executable which in theory means that they're available for dynamic
44+
/// linking and lookup. Other symbols end up only in the local symbol table of
45+
/// the file. This loosely corresponds to pub and priv functions in Rust.
46+
///
47+
/// Armed with this knowledge, we know that our solution for address to symbol
48+
/// translation will need to consult both the local and dynamic symbol tables.
49+
/// With that in mind, here's our options of translating an address to
50+
/// a symbol.
51+
///
52+
/// * Use dladdr(). The original backtrace()-based idea actually uses dladdr()
53+
/// behind the scenes to translate, and this is why backtrace() was not used.
54+
/// Conveniently, this method works fantastically on OSX. It appears dladdr()
55+
/// uses magic to consult the local symbol table, or we're putting everything
56+
/// in the dynamic symbol table anyway. Regardless, for OSX, this is the
57+
/// method used for translation. It's provided by the system and easy to do.o
58+
///
59+
/// Sadly, all other systems have a dladdr() implementation that does not
60+
/// consult the local symbol table. This means that most functions are blank
61+
/// because they don't have symbols. This means that we need another solution.
62+
///
63+
/// * Use unw_get_proc_name(). This is part of the libunwind api (not the
64+
/// libgcc_s version of the libunwind api), but involves taking a dependency
65+
/// to libunwind. We may pursue this route in the future if we bundle
66+
/// libunwind, but libunwind was unwieldy enough that it was not chosen at
67+
/// this time to provide this functionality.
68+
///
69+
/// * Shell out to a utility like `readelf`. Crazy though it may sound, it's a
70+
/// semi-reasonable solution. The stdlib already knows how to spawn processes,
71+
/// so in theory it could invoke readelf, parse the output, and consult the
72+
/// local/dynamic symbol tables from there. This ended up not getting chosen
73+
/// due to the craziness of the idea plus the advent of the next option.
74+
///
75+
/// * Use `libbacktrace`. It turns out that this is a small library bundled in
76+
/// the gcc repository which provides backtrace and symbol translation
77+
/// functionality. All we really need from it is the backtrace functionality,
78+
/// and we only really need this on everything that's not OSX, so this is the
79+
/// chosen route for now.
80+
///
81+
/// In summary, the current situation uses libgcc_s to get a trace of stack
82+
/// pointers, and we use dladdr() or libbacktrace to translate these addresses
83+
/// to symbols. This is a bit of a hokey implementation as-is, but it works for
84+
/// all unix platforms we support right now, so it at least gets the job done.
85+
86+
pub use self::tracing::write;
87+
88+
use io;
89+
use io::prelude::*;
90+
use libc;
91+
use str;
92+
93+
use sys_common::backtrace::{demangle, HEX_WIDTH};
94+
95+
// tracing impls:
96+
mod tracing;
97+
// symbol resolvers:
98+
mod printing;
99+
100+
pub fn output(w: &mut Write, idx: isize, addr: *mut libc::c_void,
101+
s: Option<&[u8]>) -> io::Result<()> {
102+
try!(write!(w, " {:2}: {:2$?} - ", idx, addr, HEX_WIDTH));
103+
match s.and_then(|s| str::from_utf8(s).ok()) {
104+
Some(string) => try!(demangle(w, string)),
105+
None => try!(write!(w, "<unknown>")),
106+
}
107+
w.write_all(&['\n' as u8])
108+
}
109+
110+
pub fn output_fileline(w: &mut Write, file: &[u8], line: libc::c_int,
111+
more: bool) -> io::Result<()> {
112+
let file = str::from_utf8(file).unwrap_or("<unknown>");
113+
// prior line: " ##: {:2$} - func"
114+
try!(write!(w, " {:3$}at {}:{}", "", file, line, HEX_WIDTH));
115+
if more {
116+
try!(write!(w, " <... and possibly more>"));
117+
}
118+
w.write_all(&['\n' as u8])
119+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2014-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+
pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
12+
_symaddr: *mut libc::c_void) -> io::Result<()> {
13+
use sys::backtrace::{output};
14+
use intrinsics;
15+
#[repr(C)]
16+
struct Dl_info {
17+
dli_fname: *const libc::c_char,
18+
dli_fbase: *mut libc::c_void,
19+
dli_sname: *const libc::c_char,
20+
dli_saddr: *mut libc::c_void,
21+
}
22+
extern {
23+
fn dladdr(addr: *const libc::c_void,
24+
info: *mut Dl_info) -> libc::c_int;
25+
}
26+
27+
let mut info: Dl_info = unsafe { intrinsics::init() };
28+
if unsafe { dladdr(addr, &mut info) == 0 } {
29+
output(w, idx,addr, None)
30+
} else {
31+
output(w, idx, addr, Some(unsafe {
32+
CStr::from_ptr(info.dli_sname).to_bytes()
33+
}))
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// Copyright 2014-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+
use io;
12+
use io::prelude::*;
13+
use libc;
14+
15+
use sys::backtrace::{output, output_fileline};
16+
pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
17+
symaddr: *mut libc::c_void) -> io::Result<()> {
18+
use env;
19+
use ffi::CStr;
20+
use os::unix::prelude::*;
21+
use ptr;
22+
23+
////////////////////////////////////////////////////////////////////////
24+
// libbacktrace.h API
25+
////////////////////////////////////////////////////////////////////////
26+
type backtrace_syminfo_callback =
27+
extern "C" fn(data: *mut libc::c_void,
28+
pc: libc::uintptr_t,
29+
symname: *const libc::c_char,
30+
symval: libc::uintptr_t,
31+
symsize: libc::uintptr_t);
32+
type backtrace_full_callback =
33+
extern "C" fn(data: *mut libc::c_void,
34+
pc: libc::uintptr_t,
35+
filename: *const libc::c_char,
36+
lineno: libc::c_int,
37+
function: *const libc::c_char) -> libc::c_int;
38+
type backtrace_error_callback =
39+
extern "C" fn(data: *mut libc::c_void,
40+
msg: *const libc::c_char,
41+
errnum: libc::c_int);
42+
enum backtrace_state {}
43+
#[link(name = "backtrace", kind = "static")]
44+
#[cfg(not(test))]
45+
extern {}
46+
47+
extern {
48+
fn backtrace_create_state(filename: *const libc::c_char,
49+
threaded: libc::c_int,
50+
error: backtrace_error_callback,
51+
data: *mut libc::c_void)
52+
-> *mut backtrace_state;
53+
fn backtrace_syminfo(state: *mut backtrace_state,
54+
addr: libc::uintptr_t,
55+
cb: backtrace_syminfo_callback,
56+
error: backtrace_error_callback,
57+
data: *mut libc::c_void) -> libc::c_int;
58+
fn backtrace_pcinfo(state: *mut backtrace_state,
59+
addr: libc::uintptr_t,
60+
cb: backtrace_full_callback,
61+
error: backtrace_error_callback,
62+
data: *mut libc::c_void) -> libc::c_int;
63+
}
64+
65+
////////////////////////////////////////////////////////////////////////
66+
// helper callbacks
67+
////////////////////////////////////////////////////////////////////////
68+
69+
type FileLine = (*const libc::c_char, libc::c_int);
70+
71+
extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char,
72+
_errnum: libc::c_int) {
73+
// do nothing for now
74+
}
75+
extern fn syminfo_cb(data: *mut libc::c_void,
76+
_pc: libc::uintptr_t,
77+
symname: *const libc::c_char,
78+
_symval: libc::uintptr_t,
79+
_symsize: libc::uintptr_t) {
80+
let slot = data as *mut *const libc::c_char;
81+
unsafe { *slot = symname; }
82+
}
83+
extern fn pcinfo_cb(data: *mut libc::c_void,
84+
_pc: libc::uintptr_t,
85+
filename: *const libc::c_char,
86+
lineno: libc::c_int,
87+
_function: *const libc::c_char) -> libc::c_int {
88+
if !filename.is_null() {
89+
let slot = data as *mut &mut [FileLine];
90+
let buffer = unsafe {ptr::read(slot)};
91+
92+
// if the buffer is not full, add file:line to the buffer
93+
// and adjust the buffer for next possible calls to pcinfo_cb.
94+
if !buffer.is_empty() {
95+
buffer[0] = (filename, lineno);
96+
unsafe { ptr::write(slot, &mut buffer[1..]); }
97+
}
98+
}
99+
100+
0
101+
}
102+
103+
// The libbacktrace API supports creating a state, but it does not
104+
// support destroying a state. I personally take this to mean that a
105+
// state is meant to be created and then live forever.
106+
//
107+
// I would love to register an at_exit() handler which cleans up this
108+
// state, but libbacktrace provides no way to do so.
109+
//
110+
// With these constraints, this function has a statically cached state
111+
// that is calculated the first time this is requested. Remember that
112+
// backtracing all happens serially (one global lock).
113+
//
114+
// An additionally oddity in this function is that we initialize the
115+
// filename via self_exe_name() to pass to libbacktrace. It turns out
116+
// that on Linux libbacktrace seamlessly gets the filename of the
117+
// current executable, but this fails on freebsd. by always providing
118+
// it, we make sure that libbacktrace never has a reason to not look up
119+
// the symbols. The libbacktrace API also states that the filename must
120+
// be in "permanent memory", so we copy it to a static and then use the
121+
// static as the pointer.
122+
//
123+
// FIXME: We also call self_exe_name() on DragonFly BSD. I haven't
124+
// tested if this is required or not.
125+
unsafe fn init_state() -> *mut backtrace_state {
126+
static mut STATE: *mut backtrace_state = 0 as *mut backtrace_state;
127+
static mut LAST_FILENAME: [libc::c_char; 256] = [0; 256];
128+
if !STATE.is_null() { return STATE }
129+
let selfname = if cfg!(target_os = "freebsd") ||
130+
cfg!(target_os = "dragonfly") ||
131+
cfg!(target_os = "bitrig") ||
132+
cfg!(target_os = "openbsd") {
133+
env::current_exe().ok()
134+
} else {
135+
None
136+
};
137+
let filename = match selfname {
138+
Some(path) => {
139+
let bytes = path.as_os_str().as_bytes();
140+
if bytes.len() < LAST_FILENAME.len() {
141+
let i = bytes.iter();
142+
for (slot, val) in LAST_FILENAME.iter_mut().zip(i) {
143+
*slot = *val as libc::c_char;
144+
}
145+
LAST_FILENAME.as_ptr()
146+
} else {
147+
ptr::null()
148+
}
149+
}
150+
None => ptr::null(),
151+
};
152+
STATE = backtrace_create_state(filename, 0, error_cb,
153+
ptr::null_mut());
154+
return STATE
155+
}
156+
157+
////////////////////////////////////////////////////////////////////////
158+
// translation
159+
////////////////////////////////////////////////////////////////////////
160+
161+
// backtrace errors are currently swept under the rug, only I/O
162+
// errors are reported
163+
let state = unsafe { init_state() };
164+
if state.is_null() {
165+
return output(w, idx, addr, None)
166+
}
167+
let mut data = ptr::null();
168+
let data_addr = &mut data as *mut *const libc::c_char;
169+
let ret = unsafe {
170+
backtrace_syminfo(state, symaddr as libc::uintptr_t,
171+
syminfo_cb, error_cb,
172+
data_addr as *mut libc::c_void)
173+
};
174+
if ret == 0 || data.is_null() {
175+
try!(output(w, idx, addr, None));
176+
} else {
177+
try!(output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() })));
178+
}
179+
180+
// pcinfo may return an arbitrary number of file:line pairs,
181+
// in the order of stack trace (i.e. inlined calls first).
182+
// in order to avoid allocation, we stack-allocate a fixed size of entries.
183+
const FILELINE_SIZE: usize = 32;
184+
let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE];
185+
let ret;
186+
let fileline_count;
187+
{
188+
let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
189+
let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
190+
ret = unsafe {
191+
backtrace_pcinfo(state, addr as libc::uintptr_t,
192+
pcinfo_cb, error_cb,
193+
fileline_addr as *mut libc::c_void)
194+
};
195+
fileline_count = FILELINE_SIZE - fileline_win.len();
196+
}
197+
if ret == 0 {
198+
for (i, &(file, line)) in fileline_buf[..fileline_count].iter().enumerate() {
199+
if file.is_null() { continue; } // just to be sure
200+
let file = unsafe { CStr::from_ptr(file).to_bytes() };
201+
try!(output_fileline(w, file, line, i == FILELINE_SIZE - 1));
202+
}
203+
}
204+
205+
Ok(())
206+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2014-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+
pub use self::imp::*;
12+
13+
#[cfg(any(target_os = "macos", target_os = "ios"))]
14+
#[path = "dladdr.rs"]
15+
mod imp;
16+
17+
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
18+
#[path = "libbacktrace.rs"]
19+
mod imp;

0 commit comments

Comments
 (0)