use crate::fas::Header;
use radare2::io_uri::on_disk_path;
use std::fs;
use std::path::{Path, PathBuf};
pub fn candidates(binary: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
let Some(binary) = on_disk_path(binary) else {
return out;
};
let Some(dir) = binary.parent() else {
return out;
};
let Some(name) = binary.file_name().and_then(|n| n.to_str()) else {
return out;
};
out.push(dir.join(format!("{name}.fas")));
let as_fas = dir.join(name).with_extension("fas");
if !out.contains(&as_fas) {
out.push(as_fas);
}
out
}
pub fn find_for_binary(binary: &Path) -> Option<PathBuf> {
let binary = on_disk_path(binary)?;
let basename = binary.file_name()?.to_str()?;
for path in candidates(&binary) {
if !path.is_file() {
continue;
}
if header_matches_binary(&path, basename) {
return Some(path);
}
}
None
}
fn header_matches_binary(path: &Path, basename: &str) -> bool {
let Ok(bytes) = fs::read(path) else {
return false;
};
let Ok(header) = Header::parse(&bytes) else {
return false;
};
match header.output_name(&bytes) {
Ok("") => true,
Ok(name) => Path::new(name)
.file_name()
.and_then(|n| n.to_str())
.map(|n| n == basename)
.unwrap_or(false),
Err(_) => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fixtures_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}
#[test]
fn prefers_named_fas() {
let p = PathBuf::from("/tmp/hello");
let c = candidates(&p);
assert_eq!(c, [PathBuf::from("/tmp/hello.fas")]);
}
#[test]
fn also_tries_replacing_a_suffix() {
let p = PathBuf::from("/tmp/msgdemo.o");
let c = candidates(&p);
assert_eq!(
c,
[
PathBuf::from("/tmp/msgdemo.o.fas"),
PathBuf::from("/tmp/msgdemo.fas"),
]
);
}
#[test]
fn finds_sibling_dump_for_elf_executable() {
let binary = fixtures_dir().join("elfexe/hello");
let found = find_for_binary(&binary).expect("hello.fas next to hello");
assert_eq!(found, fixtures_dir().join("elfexe/hello.fas"));
}
#[test]
fn finds_sibling_dump_for_elf_object() {
let binary = fixtures_dir().join("elfobj/msgdemo.o");
let found = find_for_binary(&binary).expect("msgdemo.fas next to msgdemo.o");
assert_eq!(found, fixtures_dir().join("elfobj/msgdemo.fas"));
}
#[test]
fn strips_dbg_uri_to_on_disk_path() {
let hello = fixtures_dir().join("elfexe/hello");
let uri = PathBuf::from(format!("dbg://{}", hello.display()));
assert_eq!(
radare2::io_uri::on_disk_path(&uri).as_deref(),
Some(hello.as_path())
);
let found = find_for_binary(&uri).expect("dbg:// URI still finds hello.fas");
assert_eq!(found, fixtures_dir().join("elfexe/hello.fas"));
}
#[test]
fn ignores_ptrace_pid_uri() {
assert_eq!(
radare2::io_uri::on_disk_path(Path::new("ptrace://103133")),
None
);
assert_eq!(
radare2::io_uri::ptrace_pid("ptrace://103133"),
Some("103133")
);
assert_eq!(radare2::io_uri::ptrace_pid("dbg://./hello"), None);
assert!(candidates(Path::new("ptrace://103133")).is_empty());
assert_eq!(find_for_binary(Path::new("ptrace://103133")), None);
}
#[test]
fn ptrace_exe_reads_proc() {
let pid = std::process::id();
let uri = PathBuf::from(format!("ptrace://{pid}"));
let exe = radare2::io_uri::ptrace_exe(&uri).expect("/proc/PID/exe");
assert!(exe.is_absolute());
}
}