extern crate addr2line;
extern crate itertools;
extern crate glob;
use std::env;
use std::path;
use std::process;
use std::io::prelude::*;
use itertools::Itertools;
#[test]
fn identity_map() {
let target = env::current_exe().unwrap();
let mut debug = target.clone();
if cfg!(target_os = "macos") {
let fname = debug.file_name().unwrap().to_string_lossy().into_owned();
debug.set_file_name(format!("{}.dSYM", fname));
debug.push("Contents");
debug.push("Resources");
debug.push("DWARF");
debug.push(fname);
}
let ours = addr2line::Mapping::new(&debug).unwrap();
let mut theirs = spawn_oracle(target.as_path(), &[]);
let mut theirs_in = theirs.stdin.take().unwrap();
let mappings = get_test_addresses(target.as_path(), &mut theirs_in);
drop(theirs_in);
use std::io::BufReader;
let mut theirs_out = BufReader::new(theirs.stdout.take().unwrap()).lines();
let mut all = 0;
let mut excusable = 0;
let mut got = 0;
for addr in mappings {
let oracle = theirs_out.next().unwrap().unwrap();
let oracle = canonicalize_oracle_output(&*oracle);
let loc = ours.locate(addr);
let loc = loc.expect("debug symbols for test binary should be error-free");
if let Some((file, lineno, _)) = loc {
let mut file = file.to_string_lossy();
if cfg!(target_os = "macos") {
use std::borrow::Cow;
let f = path::PathBuf::from(&*file);
file = Cow::Owned(f.file_name().unwrap().to_string_lossy().into_owned());
}
assert_eq!(oracle, (Some(&*file), lineno));
got += 1;
all += 1;
} else if oracle.0.is_some() {
println!("we missed 0x{:08x}: {:?}", addr, oracle);
all += 1;
if oracle.1.is_none() {
excusable += 1;
}
} else {
}
}
assert_ne!(got, 0);
println!("resolved {}/{} addresses ({} file-only misses)",
got,
all,
excusable);
}
#[test]
#[cfg(not(target_os = "macos"))]
fn with_functions() {
let target = env::current_exe().unwrap();
let debug = target.clone();
let ours = addr2line::Mapping::with_functions(&debug).unwrap();
let mut theirs = spawn_oracle(target.as_path(), &["-f"]);
let mut theirs_in = theirs.stdin.take().unwrap();
let mappings = get_test_addresses(target.as_path(), &mut theirs_in);
drop(theirs_in);
use std::io::BufReader;
let mut theirs_out = BufReader::new(theirs.stdout.take().unwrap()).lines();
let mut all = 0;
let mut excusable = 0;
let mut func_hits = 0;
let mut got = 0;
for addr in mappings {
let function = theirs_out.next().unwrap().unwrap();
let oracle = theirs_out.next().unwrap().unwrap();
let oracle = canonicalize_oracle_output(&*oracle);
let loc = ours.locate(addr);
let loc = loc.expect("debug symbols for test binary should be error-free");
if let Some((file, lineno, func)) = loc {
let f = &*file.to_string_lossy();
let test = (Some(f), lineno);
assert_eq!(oracle, test);
if let Some(func) = func {
assert_eq!(function, func);
func_hits += 1;
}
got += 1;
all += 1;
} else if oracle.0.is_some() {
println!("we missed 0x{:08x}: {:?}", addr, oracle);
all += 1;
if oracle.1.is_none() {
excusable += 1;
}
} else {
}
}
assert_ne!(got, 0);
assert_ne!(func_hits, 0);
println!("resolved {}/{} addresses ({} file-only misses, {} function hits)",
got,
all,
excusable,
func_hits);
}
fn spawn_oracle(target: &path::Path, args: &[&str]) -> process::Child {
let (theirs, exe) = if cfg!(target_os = "macos") {
("atos", "-o")
} else {
("addr2line", "-e")
};
process::Command::new(theirs)
.arg(exe)
.arg(target)
.args(args)
.stdin(process::Stdio::piped())
.stdout(process::Stdio::piped())
.spawn()
.expect("failed to spawn their addr2line")
}
fn get_test_addresses(target: &path::Path, oracle: &mut Write) -> Vec<u64> {
let names = process::Command::new("/usr/bin/nm")
.arg(target)
.output()
.expect("failed to execute nm");
let mappings = String::from_utf8_lossy(&names.stdout);
mappings.lines()
.filter_map(|map| {
let mut fields = map.split_whitespace();
let addr = fields.next().expect("nm address missing");
let t = fields.next().unwrap();
if t != "T" && t != "t" {
None
} else {
Some(addr)
}
})
.step(5)
.map(|addr| {
oracle.write_all(b"0x").unwrap();
oracle.write_all(addr.as_bytes()).unwrap();
oracle.write_all(b"\n").unwrap();
u64::from_str_radix(addr, 16).unwrap()
})
.collect()
}
fn canonicalize_oracle_output(line: &str) -> (Option<&str>, Option<u64>) {
let spec = if cfg!(target_os = "macos") {
let line = line.rsplitn(2, '(').next().unwrap().trim_right_matches(')');
if !line.contains(':') { "??:?" } else { line }
} else {
line
};
let mut spec = spec.rsplitn(2, ':');
let lineno = spec.next().unwrap();
let lineno = if lineno == "?" {
None
} else {
Some(u64::from_str_radix(lineno, 10).unwrap())
};
let file = spec.next().unwrap();
let file = if file == "??" || file.is_empty() {
None
} else {
Some(file)
};
(file, lineno)
}