]>
Commit | Line | Data |
---|---|---|
e9174d1e SL |
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 | use sys_common::backtrace::{output, output_fileline}; | |
15 | ||
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 ptr; | |
21 | ||
22 | //////////////////////////////////////////////////////////////////////// | |
23 | // libbacktrace.h API | |
24 | //////////////////////////////////////////////////////////////////////// | |
25 | type backtrace_syminfo_callback = | |
26 | extern "C" fn(data: *mut libc::c_void, | |
27 | pc: libc::uintptr_t, | |
28 | symname: *const libc::c_char, | |
29 | symval: libc::uintptr_t, | |
30 | symsize: libc::uintptr_t); | |
31 | type backtrace_full_callback = | |
32 | extern "C" fn(data: *mut libc::c_void, | |
33 | pc: libc::uintptr_t, | |
34 | filename: *const libc::c_char, | |
35 | lineno: libc::c_int, | |
36 | function: *const libc::c_char) -> libc::c_int; | |
37 | type backtrace_error_callback = | |
38 | extern "C" fn(data: *mut libc::c_void, | |
39 | msg: *const libc::c_char, | |
40 | errnum: libc::c_int); | |
41 | enum backtrace_state {} | |
42 | #[link(name = "backtrace", kind = "static")] | |
43 | #[cfg(not(test))] | |
44 | extern {} | |
45 | ||
46 | extern { | |
47 | fn backtrace_create_state(filename: *const libc::c_char, | |
48 | threaded: libc::c_int, | |
49 | error: backtrace_error_callback, | |
50 | data: *mut libc::c_void) | |
51 | -> *mut backtrace_state; | |
52 | fn backtrace_syminfo(state: *mut backtrace_state, | |
53 | addr: libc::uintptr_t, | |
54 | cb: backtrace_syminfo_callback, | |
55 | error: backtrace_error_callback, | |
56 | data: *mut libc::c_void) -> libc::c_int; | |
57 | fn backtrace_pcinfo(state: *mut backtrace_state, | |
58 | addr: libc::uintptr_t, | |
59 | cb: backtrace_full_callback, | |
60 | error: backtrace_error_callback, | |
61 | data: *mut libc::c_void) -> libc::c_int; | |
62 | } | |
63 | ||
64 | //////////////////////////////////////////////////////////////////////// | |
65 | // helper callbacks | |
66 | //////////////////////////////////////////////////////////////////////// | |
67 | ||
68 | type FileLine = (*const libc::c_char, libc::c_int); | |
69 | ||
70 | extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char, | |
71 | _errnum: libc::c_int) { | |
72 | // do nothing for now | |
73 | } | |
74 | extern fn syminfo_cb(data: *mut libc::c_void, | |
75 | _pc: libc::uintptr_t, | |
76 | symname: *const libc::c_char, | |
77 | _symval: libc::uintptr_t, | |
78 | _symsize: libc::uintptr_t) { | |
79 | let slot = data as *mut *const libc::c_char; | |
80 | unsafe { *slot = symname; } | |
81 | } | |
82 | extern fn pcinfo_cb(data: *mut libc::c_void, | |
83 | _pc: libc::uintptr_t, | |
84 | filename: *const libc::c_char, | |
85 | lineno: libc::c_int, | |
86 | _function: *const libc::c_char) -> libc::c_int { | |
87 | if !filename.is_null() { | |
88 | let slot = data as *mut &mut [FileLine]; | |
89 | let buffer = unsafe {ptr::read(slot)}; | |
90 | ||
91 | // if the buffer is not full, add file:line to the buffer | |
92 | // and adjust the buffer for next possible calls to pcinfo_cb. | |
93 | if !buffer.is_empty() { | |
94 | buffer[0] = (filename, lineno); | |
95 | unsafe { ptr::write(slot, &mut buffer[1..]); } | |
96 | } | |
97 | } | |
98 | ||
99 | 0 | |
100 | } | |
101 | ||
102 | // The libbacktrace API supports creating a state, but it does not | |
103 | // support destroying a state. I personally take this to mean that a | |
104 | // state is meant to be created and then live forever. | |
105 | // | |
106 | // I would love to register an at_exit() handler which cleans up this | |
107 | // state, but libbacktrace provides no way to do so. | |
108 | // | |
109 | // With these constraints, this function has a statically cached state | |
110 | // that is calculated the first time this is requested. Remember that | |
111 | // backtracing all happens serially (one global lock). | |
112 | // | |
113 | // An additionally oddity in this function is that we initialize the | |
114 | // filename via self_exe_name() to pass to libbacktrace. It turns out | |
115 | // that on Linux libbacktrace seamlessly gets the filename of the | |
116 | // current executable, but this fails on freebsd. by always providing | |
117 | // it, we make sure that libbacktrace never has a reason to not look up | |
118 | // the symbols. The libbacktrace API also states that the filename must | |
119 | // be in "permanent memory", so we copy it to a static and then use the | |
120 | // static as the pointer. | |
121 | // | |
122 | // FIXME: We also call self_exe_name() on DragonFly BSD. I haven't | |
123 | // tested if this is required or not. | |
124 | unsafe fn init_state() -> *mut backtrace_state { | |
125 | static mut STATE: *mut backtrace_state = ptr::null_mut(); | |
126 | static mut LAST_FILENAME: [libc::c_char; 256] = [0; 256]; | |
127 | if !STATE.is_null() { return STATE } | |
128 | let selfname = if cfg!(target_os = "freebsd") || | |
129 | cfg!(target_os = "dragonfly") || | |
130 | cfg!(target_os = "bitrig") || | |
131 | cfg!(target_os = "openbsd") || | |
132 | cfg!(target_os = "windows") { | |
133 | env::current_exe().ok() | |
134 | } else { | |
135 | None | |
136 | }; | |
92a42be0 | 137 | let filename = match selfname.as_ref().and_then(|s| s.to_str()) { |
e9174d1e | 138 | Some(path) => { |
92a42be0 | 139 | let bytes = path.as_bytes(); |
e9174d1e SL |
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 | 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 | } |