Skip to content

Commit 8969b92

Browse files
committed
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.
1 parent b1d2f2c commit 8969b92

File tree

3 files changed

+264
-25
lines changed

3 files changed

+264
-25
lines changed
Lines changed: 159 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use std::collections::BTreeSet;
12
use std::path::Path;
23

3-
use object::{self, Object, ObjectSymbol, SymbolIterator};
4+
use object::{self, Object, ObjectSymbol};
45

56
/// Given an [`object::File`], find the exported dynamic symbol names via
67
/// [`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>) ->
1415
.collect()
1516
}
1617

17-
/// Iterate through the symbols in an object file. See [`object::Object::symbols`].
18+
/// Check an object file's symbols for any matching **substrings**. That is, if an object file
19+
/// contains a symbol named `hello_world`, it will be matched against a provided `substrings` of
20+
/// `["hello", "bar"]`.
21+
///
22+
/// Returns `true` if **any** of the symbols found in the object file at `path` contain a
23+
/// **substring** listed in `substrings`.
1824
///
1925
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
2026
/// parsed as a recognized object file.
27+
///
28+
/// # Platform-specific behavior
29+
///
30+
/// On Windows MSVC, the binary (e.g. `main.exe`) does not contain the symbols, but in the separate
31+
/// PDB file instead. Furthermore, you will need to use [`crate::llvm::llvm_pdbutil`] as `object`
32+
/// crate does not handle PDB files.
2133
#[track_caller]
22-
pub fn with_symbol_iter<P, F, R>(path: P, func: F) -> R
34+
pub fn object_contains_any_symbol_substring<P, S>(path: P, substrings: &[S]) -> bool
2335
where
2436
P: AsRef<Path>,
25-
F: FnOnce(&mut SymbolIterator<'_, '_>) -> R,
37+
S: AsRef<str>,
2638
{
2739
let path = path.as_ref();
2840
let blob = crate::fs::read(path);
29-
let f = object::File::parse(&*blob)
41+
let obj = object::File::parse(&*blob)
3042
.unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
31-
let mut iter = f.symbols();
32-
func(&mut iter)
43+
let substrings = substrings.iter().map(|s| s.as_ref()).collect::<Vec<_>>();
44+
for sym in obj.symbols() {
45+
for substring in &substrings {
46+
if sym.name_bytes().unwrap().windows(substring.len()).any(|x| x == substring.as_bytes())
47+
{
48+
return true;
49+
}
50+
}
51+
}
52+
false
3353
}
3454

35-
/// Check an object file's symbols for substrings.
55+
/// Check an object file's symbols for any exact matches against those provided in
56+
/// `candidate_symbols`.
3657
///
37-
/// Returns `true` if any of the symbols found in the object file at `path` contain a substring
38-
/// listed in `substrings`.
58+
/// Returns `true` if **any** of the symbols found in the object file at `path` contain an **exact
59+
/// match** against those listed in `candidate_symbols`. Take care to account for (1) platform
60+
/// differences and (2) calling convention and symbol decorations differences.
3961
///
4062
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
4163
/// parsed as a recognized object file.
64+
///
65+
/// # Platform-specific behavior
66+
///
67+
/// See [`object_contains_any_symbol_substring`].
4268
#[track_caller]
43-
pub fn any_symbol_contains(path: impl AsRef<Path>, substrings: &[&str]) -> bool {
44-
with_symbol_iter(path, |syms| {
45-
for sym in syms {
46-
for substring in substrings {
47-
if sym
48-
.name_bytes()
49-
.unwrap()
50-
.windows(substring.len())
51-
.any(|x| x == substring.as_bytes())
52-
{
53-
eprintln!("{:?} contains {}", sym, substring);
54-
return true;
55-
}
69+
pub fn object_contains_any_symbol<P, S>(path: P, candidate_symbols: &[S]) -> bool
70+
where
71+
P: AsRef<Path>,
72+
S: AsRef<str>,
73+
{
74+
let path = path.as_ref();
75+
let blob = crate::fs::read(path);
76+
let obj = object::File::parse(&*blob)
77+
.unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
78+
let candidate_symbols = candidate_symbols.iter().map(|s| s.as_ref()).collect::<Vec<_>>();
79+
for sym in obj.symbols() {
80+
for candidate_symbol in &candidate_symbols {
81+
if sym.name_bytes().unwrap() == candidate_symbol.as_bytes() {
82+
return true;
5683
}
5784
}
58-
false
59-
})
85+
}
86+
false
87+
}
88+
89+
#[derive(Debug, PartialEq)]
90+
pub enum ContainsAllSymbolSubstringsOutcome<'a> {
91+
Ok,
92+
MissingSymbolSubstrings(BTreeSet<&'a str>),
93+
}
94+
95+
/// Check an object file's symbols for presence of all of provided **substrings**. That is, if an
96+
/// object file contains symbols `["hello", "goodbye", "world"]`, it will be matched against a list
97+
/// of `substrings` of `["he", "go"]`. In this case, `he` is a substring of `hello`, and `go` is a
98+
/// substring of `goodbye`, so each of `substrings` was found.
99+
///
100+
/// Returns `true` if **all** `substrings` were present in the names of symbols for the given object
101+
/// file (as substrings of symbol names).
102+
///
103+
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
104+
/// parsed as a recognized object file.
105+
///
106+
/// # Platform-specific behavior
107+
///
108+
/// See [`object_contains_any_symbol_substring`].
109+
#[track_caller]
110+
pub fn object_contains_all_symbol_substring<'s, P, S>(
111+
path: P,
112+
substrings: &'s [S],
113+
) -> ContainsAllSymbolSubstringsOutcome<'s>
114+
where
115+
P: AsRef<Path>,
116+
S: AsRef<str>,
117+
{
118+
let path = path.as_ref();
119+
let blob = crate::fs::read(path);
120+
let obj = object::File::parse(&*blob)
121+
.unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
122+
let substrings = substrings.iter().map(|s| s.as_ref());
123+
let mut unmatched_symbol_substrings = BTreeSet::from_iter(substrings);
124+
unmatched_symbol_substrings.retain(|unmatched_symbol_substring| {
125+
for sym in obj.symbols() {
126+
if sym
127+
.name_bytes()
128+
.unwrap()
129+
.windows(unmatched_symbol_substring.len())
130+
.any(|x| x == unmatched_symbol_substring.as_bytes())
131+
{
132+
return false;
133+
}
134+
}
135+
136+
true
137+
});
138+
139+
if unmatched_symbol_substrings.is_empty() {
140+
ContainsAllSymbolSubstringsOutcome::Ok
141+
} else {
142+
ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(unmatched_symbol_substrings)
143+
}
144+
}
145+
146+
#[derive(Debug, PartialEq)]
147+
pub enum ContainsAllSymbolsOutcome<'a> {
148+
Ok,
149+
MissingSymbols(BTreeSet<&'a str>),
150+
}
151+
152+
/// Check an object file contains all symbols provided in `candidate_symbols`.
153+
///
154+
/// Returns `true` if **all** of the symbols in `candidate_symbols` are found within the object file
155+
/// at `path` by **exact match**. Take care to account for (1) platform differences and (2) calling
156+
/// convention and symbol decorations differences.
157+
///
158+
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
159+
/// parsed as a recognized object file.
160+
///
161+
/// # Platform-specific behavior
162+
///
163+
/// See [`object_contains_any_symbol_substring`].
164+
#[track_caller]
165+
pub fn object_contains_all_symbols<P, S>(
166+
path: P,
167+
candidate_symbols: &[S],
168+
) -> ContainsAllSymbolsOutcome<'_>
169+
where
170+
P: AsRef<Path>,
171+
S: AsRef<str>,
172+
{
173+
let path = path.as_ref();
174+
let blob = crate::fs::read(path);
175+
let obj = object::File::parse(&*blob)
176+
.unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
177+
let candidate_symbols = candidate_symbols.iter().map(|s| s.as_ref());
178+
let mut unmatched_symbols = BTreeSet::from_iter(candidate_symbols);
179+
unmatched_symbols.retain(|unmatched_symbol| {
180+
for sym in obj.symbols() {
181+
if sym.name_bytes().unwrap() == unmatched_symbol.as_bytes() {
182+
return false;
183+
}
184+
}
185+
186+
true
187+
});
188+
189+
if unmatched_symbols.is_empty() {
190+
ContainsAllSymbolsOutcome::Ok
191+
} else {
192+
ContainsAllSymbolsOutcome::MissingSymbols(unmatched_symbols)
193+
}
60194
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//! `run_make_support::symbols` helpers self test.
2+
3+
// Only intended as a basic smoke test, does not try to account for platform or calling convention
4+
// specific symbol decorations.
5+
//@ only-x86_64-unknown-linux-gnu
6+
//@ ignore-cross-compile
7+
8+
use std::collections::BTreeSet;
9+
10+
use object::{Object, ObjectSymbol};
11+
use run_make_support::symbols::{
12+
ContainsAllSymbolSubstringsOutcome, ContainsAllSymbolsOutcome,
13+
object_contains_all_symbol_substring, object_contains_all_symbols, object_contains_any_symbol,
14+
object_contains_any_symbol_substring,
15+
};
16+
use run_make_support::{object, rfs, rust_lib_name, rustc};
17+
18+
fn main() {
19+
rustc().input("sample.rs").emit("obj").edition("2024").run();
20+
21+
// `sample.rs` has two `no_mangle` functions, `eszett` and `beta`, in addition to `main`.
22+
//
23+
// These two symbol names and the test substrings used below are carefully picked to make sure
24+
// they do not overlap with `sample` and contain non-hex characters, to avoid accidentally
25+
// matching against CGU names like `sample.dad0f15d00c84e70-cgu.0`.
26+
27+
let obj_filename = "sample.o";
28+
let blob = rfs::read(obj_filename);
29+
let obj = object::File::parse(&*blob).unwrap();
30+
eprintln!("found symbols:");
31+
for sym in obj.symbols() {
32+
eprintln!("symbol = {}", sym.name().unwrap());
33+
}
34+
35+
// `hello` contains `hel`
36+
assert!(object_contains_any_symbol_substring(obj_filename, &["zett"]));
37+
assert!(object_contains_any_symbol_substring(obj_filename, &["zett", "does_not_exist"]));
38+
assert!(!object_contains_any_symbol_substring(obj_filename, &["does_not_exist"]));
39+
40+
assert!(object_contains_any_symbol(obj_filename, &["eszett"]));
41+
assert!(object_contains_any_symbol(obj_filename, &["eszett", "beta"]));
42+
assert!(!object_contains_any_symbol(obj_filename, &["zett"]));
43+
assert!(!object_contains_any_symbol(obj_filename, &["does_not_exist"]));
44+
45+
assert_eq!(
46+
object_contains_all_symbol_substring(obj_filename, &["zett"]),
47+
ContainsAllSymbolSubstringsOutcome::Ok
48+
);
49+
assert_eq!(
50+
object_contains_all_symbol_substring(obj_filename, &["zett", "bet"]),
51+
ContainsAllSymbolSubstringsOutcome::Ok
52+
);
53+
assert_eq!(
54+
object_contains_all_symbol_substring(obj_filename, &["does_not_exist"]),
55+
ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(BTreeSet::from([
56+
"does_not_exist"
57+
]))
58+
);
59+
assert_eq!(
60+
object_contains_all_symbol_substring(obj_filename, &["zett", "does_not_exist"]),
61+
ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(BTreeSet::from([
62+
"does_not_exist"
63+
]))
64+
);
65+
assert_eq!(
66+
object_contains_all_symbol_substring(obj_filename, &["zett", "bet", "does_not_exist"]),
67+
ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(BTreeSet::from([
68+
"does_not_exist"
69+
]))
70+
);
71+
72+
assert_eq!(
73+
object_contains_all_symbols(obj_filename, &["eszett"]),
74+
ContainsAllSymbolsOutcome::Ok
75+
);
76+
assert_eq!(
77+
object_contains_all_symbols(obj_filename, &["eszett", "beta"]),
78+
ContainsAllSymbolsOutcome::Ok
79+
);
80+
assert_eq!(
81+
object_contains_all_symbols(obj_filename, &["zett"]),
82+
ContainsAllSymbolsOutcome::MissingSymbols(BTreeSet::from(["zett"]))
83+
);
84+
assert_eq!(
85+
object_contains_all_symbols(obj_filename, &["zett", "beta"]),
86+
ContainsAllSymbolsOutcome::MissingSymbols(BTreeSet::from(["zett"]))
87+
);
88+
assert_eq!(
89+
object_contains_all_symbols(obj_filename, &["does_not_exist"]),
90+
ContainsAllSymbolsOutcome::MissingSymbols(BTreeSet::from(["does_not_exist"]))
91+
);
92+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![crate_type = "lib"]
2+
3+
#[unsafe(no_mangle)]
4+
pub extern "C" fn eszett() -> i8 {
5+
42
6+
}
7+
8+
#[unsafe(no_mangle)]
9+
pub extern "C" fn beta() -> u32 {
10+
1
11+
}
12+
13+
fn main() {}

0 commit comments

Comments
 (0)