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, ParsedLayout};
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) layout: &'data ParsedLayout,
70}
71
72impl<'data> FunctionRef<'data> {
73    /// Returns the address-table index.
74    #[must_use]
75    pub const fn index(&self) -> usize {
76        self.index
77    }
78
79    /// Returns the function's half-open address range.
80    #[must_use]
81    pub const fn range(&self) -> AddressRange {
82        self.raw.range
83    }
84
85    /// Returns raw function-name bytes borrowed from the string table.
86    #[must_use]
87    pub const fn name(&self) -> &'data [u8] {
88        self.name
89    }
90
91    /// Returns the function start address.
92    #[must_use]
93    pub const fn start(&self) -> u64 {
94        self.raw.range.start
95    }
96
97    /// Decodes the record into an owned semantic model.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error for malformed or semantically invalid line, inline,
102    /// merged-function, or call-site data, an invalid string reference, or a
103    /// record type this crate cannot preserve.
104    pub fn decode(&self) -> Result<Function> {
105        let encoded = self.decode_encoded()?;
106        super::owned::decode(self, encoded)
107    }
108
109    pub(super) fn decode_encoded(&self) -> Result<EncodedFunction> {
110        function::decode(
111            self.raw.data,
112            self.layout.endian,
113            self.layout.string_offset_size,
114            self.raw.range.start,
115        )
116    }
117
118    pub(super) fn string(&self, offset: u64) -> Result<&'data [u8]> {
119        string_at(self.all_data, &self.layout.string_table, offset)
120    }
121
122    pub(super) fn file(&self, index: FileIndex) -> Result<(&'data [u8], &'data [u8])> {
123        file_at(
124            self.all_data,
125            self.layout.endian,
126            self.layout.string_offset_size,
127            &self.layout.file_table,
128            self.layout.file_count,
129            &self.layout.string_table,
130            index,
131        )
132    }
133}
134
135impl fmt::Debug for FunctionRef<'_> {
136    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
137        formatter
138            .debug_struct("FunctionRef")
139            .field("index", &self.index)
140            .field("range", &self.raw.range)
141            .field("name", &String::from_utf8_lossy(self.name))
142            .finish()
143    }
144}
145
146pub(super) fn string_at<'data>(
147    data: &'data [u8],
148    table: &Range<usize>,
149    offset: u64,
150) -> Result<&'data [u8]> {
151    let offset = usize::try_from(offset).map_err(|_| Error::Overflow("string offset"))?;
152    let start = table
153        .start
154        .checked_add(offset)
155        .ok_or(Error::Overflow("string address"))?;
156    if start >= table.end {
157        return Err(Error::InvalidOffset {
158            offset: start as u64,
159            input_len: data.len(),
160        });
161    }
162    let tail = data
163        .get(start..table.end)
164        .ok_or(Error::InvalidFormat("string table is outside the file"))?;
165    let end = tail
166        .iter()
167        .position(|byte| *byte == 0)
168        .ok_or(Error::InvalidFormat("unterminated string"))?;
169    tail.get(..end)
170        .ok_or(Error::InvalidFormat("unterminated string"))
171}
172
173pub(super) fn file_at<'data>(
174    data: &'data [u8],
175    endian: Endian,
176    string_offset_size: u8,
177    file_table: &Range<usize>,
178    file_count: u32,
179    string_table: &Range<usize>,
180    index: FileIndex,
181) -> Result<(&'data [u8], &'data [u8])> {
182    if index.get() >= file_count {
183        return Err(Error::OutOfRange {
184            field: "file index",
185            value: u64::from(index),
186            max: u64::from(file_count.saturating_sub(1)),
187        });
188    }
189    let entry_size = usize::from(string_offset_size)
190        .checked_mul(2)
191        .ok_or(Error::Overflow("file entry size"))?;
192    let entry_offset = usize::try_from(index.get())
193        .map_err(|_| Error::Overflow("file index conversion"))?
194        .checked_mul(entry_size)
195        .ok_or(Error::Overflow("file entry offset"))?;
196    let offset = file_table
197        .start
198        .checked_add(4)
199        .and_then(|start| start.checked_add(entry_offset))
200        .ok_or(Error::Overflow("file entry offset"))?;
201    let end = offset
202        .checked_add(entry_size)
203        .ok_or(Error::Overflow("file entry end"))?;
204    let bytes = data.get(offset..end).ok_or_else(|| Error::UnexpectedEof {
205        offset,
206        needed: entry_size,
207        remaining: data.len().saturating_sub(offset),
208    })?;
209    let (directory, basename) = match (string_offset_size, endian) {
210        (4, Endian::Little) => {
211            decode_pair::<U32<LittleEndian>>(bytes, |value| u64::from(value.get()))?
212        }
213        (4, Endian::Big) => decode_pair::<U32<BigEndian>>(bytes, |value| u64::from(value.get()))?,
214        (8, Endian::Little) => decode_pair::<U64<LittleEndian>>(bytes, |value| value.get())?,
215        (8, Endian::Big) => decode_pair::<U64<BigEndian>>(bytes, |value| value.get())?,
216        _ => {
217            return Err(Error::OutOfRange {
218                field: "string offset size",
219                value: u64::from(string_offset_size),
220                max: 8,
221            });
222        }
223    };
224    Ok((
225        string_at(data, string_table, directory)?,
226        string_at(data, string_table, basename)?,
227    ))
228}
229
230fn decode_pair<T>(bytes: &[u8], decode: impl Fn(&T) -> u64) -> Result<(u64, u64)>
231where
232    [T; 2]: FromBytes + KnownLayout + Immutable,
233{
234    let pair = <[T; 2]>::ref_from_bytes(bytes)
235        .map_err(|_| Error::InvalidFormat("file entry has an invalid typed layout"))?;
236    let [directory, basename] = pair;
237    Ok((decode(directory), decode(basename)))
238}
239
240/// Iterator over indexed functions in address-table order.
241///
242/// Yields `Result<FunctionRef>`, since a malformed record is only detected when
243/// it is reached. Iteration is by ascending address, and functions that share
244/// an address appear consecutively.
245///
246/// ```
247/// use gsym::{AddressRange, Function, Gsym, GsymBuilder};
248///
249/// let mut builder = GsymBuilder::new();
250/// builder.add_function(Function::new(AddressRange::new(0x2000, 0x2010), b"b"))?;
251/// builder.add_function(Function::new(AddressRange::new(0x1000, 0x1010), b"a"))?;
252/// let bytes = builder.to_bytes()?;
253/// let gsym = Gsym::parse(&bytes)?;
254///
255/// let names = gsym
256///     .functions()
257///     .map(|function| Ok(function?.name().to_vec()))
258///     .collect::<gsym::Result<Vec<_>>>()?;
259/// assert_eq!(names, [b"a".to_vec(), b"b".to_vec()]);
260/// # Ok::<(), gsym::Error>(())
261/// ```
262pub struct Functions<'gsym, D> {
263    pub(super) gsym: &'gsym Gsym<D>,
264    pub(super) next: usize,
265}
266
267impl<D> fmt::Debug for Functions<'_, D> {
268    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
269        formatter
270            .debug_struct("Functions")
271            .field("next", &self.next)
272            .finish_non_exhaustive()
273    }
274}
275
276impl<'gsym, D: AsRef<[u8]>> Iterator for Functions<'gsym, D> {
277    type Item = Result<FunctionRef<'gsym>>;
278
279    fn next(&mut self) -> Option<Self::Item> {
280        if self.next >= self.gsym.layout.address_count as usize {
281            return None;
282        }
283        let index = self.next;
284        self.next = self.next.saturating_add(1);
285        Some(self.gsym.function(index))
286    }
287
288    fn size_hint(&self) -> (usize, Option<usize>) {
289        let remaining = (self.gsym.layout.address_count as usize).saturating_sub(self.next);
290        (remaining, Some(remaining))
291    }
292}
293
294impl<D: AsRef<[u8]>> ExactSizeIterator for Functions<'_, D> {}
295
296impl<D: AsRef<[u8]>> std::iter::FusedIterator for Functions<'_, D> {}
297
298#[cfg(test)]
299mod tests {
300    use crate::model::{AddressRange, FileEntry, Function, LineEntry};
301    use crate::{Gsym, GsymBuilder};
302
303    /// Builds an image whose only fault is semantic rather than structural.
304    ///
305    /// Every record decodes, but the encoded function size is shrunk after the
306    /// fact so the second line row sits past the end of the function it
307    /// belongs to. Structural decoding cannot notice, because line rows are
308    /// encoded relative to the start address and never consult the size.
309    fn image_with_a_line_outside_its_function() -> Vec<u8> {
310        let mut builder = GsymBuilder::new();
311        let file = builder.add_file(FileEntry::new("/src", "main.c")).unwrap();
312        let mut function = Function::new(AddressRange::new(0x1000, 0x1020), b"main");
313        function.lines = vec![
314            LineEntry {
315                address: 0x1000,
316                file,
317                line: 1,
318            },
319            LineEntry {
320                address: 0x1010,
321                file,
322                line: 2,
323            },
324        ];
325        builder.add_function(function).unwrap();
326        let mut bytes = builder.to_bytes().unwrap();
327
328        let offset = Gsym::parse(bytes.as_slice())
329            .unwrap()
330            .function_offset(0)
331            .unwrap();
332        bytes
333            .split_at_mut(offset)
334            .1
335            .split_at_mut(4)
336            .0
337            .copy_from_slice(&8_u32.to_le_bytes());
338        bytes
339    }
340
341    #[test]
342    fn decode_rejects_every_record_verification_rejects() {
343        let bytes = image_with_a_line_outside_its_function();
344        let gsym = Gsym::parse(bytes.as_slice()).unwrap();
345
346        assert!(gsym.verify().is_err());
347        assert!(gsym.function(0).unwrap().decode_encoded().is_ok());
348        assert!(gsym.function(0).unwrap().decode().is_err());
349    }
350}