]>
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<()> { | |
e9174d1e SL |
18 | use ffi::CStr; |
19 | use ptr; | |
20 | ||
21 | //////////////////////////////////////////////////////////////////////// | |
22 | // libbacktrace.h API | |
23 | //////////////////////////////////////////////////////////////////////// | |
24 | type backtrace_syminfo_callback = | |
25 | extern "C" fn(data: *mut libc::c_void, | |
26 | pc: libc::uintptr_t, | |
27 | symname: *const libc::c_char, | |
28 | symval: libc::uintptr_t, | |
29 | symsize: libc::uintptr_t); | |
30 | type backtrace_full_callback = | |
31 | extern "C" fn(data: *mut libc::c_void, | |
32 | pc: libc::uintptr_t, | |
33 | filename: *const libc::c_char, | |
34 | lineno: libc::c_int, | |
35 | function: *const libc::c_char) -> libc::c_int; | |
36 | type backtrace_error_callback = | |
37 | extern "C" fn(data: *mut libc::c_void, | |
38 | msg: *const libc::c_char, | |
39 | errnum: libc::c_int); | |
40 | enum backtrace_state {} | |
41 | #[link(name = "backtrace", kind = "static")] | |
7453a54e | 42 | #[cfg(all(not(test), not(cargobuild)))] |
e9174d1e SL |
43 | extern {} |
44 | ||
45 | extern { | |
46 | fn backtrace_create_state(filename: *const libc::c_char, | |
47 | threaded: libc::c_int, | |
48 | error: backtrace_error_callback, | |
49 | data: *mut libc::c_void) | |
50 | -> *mut backtrace_state; | |
51 | fn backtrace_syminfo(state: *mut backtrace_state, | |
52 | addr: libc::uintptr_t, | |
53 | cb: backtrace_syminfo_callback, | |
54 | error: backtrace_error_callback, | |
55 | data: *mut libc::c_void) -> libc::c_int; | |
56 | fn backtrace_pcinfo(state: *mut backtrace_state, | |
57 | addr: libc::uintptr_t, | |
58 | cb: backtrace_full_callback, | |
59 | error: backtrace_error_callback, | |
60 | data: *mut libc::c_void) -> libc::c_int; | |
61 | } | |
62 | ||
63 | //////////////////////////////////////////////////////////////////////// | |
64 | // helper callbacks | |
65 | //////////////////////////////////////////////////////////////////////// | |
66 | ||
67 | type FileLine = (*const libc::c_char, libc::c_int); | |
68 | ||
69 | extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char, | |
70 | _errnum: libc::c_int) { | |
71 | // do nothing for now | |
72 | } | |
73 | extern fn syminfo_cb(data: *mut libc::c_void, | |
74 | _pc: libc::uintptr_t, | |
75 | symname: *const libc::c_char, | |
76 | _symval: libc::uintptr_t, | |
77 | _symsize: libc::uintptr_t) { | |
78 | let slot = data as *mut *const libc::c_char; | |
79 | unsafe { *slot = symname; } | |
80 | } | |
81 | extern fn pcinfo_cb(data: *mut libc::c_void, | |
82 | _pc: libc::uintptr_t, | |
83 | filename: *const libc::c_char, | |
84 | lineno: libc::c_int, | |
85 | _function: *const libc::c_char) -> libc::c_int { | |
86 | if !filename.is_null() { | |
87 | let slot = data as *mut &mut [FileLine]; | |
88 | let buffer = unsafe {ptr::read(slot)}; | |
89 | ||
90 | // if the buffer is not full, add file:line to the buffer | |
91 | // and adjust the buffer for next possible calls to pcinfo_cb. | |
92 | if !buffer.is_empty() { | |
93 | buffer[0] = (filename, lineno); | |
94 | unsafe { ptr::write(slot, &mut buffer[1..]); } | |
95 | } | |
96 | } | |
97 | ||
98 | 0 | |
99 | } | |
100 | ||
101 | // The libbacktrace API supports creating a state, but it does not | |
102 | // support destroying a state. I personally take this to mean that a | |
103 | // state is meant to be created and then live forever. | |
104 | // | |
105 | // I would love to register an at_exit() handler which cleans up this | |
106 | // state, but libbacktrace provides no way to do so. | |
107 | // | |
108 | // With these constraints, this function has a statically cached state | |
109 | // that is calculated the first time this is requested. Remember that | |
110 | // backtracing all happens serially (one global lock). | |
111 | // | |
54a0048b SL |
112 | // Things don't work so well on not-Linux since libbacktrace can't track |
113 | // down that executable this is. We at one point used env::current_exe but | |
114 | // it turns out that there are some serious security issues with that | |
115 | // approach. | |
e9174d1e | 116 | // |
54a0048b SL |
117 | // Specifically, on certain platforms like BSDs, a malicious actor can cause |
118 | // an arbitrary file to be placed at the path returned by current_exe. | |
119 | // libbacktrace does not behave defensively in the presence of ill-formed | |
120 | // DWARF information, and has been demonstrated to segfault in at least one | |
121 | // case. There is no evidence at the moment to suggest that a more carefully | |
122 | // constructed file can't cause arbitrary code execution. As a result of all | |
123 | // of this, we don't hint libbacktrace with the path to the current process. | |
e9174d1e SL |
124 | unsafe fn init_state() -> *mut backtrace_state { |
125 | static mut STATE: *mut backtrace_state = ptr::null_mut(); | |
e9174d1e | 126 | if !STATE.is_null() { return STATE } |
54a0048b | 127 | STATE = backtrace_create_state(ptr::null(), 0, error_cb, |
e9174d1e SL |
128 | ptr::null_mut()); |
129 | STATE | |
130 | } | |
131 | ||
132 | //////////////////////////////////////////////////////////////////////// | |
133 | // translation | |
134 | //////////////////////////////////////////////////////////////////////// | |
135 | ||
136 | // backtrace errors are currently swept under the rug, only I/O | |
137 | // errors are reported | |
138 | let state = unsafe { init_state() }; | |
139 | if state.is_null() { | |
140 | return output(w, idx, addr, None) | |
141 | } | |
142 | let mut data = ptr::null(); | |
143 | let data_addr = &mut data as *mut *const libc::c_char; | |
144 | let ret = unsafe { | |
145 | backtrace_syminfo(state, symaddr as libc::uintptr_t, | |
146 | syminfo_cb, error_cb, | |
147 | data_addr as *mut libc::c_void) | |
148 | }; | |
149 | if ret == 0 || data.is_null() { | |
54a0048b | 150 | output(w, idx, addr, None)?; |
e9174d1e | 151 | } else { |
54a0048b | 152 | output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() }))?; |
e9174d1e SL |
153 | } |
154 | ||
155 | // pcinfo may return an arbitrary number of file:line pairs, | |
156 | // in the order of stack trace (i.e. inlined calls first). | |
157 | // in order to avoid allocation, we stack-allocate a fixed size of entries. | |
158 | const FILELINE_SIZE: usize = 32; | |
159 | let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE]; | |
160 | let ret; | |
161 | let fileline_count; | |
162 | { | |
163 | let mut fileline_win: &mut [FileLine] = &mut fileline_buf; | |
164 | let fileline_addr = &mut fileline_win as *mut &mut [FileLine]; | |
165 | ret = unsafe { | |
166 | backtrace_pcinfo(state, addr as libc::uintptr_t, | |
167 | pcinfo_cb, error_cb, | |
168 | fileline_addr as *mut libc::c_void) | |
169 | }; | |
170 | fileline_count = FILELINE_SIZE - fileline_win.len(); | |
171 | } | |
172 | if ret == 0 { | |
173 | for (i, &(file, line)) in fileline_buf[..fileline_count].iter().enumerate() { | |
174 | if file.is_null() { continue; } // just to be sure | |
175 | let file = unsafe { CStr::from_ptr(file).to_bytes() }; | |
54a0048b | 176 | output_fileline(w, file, line, i == FILELINE_SIZE - 1)?; |
e9174d1e SL |
177 | } |
178 | } | |
179 | ||
180 | Ok(()) | |
181 | } |