1mod discovery;
2mod image;
3
4use std::fmt;
5use std::path::{Path, PathBuf};
6
7use object::Object;
8
9use self::discovery::{
10 Discovery, discover_dwp, discover_separate_debug, discover_supplementary,
11 validated_gnu_debugdata,
12};
13pub(in crate::convert) use self::image::AddressLayout;
14use self::image::{
15 cross_check_elf_architecture, cross_check_elf_identity, import_symbols, malformed, parse_elf,
16 require_supported_kind,
17};
18use super::ConversionWarning;
19use crate::builder::{BuilderOptions, GsymBuilder};
20use crate::{ElfInputKind, Endian, Error, Result, WriterOptions};
21
22#[derive(Clone, Copy, Eq, PartialEq)]
42pub struct ElfInputs<'data> {
43 pub image: &'data [u8],
45 pub debug: Option<&'data [u8]>,
47 pub symbols: Option<&'data [u8]>,
49 pub supplementary: Option<&'data [u8]>,
51 pub dwp: Option<&'data [u8]>,
53}
54
55impl fmt::Debug for ElfInputs<'_> {
56 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
57 formatter
58 .debug_struct("ElfInputs")
59 .field("image_len", &self.image.len())
60 .field("debug_len", &self.debug.map(<[u8]>::len))
61 .field("symbols_len", &self.symbols.map(<[u8]>::len))
62 .field("supplementary_len", &self.supplementary.map(<[u8]>::len))
63 .field("dwp_len", &self.dwp.map(<[u8]>::len))
64 .finish()
65 }
66}
67
68impl<'data> ElfInputs<'data> {
69 #[must_use]
71 pub const fn new(image: &'data [u8]) -> Self {
72 Self {
73 image,
74 debug: None,
75 symbols: None,
76 supplementary: None,
77 dwp: None,
78 }
79 }
80
81 #[must_use]
83 pub const fn with_debug(mut self, debug: &'data [u8]) -> Self {
84 self.debug = Some(debug);
85 self
86 }
87
88 #[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)]
182#[non_exhaustive]
183pub enum DiscoveryPolicy {
184 Disabled,
186 #[default]
187 Enabled,
189}
190
191impl Default for ConversionOptions {
192 fn default() -> Self {
193 Self {
194 writer: WriterOptions::default(),
195 include_symbols: true,
196 dwarf: Some(DwarfImportOptions::default()),
197 discovery: DiscoveryPolicy::Enabled,
198 debug_directories: vec![PathBuf::from("/usr/lib/debug")],
199 debuginfod_urls: std::env::var("DEBUGINFOD_URLS")
200 .ok()
201 .map(|urls| urls.split_whitespace().map(str::to_owned).collect())
202 .unwrap_or_default(),
203 debuginfod_cache: std::env::var_os("DEBUGINFOD_CACHE_PATH").map_or_else(
204 || std::env::temp_dir().join("gsym-rs-debuginfod"),
205 PathBuf::from,
206 ),
207 debuginfod_max_download_size: 1 << 30,
208 gnu_debugdata_max_decompressed_size: 1 << 30,
209 }
210 }
211}
212
213#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
220#[non_exhaustive]
221pub struct ConversionStats {
222 pub symbol_functions: usize,
224 pub dwarf_functions: usize,
226 pub line_rows: usize,
228 pub inline_nodes: usize,
230 pub rejected_ranges: usize,
232}
233
234#[non_exhaustive]
245pub struct ConversionReport {
246 pub builder: GsymBuilder,
248 pub stats: ConversionStats,
250 pub warnings: Vec<ConversionWarning>,
252 pub discovered_debug: Option<PathBuf>,
254 pub discovered_supplementary: Option<PathBuf>,
256 pub discovered_dwp: Option<PathBuf>,
258}
259
260impl fmt::Debug for ConversionReport {
261 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
262 formatter
263 .debug_struct("ConversionReport")
264 .field("builder", &self.builder)
265 .field("stats", &self.stats)
266 .field("warning_count", &self.warnings.len())
267 .field("discovered_debug", &self.discovered_debug)
268 .field("discovered_supplementary", &self.discovered_supplementary)
269 .field("discovered_dwp", &self.discovered_dwp)
270 .finish_non_exhaustive()
271 }
272}
273
274#[derive(Clone, Debug, Default, Eq, PartialEq)]
290pub struct ElfConverter {
291 options: ConversionOptions,
292}
293
294impl ElfConverter {
295 #[must_use]
297 pub const fn new(options: ConversionOptions) -> Self {
298 Self { options }
299 }
300
301 #[must_use]
303 pub const fn options(&self) -> &ConversionOptions {
304 &self.options
305 }
306
307 pub fn convert(&self, inputs: ElfInputs<'_>) -> Result<ConversionReport> {
318 self.convert_inner(inputs, None)
319 }
320
321 fn convert_inner(
322 &self,
323 inputs: ElfInputs<'_>,
324 dwo_base: Option<&Path>,
325 ) -> Result<ConversionReport> {
326 let image = parse_elf(inputs.image, ElfInputKind::Image)?;
327 require_supported_kind(&image)?;
328
329 let layout = AddressLayout::new(&image)?;
330 let base_address = layout.ranges.first().map_or(0, |range| range.start);
331 if layout.ranges.is_empty() {
332 return Err(Error::InvalidModel("ELF image has no executable sections"));
333 }
334
335 let build_id = image
336 .build_id()
337 .map_err(|error| malformed("ELF build ID", error))?
338 .unwrap_or_default()
339 .to_vec();
340 let mut warnings = Vec::new();
341 let mini_debug = if inputs.debug.is_none()
342 && (self.options.include_symbols || self.options.dwarf.is_some())
343 {
344 validated_gnu_debugdata(
345 &image,
346 self.options.gnu_debugdata_max_decompressed_size,
347 &mut warnings,
348 )
349 } else {
350 None
351 };
352 let debug_bytes = inputs
353 .debug
354 .or(mini_debug.as_deref())
355 .unwrap_or(inputs.image);
356 let separate_debug = inputs.debug.is_some() || mini_debug.is_some();
357 let debug = parse_elf(debug_bytes, ElfInputKind::Debug)?;
358 if inputs.debug.is_some() {
359 cross_check_elf_identity(&image, &debug, ElfInputKind::Debug)?;
360 }
361 let supplementary = inputs
362 .supplementary
363 .map(|bytes| parse_elf(bytes, ElfInputKind::Supplementary))
364 .transpose()?;
365 let dwp = inputs
366 .dwp
367 .map(|bytes| parse_elf(bytes, ElfInputKind::Dwp))
368 .transpose()?;
369 if let Some(dwp) = &dwp {
370 cross_check_elf_architecture(&image, dwp, ElfInputKind::Dwp)?;
371 }
372
373 let mut writer = self.options.writer.clone();
374 writer.endian = if image.is_little_endian() {
375 Endian::Little
376 } else {
377 Endian::Big
378 };
379 writer.base_address = Some(base_address);
380 if writer.build_id.is_empty() {
381 writer.build_id = build_id;
382 }
383 let mut builder = GsymBuilder::with_options(BuilderOptions {
384 writer,
385 executable_ranges: layout.ranges.clone().into_boxed_slice(),
386 repair_zero_sized_functions: true,
387 merge_equal_address_functions: false,
388 });
389 let mut stats = ConversionStats::default();
390
391 if self.options.include_symbols {
392 let mut imported = 0_usize;
393 if let Some(symbol_bytes) = inputs.symbols {
394 let symbol_file = parse_elf(symbol_bytes, ElfInputKind::Symbols)?;
395 cross_check_elf_identity(&image, &symbol_file, ElfInputKind::Symbols)?;
396 imported = import_symbols(
397 &symbol_file,
398 &layout,
399 &mut builder,
400 &mut stats.rejected_ranges,
401 )?;
402 } else if separate_debug {
403 imported =
404 import_symbols(&debug, &layout, &mut builder, &mut stats.rejected_ranges)?;
405 }
406 stats.symbol_functions = imported.saturating_add(import_symbols(
407 &image,
408 &layout,
409 &mut builder,
410 &mut stats.rejected_ranges,
411 )?);
412 }
413 if let Some(dwarf_options) = self.options.dwarf {
414 super::dwarf::import_dwarf(super::dwarf::DwarfImport {
415 file: &debug,
416 supplementary: supplementary.as_ref(),
417 dwp: dwp.as_ref(),
418 dwo_base,
419 layout: &layout,
420 executable_ranges: &layout.ranges,
421 builder: &mut builder,
422 include_inlines: dwarf_options.inline_info,
423 include_call_sites: dwarf_options.call_sites,
424 stats: &mut stats,
425 warnings: &mut warnings,
426 })?;
427 }
428
429 Ok(ConversionReport {
430 builder,
431 stats,
432 warnings,
433 discovered_debug: None,
434 discovered_supplementary: None,
435 discovered_dwp: None,
436 })
437 }
438
439 pub fn convert_path(&self, image_path: impl AsRef<Path>) -> Result<ConversionReport> {
454 let image_path = image_path.as_ref();
455 let image_bytes = read_file(image_path, "read ELF image")?;
456 let discovery = if self.options.discovery == DiscoveryPolicy::Enabled {
457 discover_separate_debug(image_path, &image_bytes, &self.options)?
458 } else {
459 Discovery::default()
460 };
461 let debug_path = discovery
462 .artifact
463 .as_ref()
464 .map_or(image_path, |artifact| artifact.path.as_path());
465 let debug_source = discovery
466 .artifact
467 .as_ref()
468 .map_or(image_bytes.as_slice(), |artifact| artifact.bytes.as_slice());
469 let supplementary_discovery =
470 discover_supplementary(debug_path, debug_source, &self.options)?;
471 let dwp = (self.options.discovery == DiscoveryPolicy::Enabled)
472 .then(|| discover_dwp(image_path, debug_path))
473 .flatten();
474 let dwo_base = discovery
475 .artifact
476 .as_ref()
477 .map(|artifact| artifact.path.as_path())
478 .or(Some(image_path))
479 .and_then(Path::parent);
480 let mut report = self.convert_inner(
481 ElfInputs {
482 image: &image_bytes,
483 debug: discovery
484 .artifact
485 .as_ref()
486 .map(|artifact| artifact.bytes.as_slice()),
487 symbols: None,
488 supplementary: supplementary_discovery
489 .artifact
490 .as_ref()
491 .map(|artifact| artifact.bytes.as_slice()),
492 dwp: dwp.as_ref().map(|artifact| artifact.bytes.as_slice()),
493 },
494 dwo_base,
495 )?;
496 report.warnings.extend(discovery.warnings);
497 report.warnings.extend(supplementary_discovery.warnings);
498 report.discovered_debug = discovery.artifact.map(|artifact| artifact.path);
499 report.discovered_supplementary = supplementary_discovery
500 .artifact
501 .map(|artifact| artifact.path);
502 report.discovered_dwp = dwp.map(|artifact| artifact.path);
503 Ok(report)
504 }
505}
506
507fn read_file(path: &Path, description: &'static str) -> Result<Vec<u8>> {
508 std::fs::read(path).map_err(|source| Error::IoAtPath {
509 operation: description,
510 path: path.to_path_buf(),
511 source,
512 })
513}