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
55pub 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 #[must_use]
75 pub const fn index(&self) -> usize {
76 self.index
77 }
78
79 #[must_use]
81 pub const fn range(&self) -> AddressRange {
82 self.raw.range
83 }
84
85 #[must_use]
87 pub const fn name(&self) -> &'data [u8] {
88 self.name
89 }
90
91 #[must_use]
93 pub const fn start(&self) -> u64 {
94 self.raw.range.start
95 }
96
97 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
240pub 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 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}