136 lines
3.5 KiB
Rust
136 lines
3.5 KiB
Rust
|
use std::env;
|
||
|
use std::ffi::OsStr;
|
||
|
use std::path::Path;
|
||
|
use std::process::Command;
|
||
|
|
||
|
use backtrace::Backtrace;
|
||
|
use findshlibs::{IterationControl, SharedLibrary, TargetSharedLibrary};
|
||
|
use libtest_mimic::{Arguments, Failed, Trial};
|
||
|
|
||
|
#[inline(never)]
|
||
|
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>) -> Result<(), Failed> {
|
||
|
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
|
||
|
);
|
||
|
}
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
static FLAGS: &str = "aipsf";
|
||
|
|
||
|
fn make_tests() -> Vec<Trial> {
|
||
|
(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| {
|
||
|
Trial::test(
|
||
|
format!("addr2line {}", param.as_ref().map_or("", String::as_str)),
|
||
|
move || run_test(param.as_ref().map(String::as_str)),
|
||
|
)
|
||
|
})
|
||
|
.collect()
|
||
|
}
|
||
|
|
||
|
fn main() {
|
||
|
if !cfg!(target_os = "linux") {
|
||
|
return;
|
||
|
}
|
||
|
let args = Arguments::from_args();
|
||
|
libtest_mimic::run(&args, make_tests()).exit();
|
||
|
}
|