1use std::fmt;
2
3use crate::format::function::{check_inline_depth, check_merged_depth};
4use crate::{
5 Endian, Error, FileEntry, FileIndex, Function, FunctionSetPolicy, Gsym, GsymBuilder,
6 GsymVersion, InlineNode, Result,
7};
8
9impl<D: AsRef<[u8]>> Gsym<D> {
10 pub fn decode_all(&self) -> Result<DecodedGsym> {
20 let (report, functions) = self.decode_all_verified()?;
21 let header = self.header();
22 let mut files = Vec::with_capacity(report.files.max(1));
23 if report.files == 0 {
24 files.push(FileEntry::default());
25 }
26 for index in 0..report.files {
27 let index = u32::try_from(index).map_err(|_| Error::Overflow("file index"))?;
28 let (directory, basename) = self.file(index)?;
29 files.push(FileEntry {
30 directory: directory.to_vec(),
31 basename: basename.to_vec(),
32 });
33 }
34 Ok(DecodedGsym {
35 source_version: header.version,
36 source_endian: header.endian,
37 base_address: header.base_address,
38 build_id: header.build_id.to_vec(),
39 files,
40 functions,
41 })
42 }
43
44 pub fn transcode(&self, options: TranscodeOptions) -> Result<Vec<u8>> {
51 self.decode_all()?.transcode(options)
52 }
53}
54
55#[derive(Eq, PartialEq)]
70pub struct DecodedGsym {
71 pub source_version: GsymVersion,
73 pub source_endian: Endian,
75 pub base_address: u64,
77 pub build_id: Vec<u8>,
79 pub files: Vec<FileEntry>,
81 pub functions: Vec<Function>,
83}
84
85impl fmt::Debug for DecodedGsym {
86 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
87 formatter
88 .debug_struct("DecodedGsym")
89 .field("source_version", &self.source_version)
90 .field("source_endian", &self.source_endian)
91 .field("base_address", &self.base_address)
92 .field("build_id_len", &self.build_id.len())
93 .field("file_count", &self.files.len())
94 .field("function_count", &self.functions.len())
95 .finish_non_exhaustive()
96 }
97}
98
99#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
117pub struct TranscodeOptions {
118 pub version: Option<GsymVersion>,
120 pub endian: Option<Endian>,
122}
123
124#[derive(Eq, PartialEq)]
133#[non_exhaustive]
134pub struct GsymSegment {
135 pub first_address: u64,
137 pub end_address: u64,
140 pub function_count: usize,
142 bytes: Box<[u8]>,
143}
144
145impl GsymSegment {
146 #[must_use]
148 pub fn bytes(&self) -> &[u8] {
149 &self.bytes
150 }
151
152 #[must_use]
154 pub fn into_bytes(self) -> Box<[u8]> {
155 self.bytes
156 }
157}
158
159impl fmt::Debug for GsymSegment {
160 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
161 formatter
162 .debug_struct("GsymSegment")
163 .field("first_address", &self.first_address)
164 .field("end_address", &self.end_address)
165 .field("function_count", &self.function_count)
166 .field("byte_len", &self.bytes.len())
167 .finish()
168 }
169}
170
171impl DecodedGsym {
172 pub fn to_builder(&self, options: TranscodeOptions) -> Result<GsymBuilder> {
180 self.builder_for_functions(self.functions.iter(), options)
181 }
182
183 pub fn into_builder(self, options: TranscodeOptions) -> Result<GsymBuilder> {
190 let Self {
191 source_version,
192 source_endian,
193 base_address,
194 build_id,
195 files,
196 functions,
197 } = self;
198 let used = used_files(&files, &functions)?;
199 let mut builder = new_builder(
200 source_version,
201 source_endian,
202 base_address,
203 build_id,
204 options,
205 );
206 let mut remap = vec![FileIndex::ZERO; files.len()];
207 for (old, file) in files.into_iter().enumerate().skip(1) {
208 if used.get(old).copied().unwrap_or(false)
209 && let Some(slot) = remap.get_mut(old)
210 {
211 *slot = builder.add_file(file)?;
212 }
213 }
214 for mut function in functions {
215 remap_function_files(&mut function, &remap)?;
216 builder.add_function(function)?;
217 }
218 Ok(builder)
219 }
220
221 pub fn transcode(self, options: TranscodeOptions) -> Result<Vec<u8>> {
252 self.into_builder(options)?.to_bytes()
253 }
254
255 pub fn segments(
275 &self,
276 target_size: usize,
277 options: TranscodeOptions,
278 ) -> Result<Vec<GsymSegment>> {
279 if target_size == 0 {
280 return Err(Error::InvalidModel("segment target size must not be zero"));
281 }
282 if self.functions.is_empty() {
283 return Err(Error::InvalidModel("at least one function is required"));
284 }
285 let mut functions = self.functions.iter().collect::<Vec<_>>();
286 functions.sort_by_key(|function| (function.range.start, function.range.end));
287 let mut segments = Vec::new();
288 let mut start = 0;
289 while start < functions.len() {
290 let minimum = start.saturating_add(1);
291 let mut best = minimum;
292 let window = |end: usize| {
293 functions
294 .get(start..end)
295 .ok_or(Error::InvalidModel("segment partition is out of range"))
296 };
297 let mut best_bytes = self
298 .builder_for_functions(window(best)?.iter().copied(), options)?
299 .to_bytes()?;
300 let mut ceiling = functions.len().saturating_add(1);
301 let mut span = 1_usize;
302 while best < functions.len() {
303 let candidate = minimum.saturating_add(span).min(functions.len());
304 if candidate <= best {
305 break;
306 }
307 let bytes = self
308 .builder_for_functions(window(candidate)?.iter().copied(), options)?
309 .to_bytes()?;
310 if bytes.len() > target_size {
311 ceiling = candidate;
312 break;
313 }
314 best = candidate;
315 best_bytes = bytes;
316 span = span.saturating_mul(2);
317 }
318 let mut low = best.saturating_add(1);
319 let mut high = ceiling.saturating_sub(1);
320 while low <= high && high <= functions.len() {
321 let middle = low.saturating_add(high.saturating_sub(low) / 2);
322 let bytes = self
323 .builder_for_functions(window(middle)?.iter().copied(), options)?
324 .to_bytes()?;
325 if bytes.len() <= target_size {
326 best = middle;
327 best_bytes = bytes;
328 low = middle.saturating_add(1);
329 } else {
330 high = middle.saturating_sub(1);
331 }
332 }
333 let selected = window(best)?;
334 let first = selected
335 .first()
336 .ok_or(Error::InvalidModel("segment partition is empty"))?;
337 let last = selected
338 .last()
339 .ok_or(Error::InvalidModel("segment partition is empty"))?;
340 segments.push(GsymSegment {
341 first_address: first.range.start,
342 end_address: match functions.get(best) {
343 Some(next) => next.range.start,
344 None if last.range.is_empty() => u64::MAX,
345 None => selected
346 .iter()
347 .map(|function| function.range.end)
348 .max()
349 .unwrap_or(first.range.end),
350 },
351 function_count: selected.len(),
352 bytes: best_bytes.into_boxed_slice(),
353 });
354 start = best;
355 }
356 Ok(segments)
357 }
358
359 fn builder_for_functions<'function>(
360 &self,
361 functions: impl Clone + IntoIterator<Item = &'function Function>,
362 options: TranscodeOptions,
363 ) -> Result<GsymBuilder> {
364 let used = used_files(&self.files, functions.clone())?;
365 let mut builder = new_builder(
366 self.source_version,
367 self.source_endian,
368 self.base_address,
369 self.build_id.clone(),
370 options,
371 );
372 let mut remap = vec![FileIndex::ZERO; self.files.len()];
373 for (old, file) in self.files.iter().enumerate().skip(1) {
374 if used.get(old).copied().unwrap_or(false)
375 && let Some(slot) = remap.get_mut(old)
376 {
377 *slot = builder.add_file(file.clone())?;
378 }
379 }
380 for function in functions {
381 let mut function = function.clone();
382 remap_function_files(&mut function, &remap)?;
383 builder.add_function(function)?;
384 }
385 Ok(builder)
386 }
387}
388
389fn used_files<'function>(
390 files: &[FileEntry],
391 functions: impl IntoIterator<Item = &'function Function>,
392) -> Result<Vec<bool>> {
393 if files
394 .first()
395 .is_none_or(|file| *file != FileEntry::default())
396 {
397 return Err(Error::InvalidModel("file-table index zero must be empty"));
398 }
399 let mut used = vec![false; files.len()];
400 if let Some(zero) = used.first_mut() {
401 *zero = true;
402 }
403 for function in functions {
404 mark_function_files(function, &mut used)?;
405 }
406 Ok(used)
407}
408
409fn new_builder(
410 source_version: GsymVersion,
411 source_endian: Endian,
412 base_address: u64,
413 build_id: Vec<u8>,
414 options: TranscodeOptions,
415) -> GsymBuilder {
416 GsymBuilder::new()
417 .version(options.version.unwrap_or(source_version))
418 .endian(options.endian.unwrap_or(source_endian))
419 .base_address(base_address)
420 .build_id(build_id)
421 .repair_zero_sized_functions(false)
422 .function_set(FunctionSetPolicy::Preserve)
423}
424
425fn mark_file(index: FileIndex, used: &mut [bool]) -> Result<()> {
426 let slot = used
427 .get_mut(index.get() as usize)
428 .ok_or(Error::InvalidModel("function references a missing file"))?;
429 *slot = true;
430 Ok(())
431}
432
433fn mark_inline_files(node: &InlineNode, used: &mut [bool], depth: usize) -> Result<()> {
434 check_inline_depth(depth)?;
435 mark_file(node.call_file, used)?;
436 for child in &node.children {
437 mark_inline_files(child, used, depth.saturating_add(1))?;
438 }
439 Ok(())
440}
441
442fn mark_function_files(function: &Function, used: &mut [bool]) -> Result<()> {
443 mark_function_files_at(function, used, 0)
444}
445
446fn mark_function_files_at(function: &Function, used: &mut [bool], depth: usize) -> Result<()> {
447 check_merged_depth(depth)?;
448 for line in &function.lines {
449 mark_file(line.file, used)?;
450 }
451 if let Some(inline) = &function.inline {
452 mark_inline_files(inline, used, 0)?;
453 }
454 for merged in &function.merged {
455 mark_function_files_at(merged, used, depth.saturating_add(1))?;
456 }
457 Ok(())
458}
459
460fn remap_file(index: &mut FileIndex, remap: &[FileIndex]) -> Result<()> {
461 *index = *remap
462 .get(index.get() as usize)
463 .ok_or(Error::InvalidModel("function references a missing file"))?;
464 Ok(())
465}
466
467fn remap_inline_files(node: &mut InlineNode, remap: &[FileIndex], depth: usize) -> Result<()> {
468 check_inline_depth(depth)?;
469 remap_file(&mut node.call_file, remap)?;
470 for child in &mut node.children {
471 remap_inline_files(child, remap, depth.saturating_add(1))?;
472 }
473 Ok(())
474}
475
476fn remap_function_files(function: &mut Function, remap: &[FileIndex]) -> Result<()> {
477 remap_function_files_at(function, remap, 0)
478}
479
480fn remap_function_files_at(
481 function: &mut Function,
482 remap: &[FileIndex],
483 depth: usize,
484) -> Result<()> {
485 check_merged_depth(depth)?;
486 for line in &mut function.lines {
487 remap_file(&mut line.file, remap)?;
488 }
489 if let Some(inline) = &mut function.inline {
490 remap_inline_files(inline, remap, 0)?;
491 }
492 for merged in &mut function.merged {
493 remap_function_files_at(merged, remap, depth.saturating_add(1))?;
494 }
495 Ok(())
496}