use std::collections::BTreeMap;
use object::{Object, ObjectSection, ObjectSymbol, SymbolKind};
use crate::symbols::demangle;
use crate::x86::normalize_x86;
use crate::{
ArtifactDataSegment, ArtifactFingerprint, ArtifactImport, ArtifactImportKind, ArtifactIr,
ArtifactRelocation, ArtifactSection, ArtifactSymbol, NormalizedInstructions,
};
pub fn collect_undefined_imports<'data, S>(
symbols: impl IntoIterator<Item = S>,
ir: &mut ArtifactIr,
) where
S: ObjectSymbol<'data>,
{
let mut names = BTreeMap::new();
for symbol in symbols {
if !symbol.is_undefined() {
continue;
}
let Some(name) = symbol
.name()
.ok()
.filter(|name| !name.is_empty())
.map(demangle)
else {
continue;
};
let kind = if matches!(symbol.kind(), SymbolKind::Text | SymbolKind::Unknown) {
ArtifactImportKind::Function
} else {
ArtifactImportKind::Other
};
names
.entry(name)
.and_modify(|existing| {
if kind == ArtifactImportKind::Function {
*existing = kind;
}
})
.or_insert(kind);
}
ir.imports
.extend(names.into_iter().map(|(name, kind)| ArtifactImport {
module: None,
name: Some(name),
kind,
}));
}
pub fn collect_sections(file: &object::File<'_>, ir: &mut ArtifactIr) -> Result<(), object::Error> {
for section in file.sections() {
let (offset, size) = section.file_range().unwrap_or((0, 0));
ir.sections.push(ArtifactSection {
name: section.name().ok().map(str::to_owned),
offset,
size,
executable: section.kind() == object::SectionKind::Text,
});
if section.kind() == object::SectionKind::ReadOnlyData {
let data = section.data()?;
if !data.is_empty() {
ir.data_segments.push(ArtifactDataSegment {
fingerprint: data_fingerprint(section.name().ok(), data),
section: u32::try_from(section.index().0).ok(),
offset,
bytes: data.to_vec(),
});
}
}
for (relocation_offset, relocation) in section.relocations() {
ir.relocations.push(ArtifactRelocation {
section: u32::try_from(section.index().0).ok(),
offset: offset.saturating_add(relocation_offset),
kind: format!("{:?}", relocation.kind()),
target: relocation_target_name(file, relocation.target()),
});
}
}
Ok(())
}
#[must_use]
pub fn symbol_fingerprint(
name: Option<&str>,
section: Option<&str>,
normalized: Option<&NormalizedInstructions>,
code: &[u8],
) -> ArtifactFingerprint {
let mut identity = Vec::new();
append_field(&mut identity, name.unwrap_or_default().as_bytes());
append_field(&mut identity, section.unwrap_or_default().as_bytes());
if let Some(normalized) = normalized {
identity.push(1);
append_field(&mut identity, normalized.version.as_bytes());
append_field(&mut identity, &normalized.bytes);
} else {
identity.push(0);
append_field(&mut identity, code);
}
ArtifactFingerprint::from_content("native-symbol", &identity)
}
#[must_use]
pub fn data_fingerprint(section: Option<&str>, data: &[u8]) -> ArtifactFingerprint {
let mut identity = Vec::new();
append_field(&mut identity, section.unwrap_or_default().as_bytes());
append_field(&mut identity, data);
ArtifactFingerprint::from_content("native-data", &identity)
}
#[must_use]
pub fn symbol_size(
address: u64,
declared_size: u64,
is_zero_size_alias: bool,
following_addresses: impl IntoIterator<Item = u64>,
section_end: u64,
) -> u64 {
if declared_size != 0 {
return declared_size;
}
if is_zero_size_alias {
return 0;
}
following_addresses
.into_iter()
.find(|candidate| *candidate > address)
.unwrap_or(section_end)
.saturating_sub(address)
}
#[must_use]
pub fn trim_inferred_symbol_padding(code: &[u8], architecture: object::Architecture) -> &[u8] {
crate::x86::trim_inferred_padding(code, architecture)
}
#[derive(Debug, Clone, Copy)]
pub struct NativeSymbolRange {
pub fingerprint: ArtifactFingerprint,
pub index: object::SymbolIndex,
pub section: object::SectionIndex,
pub address: u64,
pub size: u64,
}
pub fn collect_text_symbols(
file: &object::File<'_>,
ir: &mut ArtifactIr,
) -> Result<Vec<NativeSymbolRange>, object::Error> {
let mut ranges = Vec::new();
for section in file
.sections()
.filter(|section| section.kind() == object::SectionKind::Text)
{
let section_index = section.index();
let data = section.data()?;
let (section_offset, _) = section.file_range().unwrap_or((0, 0));
let mut symbols: Vec<_> = file
.symbols()
.filter(|symbol| {
symbol.section_index() == Some(section_index)
&& symbol.kind() == SymbolKind::Text
&& !symbol.is_undefined()
})
.collect();
symbols.sort_by_key(ObjectSymbol::address);
for (position, symbol) in symbols.iter().enumerate() {
let Some(relative) = symbol.address().checked_sub(section.address()) else {
continue;
};
let alias = symbol.size() == 0
&& (symbols[..position]
.iter()
.any(|prior| prior.address() == symbol.address())
|| symbols
.iter()
.any(|other| other.address() == symbol.address() && other.size() != 0));
let size = symbol_size(
symbol.address(),
symbol.size(),
alias,
symbols[position.saturating_add(1)..]
.iter()
.map(ObjectSymbol::address),
section
.address()
.saturating_add(u64::try_from(data.len()).unwrap_or(u64::MAX)),
);
let (Ok(start), Ok(size)) = (usize::try_from(relative), usize::try_from(size)) else {
continue;
};
let Some(raw) = data.get(start..start.saturating_add(size)) else {
continue;
};
let code = if symbol.size() == 0 {
trim_inferred_symbol_padding(raw, file.architecture())
} else {
raw
};
if code.is_empty() && symbol.size() != 0 {
continue;
}
let raw_name = symbol.name().ok().filter(|name| !name.is_empty());
let name = raw_name.map(demangle);
let fingerprint_name = raw_name.map(|name| {
let canonical = if file.format() == object::BinaryFormat::MachO {
name.strip_prefix('_').unwrap_or(name)
} else {
name
};
demangle(canonical)
});
let normalized = normalize_x86(code, file.architecture());
let fingerprint = symbol_fingerprint(
fingerprint_name.as_deref(),
Some("text"),
normalized.as_ref(),
code,
);
let size = u64::try_from(code.len()).unwrap_or(u64::MAX);
ir.symbols.push(ArtifactSymbol {
fingerprint,
name,
exported: symbol.is_global(),
section: u32::try_from(section_index.0).ok(),
offset: section_offset.saturating_add(relative),
size,
size_inferred: symbol.size() == 0,
code: code.to_vec(),
normalized,
inline_stack: Vec::new(),
});
ranges.push(NativeSymbolRange {
fingerprint,
index: symbol.index(),
section: section_index,
address: symbol.address(),
size,
});
}
}
Ok(ranges)
}
fn append_field(identity: &mut Vec<u8>, value: &[u8]) {
identity.extend(u64::try_from(value.len()).unwrap_or(u64::MAX).to_le_bytes());
identity.extend(value);
}
fn relocation_target_name(
file: &object::File<'_>,
target: object::RelocationTarget,
) -> Option<String> {
let object::RelocationTarget::Symbol(index) = target else {
return None;
};
file.symbol_by_index(index)
.ok()
.and_then(|symbol| symbol.name().ok())
.filter(|name| !name.is_empty())
.map(demangle)
}
pub fn infer_text_regions<F>(
file: &object::File<'_>,
ir: &mut ArtifactIr,
mut fingerprint: F,
) -> Result<Vec<(ArtifactFingerprint, u64, u64)>, object::Error>
where
F: FnMut(Option<&str>, Option<&NormalizedInstructions>, &[u8]) -> ArtifactFingerprint,
{
let mut ranges = Vec::new();
for section in file
.sections()
.filter(|section| section.kind() == object::SectionKind::Text)
{
let data = section.data()?;
if data.is_empty() {
continue;
}
let (offset, _) = section.file_range().unwrap_or((0, 0));
let normalized = normalize_x86(data, file.architecture());
let symbol_fingerprint = fingerprint(section.name().ok(), normalized.as_ref(), data);
let size = u64::try_from(data.len()).unwrap_or(u64::MAX);
ir.symbols.push(ArtifactSymbol {
fingerprint: symbol_fingerprint,
name: None,
exported: false,
section: u32::try_from(section.index().0).ok(),
offset,
size,
size_inferred: true,
code: data.to_vec(),
normalized,
inline_stack: Vec::new(),
});
ranges.push((symbol_fingerprint, section.address(), size));
}
Ok(ranges)
}
#[cfg(test)]
mod tests {
use super::{symbol_fingerprint, symbol_size};
use crate::NormalizedInstructions;
#[test]
fn normalized_and_raw_payloads_with_the_same_bytes_have_distinct_identities() {
let normalized = NormalizedInstructions {
version: "x86-shape-v1".to_owned(),
bytes: vec![1, 2, 3],
};
let normalized_fingerprint =
symbol_fingerprint(Some("render"), Some(".text"), Some(&normalized), b"ignored");
let raw_fingerprint = symbol_fingerprint(
Some("render"),
Some(".text"),
None,
b"x86-shape-v1\x01\x02\x03",
);
assert_ne!(normalized_fingerprint, raw_fingerprint);
}
#[test]
fn field_boundaries_are_part_of_the_symbol_identity() {
let left = symbol_fingerprint(Some("ab"), Some("c"), None, b"payload");
let right = symbol_fingerprint(Some("a"), Some("bc"), None, b"payload");
assert_ne!(left, right);
}
#[test]
fn inferred_symbol_size_uses_the_next_strictly_greater_address() {
assert_eq!(symbol_size(10, 0, false, [10, 10, 14], 20), 4);
}
#[test]
fn zero_size_alias_remains_an_explicit_empty_region() {
assert_eq!(symbol_size(10, 0, true, [14], 20), 0);
}
}