use std::fmt;
use std::ops::Range;
use zerocopy::byteorder::{BigEndian, LittleEndian, U32, U64};
use zerocopy::{FromBytes, Immutable, KnownLayout};
use crate::endian::{Cursor, Endian};
use crate::error::{Error, Result};
use crate::format::function::{self, EncodedFunction, InfoRecord};
use crate::model::{AddressRange, FileIndex, Function};
use super::Gsym;
#[derive(Clone, Copy, Debug)]
pub(super) struct RawFunction<'data> {
pub(super) range: AddressRange,
pub(super) name: u64,
pub(super) data: &'data [u8],
pub(super) records: &'data [u8],
}
impl<'data> RawFunction<'data> {
pub(super) const fn records(self, endian: Endian) -> InfoRecords<'data> {
InfoRecords {
cursor: Cursor::new(self.records, endian),
done: false,
}
}
}
pub(super) struct InfoRecords<'data> {
cursor: Cursor<'data>,
done: bool,
}
impl<'data> Iterator for InfoRecords<'data> {
type Item = Result<InfoRecord<'data>>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let result = function::next_record(&mut self.cursor, &mut self.done);
match result {
Ok(Some(record)) => Some(Ok(record)),
Ok(None) => None,
Err(error) => {
self.done = true;
Some(Err(error))
}
}
}
}
pub struct FunctionRef<'data> {
pub(super) index: usize,
pub(super) name: &'data [u8],
pub(super) all_data: &'data [u8],
pub(super) raw: RawFunction<'data>,
pub(super) endian: Endian,
pub(super) string_offset_size: u8,
pub(super) string_table: Range<usize>,
pub(super) file_table: Range<usize>,
pub(super) file_count: u32,
}
impl<'data> FunctionRef<'data> {
#[must_use]
pub const fn index(&self) -> usize {
self.index
}
#[must_use]
pub const fn range(&self) -> AddressRange {
self.raw.range
}
#[must_use]
pub const fn name(&self) -> &'data [u8] {
self.name
}
#[must_use]
pub const fn start(&self) -> u64 {
self.raw.range.start
}
pub fn decode(&self) -> Result<Function> {
super::owned::decode(self, self.decode_encoded()?)
}
pub(super) fn decode_encoded(&self) -> Result<EncodedFunction> {
function::decode(
self.raw.data,
self.endian,
self.string_offset_size,
self.raw.range.start,
)
}
pub(super) fn string(&self, offset: u64) -> Result<&'data [u8]> {
string_at(self.all_data, &self.string_table, offset)
}
pub(super) fn file(&self, index: FileIndex) -> Result<(&'data [u8], &'data [u8])> {
file_at(
self.all_data,
self.endian,
self.string_offset_size,
&self.file_table,
self.file_count,
&self.string_table,
index,
)
}
}
impl fmt::Debug for FunctionRef<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("FunctionRef")
.field("index", &self.index)
.field("range", &self.raw.range)
.field("name", &String::from_utf8_lossy(self.name))
.finish()
}
}
pub(super) fn string_at<'data>(
data: &'data [u8],
table: &Range<usize>,
offset: u64,
) -> Result<&'data [u8]> {
let offset = usize::try_from(offset).map_err(|_| Error::Overflow("string offset"))?;
let start = table
.start
.checked_add(offset)
.ok_or(Error::Overflow("string address"))?;
if start >= table.end {
return Err(Error::InvalidOffset {
offset: start as u64,
input_len: data.len(),
});
}
let tail = data
.get(start..table.end)
.ok_or(Error::InvalidFormat("string table is outside the file"))?;
let end = tail
.iter()
.position(|byte| *byte == 0)
.ok_or(Error::InvalidFormat("unterminated string"))?;
tail.get(..end)
.ok_or(Error::InvalidFormat("unterminated string"))
}
pub(super) fn file_at<'data>(
data: &'data [u8],
endian: Endian,
string_offset_size: u8,
file_table: &Range<usize>,
file_count: u32,
string_table: &Range<usize>,
index: FileIndex,
) -> Result<(&'data [u8], &'data [u8])> {
if index.get() >= file_count {
return Err(Error::OutOfRange {
field: "file index",
value: u64::from(index),
max: u64::from(file_count.saturating_sub(1)),
});
}
let entry_size = usize::from(string_offset_size)
.checked_mul(2)
.ok_or(Error::Overflow("file entry size"))?;
let entry_offset = usize::try_from(index.get())
.map_err(|_| Error::Overflow("file index conversion"))?
.checked_mul(entry_size)
.ok_or(Error::Overflow("file entry offset"))?;
let offset = file_table
.start
.checked_add(4)
.and_then(|start| start.checked_add(entry_offset))
.ok_or(Error::Overflow("file entry offset"))?;
let end = offset
.checked_add(entry_size)
.ok_or(Error::Overflow("file entry end"))?;
let bytes = data.get(offset..end).ok_or_else(|| Error::UnexpectedEof {
offset,
needed: entry_size,
remaining: data.len().saturating_sub(offset),
})?;
let (directory, basename) = match (string_offset_size, endian) {
(4, Endian::Little) => {
decode_pair::<U32<LittleEndian>>(bytes, |value| u64::from(value.get()))?
}
(4, Endian::Big) => decode_pair::<U32<BigEndian>>(bytes, |value| u64::from(value.get()))?,
(8, Endian::Little) => decode_pair::<U64<LittleEndian>>(bytes, |value| value.get())?,
(8, Endian::Big) => decode_pair::<U64<BigEndian>>(bytes, |value| value.get())?,
_ => {
return Err(Error::OutOfRange {
field: "string offset size",
value: u64::from(string_offset_size),
max: 8,
});
}
};
Ok((
string_at(data, string_table, directory)?,
string_at(data, string_table, basename)?,
))
}
fn decode_pair<T>(bytes: &[u8], decode: impl Fn(&T) -> u64) -> Result<(u64, u64)>
where
[T; 2]: FromBytes + KnownLayout + Immutable,
{
let pair = <[T; 2]>::ref_from_bytes(bytes)
.map_err(|_| Error::InvalidFormat("file entry has an invalid typed layout"))?;
let [directory, basename] = pair;
Ok((decode(directory), decode(basename)))
}
pub struct Functions<'gsym, D> {
pub(super) gsym: &'gsym Gsym<D>,
pub(super) next: usize,
}
impl<D> fmt::Debug for Functions<'_, D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Functions")
.field("next", &self.next)
.finish_non_exhaustive()
}
}
impl<'gsym, D: AsRef<[u8]>> Iterator for Functions<'gsym, D> {
type Item = Result<FunctionRef<'gsym>>;
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.gsym.layout.address_count as usize {
return None;
}
let index = self.next;
self.next = self.next.saturating_add(1);
Some(self.gsym.function(index))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.gsym.layout.address_count as usize).saturating_sub(self.next);
(remaining, Some(remaining))
}
}
impl<D: AsRef<[u8]>> ExactSizeIterator for Functions<'_, D> {}
impl<D: AsRef<[u8]>> std::iter::FusedIterator for Functions<'_, D> {}