Skip to main content

run_make_support/
obj.rs

1//! Helpers for working with types from the [`object`] crate.
2
3use std::collections::BTreeSet;
4#[cfg(unix)]
5use std::os::unix::ffi::OsStrExt;
6use std::path::{Path, PathBuf};
7
8use bstr::BStr;
9pub use object::read::archive::ArchiveFile;
10use object::{self, Object, ObjectSymbol, Symbol};
11pub use object::{File as ObjFile, Result as ObjResult};
12
13use crate::rfs;
14
15/// Thin wrapper for owning data used by `object`, which may be either an archive or a single
16/// object file.
17pub struct BinFile {
18    path: PathBuf,
19    data: Vec<u8>,
20}
21
22impl BinFile {
23    /// Read an archive or object file at `path`.
24    pub fn read_path<P: Into<PathBuf>>(path: P) -> Self {
25        let path = path.into();
26        Self { data: rfs::read(&path), path }
27    }
28
29    /// Access the owned buffer as an archive file.
30    pub fn parse_as_archive_file(&self) -> ObjResult<ArchiveFile<'_>> {
31        ArchiveFile::parse(self.data.as_slice())
32    }
33
34    /// Access the owned buffer as an archive file.
35    pub fn parse_as_obj_file(&self) -> ObjResult<ObjFile<'_>> {
36        ObjFile::parse(self.data.as_slice())
37    }
38
39    /// If the file is an archive, run the callback for each object file. If it is an object
40    /// file, the callback will run once.
41    ///
42    /// The callback receives the parsed object file and its name in the archive or on disk.
43    pub fn for_each_object(&self, mut f: impl FnMut(ObjFile<'_>, &BStr)) {
44        // Try as an archive first.
45        let as_archive = self.parse_as_archive_file();
46        if let Ok(archive) = as_archive {
47            for member in archive.members() {
48                let member = member.expect("failed to access member");
49                let obj_data = member.data(self.data.as_slice()).expect("failed to access object");
50                let obj = ObjFile::parse(obj_data).expect("failed to parse object");
51                f(obj, BStr::new(member.name()));
52            }
53
54            return;
55        }
56
57        // Fall back to parsing as an object file.
58        let as_obj = self.parse_as_obj_file();
59        if let Ok(obj) = as_obj {
60            let path_os = self.path.as_os_str();
61            let path = cfg_select! {
62                unix => path_os.as_bytes(),
63                _ => path_os
64                    .to_str()
65                    .unwrap_or_else(|| panic!("non-utf-8 path on non-unix: {:?}", self.path))
66                    .as_bytes(),
67            };
68            f(obj, BStr::new(path));
69            return;
70        }
71
72        panic!(
73            "failed to parse {:?} as either an archive or a object file: {:?}, {:?}",
74            self.path,
75            as_archive.unwrap_err(),
76            as_obj.unwrap_err(),
77        );
78    }
79
80    /// Do something with each symbol in an archive or object file.
81    ///
82    /// The callback receives:
83    ///
84    /// * The symbol.
85    /// * The parsed object file that contains the symbol.
86    /// * The name of the object file in its archive or on disk.
87    pub fn for_each_symbol(&self, mut f: impl FnMut(Symbol<'_, '_>, &ObjFile<'_>, &BStr)) {
88        self.for_each_object(|obj, obj_path| {
89            obj.symbols().for_each(|sym| f(sym, &obj, obj_path));
90        });
91    }
92}
93
94/// Given an [`object::File`], find the exported dynamic symbol names via
95/// [`object::Object::exports`]. This does not distinguish between which section the symbols appear
96/// in.
97#[track_caller]
98pub fn exported_dynamic_symbol_names<'file>(file: &'file object::File<'file>) -> Vec<&'file str> {
99    file.exports()
100        .unwrap()
101        .into_iter()
102        .filter_map(|sym| std::str::from_utf8(sym.name()).ok())
103        .collect()
104}
105
106/// Check an object file's symbols for any matching **substrings**. That is, if an object file
107/// contains a symbol named `hello_world`, it will be matched against a provided `substrings` of
108/// `["hello", "bar"]`.
109///
110/// Returns `true` if **any** of the symbols found in the object file at `path` contain a
111/// **substring** listed in `substrings`.
112///
113/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
114/// parsed as a recognized object file.
115///
116/// # Platform-specific behavior
117///
118/// On Windows MSVC, the binary (e.g. `main.exe`) does not contain the symbols, but in the separate
119/// PDB file instead. Furthermore, you will need to use [`crate::llvm::llvm_pdbutil`] as `object`
120/// crate does not handle PDB files.
121#[track_caller]
122pub fn object_contains_any_symbol_substring<P, S>(path: P, substrings: &[S]) -> bool
123where
124    P: AsRef<Path>,
125    S: AsRef<str>,
126{
127    let path = path.as_ref();
128    let blob = crate::fs::read(path);
129    let obj = object::File::parse(&*blob)
130        .unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
131    let substrings = substrings.iter().map(|s| s.as_ref()).collect::<Vec<_>>();
132    for sym in obj.symbols() {
133        for substring in &substrings {
134            if sym.name_bytes().unwrap().windows(substring.len()).any(|x| x == substring.as_bytes())
135            {
136                return true;
137            }
138        }
139    }
140    false
141}
142
143/// Check an object file's symbols for any exact matches against those provided in
144/// `candidate_symbols`.
145///
146/// Returns `true` if **any** of the symbols found in the object file at `path` contain an **exact
147/// match** against those listed in `candidate_symbols`. Take care to account for (1) platform
148/// differences and (2) calling convention and symbol decorations differences.
149///
150/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
151/// parsed as a recognized object file.
152///
153/// # Platform-specific behavior
154///
155/// See [`object_contains_any_symbol_substring`].
156#[track_caller]
157pub fn object_contains_any_symbol<P, S>(path: P, candidate_symbols: &[S]) -> bool
158where
159    P: AsRef<Path>,
160    S: AsRef<str>,
161{
162    let path = path.as_ref();
163    let blob = crate::fs::read(path);
164    let obj = object::File::parse(&*blob)
165        .unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
166    let candidate_symbols = candidate_symbols.iter().map(|s| s.as_ref()).collect::<Vec<_>>();
167    for sym in obj.symbols() {
168        for candidate_symbol in &candidate_symbols {
169            if sym.name_bytes().unwrap() == candidate_symbol.as_bytes() {
170                return true;
171            }
172        }
173    }
174    false
175}
176
177#[derive(Debug, PartialEq)]
178pub enum ContainsAllSymbolSubstringsOutcome<'a> {
179    Ok,
180    MissingSymbolSubstrings(BTreeSet<&'a str>),
181}
182
183/// Check an object file's symbols for presence of all of provided **substrings**. That is, if an
184/// object file contains symbols `["hello", "goodbye", "world"]`, it will be matched against a list
185/// of `substrings` of `["he", "go"]`. In this case, `he` is a substring of `hello`, and `go` is a
186/// substring of `goodbye`, so each of `substrings` was found.
187///
188/// Returns `true` if **all** `substrings` were present in the names of symbols for the given object
189/// file (as substrings of symbol names).
190///
191/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
192/// parsed as a recognized object file.
193///
194/// # Platform-specific behavior
195///
196/// See [`object_contains_any_symbol_substring`].
197#[track_caller]
198pub fn object_contains_all_symbol_substring<'s, P, S>(
199    path: P,
200    substrings: &'s [S],
201) -> ContainsAllSymbolSubstringsOutcome<'s>
202where
203    P: AsRef<Path>,
204    S: AsRef<str>,
205{
206    let path = path.as_ref();
207    let blob = crate::fs::read(path);
208    let obj = object::File::parse(&*blob)
209        .unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
210    let substrings = substrings.iter().map(|s| s.as_ref());
211    let mut unmatched_symbol_substrings = BTreeSet::from_iter(substrings);
212    unmatched_symbol_substrings.retain(|unmatched_symbol_substring| {
213        for sym in obj.symbols() {
214            if sym
215                .name_bytes()
216                .unwrap()
217                .windows(unmatched_symbol_substring.len())
218                .any(|x| x == unmatched_symbol_substring.as_bytes())
219            {
220                return false;
221            }
222        }
223
224        true
225    });
226
227    if unmatched_symbol_substrings.is_empty() {
228        ContainsAllSymbolSubstringsOutcome::Ok
229    } else {
230        ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(unmatched_symbol_substrings)
231    }
232}
233
234#[derive(Debug, PartialEq)]
235pub enum ContainsAllSymbolsOutcome<'a> {
236    Ok,
237    MissingSymbols(BTreeSet<&'a str>),
238}
239
240/// Check an object file contains all symbols provided in `candidate_symbols`.
241///
242/// Returns `true` if **all** of the symbols in `candidate_symbols` are found within the object file
243/// at `path` by **exact match**. Take care to account for (1) platform differences and (2) calling
244/// convention and symbol decorations differences.
245///
246/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
247/// parsed as a recognized object file.
248///
249/// # Platform-specific behavior
250///
251/// See [`object_contains_any_symbol_substring`].
252#[track_caller]
253pub fn object_contains_all_symbols<P, S>(
254    path: P,
255    candidate_symbols: &[S],
256) -> ContainsAllSymbolsOutcome<'_>
257where
258    P: AsRef<Path>,
259    S: AsRef<str>,
260{
261    let path = path.as_ref();
262    let blob = crate::fs::read(path);
263    let obj = object::File::parse(&*blob)
264        .unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
265    let candidate_symbols = candidate_symbols.iter().map(|s| s.as_ref());
266    let mut unmatched_symbols = BTreeSet::from_iter(candidate_symbols);
267    unmatched_symbols.retain(|unmatched_symbol| {
268        for sym in obj.symbols() {
269            if sym.name_bytes().unwrap() == unmatched_symbol.as_bytes() {
270                return false;
271            }
272        }
273
274        true
275    });
276
277    if unmatched_symbols.is_empty() {
278        ContainsAllSymbolsOutcome::Ok
279    } else {
280        ContainsAllSymbolsOutcome::MissingSymbols(unmatched_symbols)
281    }
282}