run_make_support/
symbols.rs

1use std::collections::BTreeSet;
2use std::path::Path;
3
4use object::{self, Object, ObjectSymbol};
5
6/// Given an [`object::File`], find the exported dynamic symbol names via
7/// [`object::Object::exports`]. This does not distinguish between which section the symbols appear
8/// in.
9#[track_caller]
10pub fn exported_dynamic_symbol_names<'file>(file: &'file object::File<'file>) -> Vec<&'file str> {
11    file.exports()
12        .unwrap()
13        .into_iter()
14        .filter_map(|sym| std::str::from_utf8(sym.name()).ok())
15        .collect()
16}
17
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`.
24///
25/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
26/// 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.
33#[track_caller]
34pub fn object_contains_any_symbol_substring<P, S>(path: P, substrings: &[S]) -> bool
35where
36    P: AsRef<Path>,
37    S: AsRef<str>,
38{
39    let path = path.as_ref();
40    let blob = crate::fs::read(path);
41    let obj = object::File::parse(&*blob)
42        .unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
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
53}
54
55/// Check an object file's symbols for any exact matches against those provided in
56/// `candidate_symbols`.
57///
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.
61///
62/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
63/// parsed as a recognized object file.
64///
65/// # Platform-specific behavior
66///
67/// See [`object_contains_any_symbol_substring`].
68#[track_caller]
69pub fn object_contains_any_symbol<P, S>(path: P, candidate_symbols: &[S]) -> bool
70where
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;
83            }
84        }
85    }
86    false
87}
88
89#[derive(Debug, PartialEq)]
90pub 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]
110pub fn object_contains_all_symbol_substring<'s, P, S>(
111    path: P,
112    substrings: &'s [S],
113) -> ContainsAllSymbolSubstringsOutcome<'s>
114where
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)]
147pub 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]
165pub fn object_contains_all_symbols<P, S>(
166    path: P,
167    candidate_symbols: &[S],
168) -> ContainsAllSymbolsOutcome<'_>
169where
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    }
194}