1use crate::endian::{Cursor, Endian};
2use crate::error::{Error, Result};
3use crate::format::function::{InfoType, check_inline_depth};
4use crate::format::leb::read_uleb;
5use crate::format::line;
6use crate::model::{FileIndex, LineEntry, Lookup, LookupFrame};
7use smallvec::SmallVec;
8
9use super::function::RawFunction;
10use super::{FrameLookupOptions, Gsym, LookupOptions, LookupScratch, RawInlineFrame};
11
12#[derive(Clone, Copy, Debug, Default)]
13struct ScannedRecords<'data> {
14 line: Option<LineEntry>,
15 inline: Option<&'data [u8]>,
16 call_sites: Option<&'data [u8]>,
17}
18
19#[derive(Clone, Copy, Debug)]
20struct FrameRequest<'data> {
21 address: u64,
22 options: LookupOptions,
23 function: RawFunction<'data>,
24}
25
26#[derive(Clone, Copy, Debug)]
27struct PreparedFrames<'data> {
28 function_name: &'data [u8],
29 directory: &'data [u8],
30 basename: &'data [u8],
31 line: u32,
32 count: usize,
33 call_sites: Option<&'data [u8]>,
34}
35
36impl<D: AsRef<[u8]>> Gsym<D> {
37 pub fn lookup(&self, address: u64) -> Result<Option<Lookup<'_>>> {
77 let mut scratch = LookupScratch::default();
78 self.lookup_with_options(address, LookupOptions::default(), &mut scratch)
79 }
80
81 pub fn lookup_with_options<'data>(
92 &'data self,
93 address: u64,
94 options: LookupOptions,
95 scratch: &mut LookupScratch,
96 ) -> Result<Option<Lookup<'data>>> {
97 scratch.clear();
98 let Some(function) = self.matching_function(address)? else {
99 return Ok(None);
100 };
101 let request = FrameRequest {
102 address,
103 options,
104 function,
105 };
106 let prepared = self.prepare_frames(request, scratch)?;
107 let mut frames = Vec::with_capacity(prepared.count);
108 self.emit_frames(request, prepared, scratch, |frame| {
109 frames.push(frame);
110 })?;
111 self.finish_lookup(address, function, prepared.call_sites, frames)
112 .map(Some)
113 }
114
115 pub fn for_each_frame<'data>(
156 &'data self,
157 address: u64,
158 options: FrameLookupOptions,
159 scratch: &mut LookupScratch,
160 visitor: impl FnMut(LookupFrame<'data>),
161 ) -> Result<bool> {
162 scratch.clear();
163 let Some(function) = self.matching_function(address)? else {
164 return Ok(false);
165 };
166 self.visit_function_frames(
167 FrameRequest {
168 address,
169 options: options.into(),
170 function,
171 },
172 scratch,
173 visitor,
174 )?;
175 Ok(true)
176 }
177
178 fn matching_function(&self, address: u64) -> Result<Option<RawFunction<'_>>> {
179 let Some(mut index) = self.find_address_index(address)? else {
180 return Ok(None);
181 };
182 let first_start = self.address(index)?;
183 while index > 0 && self.address(index.saturating_sub(1))? == first_start {
184 index = index.saturating_sub(1);
185 }
186 let count = self.layout.address_count as usize;
187 while index < count {
188 let raw = self.raw_function_at(index, first_start)?;
189 if raw.range.is_empty() || raw.range.contains(address) {
190 return Ok(Some(raw));
191 }
192 index = index.saturating_add(1);
193 if index >= count || self.address(index)? != first_start {
194 break;
195 }
196 }
197 Ok(None)
198 }
199
200 fn visit_function_frames<'data>(
201 &'data self,
202 request: FrameRequest<'data>,
203 scratch: &mut LookupScratch,
204 visitor: impl FnMut(LookupFrame<'data>),
205 ) -> Result<Option<&'data [u8]>> {
206 let prepared = self.prepare_frames(request, scratch)?;
207 self.emit_frames(request, prepared, scratch, visitor)?;
208 Ok(prepared.call_sites)
209 }
210
211 fn prepare_frames<'data>(
212 &'data self,
213 request: FrameRequest<'data>,
214 scratch: &mut LookupScratch,
215 ) -> Result<PreparedFrames<'data>> {
216 let records = self.scan_records(request)?;
217 let function = request.function;
218 let function_name = self.string(function.name)?;
219 let (directory, basename, line_number) = if let Some(row) = records.line {
220 let (directory, basename) = self.file(row.file)?;
221 (directory, basename, row.line)
222 } else {
223 (&[][..], &[][..], 0)
224 };
225
226 if let Some(payload) = records.inline {
227 let mut cursor = Cursor::new(payload, self.layout.endian);
228 let mut scan = InlineScan {
229 string_offset_size: self.layout.string_offset_size,
230 address: request.address,
231 frames: &mut scratch.inline_frames,
232 };
233 let (present, _) =
234 scan_inline_node(&mut cursor, function.range.start, true, 0, &mut scan)?;
235 if !present || !cursor.is_empty() {
236 return Err(Error::InvalidFormat("malformed inline-info payload"));
237 }
238 }
239
240 Ok(PreparedFrames {
241 function_name,
242 directory,
243 basename,
244 line: line_number,
245 count: scratch.inline_frames.len().max(1),
246 call_sites: records.call_sites,
247 })
248 }
249
250 fn emit_frames<'data>(
251 &'data self,
252 request: FrameRequest<'data>,
253 prepared: PreparedFrames<'data>,
254 scratch: &LookupScratch,
255 mut visitor: impl FnMut(LookupFrame<'data>),
256 ) -> Result<()> {
257 let PreparedFrames {
258 function_name,
259 directory,
260 basename,
261 line: line_number,
262 ..
263 } = prepared;
264 let address = request.address;
265 let function = request.function;
266
267 if scratch.inline_frames.is_empty() {
268 visitor(LookupFrame {
269 name: function_name,
270 directory,
271 basename,
272 line: line_number,
273 offset: address.saturating_sub(function.range.start),
274 inlined: false,
275 });
276 } else {
277 let nodes = &scratch.inline_frames;
278 for (index, node) in nodes.iter().enumerate().rev() {
279 let (frame_directory, frame_basename, frame_line) =
280 if let Some(callee) = nodes.get(index.saturating_add(1)) {
281 let (directory, basename) = self.file(callee.call_file)?;
282 (directory, basename, callee.call_line)
283 } else {
284 (directory, basename, line_number)
285 };
286 visitor(LookupFrame {
287 name: if node.name == 0 {
288 function_name
289 } else {
290 self.string(node.name)?
291 },
292 directory: frame_directory,
293 basename: frame_basename,
294 line: frame_line,
295 offset: address.saturating_sub(node.start),
296 inlined: index != 0,
297 });
298 }
299 }
300 Ok(())
301 }
302
303 fn scan_records<'data>(&self, request: FrameRequest<'data>) -> Result<ScannedRecords<'data>> {
304 let FrameRequest {
305 address,
306 options,
307 function,
308 } = request;
309 if !options.line_information && !options.inline_frames && !options.call_sites {
310 return Ok(ScannedRecords::default());
311 }
312 let mut scanned = ScannedRecords::default();
313 for record in function.records(self.layout.endian) {
314 let record = record?;
315 match record.kind {
316 InfoType::LineTable => {
317 if options.line_information {
318 scanned.line = line::lookup(
319 record.payload,
320 self.layout.endian,
321 function.range.start,
322 address,
323 )?;
324 }
325 }
326 InfoType::Inline => {
327 if options.inline_frames {
328 scanned.inline = Some(record.payload);
329 }
330 }
331 InfoType::Merged | InfoType::Unknown(_) => {}
332 InfoType::CallSite => {
333 if options.call_sites {
334 scanned.call_sites = Some(record.payload);
335 }
336 }
337 }
338 }
339 Ok(scanned)
340 }
341
342 fn finish_lookup<'data>(
343 &'data self,
344 address: u64,
345 function: RawFunction<'data>,
346 call_site_payload: Option<&[u8]>,
347 frames: Vec<LookupFrame<'data>>,
348 ) -> Result<Lookup<'data>> {
349 let mut call_site_patterns = Vec::new();
350 if let Some(payload) = call_site_payload {
351 read_call_site_patterns(
352 payload,
353 self.layout.endian,
354 self.layout.string_offset_size,
355 address.saturating_sub(function.range.start),
356 self,
357 &mut call_site_patterns,
358 )?;
359 }
360 Ok(Lookup::new(
361 address,
362 function.range,
363 frames.into_boxed_slice(),
364 call_site_patterns.into_boxed_slice(),
365 ))
366 }
367}
368
369struct InlineScan<'scratch> {
370 string_offset_size: u8,
371 address: u64,
372 frames: &'scratch mut SmallVec<[RawInlineFrame; 4]>,
373}
374
375fn scan_inline_node(
376 cursor: &mut Cursor<'_>,
377 base: u64,
378 collect: bool,
379 depth: usize,
380 scan: &mut InlineScan<'_>,
381) -> Result<(bool, bool)> {
382 check_inline_depth(depth)?;
383 let count = read_uleb(cursor)?;
384 if count == 0 {
385 return Ok((false, false));
386 }
387 if count > cursor.remaining() as u64 / 2 {
388 return Err(Error::InvalidFormat(
389 "inline range count exceeds remaining payload",
390 ));
391 }
392 let mut contains = false;
393 let mut first_start = None;
394 for _ in 0..count {
395 let start = base
396 .checked_add(read_uleb(cursor)?)
397 .ok_or(Error::Overflow("inline range start"))?;
398 let end = start
399 .checked_add(read_uleb(cursor)?)
400 .ok_or(Error::Overflow("inline range end"))?;
401 first_start.get_or_insert(start);
402 if collect {
403 contains |= start <= scan.address && scan.address < end;
404 }
405 }
406 let first_start = first_start.ok_or(Error::InvalidFormat(
407 "inline node declares no address range",
408 ))?;
409 let has_children = cursor.read_u8()? != 0;
410 let name = cursor.read_uint(scan.string_offset_size)?;
411 let call_file = read_uleb(cursor)?;
412 let call_line = read_uleb(cursor)?;
413 let matched = collect && contains;
414 let original_len = scan.frames.len();
415 if matched && name != 0 {
416 scan.frames.push(RawInlineFrame {
417 name,
418 call_file: FileIndex::new(u32::try_from(call_file).map_err(|_| Error::OutOfRange {
419 field: "inline call-file index",
420 value: call_file,
421 max: u64::from(u32::MAX),
422 })?),
423 call_line: u32::try_from(call_line).map_err(|_| Error::OutOfRange {
424 field: "inline call-line",
425 value: call_line,
426 max: u64::from(u32::MAX),
427 })?,
428 start: first_start,
429 });
430 }
431 if has_children {
432 let child_base = first_start;
433 let mut found_child = false;
434 loop {
435 let (present, child_matched) = scan_inline_node(
436 cursor,
437 child_base,
438 matched && !found_child,
439 depth.saturating_add(1),
440 scan,
441 )?;
442 if !present {
443 break;
444 }
445 found_child |= child_matched;
446 }
447 }
448 if !matched {
449 scan.frames.truncate(original_len);
450 }
451 Ok((true, matched))
452}
453
454fn read_call_site_patterns<'data, D: AsRef<[u8]>>(
455 payload: &[u8],
456 endian: Endian,
457 string_offset_size: u8,
458 return_offset: u64,
459 gsym: &'data Gsym<D>,
460 output: &mut Vec<&'data [u8]>,
461) -> Result<()> {
462 let mut cursor = Cursor::new(payload, endian);
463 let count = cursor.read_u32()?;
464 for _ in 0..count {
465 let candidate = cursor.read_u64()?;
466 let _flags = cursor.read_u8()?;
467 let regex_count = cursor.read_u32()?;
468 for _ in 0..regex_count {
469 let offset = cursor.read_uint(string_offset_size)?;
470 if candidate == return_offset {
471 output.push(gsym.string(offset)?);
472 }
473 }
474 }
475 if !cursor.is_empty() {
476 return Err(Error::InvalidFormat("trailing call-site bytes"));
477 }
478 Ok(())
479}