1 use backtrace::Frame;
2 use std::thread;
3
4 // Reflects the conditional compilation logic at end of src/symbolize/mod.rs
5 static NOOP: bool = false;
6 static DBGHELP: bool = !NOOP
7 && cfg!(all(
8 windows,
9 target_env = "msvc",
10 not(target_vendor = "uwp")
11 ));
12 static LIBBACKTRACE: bool = !NOOP
13 && !DBGHELP
14 && cfg!(all(
15 feature = "libbacktrace",
16 any(
17 unix,
18 all(windows, not(target_vendor = "uwp"), target_env = "gnu")
19 ),
20 not(target_os = "fuchsia"),
21 not(target_os = "emscripten"),
22 not(target_env = "uclibc"),
23 not(target_env = "libnx"),
24 ));
25 static GIMLI_SYMBOLIZE: bool = !NOOP
26 && !DBGHELP
27 && !LIBBACKTRACE
28 && cfg!(all(
29 feature = "gimli-symbolize",
30 any(unix, windows),
31 not(target_vendor = "uwp"),
32 not(target_os = "emscripten"),
33 ));
34 static MIRI_SYMBOLIZE: bool = cfg!(miri);
35
36 #[test]
37 // FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing
38 #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
39 #[rustfmt::skip] // we care about line numbers here
smoke_test_frames()40 fn smoke_test_frames() {
41 frame_1(line!());
42 #[inline(never)] fn frame_1(start_line: u32) { frame_2(start_line) }
43 #[inline(never)] fn frame_2(start_line: u32) { frame_3(start_line) }
44 #[inline(never)] fn frame_3(start_line: u32) { frame_4(start_line) }
45 #[inline(never)] fn frame_4(start_line: u32) {
46 let mut v = Vec::new();
47 backtrace::trace(|cx| {
48 v.push(cx.clone());
49 true
50 });
51
52 // Various platforms have various bits of weirdness about their
53 // backtraces. To find a good starting spot let's search through the
54 // frames
55 let target = frame_4 as usize;
56 let offset = v
57 .iter()
58 .map(|frame| frame.symbol_address() as usize)
59 .enumerate()
60 .filter_map(|(i, sym)| {
61 if sym >= target {
62 Some((sym, i))
63 } else {
64 None
65 }
66 })
67 .min()
68 .unwrap()
69 .1;
70 let mut frames = v[offset..].iter();
71
72 assert_frame(
73 frames.next().unwrap(),
74 frame_4 as usize,
75 "frame_4",
76 "tests/smoke.rs",
77 start_line + 6,
78 9,
79 );
80 assert_frame(
81 frames.next().unwrap(),
82 frame_3 as usize,
83 "frame_3",
84 "tests/smoke.rs",
85 start_line + 3,
86 52,
87 );
88 assert_frame(
89 frames.next().unwrap(),
90 frame_2 as usize,
91 "frame_2",
92 "tests/smoke.rs",
93 start_line + 2,
94 52,
95 );
96 assert_frame(
97 frames.next().unwrap(),
98 frame_1 as usize,
99 "frame_1",
100 "tests/smoke.rs",
101 start_line + 1,
102 52,
103 );
104 assert_frame(
105 frames.next().unwrap(),
106 smoke_test_frames as usize,
107 "smoke_test_frames",
108 "",
109 0,
110 0,
111 );
112 }
113
114 fn assert_frame(
115 frame: &Frame,
116 actual_fn_pointer: usize,
117 expected_name: &str,
118 expected_file: &str,
119 expected_line: u32,
120 expected_col: u32,
121 ) {
122 backtrace::resolve_frame(frame, |sym| {
123 print!("symbol ip:{:?} address:{:?} ", frame.ip(), frame.symbol_address());
124 if let Some(name) = sym.name() {
125 print!("name:{} ", name);
126 }
127 if let Some(file) = sym.filename() {
128 print!("file:{} ", file.display());
129 }
130 if let Some(lineno) = sym.lineno() {
131 print!("lineno:{} ", lineno);
132 }
133 if let Some(colno) = sym.colno() {
134 print!("colno:{} ", colno);
135 }
136 println!();
137 });
138
139 let ip = frame.ip() as usize;
140 let sym = frame.symbol_address() as usize;
141 assert!(ip >= sym);
142 assert!(
143 sym >= actual_fn_pointer,
144 "{:?} < {:?} ({} {}:{}:{})",
145 sym as *const usize,
146 actual_fn_pointer as *const usize,
147 expected_name,
148 expected_file,
149 expected_line,
150 expected_col,
151 );
152
153 // windows dbghelp is *quite* liberal (and wrong) in many of its reports
154 // right now...
155 //
156 // This assertion can also fail for release builds, so skip it there
157 if cfg!(debug_assertions) {
158 assert!(sym - actual_fn_pointer < 1024);
159 }
160
161 let mut resolved = 0;
162 let can_resolve = LIBBACKTRACE || GIMLI_SYMBOLIZE || MIRI_SYMBOLIZE;
163 let can_resolve_cols = GIMLI_SYMBOLIZE || MIRI_SYMBOLIZE;
164
165 let mut name = None;
166 let mut addr = None;
167 let mut col = None;
168 let mut line = None;
169 let mut file = None;
170 backtrace::resolve_frame(frame, |sym| {
171 resolved += 1;
172 name = sym.name().map(|v| v.to_string());
173 addr = sym.addr();
174 col = sym.colno();
175 line = sym.lineno();
176 file = sym.filename().map(|v| v.to_path_buf());
177 });
178
179 // dbghelp doesn't always resolve symbols right now
180 match resolved {
181 0 => return assert!(!can_resolve),
182 _ => {}
183 }
184
185 if can_resolve {
186 let name = name.expect("didn't find a name");
187
188 // in release mode names get weird as functions can get merged
189 // together with `mergefunc`, so only assert this in debug mode
190 if cfg!(debug_assertions) {
191 assert!(
192 name.contains(expected_name),
193 "didn't find `{}` in `{}`",
194 expected_name,
195 name
196 );
197 }
198 }
199
200 if can_resolve {
201 addr.expect("didn't find a symbol");
202 }
203
204 if cfg!(debug_assertions) {
205 let line = line.expect("didn't find a line number");
206 let file = file.expect("didn't find a line number");
207 if !expected_file.is_empty() {
208 assert!(
209 file.ends_with(expected_file),
210 "{:?} didn't end with {:?}",
211 file,
212 expected_file
213 );
214 }
215 if expected_line != 0 {
216 assert!(
217 line == expected_line,
218 "bad line number on frame for `{}`: {} != {}",
219 expected_name,
220 line,
221 expected_line
222 );
223 }
224 if can_resolve_cols {
225 let col = col.expect("didn't find a column number");
226 if expected_col != 0 {
227 assert!(
228 col == expected_col,
229 "bad column number on frame for `{}`: {} != {}",
230 expected_name,
231 col,
232 expected_col
233 );
234 }
235 }
236 }
237 }
238 }
239
240 #[test]
many_threads()241 fn many_threads() {
242 let threads = (0..16)
243 .map(|_| {
244 thread::spawn(|| {
245 for _ in 0..16 {
246 backtrace::trace(|frame| {
247 backtrace::resolve(frame.ip(), |symbol| {
248 let _s = symbol.name().map(|s| s.to_string());
249 });
250 true
251 });
252 }
253 })
254 })
255 .collect::<Vec<_>>();
256
257 for t in threads {
258 t.join().unwrap()
259 }
260 }
261
262 #[test]
263 #[cfg(feature = "rustc-serialize")]
is_rustc_serialize()264 fn is_rustc_serialize() {
265 extern crate rustc_serialize;
266
267 fn is_encode<T: rustc_serialize::Encodable>() {}
268 fn is_decode<T: rustc_serialize::Decodable>() {}
269
270 is_encode::<backtrace::Backtrace>();
271 is_decode::<backtrace::Backtrace>();
272 }
273
274 #[test]
275 #[cfg(feature = "serde")]
is_serde()276 fn is_serde() {
277 extern crate serde;
278
279 fn is_serialize<T: serde::ser::Serialize>() {}
280 fn is_deserialize<T: serde::de::DeserializeOwned>() {}
281
282 is_serialize::<backtrace::Backtrace>();
283 is_deserialize::<backtrace::Backtrace>();
284 }
285
286 #[test]
sp_smoke_test()287 fn sp_smoke_test() {
288 let mut refs = vec![];
289 recursive_stack_references(&mut refs);
290 return;
291
292 #[inline(never)]
293 fn recursive_stack_references(refs: &mut Vec<usize>) {
294 assert!(refs.len() < 5);
295
296 let x = refs.len();
297 refs.push(&x as *const _ as usize);
298
299 if refs.len() < 5 {
300 recursive_stack_references(refs);
301 eprintln!("exiting: {}", x);
302 return;
303 }
304
305 backtrace::trace(make_trace_closure(refs));
306 eprintln!("exiting: {}", x);
307 }
308
309 // NB: the following `make_*` functions are pulled out of line, rather than
310 // defining their results as inline closures at their call sites, so that
311 // the resulting closures don't have "recursive_stack_references" in their
312 // mangled names.
313
314 fn make_trace_closure<'a>(
315 refs: &'a mut Vec<usize>,
316 ) -> impl FnMut(&backtrace::Frame) -> bool + 'a {
317 let mut child_sp = None;
318 let mut child_ref = None;
319 move |frame| {
320 eprintln!("\n=== frame ===================================");
321
322 let mut is_recursive_stack_references = false;
323 backtrace::resolve(frame.ip(), |sym| {
324 is_recursive_stack_references |= (LIBBACKTRACE || GIMLI_SYMBOLIZE)
325 && sym
326 .name()
327 .and_then(|name| name.as_str())
328 .map_or(false, |name| {
329 eprintln!("name = {}", name);
330 name.contains("recursive_stack_references")
331 })
332 });
333
334 let sp = frame.sp() as usize;
335 eprintln!("sp = {:p}", sp as *const u8);
336 if sp == 0 {
337 // If the SP is null, then we don't have an implementation for
338 // getting the SP on this target. Just keep walking the stack,
339 // but don't make our assertions about the on-stack pointers and
340 // SP values.
341 return true;
342 }
343
344 // The stack grows down.
345 if let Some(child_sp) = child_sp {
346 assert!(child_sp <= sp);
347 }
348
349 if is_recursive_stack_references {
350 let r = refs.pop().unwrap();
351 eprintln!("ref = {:p}", r as *const u8);
352 if sp != 0 {
353 assert!(r > sp);
354 if let Some(child_ref) = child_ref {
355 assert!(sp >= child_ref);
356 }
357 }
358 child_ref = Some(r);
359 }
360
361 child_sp = Some(sp);
362 true
363 }
364 }
365 }
366