1use crate::dwarf::attach_dwarf_frames;
7use crate::native::{
8 collect_sections, collect_text_symbols, collect_undefined_imports, symbol_fingerprint,
9};
10use crate::x86::X86_NORMALIZATION_VERSION;
11use crate::{
12 ArtifactBackend, ArtifactCall, ArtifactCapabilities, ArtifactError, ArtifactFingerprint,
13 ArtifactFormat, ArtifactIr, ArtifactSymbol, UnresolvedCall,
14};
15use iced_x86::{Decoder, DecoderOptions, Mnemonic, OpKind};
16use object::{
17 Architecture, Endianness, Object, ObjectKind, ObjectSection, RelocationKind, RelocationTarget,
18 SectionKind,
19};
20use std::collections::{BTreeSet, HashMap};
21
22#[derive(Debug, Default, Clone, Copy)]
24pub struct ElfBackend;
25
26pub const ELF_NORMALIZATION_VERSION: &str = X86_NORMALIZATION_VERSION;
28
29impl ArtifactBackend for ElfBackend {
30 fn format(&self) -> ArtifactFormat {
31 ArtifactFormat::Elf
32 }
33
34 fn detects(&self, bytes: &[u8]) -> bool {
35 bytes.starts_with(b"\x7fELF")
36 }
37
38 fn parse(&self, bytes: &[u8]) -> Result<ArtifactIr, ArtifactError> {
39 self.parse_with_debug_companion(bytes, None)
40 }
41
42 fn capabilities(&self) -> ArtifactCapabilities {
43 ArtifactCapabilities {
44 symbols: true,
45 call_graph: true,
46 source_mapping: false,
47 debug_info_unreadable: false,
48 normalized_duplicates: false,
49 independent_data_segments: false,
50 relocations: false,
51 data_segments: true,
52 }
53 }
54}
55
56impl ElfBackend {
57 #[allow(
70 clippy::too_many_lines,
71 reason = "parsing one artifact keeps all fallible format reads in one transaction"
72 )]
73 pub fn parse_with_debug_companion(
74 &self,
75 bytes: &[u8],
76 debug_companion: Option<&[u8]>,
77 ) -> Result<ArtifactIr, ArtifactError> {
78 if !self.detects(bytes) {
79 return Err(ArtifactError::WrongFormat {
80 expected: ArtifactFormat::Elf,
81 });
82 }
83 let file = object::File::parse(bytes).map_err(|error| malformed(error.to_string()))?;
84 let debug_file = debug_companion
85 .map(|companion| {
86 let companion =
87 object::File::parse(companion).map_err(|error| malformed(error.to_string()))?;
88 if !matching_build_id(&file, &companion) {
89 return Err(malformed(
90 "external debug companion does not have the artifact's build ID".to_owned(),
91 ));
92 }
93 Ok(companion)
94 })
95 .transpose()?;
96 let mut ir = ArtifactIr::empty(ArtifactFormat::Elf, bytes);
97 let mut symbol_fingerprints = HashMap::new();
98 let mut symbol_addresses = HashMap::new();
99 let mut symbol_addresses_by_section = HashMap::new();
100 let mut symbol_addresses_by_fingerprint = HashMap::new();
101 collect_sections(&file, &mut ir).map_err(|error| malformed(error.to_string()))?;
102 collect_undefined_imports(file.symbols().chain(file.dynamic_symbols()), &mut ir);
103 let supports_global_address_join = file.kind() != ObjectKind::Relocatable;
104 for symbol in
105 collect_text_symbols(&file, &mut ir).map_err(|error| malformed(error.to_string()))?
106 {
107 symbol_fingerprints.insert(symbol.index, Some(symbol.fingerprint));
108 symbol_addresses_by_section
109 .insert((symbol.section, symbol.address), symbol.fingerprint);
110 if supports_global_address_join {
111 symbol_addresses
112 .entry(symbol.address)
113 .or_insert(symbol.fingerprint);
114 }
115 symbol_addresses_by_fingerprint
116 .insert(symbol.fingerprint, (symbol.address, symbol.size));
117 }
118 if ir.symbols.is_empty() {
119 infer_text_regions(&file, &mut ir)?;
120 }
121 record_entry_point(file.entry(), &symbol_addresses, &mut ir);
122 record_init_fini_roots(&file, &symbol_fingerprints, &symbol_addresses, &mut ir);
123 ir.calls = x86_direct_calls(
124 &file,
125 &ir.symbols,
126 &symbol_fingerprints,
127 &symbol_addresses_by_section,
128 );
129 attach_dwarf_frames(
130 debug_file.as_ref().unwrap_or(&file),
131 &symbol_addresses_by_fingerprint,
132 &mut ir,
133 );
134 ir.capabilities = ArtifactCapabilities {
135 symbols: !ir.symbols.is_empty(),
136 call_graph: !ir.calls.is_empty(),
137 source_mapping: !ir.source_mappings.is_empty(),
138 debug_info_unreadable: ir.capabilities.debug_info_unreadable,
139 normalized_duplicates: crate::x86::supports_normalized_duplicates(file.architecture()),
140 independent_data_segments: false,
141 relocations: !ir.relocations.is_empty(),
142 data_segments: !ir.data_segments.is_empty(),
143 };
144 Ok(ir)
145 }
146}
147
148fn matching_build_id(artifact: &object::File<'_>, companion: &object::File<'_>) -> bool {
153 let Ok(Some(artifact_id)) = artifact.build_id() else {
154 return false;
155 };
156 let Ok(Some(companion_id)) = companion.build_id() else {
157 return false;
158 };
159 artifact_id == companion_id
160}
161
162fn record_entry_point(
168 entry_address: u64,
169 addresses: &HashMap<u64, ArtifactFingerprint>,
170 ir: &mut ArtifactIr,
171) {
172 if entry_address != 0 {
173 if let Some(fingerprint) = addresses.get(&entry_address) {
174 ir.entry_points.push(*fingerprint);
175 }
176 }
177}
178
179fn record_init_fini_roots(
185 file: &object::File<'_>,
186 fingerprints: &HashMap<object::SymbolIndex, Option<ArtifactFingerprint>>,
187 addresses: &HashMap<u64, ArtifactFingerprint>,
188 ir: &mut ArtifactIr,
189) {
190 let mut roots = BTreeSet::new();
191 for section in file.sections() {
192 if !matches!(section.name().ok(), Some(".init_array" | ".fini_array")) {
193 continue;
194 }
195 for (_, relocation) in section.relocations() {
196 if let RelocationTarget::Symbol(index) = relocation.target() {
197 if let Some(Some(fingerprint)) = fingerprints.get(&index) {
198 roots.insert(*fingerprint);
199 }
200 }
201 }
202 if let Ok(data) = section.data() {
203 roots.extend(pointer_roots(
204 data,
205 file.is_64(),
206 file.endianness(),
207 addresses,
208 ));
209 }
210 }
211 let existing: BTreeSet<_> = ir.entry_points.iter().copied().collect();
212 ir.entry_points.extend(
213 roots
214 .into_iter()
215 .filter(|fingerprint| !existing.contains(fingerprint)),
216 );
217}
218
219fn pointer_roots(
221 bytes: &[u8],
222 is_64: bool,
223 endianness: Endianness,
224 addresses: &HashMap<u64, ArtifactFingerprint>,
225) -> BTreeSet<ArtifactFingerprint> {
226 let width = if is_64 { 8 } else { 4 };
227 bytes
228 .chunks_exact(width)
229 .filter_map(|chunk| pointer_value(chunk, endianness))
230 .filter_map(|address| addresses.get(&address).copied())
231 .collect()
232}
233
234fn pointer_value(bytes: &[u8], endianness: Endianness) -> Option<u64> {
235 match bytes.len() {
236 4 => {
237 let bytes: [u8; 4] = bytes.try_into().ok()?;
238 Some(match endianness {
239 Endianness::Little => u64::from(u32::from_le_bytes(bytes)),
240 Endianness::Big => u64::from(u32::from_be_bytes(bytes)),
241 })
242 }
243 8 => {
244 let bytes: [u8; 8] = bytes.try_into().ok()?;
245 Some(match endianness {
246 Endianness::Little => u64::from_le_bytes(bytes),
247 Endianness::Big => u64::from_be_bytes(bytes),
248 })
249 }
250 _ => None,
251 }
252}
253
254fn infer_text_regions(file: &object::File<'_>, ir: &mut ArtifactIr) -> Result<(), ArtifactError> {
257 crate::native::infer_text_regions(file, ir, |section, normalized, data| {
258 symbol_fingerprint(None, section, normalized, data)
259 })
260 .map_err(|error| malformed(error.to_string()))?;
261 Ok(())
262}
263
264fn x86_direct_calls(
265 file: &object::File<'_>,
266 symbols: &[ArtifactSymbol],
267 fingerprints: &HashMap<object::SymbolIndex, Option<ArtifactFingerprint>>,
268 addresses: &HashMap<(object::SectionIndex, u64), ArtifactFingerprint>,
269) -> Vec<ArtifactCall> {
270 let bitness = match file.architecture() {
271 Architecture::I386 => 32,
272 Architecture::X86_64 => 64,
273 _ => return Vec::new(),
274 };
275 if symbols.is_empty() {
276 return Vec::new();
277 }
278 let mut calls = Vec::new();
279 for section in file
280 .sections()
281 .filter(|section| section.kind() == SectionKind::Text)
282 {
283 let (section_offset, _) = section.file_range().unwrap_or((0, 0));
284 let section_index = u32::try_from(section.index().0).ok();
285 let mut relocation_targets = HashMap::new();
286 for (offset, relocation) in section.relocations() {
287 if !matches!(
288 relocation.kind(),
289 RelocationKind::Relative | RelocationKind::PltRelative
290 ) {
291 continue;
292 }
293 relocation_targets.insert(offset, relocation.target());
294 }
295 for caller in symbols
296 .iter()
297 .filter(|symbol| symbol.section == section_index)
298 {
299 let Some(relative) = caller.offset.checked_sub(section_offset) else {
300 continue;
301 };
302 let Some(ip) = section.address().checked_add(relative) else {
303 continue;
304 };
305 let mut decoder = Decoder::with_ip(bitness, &caller.code, ip, DecoderOptions::NONE);
306 while decoder.can_decode() {
307 let instruction = decoder.decode();
308 if instruction.is_invalid()
309 || instruction.mnemonic() != Mnemonic::Call
310 || !matches!(
311 instruction.op0_kind(),
312 OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64
313 )
314 {
315 continue;
316 }
317 let Some(operand_offset) = instruction
318 .ip()
319 .checked_sub(section.address())
320 .and_then(|offset| offset.checked_add(1))
321 else {
322 continue;
323 };
324 let (target, unresolved) = relocation_targets.get(&operand_offset).map_or_else(
325 || {
326 let target = addresses
327 .get(&(section.index(), instruction.near_branch_target()))
328 .copied();
329 (
330 target,
331 target
332 .is_none()
333 .then_some(UnresolvedCall::MissingRelocation),
334 )
335 },
336 |relocation_target| match relocation_target {
337 RelocationTarget::Symbol(index) => fingerprints
338 .get(index)
339 .and_then(|value| *value)
340 .map_or((None, Some(UnresolvedCall::ExternalImport)), |target| {
341 (Some(target), None)
342 }),
343 RelocationTarget::Section(_) | RelocationTarget::Absolute => {
344 (None, Some(UnresolvedCall::MissingRelocation))
345 }
346 _ => (None, Some(UnresolvedCall::MissingRelocation)),
347 },
348 );
349 calls.push(ArtifactCall {
350 caller: caller.fingerprint,
351 target,
352 unresolved,
353 });
354 }
355 }
356 }
357 calls
358}
359
360const fn malformed(message: String) -> ArtifactError {
361 ArtifactError::Malformed {
362 format: ArtifactFormat::Elf,
363 message,
364 }
365}
366
367#[cfg(test)]
368#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
369mod tests;