diff --git a/Cargo.lock b/Cargo.lock index 69fdb11..f24c28c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,7 @@ dependencies = [ "assert_cmd", "byteorder", "cargo_metadata", + "ctor", "lcov", "regex", "tempfile", @@ -154,6 +155,22 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ctor" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e9666f4a9a948d4f1dff0c08a4512b0f7c86414b23960104c243c10d79f4c3" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" + [[package]] name = "difflib" version = "0.4.0" @@ -166,6 +183,21 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dtor" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222ef136a1c687d4aa0395c175f2c4586e379924c352fd02f7870cf7de783c23" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" + [[package]] name = "errno" version = "0.3.11" diff --git a/Cargo.toml b/Cargo.toml index 0e99780..85a20e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,12 @@ repository = "https://github.com/trailofbits/anchor-coverage" [dependencies] addr2line = "0.24" anyhow = "1.0" -assert_cmd = "2.0" byteorder = "1.5" cargo_metadata = "0.19" [dev-dependencies] +assert_cmd = "2.0" +ctor = "0.4" lcov = "0.8" regex = "1.11" tempfile = "3.19" diff --git a/src/bin/anchor-coverage.rs b/src/bin/anchor-coverage.rs index ee40a4e..e38a7ae 100644 --- a/src/bin/anchor-coverage.rs +++ b/src/bin/anchor-coverage.rs @@ -1,9 +1,8 @@ use anyhow::{Result, bail, ensure}; use std::{ env::{args, current_dir}, - ffi::OsStr, - fs::{create_dir_all, read_dir, remove_dir_all}, - path::{Path, PathBuf}, + fs::{create_dir_all, remove_dir_all}, + path::Path, process::Command, }; @@ -45,7 +44,7 @@ Usage: {0} [ANCHOR_TEST_ARGS]... anchor_test(&options.args, &sbf_trace_dir)?; - let pcs_paths = collect_pcs_paths(&sbf_trace_dir)?; + let pcs_paths = anchor_coverage::util::files_with_extension(&sbf_trace_dir, "pcs")?; if pcs_paths.is_empty() { bail!( @@ -94,15 +93,3 @@ fn anchor_test(args: &[String], sbf_trace_dir: &Path) -> Result<()> { ensure!(status.success(), "command failed: {:?}", command); Ok(()) } - -fn collect_pcs_paths(path: &Path) -> Result> { - let mut pcs_paths = Vec::new(); - for result in read_dir(path)? { - let entry = result?; - let path = entry.path(); - if entry.path().extension() == Some(OsStr::new("pcs")) { - pcs_paths.push(path); - } - } - Ok(pcs_paths) -} diff --git a/src/lib.rs b/src/lib.rs index 0c2cf75..227086d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ use insn::Insn; mod start_address; use start_address::start_address; -mod util; +pub mod util; use util::{StripCurrentDir, files_with_extension}; mod vaddr; diff --git a/src/start_address.rs b/src/start_address.rs index a3eb99d..e5cd2b6 100644 --- a/src/start_address.rs +++ b/src/start_address.rs @@ -1,39 +1,35 @@ -//! A hack to get an ELF's start address by calling `objdump` on the command line. -//! -//! A proper solution would use [`gimli`] or something similar. -//! -//! [`gimli`]: https://crates.io/crates/gimli +//! Gets an ELF's start address by reading its `Elf64Hdr`. -use anyhow::{Result, bail, ensure}; -use assert_cmd::output::OutputError; -use std::{path::Path, process::Command}; +use anyhow::Result; +use std::{fs::File, io::Read, path::Path, slice::from_raw_parts_mut}; + +const EI_NIDENT: usize = 16; + +#[allow(clippy::struct_field_names)] +#[derive(Default)] +#[repr(C)] +pub struct Elf64Hdr { + pub e_ident: [u8; EI_NIDENT], + pub e_type: u16, + pub e_machine: u16, + pub e_version: u32, + pub e_entry: u64, + pub e_phoff: u64, + pub e_shoff: u64, + pub e_flags: u32, + pub e_ehsize: u16, + pub e_phentsize: u16, + pub e_phnum: u16, + pub e_shentsize: u16, + pub e_shnum: u16, + pub e_shstrndx: u16, +} pub fn start_address(path: impl AsRef) -> Result { - let mut command = Command::new("objdump"); - command.arg("-f"); - command.arg(path.as_ref()); - let output = command.output()?; - ensure!( - output.status.success(), - "command failed `{command:?}`: {}", - OutputError::new(output) - ); - let stdout = std::str::from_utf8(&output.stdout)?; - for line in stdout.lines() { - // smoelius: "start address" may (LLVM `objdump`) or may not (GNU `objdump`) be followed by - // a colon (':'). Hence, we cannot simply use `strip_prefix`. - if !line.starts_with("start address") { - continue; - } - let Some(position) = line.rfind("0x") else { - continue; - }; - if let Ok(address) = u64::from_str_radix(&line[position + 2..], 16) { - return Ok(address); - } - } - bail!( - "failed to determine start address for `{}`", - path.as_ref().display() - ); + let mut file = File::open(path)?; + let mut elf64_hdr = Elf64Hdr::default(); + let buf: &mut [u8] = + unsafe { from_raw_parts_mut((&raw mut elf64_hdr).cast::(), size_of::()) }; + file.read_exact(buf)?; + Ok(elf64_hdr.e_entry) } diff --git a/tests/ci.rs b/tests/ci.rs index f009cf4..e352e8f 100644 --- a/tests/ci.rs +++ b/tests/ci.rs @@ -1,6 +1,13 @@ use assert_cmd::Command; use regex::Regex; -use std::{fs::read_to_string, path::Path}; +use std::{env::remove_var, fs::read_to_string, path::Path}; + +#[ctor::ctor] +fn initialize() { + unsafe { + remove_var("CARGO_TERM_COLOR"); + } +} #[test] fn clippy() {