use std::fmt;
use crate::format::function::{check_inline_depth, check_merged_depth};
use crate::{
Endian, Error, FileEntry, FileIndex, Function, FunctionSetPolicy, Gsym, GsymBuilder,
GsymVersion, InlineNode, Result,
};
impl<D: AsRef<[u8]>> Gsym<D> {
pub fn decode_all(&self) -> Result<DecodedGsym> {
let (report, functions) = self.decode_all_verified()?;
let header = self.header();
let mut files = Vec::with_capacity(report.files.max(1));
if report.files == 0 {
files.push(FileEntry::default());
}
for index in 0..report.files {
let index = u32::try_from(index).map_err(|_| Error::Overflow("file index"))?;
let (directory, basename) = self.file(index)?;
files.push(FileEntry {
directory: directory.to_vec(),
basename: basename.to_vec(),
});
}
Ok(DecodedGsym {
source_version: header.version,
source_endian: header.endian,
base_address: header.base_address,
build_id: header.build_id.to_vec(),
files,
functions,
})
}
pub fn transcode(&self, options: TranscodeOptions) -> Result<Vec<u8>> {
self.decode_all()?.transcode(options)
}
}
#[derive(Eq, PartialEq)]
pub struct DecodedGsym {
pub source_version: GsymVersion,
pub source_endian: Endian,
pub base_address: u64,
pub build_id: Vec<u8>,
pub files: Vec<FileEntry>,
pub functions: Vec<Function>,
}
impl fmt::Debug for DecodedGsym {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("DecodedGsym")
.field("source_version", &self.source_version)
.field("source_endian", &self.source_endian)
.field("base_address", &self.base_address)
.field("build_id_len", &self.build_id.len())
.field("file_count", &self.files.len())
.field("function_count", &self.functions.len())
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct TranscodeOptions {
pub version: Option<GsymVersion>,
pub endian: Option<Endian>,
}
#[derive(Eq, PartialEq)]
#[non_exhaustive]
pub struct GsymSegment {
pub first_address: u64,
pub end_address: u64,
pub function_count: usize,
bytes: Box<[u8]>,
}
impl GsymSegment {
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn into_bytes(self) -> Box<[u8]> {
self.bytes
}
}
impl fmt::Debug for GsymSegment {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("GsymSegment")
.field("first_address", &self.first_address)
.field("end_address", &self.end_address)
.field("function_count", &self.function_count)
.field("byte_len", &self.bytes.len())
.finish()
}
}
impl DecodedGsym {
pub fn to_builder(&self, options: TranscodeOptions) -> Result<GsymBuilder> {
self.builder_for_functions(self.functions.iter(), options)
}
pub fn into_builder(self, options: TranscodeOptions) -> Result<GsymBuilder> {
let Self {
source_version,
source_endian,
base_address,
build_id,
files,
functions,
} = self;
let used = used_files(&files, &functions)?;
let mut builder = new_builder(
source_version,
source_endian,
base_address,
build_id,
options,
);
let mut remap = vec![FileIndex::ZERO; files.len()];
for (old, file) in files.into_iter().enumerate().skip(1) {
if used.get(old).copied().unwrap_or(false)
&& let Some(slot) = remap.get_mut(old)
{
*slot = builder.add_file(file)?;
}
}
for mut function in functions {
remap_function_files(&mut function, &remap)?;
builder.add_function(function)?;
}
Ok(builder)
}
pub fn transcode(self, options: TranscodeOptions) -> Result<Vec<u8>> {
self.into_builder(options)?.to_bytes()
}
pub fn segments(
&self,
target_size: usize,
options: TranscodeOptions,
) -> Result<Vec<GsymSegment>> {
if target_size == 0 {
return Err(Error::InvalidModel("segment target size must not be zero"));
}
if self.functions.is_empty() {
return Err(Error::InvalidModel("at least one function is required"));
}
let mut functions = self.functions.iter().collect::<Vec<_>>();
functions.sort_by_key(|function| (function.range.start, function.range.end));
let mut segments = Vec::new();
let mut start = 0;
while start < functions.len() {
let minimum = start.saturating_add(1);
let mut best = minimum;
let window = |end: usize| {
functions
.get(start..end)
.ok_or(Error::InvalidModel("segment partition is out of range"))
};
let mut best_bytes = self
.builder_for_functions(window(best)?.iter().copied(), options)?
.to_bytes()?;
let mut ceiling = functions.len().saturating_add(1);
let mut span = 1_usize;
while best < functions.len() {
let candidate = minimum.saturating_add(span).min(functions.len());
if candidate <= best {
break;
}
let bytes = self
.builder_for_functions(window(candidate)?.iter().copied(), options)?
.to_bytes()?;
if bytes.len() > target_size {
ceiling = candidate;
break;
}
best = candidate;
best_bytes = bytes;
span = span.saturating_mul(2);
}
let mut low = best.saturating_add(1);
let mut high = ceiling.saturating_sub(1);
while low <= high && high <= functions.len() {
let middle = low.saturating_add(high.saturating_sub(low) / 2);
let bytes = self
.builder_for_functions(window(middle)?.iter().copied(), options)?
.to_bytes()?;
if bytes.len() <= target_size {
best = middle;
best_bytes = bytes;
low = middle.saturating_add(1);
} else {
high = middle.saturating_sub(1);
}
}
let selected = window(best)?;
let first = selected
.first()
.ok_or(Error::InvalidModel("segment partition is empty"))?;
let last = selected
.last()
.ok_or(Error::InvalidModel("segment partition is empty"))?;
segments.push(GsymSegment {
first_address: first.range.start,
end_address: match functions.get(best) {
Some(next) => next.range.start,
None if last.range.is_empty() => u64::MAX,
None => selected
.iter()
.map(|function| function.range.end)
.max()
.unwrap_or(first.range.end),
},
function_count: selected.len(),
bytes: best_bytes.into_boxed_slice(),
});
start = best;
}
Ok(segments)
}
fn builder_for_functions<'function>(
&self,
functions: impl Clone + IntoIterator<Item = &'function Function>,
options: TranscodeOptions,
) -> Result<GsymBuilder> {
let used = used_files(&self.files, functions.clone())?;
let mut builder = new_builder(
self.source_version,
self.source_endian,
self.base_address,
self.build_id.clone(),
options,
);
let mut remap = vec![FileIndex::ZERO; self.files.len()];
for (old, file) in self.files.iter().enumerate().skip(1) {
if used.get(old).copied().unwrap_or(false)
&& let Some(slot) = remap.get_mut(old)
{
*slot = builder.add_file(file.clone())?;
}
}
for function in functions {
let mut function = function.clone();
remap_function_files(&mut function, &remap)?;
builder.add_function(function)?;
}
Ok(builder)
}
}
fn used_files<'function>(
files: &[FileEntry],
functions: impl IntoIterator<Item = &'function Function>,
) -> Result<Vec<bool>> {
if files
.first()
.is_none_or(|file| *file != FileEntry::default())
{
return Err(Error::InvalidModel("file-table index zero must be empty"));
}
let mut used = vec![false; files.len()];
if let Some(zero) = used.first_mut() {
*zero = true;
}
for function in functions {
mark_function_files(function, &mut used)?;
}
Ok(used)
}
fn new_builder(
source_version: GsymVersion,
source_endian: Endian,
base_address: u64,
build_id: Vec<u8>,
options: TranscodeOptions,
) -> GsymBuilder {
GsymBuilder::new()
.version(options.version.unwrap_or(source_version))
.endian(options.endian.unwrap_or(source_endian))
.base_address(base_address)
.build_id(build_id)
.repair_zero_sized_functions(false)
.function_set(FunctionSetPolicy::Preserve)
}
fn mark_file(index: FileIndex, used: &mut [bool]) -> Result<()> {
let slot = used
.get_mut(index.get() as usize)
.ok_or(Error::InvalidModel("function references a missing file"))?;
*slot = true;
Ok(())
}
fn mark_inline_files(node: &InlineNode, used: &mut [bool], depth: usize) -> Result<()> {
check_inline_depth(depth)?;
mark_file(node.call_file, used)?;
for child in &node.children {
mark_inline_files(child, used, depth.saturating_add(1))?;
}
Ok(())
}
fn mark_function_files(function: &Function, used: &mut [bool]) -> Result<()> {
mark_function_files_at(function, used, 0)
}
fn mark_function_files_at(function: &Function, used: &mut [bool], depth: usize) -> Result<()> {
check_merged_depth(depth)?;
for line in &function.lines {
mark_file(line.file, used)?;
}
if let Some(inline) = &function.inline {
mark_inline_files(inline, used, 0)?;
}
for merged in &function.merged {
mark_function_files_at(merged, used, depth.saturating_add(1))?;
}
Ok(())
}
fn remap_file(index: &mut FileIndex, remap: &[FileIndex]) -> Result<()> {
*index = *remap
.get(index.get() as usize)
.ok_or(Error::InvalidModel("function references a missing file"))?;
Ok(())
}
fn remap_inline_files(node: &mut InlineNode, remap: &[FileIndex], depth: usize) -> Result<()> {
check_inline_depth(depth)?;
remap_file(&mut node.call_file, remap)?;
for child in &mut node.children {
remap_inline_files(child, remap, depth.saturating_add(1))?;
}
Ok(())
}
fn remap_function_files(function: &mut Function, remap: &[FileIndex]) -> Result<()> {
remap_function_files_at(function, remap, 0)
}
fn remap_function_files_at(
function: &mut Function,
remap: &[FileIndex],
depth: usize,
) -> Result<()> {
check_merged_depth(depth)?;
for line in &mut function.lines {
remap_file(&mut line.file, remap)?;
}
if let Some(inline) = &mut function.inline {
remap_inline_files(inline, remap, 0)?;
}
for merged in &mut function.merged {
remap_function_files_at(merged, remap, depth.saturating_add(1))?;
}
Ok(())
}