use super::stack_bounds::StackBounds;
pub(crate) const MAX_FRAMES: usize = 8;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Frames {
pub frames: [u64; MAX_FRAMES],
pub count: u8,
}
impl Frames {
pub(crate) const fn empty() -> Self {
Self {
frames: [0; MAX_FRAMES],
count: 0,
}
}
}
#[inline]
pub(crate) fn walk(initial_fp: u64, bounds: StackBounds) -> Frames {
let mut out = Frames::empty();
let mut fp = initial_fp;
let low = bounds.low as u64;
let high = bounds.high as u64;
let mut i = 0;
while i < MAX_FRAMES {
if fp == 0 {
break;
}
if fp & 0xF != 0 {
break;
}
if fp < low || fp.checked_add(16).map_or(true, |hi| hi > high) {
break;
}
let new_fp = unsafe { *(fp as *const u64) };
let return_addr = unsafe { *((fp + 8) as *const u64) };
if new_fp != 0 && new_fp <= fp {
if return_addr != 0 {
out.frames[i] = return_addr;
out.count += 1;
}
break;
}
if return_addr == 0 {
break;
}
out.frames[i] = return_addr;
out.count += 1;
fp = new_fp;
i += 1;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn fake_stack(words: &[u64]) -> Vec<u64> {
words.to_vec()
}
fn bounds_for(buf: &[u64]) -> StackBounds {
let base = buf.as_ptr() as usize;
StackBounds {
low: base,
high: base + buf.len() * 8,
}
}
#[test]
fn empty_chain_returns_zero() {
let buf: Vec<u64> = fake_stack(&[]);
let b = StackBounds { low: 0, high: 0 };
let frames = walk(0, b);
assert_eq!(frames.count, 0);
let _ = buf;
}
#[test]
fn walks_two_frame_chain() {
let mut buf: Vec<u64> = vec![0; 16];
let base = buf.as_mut_ptr() as u64;
buf[0] = base + 16;
buf[1] = 0xAAAA;
buf[2] = 0;
buf[3] = 0xBBBB;
let b = bounds_for(&buf);
let frames = walk(base, b);
assert_eq!(frames.count, 2);
assert_eq!(frames.frames[0], 0xAAAA);
assert_eq!(frames.frames[1], 0xBBBB);
}
#[test]
fn stops_at_null_saved_fp_with_zero_return_addr() {
let mut buf: Vec<u64> = vec![0; 8];
let base = buf.as_mut_ptr() as u64;
buf[0] = 0; buf[1] = 0; let b = bounds_for(&buf);
let frames = walk(base, b);
assert_eq!(frames.count, 0);
}
#[test]
fn stops_at_misaligned_fp() {
let mut buf: Vec<u64> = vec![0; 8];
let base = buf.as_mut_ptr() as u64;
let frames = walk(base + 1, bounds_for(&buf));
assert_eq!(frames.count, 0);
}
#[test]
fn stops_at_out_of_range_fp() {
let buf: Vec<u64> = vec![0; 8];
let bogus = (buf.as_ptr() as u64).wrapping_add(0x100000);
let frames = walk(bogus, bounds_for(&buf));
assert_eq!(frames.count, 0);
}
#[test]
fn stops_at_non_monotonic_chain() {
let mut buf: Vec<u64> = vec![0; 16];
let base = buf.as_mut_ptr() as u64;
buf[0] = base;
buf[1] = 0xCAFE;
let frames = walk(base, bounds_for(&buf));
assert_eq!(frames.count, 1);
assert_eq!(frames.frames[0], 0xCAFE);
}
#[test]
fn caps_at_max_frames() {
let mut buf: Vec<u64> = vec![0; 64];
let base = buf.as_mut_ptr() as u64;
for i in 0..15 {
buf[i * 2] = base + ((i + 1) * 16) as u64;
buf[i * 2 + 1] = 0x1000 + i as u64;
}
buf[30] = 0;
buf[31] = 0;
let frames = walk(base, bounds_for(&buf));
assert_eq!(frames.count as usize, MAX_FRAMES);
}
}