symcheck: Support both archives and object files

If parsing as an archive is unsuccessful, try parsing as an object
instead before erroring out.
This commit is contained in:
Trevor Gross
2025-09-07 04:42:44 -04:00
parent 641fc3f092
commit b2aa2200aa
@@ -7,9 +7,10 @@
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use object::read::archive::{ArchiveFile, ArchiveMember};
use object::read::archive::ArchiveFile;
use object::{
File as ObjFile, Object, ObjectSection, ObjectSymbol, Symbol, SymbolKind, SymbolScope,
File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, Symbol, SymbolKind,
SymbolScope,
};
use serde_json::Value;
@@ -25,9 +26,10 @@
If TARGET is not specified, the host target is used.
check ARCHIVE_PATHS ...
check PATHS ...
Run the same checks on the given set of paths, without invoking Cargo.
Run the same checks on the given set of paths, without invoking Cargo. Paths
may be either archives or object files.
";
fn main() {
@@ -71,7 +73,7 @@ fn check_paths<P: AsRef<Path>>(paths: &[P]) {
for path in paths {
let path = path.as_ref();
println!("Checking {}", path.display());
let archive = Archive::from_path(path);
let archive = BinFile::from_path(path);
verify_no_duplicates(&archive);
verify_core_symbols(&archive);
@@ -174,7 +176,7 @@ struct SymInfo {
}
impl SymInfo {
fn new(sym: &Symbol, obj: &ObjFile, member: &ArchiveMember) -> Self {
fn new(sym: &Symbol, obj: &ObjFile, obj_path: &str) -> Self {
// Include the section name if possible. Fall back to the `Section` debug impl if not.
let section = sym.section();
let section_name = sym
@@ -196,7 +198,7 @@ fn new(sym: &Symbol, obj: &ObjFile, member: &ArchiveMember) -> Self {
is_weak: sym.is_weak(),
is_common: sym.is_common(),
address: sym.address(),
object: String::from_utf8_lossy(member.name()).into_owned(),
object: obj_path.to_owned(),
}
}
}
@@ -206,7 +208,7 @@ fn new(sym: &Symbol, obj: &ObjFile, member: &ArchiveMember) -> Self {
/// Note that this will also locate cases where a symbol is weakly defined in more than one place.
/// Technically there are no linker errors that will come from this, but it keeps our binary more
/// straightforward and saves some distribution size.
fn verify_no_duplicates(archive: &Archive) {
fn verify_no_duplicates(archive: &BinFile) {
let mut syms = BTreeMap::<String, SymInfo>::new();
let mut dups = Vec::new();
let mut found_any = false;
@@ -263,7 +265,7 @@ fn verify_no_duplicates(archive: &Archive) {
}
/// Ensure that there are no references to symbols from `core` that aren't also (somehow) defined.
fn verify_core_symbols(archive: &Archive) {
fn verify_core_symbols(archive: &BinFile) {
let mut defined = BTreeSet::new();
let mut undefined = Vec::new();
let mut has_symbols = false;
@@ -298,39 +300,63 @@ fn verify_core_symbols(archive: &Archive) {
}
/// Thin wrapper for owning data used by `object`.
struct Archive {
struct BinFile {
path: PathBuf,
data: Vec<u8>,
}
impl Archive {
impl BinFile {
fn from_path(path: &Path) -> Self {
Self {
path: path.to_owned(),
data: fs::read(path).expect("reading file failed"),
}
}
fn file(&self) -> ArchiveFile<'_> {
ArchiveFile::parse(self.data.as_slice()).expect("archive parse failed")
fn as_archive_file(&self) -> ObjResult<ArchiveFile<'_>> {
ArchiveFile::parse(self.data.as_slice())
}
/// For a given archive, do something with each object file.
fn for_each_object(&self, mut f: impl FnMut(ObjFile, &ArchiveMember)) {
let archive = self.file();
fn as_obj_file(&self) -> ObjResult<ObjFile<'_>> {
ObjFile::parse(self.data.as_slice())
}
for member in archive.members() {
let member = member.expect("failed to access member");
let obj_data = member
.data(self.data.as_slice())
.expect("failed to access object");
let obj = ObjFile::parse(obj_data).expect("failed to parse object");
f(obj, &member);
/// For a given archive, do something with each object file. For an object file, do
/// something once.
fn for_each_object(&self, mut f: impl FnMut(ObjFile, &str)) {
// Try as an archive first.
let as_archive = self.as_archive_file();
if let Ok(archive) = as_archive {
for member in archive.members() {
let member = member.expect("failed to access member");
let obj_data = member
.data(self.data.as_slice())
.expect("failed to access object");
let obj = ObjFile::parse(obj_data).expect("failed to parse object");
f(obj, &String::from_utf8_lossy(member.name()));
}
return;
}
// Fall back to parsing as an object file.
let as_obj = self.as_obj_file();
if let Ok(obj) = as_obj {
f(obj, &self.path.to_string_lossy());
return;
}
panic!(
"failed to parse as either archive or object file: {:?}, {:?}",
as_archive.unwrap_err(),
as_obj.unwrap_err(),
);
}
/// For a given archive, do something with each symbol.
fn for_each_symbol(&self, mut f: impl FnMut(Symbol, &ObjFile, &ArchiveMember)) {
self.for_each_object(|obj, member| {
obj.symbols().for_each(|sym| f(sym, &obj, member));
/// D something with each symbol in an archive or object file.
fn for_each_symbol(&self, mut f: impl FnMut(Symbol, &ObjFile, &str)) {
self.for_each_object(|obj, obj_path| {
obj.symbols().for_each(|sym| f(sym, &obj, obj_path));
});
}
}