1 extern crate backtrace;
2 extern crate findshlibs;
3 extern crate rustc_test as test;
4
5 use std::env;
6 use std::ffi::OsStr;
7 use std::path::Path;
8 use std::process::Command;
9
10 use backtrace::Backtrace;
11 use findshlibs::{IterationControl, SharedLibrary, TargetSharedLibrary};
12 use test::{ShouldPanic, TestDesc, TestDescAndFn, TestFn, TestName};
13
make_trace() -> Vec<String>14 fn make_trace() -> Vec<String> {
15 fn foo() -> Backtrace {
16 bar()
17 }
18 #[inline(never)]
19 fn bar() -> Backtrace {
20 baz()
21 }
22 #[inline(always)]
23 fn baz() -> Backtrace {
24 Backtrace::new_unresolved()
25 }
26
27 let mut base_addr = None;
28 TargetSharedLibrary::each(|lib| {
29 base_addr = Some(lib.virtual_memory_bias().0 as isize);
30 IterationControl::Break
31 });
32 let addrfix = -base_addr.unwrap();
33
34 let trace = foo();
35 trace
36 .frames()
37 .iter()
38 .take(5)
39 .map(|x| format!("{:p}", (x.ip() as *const u8).wrapping_offset(addrfix)))
40 .collect()
41 }
42
run_cmd<P: AsRef<OsStr>>(exe: P, me: &Path, flags: Option<&str>, trace: &str) -> String43 fn run_cmd<P: AsRef<OsStr>>(exe: P, me: &Path, flags: Option<&str>, trace: &str) -> String {
44 let mut cmd = Command::new(exe);
45 cmd.env("LC_ALL", "C"); // GNU addr2line is localized, we aren't
46 cmd.env("RUST_BACKTRACE", "1"); // if a child crashes, we want to know why
47
48 if let Some(flags) = flags {
49 cmd.arg(flags);
50 }
51 cmd.arg("--exe").arg(me).arg(trace);
52
53 let output = cmd.output().unwrap();
54
55 assert!(output.status.success());
56 String::from_utf8(output.stdout).unwrap()
57 }
58
run_test(flags: Option<&str>)59 fn run_test(flags: Option<&str>) {
60 let me = env::current_exe().unwrap();
61 let mut exe = me.clone();
62 assert!(exe.pop());
63 if exe.file_name().unwrap().to_str().unwrap() == "deps" {
64 assert!(exe.pop());
65 }
66 exe.push("examples");
67 exe.push("addr2line");
68
69 assert!(exe.is_file());
70
71 let trace = make_trace();
72
73 // HACK: GNU addr2line has a bug where looking up multiple addresses can cause the second
74 // lookup to fail. Workaround by doing one address at a time.
75 for addr in &trace {
76 let theirs = run_cmd("addr2line", &me, flags, addr);
77 let ours = run_cmd(&exe, &me, flags, addr);
78
79 // HACK: GNU addr2line does not tidy up paths properly, causing double slashes to be printed.
80 // We consider our behavior to be correct, so we fix their output to match ours.
81 let theirs = theirs.replace("//", "/");
82
83 assert!(
84 theirs == ours,
85 "Output not equivalent:
86
87 $ addr2line {0} --exe {1} {2}
88 {4}
89 $ {3} {0} --exe {1} {2}
90 {5}
91
92
93 ",
94 flags.unwrap_or(""),
95 me.display(),
96 trace.join(" "),
97 exe.display(),
98 theirs,
99 ours
100 );
101 }
102 }
103
104 static FLAGS: &'static str = "aipsf";
105
make_tests() -> Vec<TestDescAndFn>106 fn make_tests() -> Vec<TestDescAndFn> {
107 (0..(1 << FLAGS.len()))
108 .map(|bits| {
109 if bits == 0 {
110 None
111 } else {
112 let mut param = String::new();
113 param.push('-');
114 for (i, flag) in FLAGS.chars().enumerate() {
115 if (bits & (1 << i)) != 0 {
116 param.push(flag);
117 }
118 }
119 Some(param)
120 }
121 })
122 .map(|param| TestDescAndFn {
123 desc: TestDesc {
124 name: TestName::DynTestName(format!(
125 "addr2line {}",
126 param.as_ref().map_or("", String::as_str)
127 )),
128 ignore: false,
129 should_panic: ShouldPanic::No,
130 allow_fail: false,
131 },
132 testfn: TestFn::DynTestFn(Box::new(move || {
133 run_test(param.as_ref().map(String::as_str))
134 })),
135 })
136 .collect()
137 }
138
main()139 fn main() {
140 if !cfg!(target_os = "linux") {
141 return;
142 }
143 let args: Vec<_> = env::args().collect();
144 test::test_main(&args, make_tests());
145 }
146