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:
parent
b1d2f2c64c
commit
afeed50677
3 changed files with 264 additions and 25 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"]))
|
||||
);
|
||||
}
|
||||
|
|
@ -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() {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue