| extern crate backtrace; |
| extern crate findshlibs; |
| extern crate rustc_test as test; |
| |
| use std::env; |
| use std::ffi::OsStr; |
| use std::path::Path; |
| use std::process::Command; |
| |
| use backtrace::Backtrace; |
| use findshlibs::{IterationControl, SharedLibrary, TargetSharedLibrary}; |
| use test::{ShouldPanic, TestDesc, TestDescAndFn, TestFn, TestName}; |
| |
| fn make_trace() -> Vec<String> { |
| fn foo() -> Backtrace { |
| bar() |
| } |
| #[inline(never)] |
| fn bar() -> Backtrace { |
| baz() |
| } |
| #[inline(always)] |
| fn baz() -> Backtrace { |
| Backtrace::new_unresolved() |
| } |
| |
| let mut base_addr = None; |
| TargetSharedLibrary::each(|lib| { |
| base_addr = Some(lib.virtual_memory_bias().0 as isize); |
| IterationControl::Break |
| }); |
| let addrfix = -base_addr.unwrap(); |
| |
| let trace = foo(); |
| trace |
| .frames() |
| .iter() |
| .take(5) |
| .map(|x| format!("{:p}", (x.ip() as *const u8).wrapping_offset(addrfix))) |
| .collect() |
| } |
| |
| fn run_cmd<P: AsRef<OsStr>>(exe: P, me: &Path, flags: Option<&str>, trace: &str) -> String { |
| let mut cmd = Command::new(exe); |
| cmd.env("LC_ALL", "C"); // GNU addr2line is localized, we aren't |
| cmd.env("RUST_BACKTRACE", "1"); // if a child crashes, we want to know why |
| |
| if let Some(flags) = flags { |
| cmd.arg(flags); |
| } |
| cmd.arg("--exe").arg(me).arg(trace); |
| |
| let output = cmd.output().unwrap(); |
| |
| assert!(output.status.success()); |
| String::from_utf8(output.stdout).unwrap() |
| } |
| |
| fn run_test(flags: Option<&str>) { |
| let me = env::current_exe().unwrap(); |
| let mut exe = me.clone(); |
| assert!(exe.pop()); |
| if exe.file_name().unwrap().to_str().unwrap() == "deps" { |
| assert!(exe.pop()); |
| } |
| exe.push("examples"); |
| exe.push("addr2line"); |
| |
| assert!(exe.is_file()); |
| |
| let trace = make_trace(); |
| |
| // HACK: GNU addr2line has a bug where looking up multiple addresses can cause the second |
| // lookup to fail. Workaround by doing one address at a time. |
| for addr in &trace { |
| let theirs = run_cmd("addr2line", &me, flags, addr); |
| let ours = run_cmd(&exe, &me, flags, addr); |
| |
| // HACK: GNU addr2line does not tidy up paths properly, causing double slashes to be printed. |
| // We consider our behavior to be correct, so we fix their output to match ours. |
| let theirs = theirs.replace("//", "/"); |
| |
| assert!( |
| theirs == ours, |
| "Output not equivalent: |
| |
| $ addr2line {0} --exe {1} {2} |
| {4} |
| $ {3} {0} --exe {1} {2} |
| {5} |
| |
| |
| ", |
| flags.unwrap_or(""), |
| me.display(), |
| trace.join(" "), |
| exe.display(), |
| theirs, |
| ours |
| ); |
| } |
| } |
| |
| static FLAGS: &'static str = "aipsf"; |
| |
| fn make_tests() -> Vec<TestDescAndFn> { |
| (0..(1 << FLAGS.len())) |
| .map(|bits| { |
| if bits == 0 { |
| None |
| } else { |
| let mut param = String::new(); |
| param.push('-'); |
| for (i, flag) in FLAGS.chars().enumerate() { |
| if (bits & (1 << i)) != 0 { |
| param.push(flag); |
| } |
| } |
| Some(param) |
| } |
| }) |
| .map(|param| TestDescAndFn { |
| desc: TestDesc { |
| name: TestName::DynTestName(format!( |
| "addr2line {}", |
| param.as_ref().map_or("", String::as_str) |
| )), |
| ignore: false, |
| should_panic: ShouldPanic::No, |
| allow_fail: false, |
| }, |
| testfn: TestFn::DynTestFn(Box::new(move || { |
| run_test(param.as_ref().map(String::as_str)) |
| })), |
| }) |
| .collect() |
| } |
| |
| fn main() { |
| if !cfg!(target_os = "linux") { |
| return; |
| } |
| let args: Vec<_> = env::args().collect(); |
| test::test_main(&args, make_tests()); |
| } |