1use 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
15pub struct BinFile {
18 path: PathBuf,
19 data: Vec<u8>,
20}
21
22impl BinFile {
23 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 pub fn parse_as_archive_file(&self) -> ObjResult<ArchiveFile<'_>> {
31 ArchiveFile::parse(self.data.as_slice())
32 }
33
34 pub fn parse_as_obj_file(&self) -> ObjResult<ObjFile<'_>> {
36 ObjFile::parse(self.data.as_slice())
37 }
38
39 pub fn for_each_object(&self, mut f: impl FnMut(ObjFile<'_>, &BStr)) {
44 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 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 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#[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#[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#[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#[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#[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}