Skip to content

Commit 2fd4663

Browse files
committed
Make backtraces work on Redox, copying Unix implementation
1 parent 0fad1b1 commit 2fd4663

File tree

9 files changed

+366
-33
lines changed

9 files changed

+366
-33
lines changed

src/libstd/build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fn main() {
2121
let target = env::var("TARGET").expect("TARGET was not set");
2222
let host = env::var("HOST").expect("HOST was not set");
2323
if cfg!(feature = "backtrace") && !target.contains("apple") && !target.contains("msvc") &&
24-
!target.contains("emscripten") && !target.contains("fuchsia") && !target.contains("redox") {
24+
!target.contains("emscripten") && !target.contains("fuchsia") {
2525
let _ = build_libbacktrace(&host, &target);
2626
}
2727

src/libstd/sys/redox/backtrace.rs

-32
This file was deleted.

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

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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 macOS, 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 macOS. 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 macOS, 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 macOS, 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::unwind_backtrace;
87+
pub use self::printing::{foreach_symbol_fileline, resolve_symname};
88+
89+
// tracing impls:
90+
mod tracing;
91+
// symbol resolvers:
92+
mod printing;
93+
94+
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "emscripten")))]
95+
pub mod gnu {
96+
use io;
97+
use fs;
98+
use libc::c_char;
99+
use vec::Vec;
100+
use ffi::OsStr;
101+
use os::unix::ffi::OsStrExt;
102+
use io::Read;
103+
104+
pub fn get_executable_filename() -> io::Result<(Vec<c_char>, fs::File)> {
105+
let mut exefile = fs::File::open("sys:exe")?;
106+
let mut exename = Vec::new();
107+
exefile.read_to_end(&mut exename)?;
108+
if exename.last() == Some(&b'\n') {
109+
exename.pop();
110+
}
111+
let file = fs::File::open(OsStr::from_bytes(&exename))?;
112+
Ok((exename.into_iter().map(|c| c as c_char).collect(), file))
113+
}
114+
}
115+
116+
pub struct BacktraceContext;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 intrinsics;
13+
use ffi::CStr;
14+
use libc;
15+
use sys::backtrace::BacktraceContext;
16+
use sys_common::backtrace::Frame;
17+
18+
pub fn resolve_symname<F>(frame: Frame,
19+
callback: F,
20+
_: &BacktraceContext) -> io::Result<()>
21+
where F: FnOnce(Option<&str>) -> io::Result<()>
22+
{
23+
unsafe {
24+
let mut info: Dl_info = intrinsics::init();
25+
let symname = if dladdr(frame.exact_position, &mut info) == 0 {
26+
None
27+
} else {
28+
CStr::from_ptr(info.dli_sname).to_str().ok()
29+
};
30+
callback(symname)
31+
}
32+
}
33+
34+
pub fn foreach_symbol_fileline<F>(_symbol_addr: Frame,
35+
_f: F,
36+
_: &BacktraceContext) -> io::Result<bool>
37+
where F: FnMut(&[u8], libc::c_int) -> io::Result<()>
38+
{
39+
Ok(false)
40+
}
41+
42+
#[repr(C)]
43+
struct Dl_info {
44+
dli_fname: *const libc::c_char,
45+
dli_fbase: *mut libc::c_void,
46+
dli_sname: *const libc::c_char,
47+
dli_saddr: *mut libc::c_void,
48+
}
49+
50+
extern {
51+
fn dladdr(addr: *const libc::c_void,
52+
info: *mut Dl_info) -> libc::c_int;
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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::{foreach_symbol_fileline, resolve_symname};
12+
13+
#[cfg(any(target_os = "macos", target_os = "ios",
14+
target_os = "emscripten"))]
15+
#[path = "dladdr.rs"]
16+
mod imp;
17+
18+
#[cfg(not(any(target_os = "macos", target_os = "ios",
19+
target_os = "emscripten")))]
20+
mod imp {
21+
pub use sys_common::gnu::libbacktrace::{foreach_symbol_fileline, resolve_symname};
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
/// As always - iOS on arm uses SjLj exceptions and
12+
/// _Unwind_Backtrace is even not available there. Still,
13+
/// backtraces could be extracted using a backtrace function,
14+
/// which thanks god is public
15+
///
16+
/// As mentioned in a huge comment block in `super::super`, backtrace
17+
/// doesn't play well with green threads, so while it is extremely nice and
18+
/// simple to use it should be used only on iOS devices as the only viable
19+
/// option.
20+
21+
use io;
22+
use libc;
23+
use sys::backtrace::BacktraceContext;
24+
use sys_common::backtrace::Frame;
25+
26+
#[inline(never)] // if we know this is a function call, we can skip it when
27+
// tracing
28+
pub fn unwind_backtrace(frames: &mut [Frame])
29+
-> io::Result<(usize, BacktraceContext)>
30+
{
31+
const FRAME_LEN: usize = 100;
32+
assert!(FRAME_LEN >= frames.len());
33+
let mut raw_frames = [::ptr::null_mut(); FRAME_LEN];
34+
let nb_frames = unsafe {
35+
backtrace(raw_frames.as_mut_ptr(), raw_frames.len() as libc::c_int)
36+
} as usize;
37+
for (from, to) in raw_frames.iter().zip(frames.iter_mut()).take(nb_frames) {
38+
*to = Frame {
39+
exact_position: *from,
40+
symbol_addr: *from,
41+
};
42+
}
43+
Ok((nb_frames as usize, BacktraceContext))
44+
}
45+
46+
extern {
47+
fn backtrace(buf: *mut *mut libc::c_void, sz: libc::c_int) -> libc::c_int;
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 error::Error;
12+
use io;
13+
use libc;
14+
use sys::backtrace::BacktraceContext;
15+
use sys_common::backtrace::Frame;
16+
17+
use unwind as uw;
18+
19+
struct Context<'a> {
20+
idx: usize,
21+
frames: &'a mut [Frame],
22+
}
23+
24+
#[derive(Debug)]
25+
struct UnwindError(uw::_Unwind_Reason_Code);
26+
27+
impl Error for UnwindError {
28+
fn description(&self) -> &'static str {
29+
"unexpected return value while unwinding"
30+
}
31+
}
32+
33+
impl ::fmt::Display for UnwindError {
34+
fn fmt(&self, f: &mut ::fmt::Formatter) -> ::fmt::Result {
35+
write!(f, "{}: {:?}", self.description(), self.0)
36+
}
37+
}
38+
39+
#[inline(never)] // if we know this is a function call, we can skip it when
40+
// tracing
41+
pub fn unwind_backtrace(frames: &mut [Frame])
42+
-> io::Result<(usize, BacktraceContext)>
43+
{
44+
let mut cx = Context {
45+
idx: 0,
46+
frames: frames,
47+
};
48+
let result_unwind = unsafe {
49+
uw::_Unwind_Backtrace(trace_fn,
50+
&mut cx as *mut Context
51+
as *mut libc::c_void)
52+
};
53+
// See libunwind:src/unwind/Backtrace.c for the return values.
54+
// No, there is no doc.
55+
match result_unwind {
56+
// These return codes seem to be benign and need to be ignored for backtraces
57+
// to show up properly on all tested platforms.
58+
uw::_URC_END_OF_STACK | uw::_URC_FATAL_PHASE1_ERROR | uw::_URC_FAILURE => {
59+
Ok((cx.idx, BacktraceContext))
60+
}
61+
_ => {
62+
Err(io::Error::new(io::ErrorKind::Other,
63+
UnwindError(result_unwind)))
64+
}
65+
}
66+
}
67+
68+
extern fn trace_fn(ctx: *mut uw::_Unwind_Context,
69+
arg: *mut libc::c_void) -> uw::_Unwind_Reason_Code {
70+
let cx = unsafe { &mut *(arg as *mut Context) };
71+
let mut ip_before_insn = 0;
72+
let mut ip = unsafe {
73+
uw::_Unwind_GetIPInfo(ctx, &mut ip_before_insn) as *mut libc::c_void
74+
};
75+
if !ip.is_null() && ip_before_insn == 0 {
76+
// this is a non-signaling frame, so `ip` refers to the address
77+
// after the calling instruction. account for that.
78+
ip = (ip as usize - 1) as *mut _;
79+
}
80+
81+
// dladdr() on osx gets whiny when we use FindEnclosingFunction, and
82+
// it appears to work fine without it, so we only use
83+
// FindEnclosingFunction on non-osx platforms. In doing so, we get a
84+
// slightly more accurate stack trace in the process.
85+
//
86+
// This is often because panic involves the last instruction of a
87+
// function being "call std::rt::begin_unwind", with no ret
88+
// instructions after it. This means that the return instruction
89+
// pointer points *outside* of the calling function, and by
90+
// unwinding it we go back to the original function.
91+
let symaddr = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
92+
ip
93+
} else {
94+
unsafe { uw::_Unwind_FindEnclosingFunction(ip) }
95+
};
96+
97+
if cx.idx < cx.frames.len() {
98+
cx.frames[cx.idx] = Frame {
99+
symbol_addr: symaddr,
100+
exact_position: ip,
101+
};
102+
cx.idx += 1;
103+
}
104+
105+
uw::_URC_NO_REASON
106+
}

0 commit comments

Comments
 (0)