Skip to main content

gsym/reader/
function.rs

1use std::fmt;
2use std::ops::Range;
3
4use zerocopy::byteorder::{BigEndian, LittleEndian, U32, U64};
5use zerocopy::{FromBytes, Immutable, KnownLayout};
6
7use crate::endian::{Cursor, Endian};
8use crate::error::{Error, Result};
9use crate::format::function::{self, EncodedFunction, InfoRecord};
10use crate::model::{AddressRange, FileIndex, Function};
11
12use super::Gsym;
13
14#[derive(Clone, Copy, Debug)]
15pub(super) struct RawFunction<'data> {
16    pub(super) range: AddressRange,
17    pub(super) name: u64,
18    pub(super) data: &'data [u8],
19    pub(super) records: &'data [u8],
20}
21
22impl<'data> RawFunction<'data> {
23    pub(super) const fn records(self, endian: Endian) -> InfoRecords<'data> {
24        InfoRecords {
25            cursor: Cursor::new(self.records, endian),
26            done: false,
27        }
28    }
29}
30
31pub(super) struct InfoRecords<'data> {
32    cursor: Cursor<'data>,
33    done: bool,
34}
35
36impl<'data> Iterator for InfoRecords<'data> {
37    type Item = Result<InfoRecord<'data>>;
38
39    fn next(&mut self) -> Option<Self::Item> {
40        if self.done {
41            return None;
42        }
43        let result = function::next_record(&mut self.cursor, &mut self.done);
44        match result {
45            Ok(Some(record)) => Some(Ok(record)),
46            Ok(None) => None,
47            Err(error) => {
48                self.done = true;
49                Some(Err(error))
50            }
51        }
52    }
53}
54
55/// Borrowed view of one indexed `FunctionInfo` record.
56///
57/// [`Self::index`], [`Self::range`], and [`Self::name`] are cheap, so listing
58/// or filtering symbols does not need to decode anything. [`Self::decode`]
59/// returns the whole record as an owned [`Function`].
60///
61/// Obtained from [`Gsym::function`](crate::Gsym::function),
62/// [`Gsym::get_function`](crate::Gsym::get_function), or
63/// [`Gsym::functions`](crate::Gsym::functions).
64pub struct FunctionRef<'data> {
65    pub(super) index: usize,
66    pub(super) name: &'data [u8],
67    pub(super) all_data: &'data [u8],
68    pub(super) raw: RawFunction<'data>,
69    pub(super) endian: Endian,
70    pub(super) string_offset_size: u8,
71    pub(super) string_table: Range<usize>,
72    pub(super) file_table: Range<usize>,
73    pub(super) file_count: u32,
74}
75
76impl<'data> FunctionRef<'data> {
77    /// Returns the address-table index.
78    #[must_use]
79    pub const fn index(&self) -> usize {
80        self.index
81    }
82
83    /// Returns the function's half-open address range.
84    #[must_use]
85    pub const fn range(&self) -> AddressRange {
86        self.raw.range
87    }
88
89    /// Returns raw function-name bytes borrowed from the string table.
90    #[must_use]
91    pub const fn name(&self) -> &'data [u8] {
92        self.name
93    }
94
95    /// Returns the function start address.
96    #[must_use]
97    pub const fn start(&self) -> u64 {
98        self.raw.range.start
99    }
100
101    /// Decodes the complete record into an owned semantic model.
102    ///
103    /// # Errors
104    ///
105    /// Returns an error for malformed line, inline, merged-function, or
106    /// call-site data, or for an invalid string reference.
107    pub fn decode(&self) -> Result<Function> {
108        super::owned::decode(self, self.decode_encoded()?)
109    }
110
111    pub(super) fn decode_encoded(&self) -> Result<EncodedFunction> {
112        function::decode(
113            self.raw.data,
114            self.endian,
115            self.string_offset_size,
116            self.raw.range.start,
117        )
118    }
119
120    pub(super) fn string(&self, offset: u64) -> Result<&'data [u8]> {
121        string_at(self.all_data, &self.string_table, offset)
122    }
123
124    pub(super) fn file(&self, index: FileIndex) -> Result<(&'data [u8], &'data [u8])> {
125        file_at(
126            self.all_data,
127            self.endian,
128            self.string_offset_size,
129            &self.file_table,
130            self.file_count,
131            &self.string_table,
132            index,
133        )
134    }
135}
136
137impl fmt::Debug for FunctionRef<'_> {
138    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
139        formatter
140            .debug_struct("FunctionRef")
141            .field("index", &self.index)
142            .field("range", &self.raw.range)
143            .field("name", &String::from_utf8_lossy(self.name))
144            .finish()
145    }
146}
147
148pub(super) fn string_at<'data>(
149    data: &'data [u8],
150    table: &Range<usize>,
151    offset: u64,
152) -> Result<&'data [u8]> {
153    let offset = usize::try_from(offset).map_err(|_| Error::Overflow("string offset"))?;
154    let start = table
155        .start
156        .checked_add(offset)
157        .ok_or(Error::Overflow("string address"))?;
158    if start >= table.end {
159        return Err(Error::InvalidOffset {
160            offset: start as u64,
161            input_len: data.len(),
162        });
163    }
164    let tail = data
165        .get(start..table.end)
166        .ok_or(Error::InvalidFormat("string table is outside the file"))?;
167    let end = tail
168        .iter()
169        .position(|byte| *byte == 0)
170        .ok_or(Error::InvalidFormat("unterminated string"))?;
171    tail.get(..end)
172        .ok_or(Error::InvalidFormat("unterminated string"))
173}
174
175pub(super) fn file_at<'data>(
176    data: &'data [u8],
177    endian: Endian,
178    string_offset_size: u8,
179    file_table: &Range<usize>,
180    file_count: u32,
181    string_table: &Range<usize>,
182    index: FileIndex,
183) -> Result<(&'data [u8], &'data [u8])> {
184    if index.get() >= file_count {
185        return Err(Error::OutOfRange {
186            field: "file index",
187            value: u64::from(index),
188            max: u64::from(file_count.saturating_sub(1)),
189        });
190    }
191    let entry_size = usize::from(string_offset_size)
192        .checked_mul(2)
193        .ok_or(Error::Overflow("file entry size"))?;
194    let entry_offset = usize::try_from(index.get())
195        .map_err(|_| Error::Overflow("file index conversion"))?
196        .checked_mul(entry_size)
197        .ok_or(Error::Overflow("file entry offset"))?;
198    let offset = file_table
199        .start
200        .checked_add(4)
201        .and_then(|start| start.checked_add(entry_offset))
202        .ok_or(Error::Overflow("file entry offset"))?;
203    let end = offset
204        .checked_add(entry_size)
205        .ok_or(Error::Overflow("file entry end"))?;
206    let bytes = data.get(offset..end).ok_or_else(|| Error::UnexpectedEof {
207        offset,
208        needed: entry_size,
209        remaining: data.len().saturating_sub(offset),
210    })?;
211    let (directory, basename) = match (string_offset_size, endian) {
212        (4, Endian::Little) => {
213            decode_pair::<U32<LittleEndian>>(bytes, |value| u64::from(value.get()))?
214        }
215        (4, Endian::Big) => decode_pair::<U32<BigEndian>>(bytes, |value| u64::from(value.get()))?,
216        (8, Endian::Little) => decode_pair::<U64<LittleEndian>>(bytes, |value| value.get())?,
217        (8, Endian::Big) => decode_pair::<U64<BigEndian>>(bytes, |value| value.get())?,
218        _ => {
219            return Err(Error::OutOfRange {
220                field: "string offset size",
221                value: u64::from(string_offset_size),
222                max: 8,
223            });
224        }
225    };
226    Ok((
227        string_at(data, string_table, directory)?,
228        string_at(data, string_table, basename)?,
229    ))
230}
231
232fn decode_pair<T>(bytes: &[u8], decode: impl Fn(&T) -> u64) -> Result<(u64, u64)>
233where
234    [T; 2]: FromBytes + KnownLayout + Immutable,
235{
236    let pair = <[T; 2]>::ref_from_bytes(bytes)
237        .map_err(|_| Error::InvalidFormat("file entry has an invalid typed layout"))?;
238    let [directory, basename] = pair;
239    Ok((decode(directory), decode(basename)))
240}
241
242/// Iterator over indexed functions in address-table order.
243///
244/// Yields `Result<FunctionRef>`, since a malformed record is only detected when
245/// it is reached. Iteration is by ascending address, and functions that share
246/// an address appear consecutively.
247///
248/// ```
249/// use gsym::{AddressRange, Function, Gsym, GsymBuilder};
250///
251/// let mut builder = GsymBuilder::new();
252/// builder.add_function(Function::new(AddressRange::new(0x2000, 0x2010), b"b"))?;
253/// builder.add_function(Function::new(AddressRange::new(0x1000, 0x1010), b"a"))?;
254/// let bytes = builder.to_bytes()?;
255/// let gsym = Gsym::parse(&bytes)?;
256///
257/// let names = gsym
258///     .functions()
259///     .map(|function| Ok(function?.name().to_vec()))
260///     .collect::<gsym::Result<Vec<_>>>()?;
261/// assert_eq!(names, [b"a".to_vec(), b"b".to_vec()]);
262/// # Ok::<(), gsym::Error>(())
263/// ```
264pub struct Functions<'gsym, D> {
265    pub(super) gsym: &'gsym Gsym<D>,
266    pub(super) next: usize,
267}
268
269impl<D> fmt::Debug for Functions<'_, D> {
270    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
271        formatter
272            .debug_struct("Functions")
273            .field("next", &self.next)
274            .finish_non_exhaustive()
275    }
276}
277
278impl<'gsym, D: AsRef<[u8]>> Iterator for Functions<'gsym, D> {
279    type Item = Result<FunctionRef<'gsym>>;
280
281    fn next(&mut self) -> Option<Self::Item> {
282        if self.next >= self.gsym.layout.address_count as usize {
283            return None;
284        }
285        let index = self.next;
286        self.next = self.next.saturating_add(1);
287        Some(self.gsym.function(index))
288    }
289
290    fn size_hint(&self) -> (usize, Option<usize>) {
291        let remaining = (self.gsym.layout.address_count as usize).saturating_sub(self.next);
292        (remaining, Some(remaining))
293    }
294}
295
296impl<D: AsRef<[u8]>> ExactSizeIterator for Functions<'_, D> {}
297
298impl<D: AsRef<[u8]>> std::iter::FusedIterator for Functions<'_, D> {}