1use std::collections::BTreeMap;
4
5use object::{Object, ObjectSection, ObjectSymbol, SymbolKind};
6
7use crate::symbols::demangle;
8use crate::x86::normalize_x86;
9use crate::{
10 ArtifactDataSegment, ArtifactFingerprint, ArtifactImport, ArtifactImportKind, ArtifactIr,
11 ArtifactRelocation, ArtifactSection, ArtifactSymbol, NormalizedInstructions,
12};
13
14pub fn collect_undefined_imports<'data, S>(
21 symbols: impl IntoIterator<Item = S>,
22 ir: &mut ArtifactIr,
23) where
24 S: ObjectSymbol<'data>,
25{
26 let mut names = BTreeMap::new();
27 for symbol in symbols {
28 if !symbol.is_undefined() {
29 continue;
30 }
31 let Some(name) = symbol
32 .name()
33 .ok()
34 .filter(|name| !name.is_empty())
35 .map(demangle)
36 else {
37 continue;
38 };
39 let kind = if matches!(symbol.kind(), SymbolKind::Text | SymbolKind::Unknown) {
43 ArtifactImportKind::Function
44 } else {
45 ArtifactImportKind::Other
46 };
47 names
48 .entry(name)
49 .and_modify(|existing| {
50 if kind == ArtifactImportKind::Function {
51 *existing = kind;
52 }
53 })
54 .or_insert(kind);
55 }
56 ir.imports
57 .extend(names.into_iter().map(|(name, kind)| ArtifactImport {
58 module: None,
59 name: Some(name),
60 kind,
61 }));
62}
63
64pub fn collect_sections(file: &object::File<'_>, ir: &mut ArtifactIr) -> Result<(), object::Error> {
71 for section in file.sections() {
72 let (offset, size) = section.file_range().unwrap_or((0, 0));
73 ir.sections.push(ArtifactSection {
74 name: section.name().ok().map(str::to_owned),
75 offset,
76 size,
77 executable: section.kind() == object::SectionKind::Text,
78 });
79 if section.kind() == object::SectionKind::ReadOnlyData {
80 let data = section.data()?;
81 if !data.is_empty() {
82 ir.data_segments.push(ArtifactDataSegment {
83 fingerprint: data_fingerprint(section.name().ok(), data),
84 section: u32::try_from(section.index().0).ok(),
85 offset,
86 bytes: data.to_vec(),
87 });
88 }
89 }
90 for (relocation_offset, relocation) in section.relocations() {
91 ir.relocations.push(ArtifactRelocation {
92 section: u32::try_from(section.index().0).ok(),
93 offset: offset.saturating_add(relocation_offset),
94 kind: format!("{:?}", relocation.kind()),
95 target: relocation_target_name(file, relocation.target()),
96 });
97 }
98 }
99 Ok(())
100}
101
102#[must_use]
109pub fn symbol_fingerprint(
110 name: Option<&str>,
111 section: Option<&str>,
112 normalized: Option<&NormalizedInstructions>,
113 code: &[u8],
114) -> ArtifactFingerprint {
115 let mut identity = Vec::new();
116 append_field(&mut identity, name.unwrap_or_default().as_bytes());
117 append_field(&mut identity, section.unwrap_or_default().as_bytes());
118 if let Some(normalized) = normalized {
119 identity.push(1);
120 append_field(&mut identity, normalized.version.as_bytes());
121 append_field(&mut identity, &normalized.bytes);
122 } else {
123 identity.push(0);
124 append_field(&mut identity, code);
125 }
126 ArtifactFingerprint::from_content("native-symbol", &identity)
127}
128
129#[must_use]
131pub fn data_fingerprint(section: Option<&str>, data: &[u8]) -> ArtifactFingerprint {
132 let mut identity = Vec::new();
133 append_field(&mut identity, section.unwrap_or_default().as_bytes());
134 append_field(&mut identity, data);
135 ArtifactFingerprint::from_content("native-data", &identity)
136}
137
138#[must_use]
147pub fn symbol_size(
148 address: u64,
149 declared_size: u64,
150 is_zero_size_alias: bool,
151 following_addresses: impl IntoIterator<Item = u64>,
152 section_end: u64,
153) -> u64 {
154 if declared_size != 0 {
155 return declared_size;
156 }
157 if is_zero_size_alias {
158 return 0;
159 }
160 following_addresses
161 .into_iter()
162 .find(|candidate| *candidate > address)
163 .unwrap_or(section_end)
164 .saturating_sub(address)
165}
166
167#[must_use]
171pub fn trim_inferred_symbol_padding(code: &[u8], architecture: object::Architecture) -> &[u8] {
172 crate::x86::trim_inferred_padding(code, architecture)
173}
174
175#[derive(Debug, Clone, Copy)]
177pub struct NativeSymbolRange {
178 pub fingerprint: ArtifactFingerprint,
180 pub index: object::SymbolIndex,
182 pub section: object::SectionIndex,
184 pub address: u64,
186 pub size: u64,
188}
189
190pub fn collect_text_symbols(
196 file: &object::File<'_>,
197 ir: &mut ArtifactIr,
198) -> Result<Vec<NativeSymbolRange>, object::Error> {
199 let mut ranges = Vec::new();
200 for section in file
201 .sections()
202 .filter(|section| section.kind() == object::SectionKind::Text)
203 {
204 let section_index = section.index();
205 let data = section.data()?;
206 let (section_offset, _) = section.file_range().unwrap_or((0, 0));
207 let mut symbols: Vec<_> = file
208 .symbols()
209 .filter(|symbol| {
210 symbol.section_index() == Some(section_index)
211 && symbol.kind() == SymbolKind::Text
212 && !symbol.is_undefined()
213 })
214 .collect();
215 symbols.sort_by_key(ObjectSymbol::address);
216 for (position, symbol) in symbols.iter().enumerate() {
217 let Some(relative) = symbol.address().checked_sub(section.address()) else {
218 continue;
219 };
220 let alias = symbol.size() == 0
221 && (symbols[..position]
222 .iter()
223 .any(|prior| prior.address() == symbol.address())
224 || symbols
225 .iter()
226 .any(|other| other.address() == symbol.address() && other.size() != 0));
227 let size = symbol_size(
228 symbol.address(),
229 symbol.size(),
230 alias,
231 symbols[position.saturating_add(1)..]
232 .iter()
233 .map(ObjectSymbol::address),
234 section
235 .address()
236 .saturating_add(u64::try_from(data.len()).unwrap_or(u64::MAX)),
237 );
238 let (Ok(start), Ok(size)) = (usize::try_from(relative), usize::try_from(size)) else {
239 continue;
240 };
241 let Some(raw) = data.get(start..start.saturating_add(size)) else {
242 continue;
243 };
244 let code = if symbol.size() == 0 {
245 trim_inferred_symbol_padding(raw, file.architecture())
246 } else {
247 raw
248 };
249 if code.is_empty() && symbol.size() != 0 {
250 continue;
251 }
252 let raw_name = symbol.name().ok().filter(|name| !name.is_empty());
253 let name = raw_name.map(demangle);
254 let fingerprint_name = raw_name.map(|name| {
255 let canonical = if file.format() == object::BinaryFormat::MachO {
256 name.strip_prefix('_').unwrap_or(name)
257 } else {
258 name
259 };
260 demangle(canonical)
261 });
262 let normalized = normalize_x86(code, file.architecture());
263 let fingerprint = symbol_fingerprint(
264 fingerprint_name.as_deref(),
265 Some("text"),
266 normalized.as_ref(),
267 code,
268 );
269 let size = u64::try_from(code.len()).unwrap_or(u64::MAX);
270 ir.symbols.push(ArtifactSymbol {
271 fingerprint,
272 name,
273 exported: symbol.is_global(),
274 section: u32::try_from(section_index.0).ok(),
275 offset: section_offset.saturating_add(relative),
276 size,
277 size_inferred: symbol.size() == 0,
278 code: code.to_vec(),
279 normalized,
280 inline_stack: Vec::new(),
281 });
282 ranges.push(NativeSymbolRange {
283 fingerprint,
284 index: symbol.index(),
285 section: section_index,
286 address: symbol.address(),
287 size,
288 });
289 }
290 }
291 Ok(ranges)
292}
293
294fn append_field(identity: &mut Vec<u8>, value: &[u8]) {
295 identity.extend(u64::try_from(value.len()).unwrap_or(u64::MAX).to_le_bytes());
296 identity.extend(value);
297}
298
299fn relocation_target_name(
300 file: &object::File<'_>,
301 target: object::RelocationTarget,
302) -> Option<String> {
303 let object::RelocationTarget::Symbol(index) = target else {
304 return None;
305 };
306 file.symbol_by_index(index)
307 .ok()
308 .and_then(|symbol| symbol.name().ok())
309 .filter(|name| !name.is_empty())
310 .map(demangle)
311}
312
313pub fn infer_text_regions<F>(
323 file: &object::File<'_>,
324 ir: &mut ArtifactIr,
325 mut fingerprint: F,
326) -> Result<Vec<(ArtifactFingerprint, u64, u64)>, object::Error>
327where
328 F: FnMut(Option<&str>, Option<&NormalizedInstructions>, &[u8]) -> ArtifactFingerprint,
329{
330 let mut ranges = Vec::new();
331 for section in file
332 .sections()
333 .filter(|section| section.kind() == object::SectionKind::Text)
334 {
335 let data = section.data()?;
336 if data.is_empty() {
337 continue;
338 }
339 let (offset, _) = section.file_range().unwrap_or((0, 0));
340 let normalized = normalize_x86(data, file.architecture());
341 let symbol_fingerprint = fingerprint(section.name().ok(), normalized.as_ref(), data);
342 let size = u64::try_from(data.len()).unwrap_or(u64::MAX);
343 ir.symbols.push(ArtifactSymbol {
344 fingerprint: symbol_fingerprint,
345 name: None,
346 exported: false,
347 section: u32::try_from(section.index().0).ok(),
348 offset,
349 size,
350 size_inferred: true,
351 code: data.to_vec(),
352 normalized,
353 inline_stack: Vec::new(),
354 });
355 ranges.push((symbol_fingerprint, section.address(), size));
356 }
357 Ok(ranges)
358}
359
360#[cfg(test)]
361mod tests {
362 use super::{symbol_fingerprint, symbol_size};
363 use crate::NormalizedInstructions;
364
365 #[test]
366 fn normalized_and_raw_payloads_with_the_same_bytes_have_distinct_identities() {
367 let normalized = NormalizedInstructions {
368 version: "x86-shape-v1".to_owned(),
369 bytes: vec![1, 2, 3],
370 };
371 let normalized_fingerprint =
372 symbol_fingerprint(Some("render"), Some(".text"), Some(&normalized), b"ignored");
373 let raw_fingerprint = symbol_fingerprint(
374 Some("render"),
375 Some(".text"),
376 None,
377 b"x86-shape-v1\x01\x02\x03",
378 );
379 assert_ne!(normalized_fingerprint, raw_fingerprint);
380 }
381
382 #[test]
383 fn field_boundaries_are_part_of_the_symbol_identity() {
384 let left = symbol_fingerprint(Some("ab"), Some("c"), None, b"payload");
385 let right = symbol_fingerprint(Some("a"), Some("bc"), None, b"payload");
386 assert_ne!(left, right);
387 }
388
389 #[test]
390 fn inferred_symbol_size_uses_the_next_strictly_greater_address() {
391 assert_eq!(symbol_size(10, 0, false, [10, 10, 14], 20), 4);
392 }
393
394 #[test]
395 fn zero_size_alias_remains_an_explicit_empty_region() {
396 assert_eq!(symbol_size(10, 0, true, [14], 20), 0);
397 }
398}