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 { 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>(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 { (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()); }