1mod discovery;
2mod image;
3
4use std::fmt;
5use std::path::{Path, PathBuf};
6use std::sync::{Arc, OnceLock};
7
8use object::Object;
9
10use self::discovery::{
11 Discovery, DiscoveryCache, discover_dwp, discover_separate_debug, discover_supplementary,
12 validated_gnu_debugdata,
13};
14pub(in crate::convert) use self::image::AddressLayout;
15use self::image::{
16 cross_check_elf_architecture, cross_check_elf_identity, malformed, parse_elf,
17 require_supported_kind,
18};
19use super::ConversionWarning;
20use crate::builder::{BuilderOptions, GsymBuilder};
21use crate::{ElfInputKind, Endian, Error, Result, WriterOptions};
22
23#[derive(Clone, Copy, Eq, PartialEq)]
43pub struct ElfInputs<'data> {
44 pub image: &'data [u8],
46 pub debug: Option<&'data [u8]>,
48 pub symbols: Option<&'data [u8]>,
51 pub supplementary: Option<&'data [u8]>,
53 pub dwp: Option<&'data [u8]>,
55}
56
57impl fmt::Debug for ElfInputs<'_> {
58 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59 formatter
60 .debug_struct("ElfInputs")
61 .field("image_len", &self.image.len())
62 .field("debug_len", &self.debug.map(<[u8]>::len))
63 .field("symbols_len", &self.symbols.map(<[u8]>::len))
64 .field("supplementary_len", &self.supplementary.map(<[u8]>::len))
65 .field("dwp_len", &self.dwp.map(<[u8]>::len))
66 .finish()
67 }
68}
69
70impl<'data> ElfInputs<'data> {
71 #[must_use]
73 pub const fn new(image: &'data [u8]) -> Self {
74 Self {
75 image,
76 debug: None,
77 symbols: None,
78 supplementary: None,
79 dwp: None,
80 }
81 }
82
83 #[must_use]
85 pub const fn with_debug(mut self, debug: &'data [u8]) -> Self {
86 self.debug = Some(debug);
87 self
88 }
89
90 #[must_use]
92 pub const fn with_symbols(mut self, symbols: &'data [u8]) -> Self {
93 self.symbols = Some(symbols);
94 self
95 }
96
97 #[must_use]
99 pub const fn with_supplementary(mut self, supplementary: &'data [u8]) -> Self {
100 self.supplementary = Some(supplementary);
101 self
102 }
103
104 #[must_use]
106 pub const fn with_dwp(mut self, dwp: &'data [u8]) -> Self {
107 self.dwp = Some(dwp);
108 self
109 }
110}
111
112#[derive(Clone, Debug, Eq, PartialEq)]
129pub struct ConversionOptions {
130 pub writer: WriterOptions,
132 pub include_symbols: bool,
134 pub dwarf: Option<DwarfImportOptions>,
136 pub discovery: DiscoveryPolicy,
138 pub debug_directories: Vec<PathBuf>,
140 pub debuginfod_urls: Vec<String>,
142 pub debuginfod_cache: PathBuf,
144 pub debuginfod_max_download_size: u64,
146 pub gnu_debugdata_max_decompressed_size: u64,
148}
149
150#[derive(Clone, Copy, Debug, Eq, PartialEq)]
158pub struct DwarfImportOptions {
159 pub inline_info: bool,
161 pub call_sites: bool,
163}
164
165impl Default for DwarfImportOptions {
166 fn default() -> Self {
167 Self {
168 inline_info: true,
169 call_sites: false,
170 }
171 }
172}
173
174#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
180#[non_exhaustive]
181pub enum DiscoveryPolicy {
182 Disabled,
184 #[default]
185 Enabled,
187}
188
189#[derive(Clone, Copy, Debug, Eq, PartialEq)]
194#[non_exhaustive]
195pub enum DiscoveryEvent<'path> {
196 DebuginfodRequest {
198 artifact: &'static str,
200 build_id: &'path str,
202 endpoint: &'path str,
204 related_path: &'path Path,
206 },
207}
208
209impl Default for ConversionOptions {
210 fn default() -> Self {
211 Self {
212 writer: WriterOptions::default(),
213 include_symbols: true,
214 dwarf: Some(DwarfImportOptions::default()),
215 discovery: DiscoveryPolicy::Enabled,
216 debug_directories: vec![PathBuf::from("/usr/lib/debug")],
217 debuginfod_urls: std::env::var("DEBUGINFOD_URLS")
218 .ok()
219 .map(|urls| urls.split_whitespace().map(str::to_owned).collect())
220 .unwrap_or_default(),
221 debuginfod_cache: std::env::var_os("DEBUGINFOD_CACHE_PATH").map_or_else(
222 || std::env::temp_dir().join("gsym-rs-debuginfod"),
223 PathBuf::from,
224 ),
225 debuginfod_max_download_size: 1 << 30,
226 gnu_debugdata_max_decompressed_size: 1 << 30,
227 }
228 }
229}
230
231#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
238#[non_exhaustive]
239pub struct ConversionStats {
240 pub symbol_functions: usize,
242 pub dwarf_functions: usize,
244 pub split_dwarf_units: usize,
246 pub line_rows: usize,
248 pub inline_nodes: usize,
250 pub rejected_ranges: usize,
255}
256
257#[non_exhaustive]
268pub struct ConversionReport {
269 pub builder: GsymBuilder,
271 pub stats: ConversionStats,
273 pub warnings: Vec<ConversionWarning>,
275 pub discovered_debug: Option<PathBuf>,
277 pub discovered_supplementary: Option<PathBuf>,
279 pub discovered_dwp: Option<PathBuf>,
281}
282
283impl fmt::Debug for ConversionReport {
284 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
285 formatter
286 .debug_struct("ConversionReport")
287 .field("builder", &self.builder)
288 .field("stats", &self.stats)
289 .field("warning_count", &self.warnings.len())
290 .field("discovered_debug", &self.discovered_debug)
291 .field("discovered_supplementary", &self.discovered_supplementary)
292 .field("discovered_dwp", &self.discovered_dwp)
293 .finish_non_exhaustive()
294 }
295}
296
297#[derive(Clone, Debug, Default)]
313pub struct ElfConverter {
314 options: ConversionOptions,
315 discovery_cache: OnceLock<Arc<DiscoveryCache>>,
316}
317
318impl PartialEq for ElfConverter {
319 fn eq(&self, other: &Self) -> bool {
320 self.options == other.options
321 }
322}
323
324impl Eq for ElfConverter {}
325
326impl ElfConverter {
327 #[must_use]
329 pub const fn new(options: ConversionOptions) -> Self {
330 Self {
331 options,
332 discovery_cache: OnceLock::new(),
333 }
334 }
335
336 #[must_use]
338 pub const fn options(&self) -> &ConversionOptions {
339 &self.options
340 }
341
342 pub fn convert(&self, inputs: ElfInputs<'_>) -> Result<ConversionReport> {
353 self.convert_inner(inputs, super::dwarf::DwoResolver::Disabled)
354 }
355
356 fn convert_inner(
357 &self,
358 inputs: ElfInputs<'_>,
359 dwo_resolver: super::dwarf::DwoResolver<'_>,
360 ) -> Result<ConversionReport> {
361 let image = parse_elf(inputs.image, ElfInputKind::Image)?;
362 require_supported_kind(&image)?;
363
364 let layout = AddressLayout::new(&image)?;
365 let base_address = layout.ranges.first().map_or(0, |range| range.start);
366 if layout.ranges.is_empty() {
367 return Err(Error::InvalidModel("ELF image has no executable sections"));
368 }
369
370 let build_id = image
371 .build_id()
372 .map_err(|error| malformed("ELF build ID", error))?
373 .unwrap_or_default()
374 .to_vec();
375 let mut warnings = Vec::new();
376 let mini_debug = if inputs.debug.is_none()
377 && (self.options.include_symbols || self.options.dwarf.is_some())
378 {
379 validated_gnu_debugdata(
380 &image,
381 self.options.gnu_debugdata_max_decompressed_size,
382 &mut warnings,
383 )
384 } else {
385 None
386 };
387 let debug_bytes = inputs
388 .debug
389 .or(mini_debug.as_deref())
390 .unwrap_or(inputs.image);
391 let separate_debug = inputs.debug.is_some() || mini_debug.is_some();
392 let debug = parse_elf(debug_bytes, ElfInputKind::Debug)?;
393 if inputs.debug.is_some() {
394 cross_check_elf_identity(&image, &debug, ElfInputKind::Debug)?;
395 }
396 let supplementary = inputs
397 .supplementary
398 .map(|bytes| parse_elf(bytes, ElfInputKind::Supplementary))
399 .transpose()?;
400 let dwp = inputs
401 .dwp
402 .map(|bytes| parse_elf(bytes, ElfInputKind::Dwp))
403 .transpose()?;
404 if let Some(dwp) = &dwp {
405 cross_check_elf_architecture(&image, dwp, ElfInputKind::Dwp)?;
406 }
407
408 let mut writer = self.options.writer.clone();
409 writer.endian = if image.is_little_endian() {
410 Endian::Little
411 } else {
412 Endian::Big
413 };
414 writer.base_address = Some(base_address);
415 if writer.build_id.is_empty() {
416 writer.build_id = build_id;
417 }
418 let mut builder = GsymBuilder::with_options(BuilderOptions {
419 writer,
420 executable_ranges: layout.ranges.clone().into_boxed_slice(),
421 ..BuilderOptions::default()
422 });
423 let mut stats = ConversionStats::default();
424
425 if self.options.include_symbols {
426 let symbol_file = inputs
427 .symbols
428 .map(|bytes| parse_elf(bytes, ElfInputKind::Symbols))
429 .transpose()?;
430 if let Some(symbol_file) = &symbol_file {
431 cross_check_elf_identity(&image, symbol_file, ElfInputKind::Symbols)?;
432 }
433 let mut sources: Vec<(&[u8], &object::File<'_>)> = Vec::with_capacity(3);
434 if let (Some(bytes), Some(file)) = (inputs.symbols, symbol_file.as_ref()) {
435 sources.push((bytes, file));
436 }
437 if separate_debug {
438 sources.push((debug_bytes, &debug));
439 }
440 sources.push((inputs.image, &image));
441 stats.symbol_functions = import_distinct_symbols(
442 &sources,
443 &layout,
444 &mut builder,
445 &mut stats.rejected_ranges,
446 )?;
447 }
448 if let Some(dwarf_options) = self.options.dwarf {
449 super::dwarf::import_dwarf(super::dwarf::DwarfImport {
450 file: &debug,
451 supplementary: supplementary.as_ref(),
452 dwp: dwp.as_ref(),
453 dwo_resolver,
454 layout: &layout,
455 executable_ranges: &layout.ranges,
456 builder: &mut builder,
457 include_inlines: dwarf_options.inline_info,
458 include_call_sites: dwarf_options.call_sites,
459 stats: &mut stats,
460 warnings: &mut warnings,
461 })?;
462 }
463
464 Ok(ConversionReport {
465 builder,
466 stats,
467 warnings,
468 discovered_debug: None,
469 discovered_supplementary: None,
470 discovered_dwp: None,
471 })
472 }
473
474 pub fn convert_path(&self, image_path: impl AsRef<Path>) -> Result<ConversionReport> {
488 self.convert_path_with_observer(image_path, |_| {})
489 }
490
491 pub fn convert_path_with_observer(
501 &self,
502 image_path: impl AsRef<Path>,
503 mut observer: impl FnMut(DiscoveryEvent<'_>),
504 ) -> Result<ConversionReport> {
505 let image_path = image_path.as_ref();
506 let image_bytes = read_file(image_path, "read ELF image")?;
507 let discovery_cache = self.discovery_cache.get_or_init(Arc::default);
508 let discovery_enabled = self.options.discovery == DiscoveryPolicy::Enabled;
509 let companion_discovery_enabled =
510 discovery_enabled && (self.options.include_symbols || self.options.dwarf.is_some());
511 let discovery = if companion_discovery_enabled {
512 discover_separate_debug(
513 image_path,
514 &image_bytes,
515 &self.options,
516 discovery_cache,
517 &mut observer,
518 )?
519 } else {
520 Discovery::default()
521 };
522 let debug_path = discovery
523 .artifact
524 .as_ref()
525 .map_or(image_path, |artifact| artifact.path.as_path());
526 let debug_source = discovery
527 .artifact
528 .as_ref()
529 .map_or(image_bytes.as_slice(), |artifact| artifact.bytes.as_slice());
530 let dwarf_discovery_enabled = discovery_enabled && self.options.dwarf.is_some();
531 let supplementary_discovery = if dwarf_discovery_enabled {
532 discover_supplementary(
533 debug_path,
534 debug_source,
535 &self.options,
536 discovery_cache,
537 &mut observer,
538 )?
539 } else {
540 Discovery::default()
541 };
542 let dwp = dwarf_discovery_enabled
543 .then(|| discover_dwp(image_path, debug_path))
544 .flatten();
545 let dwo_resolver = if dwarf_discovery_enabled {
546 debug_path
547 .parent()
548 .map_or(super::dwarf::DwoResolver::Disabled, |base| {
549 super::dwarf::DwoResolver::Filesystem { base }
550 })
551 } else {
552 super::dwarf::DwoResolver::Disabled
553 };
554 let mut report = self.convert_inner(
555 ElfInputs {
556 image: &image_bytes,
557 debug: discovery
558 .artifact
559 .as_ref()
560 .map(|artifact| artifact.bytes.as_slice()),
561 symbols: None,
562 supplementary: supplementary_discovery
563 .artifact
564 .as_ref()
565 .map(|artifact| artifact.bytes.as_slice()),
566 dwp: dwp.as_ref().map(|artifact| artifact.bytes.as_slice()),
567 },
568 dwo_resolver,
569 )?;
570 report.warnings.extend(discovery.warnings);
571 report.warnings.extend(supplementary_discovery.warnings);
572 report.discovered_debug = discovery.artifact.map(|artifact| artifact.path);
573 report.discovered_supplementary = supplementary_discovery
574 .artifact
575 .map(|artifact| artifact.path);
576 report.discovered_dwp = dwp.map(|artifact| artifact.path);
577 Ok(report)
578 }
579}
580
581fn import_distinct_symbols(
582 sources: &[(&[u8], &object::File<'_>)],
583 layout: &AddressLayout,
584 builder: &mut GsymBuilder,
585 rejected: &mut usize,
586) -> Result<usize> {
587 let mut visited: Vec<&[u8]> = Vec::with_capacity(sources.len());
588 let mut unique = Vec::with_capacity(sources.len());
589 for &(bytes, file) in sources {
590 if !visited.iter().any(|other| std::ptr::eq(*other, bytes)) {
591 visited.push(bytes);
592 unique.push(file);
593 }
594 }
595
596 if let [file] = unique.as_slice() {
597 let mut imported = 0_usize;
598 return image::visit_symbols(file, layout, |function, disposition| {
599 match disposition {
600 image::SymbolDisposition::Import => {
601 builder.add_function(function)?;
602 imported = imported.saturating_add(1);
603 }
604 image::SymbolDisposition::Reject => {
605 *rejected = rejected.saturating_add(1);
606 }
607 }
608 Ok(())
609 })
610 .map(|()| imported);
611 }
612
613 let mut accepted = Vec::new();
614 let mut skipped = Vec::new();
615 for file in unique {
616 image::visit_symbols(file, layout, |function, disposition| {
617 match disposition {
618 image::SymbolDisposition::Import => accepted.push(function),
619 image::SymbolDisposition::Reject => skipped.push(function),
620 }
621 Ok(())
622 })?;
623 }
624 accepted.sort_unstable();
625 accepted.dedup();
626 skipped.sort_unstable();
627 skipped.dedup();
628 skipped.retain(|function| accepted.binary_search(function).is_err());
629
630 let imported = accepted.len();
631 *rejected = rejected.saturating_add(skipped.len());
632 for function in accepted {
633 builder.add_function(function)?;
634 }
635 Ok(imported)
636}
637
638fn read_file(path: &Path, description: &'static str) -> Result<Vec<u8>> {
639 std::fs::read(path).map_err(|source| Error::IoAtPath {
640 operation: description,
641 path: path.to_path_buf(),
642 source,
643 })
644}