1use std::fmt;
2
3use crate::{
4 Endian, Error, FileEntry, FileIndex, Function, Gsym, GsymBuilder, GsymVersion, InlineNode,
5 Result,
6};
7
8impl<D: AsRef<[u8]>> Gsym<D> {
9 pub fn decode_all(&self) -> Result<DecodedGsym> {
21 let (report, functions) = self.decode_all_verified()?;
22 let header = self.header();
23 let mut files = Vec::with_capacity(report.files.max(1));
24 if report.files == 0 {
25 files.push(FileEntry::default());
26 }
27 for index in 0..report.files {
28 let index = u32::try_from(index).map_err(|_| Error::Overflow("file index"))?;
29 let (directory, basename) = self.file(index)?;
30 files.push(FileEntry {
31 directory: directory.to_vec(),
32 basename: basename.to_vec(),
33 });
34 }
35 Ok(DecodedGsym {
36 source_version: header.version,
37 source_endian: header.endian,
38 base_address: header.base_address,
39 build_id: header.build_id.to_vec(),
40 files,
41 functions,
42 })
43 }
44
45 pub fn transcode(&self, options: TranscodeOptions) -> Result<Vec<u8>> {
52 self.decode_all()?.transcode(options)
53 }
54}
55
56#[derive(Eq, PartialEq)]
71pub struct DecodedGsym {
72 pub source_version: GsymVersion,
74 pub source_endian: Endian,
76 pub base_address: u64,
78 pub build_id: Vec<u8>,
80 pub files: Vec<FileEntry>,
82 pub functions: Vec<Function>,
84}
85
86impl fmt::Debug for DecodedGsym {
87 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
88 formatter
89 .debug_struct("DecodedGsym")
90 .field("source_version", &self.source_version)
91 .field("source_endian", &self.source_endian)
92 .field("base_address", &self.base_address)
93 .field("build_id_len", &self.build_id.len())
94 .field("file_count", &self.files.len())
95 .field("function_count", &self.functions.len())
96 .finish_non_exhaustive()
97 }
98}
99
100#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
118pub struct TranscodeOptions {
119 pub version: Option<GsymVersion>,
121 pub endian: Option<Endian>,
123}
124
125#[derive(Eq, PartialEq)]
134#[non_exhaustive]
135pub struct GsymSegment {
136 pub first_address: u64,
138 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 if files
199 .first()
200 .is_none_or(|file| *file != FileEntry::default())
201 {
202 return Err(Error::InvalidModel("file-table index zero must be empty"));
203 }
204 let mut used = vec![false; files.len()];
205 if let Some(zero) = used.first_mut() {
206 *zero = true;
207 }
208 for function in &functions {
209 mark_function_files(function, &mut used)?;
210 }
211 let mut builder = GsymBuilder::new()
212 .version(options.version.unwrap_or(source_version))
213 .endian(options.endian.unwrap_or(source_endian))
214 .base_address(base_address)
215 .build_id(build_id)
216 .repair_zero_sized_functions(false);
217 let mut remap = vec![FileIndex::ZERO; files.len()];
218 for (old, file) in files.into_iter().enumerate().skip(1) {
219 if used.get(old).copied().unwrap_or(false)
220 && let Some(slot) = remap.get_mut(old)
221 {
222 *slot = builder.add_file(file)?;
223 }
224 }
225 for mut function in functions {
226 remap_function_files(&mut function, &remap)?;
227 builder.add_function(function)?;
228 }
229 Ok(builder)
230 }
231
232 pub fn transcode(self, options: TranscodeOptions) -> Result<Vec<u8>> {
263 self.into_builder(options)?.to_bytes()
264 }
265
266 pub fn segments(
283 &self,
284 target_size: usize,
285 options: TranscodeOptions,
286 ) -> Result<Vec<GsymSegment>> {
287 if target_size == 0 {
288 return Err(Error::InvalidModel("segment target size must not be zero"));
289 }
290 if self.functions.is_empty() {
291 return Err(Error::InvalidModel("at least one function is required"));
292 }
293 let mut functions = self.functions.iter().collect::<Vec<_>>();
294 functions.sort_by_key(|function| (function.range.start, function.range.end));
295 let mut segments = Vec::new();
296 let mut start = 0;
297 while start < functions.len() {
298 let minimum = start.saturating_add(1);
299 let mut low = minimum;
300 let mut high = functions.len();
301 let mut best = minimum;
302 let window = |end: usize| {
303 functions
304 .get(start..end)
305 .ok_or(Error::InvalidModel("segment partition is out of range"))
306 };
307 let mut best_bytes = self
308 .builder_for_functions(window(best)?.iter().copied(), options)?
309 .to_bytes()?;
310 while low <= high {
311 let middle = low.saturating_add(high.saturating_sub(low) / 2);
312 let bytes = self
313 .builder_for_functions(window(middle)?.iter().copied(), options)?
314 .to_bytes()?;
315 if bytes.len() <= target_size || middle == minimum {
316 best = middle;
317 best_bytes = bytes;
318 low = middle.saturating_add(1);
319 } else {
320 high = middle.saturating_sub(1);
321 }
322 }
323 let selected = window(best)?;
324 let first = selected
325 .first()
326 .ok_or(Error::InvalidModel("segment partition is empty"))?;
327 segments.push(GsymSegment {
328 first_address: first.range.start,
329 end_address: selected
330 .iter()
331 .map(|function| function.range.end)
332 .max()
333 .unwrap_or(first.range.end),
334 function_count: selected.len(),
335 bytes: best_bytes.into_boxed_slice(),
336 });
337 start = best;
338 }
339 Ok(segments)
340 }
341
342 fn builder_for_functions<'function>(
343 &self,
344 functions: impl Clone + IntoIterator<Item = &'function Function>,
345 options: TranscodeOptions,
346 ) -> Result<GsymBuilder> {
347 if self
348 .files
349 .first()
350 .is_none_or(|file| *file != FileEntry::default())
351 {
352 return Err(Error::InvalidModel("file-table index zero must be empty"));
353 }
354 let mut used = vec![false; self.files.len()];
355 if let Some(zero) = used.first_mut() {
356 *zero = true;
357 }
358 for function in functions.clone() {
359 mark_function_files(function, &mut used)?;
360 }
361 let mut builder = GsymBuilder::new()
362 .version(options.version.unwrap_or(self.source_version))
363 .endian(options.endian.unwrap_or(self.source_endian))
364 .base_address(self.base_address)
365 .build_id(self.build_id.clone())
366 .repair_zero_sized_functions(false);
367 let mut remap = vec![FileIndex::ZERO; self.files.len()];
368 for (old, file) in self.files.iter().enumerate().skip(1) {
369 if used.get(old).copied().unwrap_or(false)
370 && let Some(slot) = remap.get_mut(old)
371 {
372 *slot = builder.add_file(file.clone())?;
373 }
374 }
375 for function in functions {
376 let mut function = function.clone();
377 remap_function_files(&mut function, &remap)?;
378 builder.add_function(function)?;
379 }
380 Ok(builder)
381 }
382}
383
384fn mark_file(index: FileIndex, used: &mut [bool]) -> Result<()> {
385 let slot = used
386 .get_mut(index.get() as usize)
387 .ok_or(Error::InvalidModel("function references a missing file"))?;
388 *slot = true;
389 Ok(())
390}
391
392fn mark_inline_files(node: &InlineNode, used: &mut [bool]) -> Result<()> {
393 mark_file(node.call_file, used)?;
394 for child in &node.children {
395 mark_inline_files(child, used)?;
396 }
397 Ok(())
398}
399
400fn mark_function_files(function: &Function, used: &mut [bool]) -> Result<()> {
401 for line in &function.lines {
402 mark_file(line.file, used)?;
403 }
404 if let Some(inline) = &function.inline {
405 mark_inline_files(inline, used)?;
406 }
407 for merged in &function.merged {
408 mark_function_files(merged, used)?;
409 }
410 Ok(())
411}
412
413fn remap_file(index: &mut FileIndex, remap: &[FileIndex]) -> Result<()> {
414 *index = *remap
415 .get(index.get() as usize)
416 .ok_or(Error::InvalidModel("function references a missing file"))?;
417 Ok(())
418}
419
420fn remap_inline_files(node: &mut InlineNode, remap: &[FileIndex]) -> Result<()> {
421 remap_file(&mut node.call_file, remap)?;
422 for child in &mut node.children {
423 remap_inline_files(child, remap)?;
424 }
425 Ok(())
426}
427
428fn remap_function_files(function: &mut Function, remap: &[FileIndex]) -> Result<()> {
429 for line in &mut function.lines {
430 remap_file(&mut line.file, remap)?;
431 }
432 if let Some(inline) = &mut function.inline {
433 remap_inline_files(inline, remap)?;
434 }
435 for merged in &mut function.merged {
436 remap_function_files(merged, remap)?;
437 }
438 Ok(())
439}