use core::ops::Range;
use crate::{
formats::BinaryContext,
structures::{
moduledata::Moduledata,
pclntab::{FuncData, ParsedPclntab},
util::slice_at,
},
};
const FUNCDATA_INL_TREE: u8 = 3;
const PCDATA_INL_TREE_INDEX: u32 = 2;
const FUNCDATA_NIL: u32 = u32::MAX;
const INLINED_CALL_SIZE: usize = 16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlineEntry<'a> {
pub pc_range: Range<u32>,
pub index: u32,
pub function_name: &'a str,
pub parent_pc: u32,
pub start_line: i32,
pub func_id: u8,
pub depth: u32,
}
pub struct InlineTreeIter<'a> {
pclntab: Option<ParsedPclntab<'a>>,
pcdata: Vec<(u32, i32)>,
pos: usize,
prev_pc: u32,
blob: &'a [u8],
}
impl<'a> InlineTreeIter<'a> {
pub fn empty() -> Self {
Self {
pclntab: None,
pcdata: Vec::new(),
pos: 0,
prev_pc: 0,
blob: &[],
}
}
fn read_entry(&self, index: u32) -> Option<InlinedCall> {
let off = (index as usize).checked_mul(INLINED_CALL_SIZE)?;
let bytes = slice_at::<INLINED_CALL_SIZE>(self.blob, off)?;
Some(InlinedCall {
func_id: *bytes.first()?,
name_off: i32::from_le_bytes(slice_at::<4>(&bytes, 4)?),
parent_pc: i32::from_le_bytes(slice_at::<4>(&bytes, 8)?),
start_line: i32::from_le_bytes(slice_at::<4>(&bytes, 12)?),
})
}
fn index_at_pc(&self, pc: u32) -> i32 {
for &(end, val) in &self.pcdata {
if pc < end {
return val;
}
}
-1
}
fn depth_at(&self, start_index: i32) -> u32 {
if start_index < 0 {
return 0;
}
let mut depth: u32 = 0;
let mut index = start_index;
let mut visited: [i32; 32] = [-1; 32];
let mut visited_len: usize = 0;
loop {
for &v in visited.iter().take(visited_len) {
if v == index {
return depth;
}
}
if visited_len < visited.len() {
if let Some(slot) = visited.get_mut(visited_len) {
*slot = index;
visited_len = visited_len.saturating_add(1);
}
} else {
return depth;
}
let entry = match self.read_entry(index as u32) {
Some(e) => e,
None => return depth,
};
let parent_pc = entry.parent_pc as u32;
let parent_idx = self.index_at_pc(parent_pc);
if parent_idx < 0 || parent_idx == index {
return depth;
}
depth = depth.saturating_add(1);
index = parent_idx;
}
}
}
impl<'a> Iterator for InlineTreeIter<'a> {
type Item = InlineEntry<'a>;
fn next(&mut self) -> Option<Self::Item> {
let pclntab = self.pclntab?;
loop {
let &(pc_end, val) = self.pcdata.get(self.pos)?;
let range = self.prev_pc..pc_end;
self.prev_pc = pc_end;
self.pos = self.pos.checked_add(1)?;
if val < 0 {
continue; }
let index = val as u32;
let entry = match self.read_entry(index) {
Some(e) => e,
None => continue,
};
let function_name = pclntab.func_name(entry.name_off as u32).unwrap_or("");
let depth = self.depth_at(val);
return Some(InlineEntry {
pc_range: range,
index,
function_name,
parent_pc: entry.parent_pc as u32,
start_line: entry.start_line,
func_id: entry.func_id,
depth,
});
}
}
}
#[derive(Debug, Clone, Copy)]
struct InlinedCall {
func_id: u8,
name_off: i32,
parent_pc: i32,
start_line: i32,
}
pub fn extract_iter<'a>(
ctx: &'a BinaryContext<'_>,
pclntab: ParsedPclntab<'a>,
moduledata: Option<&Moduledata>,
func: &FuncData,
) -> InlineTreeIter<'a> {
let md = match moduledata {
Some(m) => m,
None => return InlineTreeIter::empty(),
};
let gofunc = match md.gofunc {
Some(g) => g,
None => return InlineTreeIter::empty(),
};
let off = match pclntab.funcdata_at(func, FUNCDATA_INL_TREE) {
Some(o) if o != FUNCDATA_NIL => o,
_ => return InlineTreeIter::empty(),
};
let blob_va = match gofunc.checked_add(off as u64) {
Some(v) => v,
None => return InlineTreeIter::empty(),
};
let blob = match ctx.slice_at_va(blob_va) {
Some(s) => s,
None => return InlineTreeIter::empty(),
};
let pcdata_off = match pclntab.pcdata_at(func, PCDATA_INL_TREE_INDEX) {
Some(o) => o,
None => return InlineTreeIter::empty(),
};
let pcdata: Vec<(u32, i32)> = pclntab.decode_pcvalue(pcdata_off).collect();
InlineTreeIter {
pclntab: Some(pclntab),
pcdata,
pos: 0,
prev_pc: 0,
blob,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inlined_call_size_is_16_bytes() {
assert_eq!(INLINED_CALL_SIZE, 16);
}
#[test]
fn funcdata_constants_match_go() {
assert_eq!(FUNCDATA_INL_TREE, 3);
assert_eq!(PCDATA_INL_TREE_INDEX, 2);
}
}