Adjust run_make_support::symbols helpers

Massage the `symbols` helpers to fill out {match all, match any} x
{substring match, exact match}:

|           | Substring match                        | Exact match                   |
|-----------|----------------------------------------|-------------------------------|
| Match any | `object_contains_any_symbol_substring` | `object_contains_any_symbol`  |
| Match all | `object_contains_all_symbol_substring` | `object_contains_all_symbols` |

As part of this, rename `any_symbol_contains` to
`object_contains_any_symbol_substring` for accuracy.
This commit is contained in:
Jieyou Xu 2025-07-12 20:10:36 +08:00
parent b1d2f2c64c
commit afeed50677
No known key found for this signature in database
GPG key ID: 045B995028EA6AFC
3 changed files with 264 additions and 25 deletions

View file

@ -1,6 +1,7 @@
use std::collections::BTreeSet;
use std::path::Path;
use object::{self, Object, ObjectSymbol, SymbolIterator};
use object::{self, Object, ObjectSymbol};
/// Given an [`object::File`], find the exported dynamic symbol names via
/// [`object::Object::exports`]. This does not distinguish between which section the symbols appear
@ -14,47 +15,180 @@ pub fn exported_dynamic_symbol_names<'file>(file: &'file object::File<'file>) ->
.collect()
}
/// Iterate through the symbols in an object file. See [`object::Object::symbols`].
/// Check an object file's symbols for any matching **substrings**. That is, if an object file
/// contains a symbol named `hello_world`, it will be matched against a provided `substrings` of
/// `["hello", "bar"]`.
///
/// Returns `true` if **any** of the symbols found in the object file at `path` contain a
/// **substring** listed in `substrings`.
///
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
/// parsed as a recognized object file.
///
/// # Platform-specific behavior
///
/// On Windows MSVC, the binary (e.g. `main.exe`) does not contain the symbols, but in the separate
/// PDB file instead. Furthermore, you will need to use [`crate::llvm::llvm_pdbutil`] as `object`
/// crate does not handle PDB files.
#[track_caller]
pub fn with_symbol_iter<P, F, R>(path: P, func: F) -> R
pub fn object_contains_any_symbol_substring<P, S>(path: P, substrings: &[S]) -> bool
where
P: AsRef<Path>,
F: FnOnce(&mut SymbolIterator<'_, '_>) -> R,
S: AsRef<str>,
{
let path = path.as_ref();
let blob = crate::fs::read(path);
let f = object::File::parse(&*blob)
let obj = object::File::parse(&*blob)
.unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
let mut iter = f.symbols();
func(&mut iter)
let substrings = substrings.iter().map(|s| s.as_ref()).collect::<Vec<_>>();
for sym in obj.symbols() {
for substring in &substrings {
if sym.name_bytes().unwrap().windows(substring.len()).any(|x| x == substring.as_bytes())
{
return true;
}
}
}
false
}
/// Check an object file's symbols for substrings.
/// Check an object file's symbols for any exact matches against those provided in
/// `candidate_symbols`.
///
/// Returns `true` if any of the symbols found in the object file at `path` contain a substring
/// listed in `substrings`.
/// Returns `true` if **any** of the symbols found in the object file at `path` contain an **exact
/// match** against those listed in `candidate_symbols`. Take care to account for (1) platform
/// differences and (2) calling convention and symbol decorations differences.
///
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
/// parsed as a recognized object file.
///
/// # Platform-specific behavior
///
/// See [`object_contains_any_symbol_substring`].
#[track_caller]
pub fn any_symbol_contains(path: impl AsRef<Path>, substrings: &[&str]) -> bool {
with_symbol_iter(path, |syms| {
for sym in syms {
for substring in substrings {
if sym
.name_bytes()
.unwrap()
.windows(substring.len())
.any(|x| x == substring.as_bytes())
{
eprintln!("{:?} contains {}", sym, substring);
return true;
}
pub fn object_contains_any_symbol<P, S>(path: P, candidate_symbols: &[S]) -> bool
where
P: AsRef<Path>,
S: AsRef<str>,
{
let path = path.as_ref();
let blob = crate::fs::read(path);
let obj = object::File::parse(&*blob)
.unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
let candidate_symbols = candidate_symbols.iter().map(|s| s.as_ref()).collect::<Vec<_>>();
for sym in obj.symbols() {
for candidate_symbol in &candidate_symbols {
if sym.name_bytes().unwrap() == candidate_symbol.as_bytes() {
return true;
}
}
false
})
}
false
}
#[derive(Debug, PartialEq)]
pub enum ContainsAllSymbolSubstringsOutcome<'a> {
Ok,
MissingSymbolSubstrings(BTreeSet<&'a str>),
}
/// Check an object file's symbols for presence of all of provided **substrings**. That is, if an
/// object file contains symbols `["hello", "goodbye", "world"]`, it will be matched against a list
/// of `substrings` of `["he", "go"]`. In this case, `he` is a substring of `hello`, and `go` is a
/// substring of `goodbye`, so each of `substrings` was found.
///
/// Returns `true` if **all** `substrings` were present in the names of symbols for the given object
/// file (as substrings of symbol names).
///
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
/// parsed as a recognized object file.
///
/// # Platform-specific behavior
///
/// See [`object_contains_any_symbol_substring`].
#[track_caller]
pub fn object_contains_all_symbol_substring<'s, P, S>(
path: P,
substrings: &'s [S],
) -> ContainsAllSymbolSubstringsOutcome<'s>
where
P: AsRef<Path>,
S: AsRef<str>,
{
let path = path.as_ref();
let blob = crate::fs::read(path);
let obj = object::File::parse(&*blob)
.unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
let substrings = substrings.iter().map(|s| s.as_ref());
let mut unmatched_symbol_substrings = BTreeSet::from_iter(substrings);
unmatched_symbol_substrings.retain(|unmatched_symbol_substring| {
for sym in obj.symbols() {
if sym
.name_bytes()
.unwrap()
.windows(unmatched_symbol_substring.len())
.any(|x| x == unmatched_symbol_substring.as_bytes())
{
return false;
}
}
true
});
if unmatched_symbol_substrings.is_empty() {
ContainsAllSymbolSubstringsOutcome::Ok
} else {
ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(unmatched_symbol_substrings)
}
}
#[derive(Debug, PartialEq)]
pub enum ContainsAllSymbolsOutcome<'a> {
Ok,
MissingSymbols(BTreeSet<&'a str>),
}
/// Check an object file contains all symbols provided in `candidate_symbols`.
///
/// Returns `true` if **all** of the symbols in `candidate_symbols` are found within the object file
/// at `path` by **exact match**. Take care to account for (1) platform differences and (2) calling
/// convention and symbol decorations differences.
///
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
/// parsed as a recognized object file.
///
/// # Platform-specific behavior
///
/// See [`object_contains_any_symbol_substring`].
#[track_caller]
pub fn object_contains_all_symbols<P, S>(
path: P,
candidate_symbols: &[S],
) -> ContainsAllSymbolsOutcome<'_>
where
P: AsRef<Path>,
S: AsRef<str>,
{
let path = path.as_ref();
let blob = crate::fs::read(path);
let obj = object::File::parse(&*blob)
.unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
let candidate_symbols = candidate_symbols.iter().map(|s| s.as_ref());
let mut unmatched_symbols = BTreeSet::from_iter(candidate_symbols);
unmatched_symbols.retain(|unmatched_symbol| {
for sym in obj.symbols() {
if sym.name_bytes().unwrap() == unmatched_symbol.as_bytes() {
return false;
}
}
true
});
if unmatched_symbols.is_empty() {
ContainsAllSymbolsOutcome::Ok
} else {
ContainsAllSymbolsOutcome::MissingSymbols(unmatched_symbols)
}
}

View file

@ -0,0 +1,92 @@
//! `run_make_support::symbols` helpers self test.
// Only intended as a basic smoke test, does not try to account for platform or calling convention
// specific symbol decorations.
//@ only-x86_64-unknown-linux-gnu
//@ ignore-cross-compile
use std::collections::BTreeSet;
use object::{Object, ObjectSymbol};
use run_make_support::symbols::{
ContainsAllSymbolSubstringsOutcome, ContainsAllSymbolsOutcome,
object_contains_all_symbol_substring, object_contains_all_symbols, object_contains_any_symbol,
object_contains_any_symbol_substring,
};
use run_make_support::{object, rfs, rust_lib_name, rustc};
fn main() {
rustc().input("sample.rs").emit("obj").edition("2024").run();
// `sample.rs` has two `no_mangle` functions, `eszett` and `beta`, in addition to `main`.
//
// These two symbol names and the test substrings used below are carefully picked to make sure
// they do not overlap with `sample` and contain non-hex characters, to avoid accidentally
// matching against CGU names like `sample.dad0f15d00c84e70-cgu.0`.
let obj_filename = "sample.o";
let blob = rfs::read(obj_filename);
let obj = object::File::parse(&*blob).unwrap();
eprintln!("found symbols:");
for sym in obj.symbols() {
eprintln!("symbol = {}", sym.name().unwrap());
}
// `hello` contains `hel`
assert!(object_contains_any_symbol_substring(obj_filename, &["zett"]));
assert!(object_contains_any_symbol_substring(obj_filename, &["zett", "does_not_exist"]));
assert!(!object_contains_any_symbol_substring(obj_filename, &["does_not_exist"]));
assert!(object_contains_any_symbol(obj_filename, &["eszett"]));
assert!(object_contains_any_symbol(obj_filename, &["eszett", "beta"]));
assert!(!object_contains_any_symbol(obj_filename, &["zett"]));
assert!(!object_contains_any_symbol(obj_filename, &["does_not_exist"]));
assert_eq!(
object_contains_all_symbol_substring(obj_filename, &["zett"]),
ContainsAllSymbolSubstringsOutcome::Ok
);
assert_eq!(
object_contains_all_symbol_substring(obj_filename, &["zett", "bet"]),
ContainsAllSymbolSubstringsOutcome::Ok
);
assert_eq!(
object_contains_all_symbol_substring(obj_filename, &["does_not_exist"]),
ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(BTreeSet::from([
"does_not_exist"
]))
);
assert_eq!(
object_contains_all_symbol_substring(obj_filename, &["zett", "does_not_exist"]),
ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(BTreeSet::from([
"does_not_exist"
]))
);
assert_eq!(
object_contains_all_symbol_substring(obj_filename, &["zett", "bet", "does_not_exist"]),
ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(BTreeSet::from([
"does_not_exist"
]))
);
assert_eq!(
object_contains_all_symbols(obj_filename, &["eszett"]),
ContainsAllSymbolsOutcome::Ok
);
assert_eq!(
object_contains_all_symbols(obj_filename, &["eszett", "beta"]),
ContainsAllSymbolsOutcome::Ok
);
assert_eq!(
object_contains_all_symbols(obj_filename, &["zett"]),
ContainsAllSymbolsOutcome::MissingSymbols(BTreeSet::from(["zett"]))
);
assert_eq!(
object_contains_all_symbols(obj_filename, &["zett", "beta"]),
ContainsAllSymbolsOutcome::MissingSymbols(BTreeSet::from(["zett"]))
);
assert_eq!(
object_contains_all_symbols(obj_filename, &["does_not_exist"]),
ContainsAllSymbolsOutcome::MissingSymbols(BTreeSet::from(["does_not_exist"]))
);
}

View file

@ -0,0 +1,13 @@
#![crate_type = "lib"]
#[unsafe(no_mangle)]
pub extern "C" fn eszett() -> i8 {
42
}
#[unsafe(no_mangle)]
pub extern "C" fn beta() -> u32 {
1
}
fn main() {}