use crate::*;
use minidump::system_info::{Cpu, Os};
use std::collections::HashMap;
use test_assembler::*;
type Context = minidump::format::CONTEXT_ARM64;
struct TestFixture {
pub raw: Context,
pub modules: MinidumpModuleList,
pub symbols: HashMap<String, String>,
}
impl TestFixture {
pub fn new() -> TestFixture {
TestFixture {
raw: Context::default(),
modules: MinidumpModuleList::from_modules(vec![
MinidumpModule::new(0x40000000, 0x10000, "module1"),
MinidumpModule::new(0x50000000, 0x10000, "module2"),
]),
symbols: HashMap::new(),
}
}
pub fn high_module() -> TestFixture {
TestFixture {
raw: Context::default(),
modules: MinidumpModuleList::from_modules(vec![
MinidumpModule::new(0x40000000, 0x10000, "module1"),
MinidumpModule::new(0x50000000, 0x10000, "module2"),
MinidumpModule::new(0x10000000000000, 0x10000, "high-module"),
]),
symbols: HashMap::new(),
}
}
pub fn highest_module() -> TestFixture {
TestFixture {
raw: Context::default(),
modules: MinidumpModuleList::from_modules(vec![
MinidumpModule::new(0x40000000, 0x10000, "module1"),
MinidumpModule::new(0x50000000, 0x10000, "module2"),
MinidumpModule::new(0xa000_0000_0000_0000, 0x10000, "highest-module"),
]),
symbols: HashMap::new(),
}
}
pub async fn walk_stack(&self, stack: Section) -> CallStack {
let context = MinidumpContext {
raw: MinidumpRawContext::Arm64(self.raw.clone()),
valid: MinidumpContextValidity::All,
};
let base = stack.start().value().unwrap();
let size = stack.size();
let stack = stack.get_contents().unwrap();
let stack_memory = MinidumpMemory {
desc: Default::default(),
base_address: base,
size,
bytes: &stack,
endian: scroll::LE,
};
let system_info = SystemInfo {
os: Os::Windows,
os_version: None,
os_build: None,
cpu: Cpu::Arm64,
cpu_info: None,
cpu_microcode_version: None,
cpu_count: 1,
};
let symbolizer = Symbolizer::new(string_symbol_supplier(self.symbols.clone()));
let mut stack = CallStack::with_context(context);
walk_stack(
0,
(),
&mut stack,
Some(UnifiedMemory::Memory(&stack_memory)),
&self.modules,
&system_info,
&symbolizer,
)
.await;
stack
}
pub fn add_symbols(&mut self, name: String, symbols: String) {
self.symbols.insert(name, symbols);
}
}
#[tokio::test]
async fn test_simple() {
let mut f = TestFixture::new();
let stack = Section::new();
stack.start().set_const(0x80000000);
f.raw.set_register("pc", 0x4000c020);
f.raw.set_register("fp", 0x80000000);
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 1);
let f = &s.frames[0];
let m = f.module.as_ref().unwrap();
assert_eq!(m.code_file(), "module1");
}
#[tokio::test]
async fn test_scan_without_symbols() {
let mut f = TestFixture::new();
let mut stack = Section::new();
stack.start().set_const(0x80000000);
let return_address1 = 0x50000100u64;
let return_address2 = 0x50000900u64;
let frame1_sp = Label::new();
let frame2_sp = Label::new();
stack = stack
.append_repeated(0, 16) .D64(0x40090000) .D64(0x60000000) .D64(return_address1) .mark(&frame1_sp)
.append_repeated(0, 16) .D64(0xF0000000) .D64(0x0000000D)
.D64(return_address2) .mark(&frame2_sp)
.append_repeated(0, 64);
f.raw.set_register("pc", 0x40005510);
f.raw.set_register("sp", stack.start().value().unwrap());
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 3);
{
let frame = &s.frames[0];
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
}
{
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::Scan);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 2);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame1_sp.value().unwrap()
);
} else {
unreachable!();
}
}
{
let frame = &s.frames[2];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::Scan);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 2);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame2_sp.value().unwrap()
);
} else {
unreachable!();
}
}
}
#[tokio::test]
async fn test_scan_with_symbols() {
let mut f = TestFixture::new();
let mut stack = Section::new();
let stack_start = 0x80000000;
stack.start().set_const(stack_start);
let return_address = 0x50000200;
let frame1_sp = Label::new();
stack = stack
.append_repeated(0, 16) .D64(0x40090000) .D64(0x60000000) .D64(0x40001000) .D64(0x5000F000) .D64(return_address) .mark(&frame1_sp)
.append_repeated(0, 64);
f.raw.set_register("pc", 0x40000200);
f.raw.set_register("sp", stack.start().value().unwrap());
f.add_symbols(
String::from("module1"),
String::from("FUNC 100 400 10 monotreme\n"),
);
f.add_symbols(
String::from("module2"),
String::from("FUNC 100 400 10 marsupial\n"),
);
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{
let frame = &s.frames[0];
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
}
{
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::Scan);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 2);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame1_sp.value().unwrap()
);
} else {
unreachable!();
}
}
}
#[tokio::test]
async fn test_scan_first_frame() {
let mut f = TestFixture::new();
let mut stack = Section::new();
stack.start().set_const(0x80000000);
let return_address1 = 0x50000100u64;
let return_address2 = 0x50000900u64;
let frame1_sp = Label::new();
let frame2_sp = Label::new();
stack = stack
.append_repeated(0, 16) .D64(0x40090000) .D64(0x60000000) .append_repeated(0, 96) .D64(return_address1) .mark(&frame1_sp)
.append_repeated(0, 32) .D64(0xF0000000) .D64(0x0000000D)
.append_repeated(0, 336) .D64(return_address2) .mark(&frame2_sp)
.append_repeated(0, 64);
f.raw.set_register("pc", 0x40005510);
f.raw.set_register("sp", stack.start().value().unwrap());
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{
let frame = &s.frames[0];
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
}
{
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::Scan);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 2);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame1_sp.value().unwrap()
);
} else {
unreachable!();
}
}
}
#[tokio::test]
async fn test_frame_pointer() {
let mut f = TestFixture::new();
let mut stack = Section::new();
stack.start().set_const(0x80000000);
let return_address1 = 0x50000100u64;
let return_address2 = 0x50000900u64;
let frame1_sp = Label::new();
let frame2_sp = Label::new();
let frame0_fp = Label::new();
let frame1_fp = Label::new();
let frame2_fp = Label::new();
stack = stack
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame0_fp) .D64(&frame1_fp) .D64(return_address1) .mark(&frame1_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame1_fp)
.D64(&frame2_fp)
.D64(return_address2)
.mark(&frame2_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame2_fp) .D64(0)
.D64(0);
f.raw.set_register("pc", 0x40005510);
f.raw.set_register("lr", 0x1fe0fe10);
f.raw.set_register("fp", frame0_fp.value().unwrap());
f.raw.set_register("sp", stack.start().value().unwrap());
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 3);
{
let frame = &s.frames[0];
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
}
{
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::FramePointer);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 3);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame1_sp.value().unwrap()
);
assert_eq!(
ctx.get_register("fp", valid).unwrap(),
frame1_fp.value().unwrap()
);
} else {
unreachable!();
}
}
{
let frame = &s.frames[2];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::FramePointer);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 3);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame2_sp.value().unwrap()
);
assert_eq!(
ctx.get_register("fp", valid).unwrap(),
frame2_fp.value().unwrap()
);
} else {
unreachable!();
}
}
}
#[tokio::test]
async fn test_frame_pointer_stackless_leaf() {
let mut f = TestFixture::new();
let mut stack = Section::new();
stack.start().set_const(0x80000000);
let return_address1 = 0x50000100u64;
let return_address2 = 0x50000900u64;
let frame1_sp = Label::new();
let frame2_sp = Label::new();
let frame1_fp = Label::new();
let frame2_fp = Label::new();
stack = stack
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame1_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame1_fp)
.D64(&frame2_fp)
.D64(return_address2)
.mark(&frame2_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame2_fp) .D64(0)
.D64(0);
f.raw.set_register("pc", 0x40005510);
f.raw.set_register("lr", return_address1); f.raw.set_register("fp", frame1_fp.value().unwrap());
f.raw.set_register("sp", stack.start().value().unwrap());
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{
let frame = &s.frames[0];
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
}
{
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::FramePointer);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 3);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame2_sp.value().unwrap()
);
assert_eq!(
ctx.get_register("fp", valid).unwrap(),
frame2_fp.value().unwrap()
);
} else {
unreachable!();
}
}
}
#[tokio::test]
async fn test_frame_pointer_stackful_leaf() {
let mut f = TestFixture::new();
let mut stack = Section::new();
stack.start().set_const(0x80000000);
let return_address1 = 0x50000100u64;
let return_address2 = 0x50000900u64;
let frame1_sp = Label::new();
let frame2_sp = Label::new();
let frame1_fp = Label::new();
let frame2_fp = Label::new();
stack = stack
.mark(&frame1_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame1_fp)
.D64(&frame2_fp)
.D64(return_address2)
.mark(&frame2_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame2_fp) .D64(0)
.D64(0);
f.raw.set_register("pc", 0x40005510);
f.raw.set_register("lr", return_address1); f.raw.set_register("fp", frame1_fp.value().unwrap());
f.raw.set_register("sp", stack.start().value().unwrap());
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{
let frame = &s.frames[0];
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
}
{
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::FramePointer);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 3);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame2_sp.value().unwrap()
);
assert_eq!(
ctx.get_register("fp", valid).unwrap(),
frame2_fp.value().unwrap()
);
} else {
unreachable!();
}
}
}
#[tokio::test]
async fn test_frame_pointer_ptr_auth_strip() {
let mut f = TestFixture::new();
let mut stack = Section::new();
stack.start().set_const(0x80000000);
let return_address1 = 0x50000100u64;
let return_address2 = 0x50000900u64;
let authenticated_return_address1 = return_address1 | 0x0013_8000_0000_0000;
let authenticated_return_address2 = return_address2 | 0x1110_0000_0000_0000;
let frame1_sp = Label::new();
let frame2_sp = Label::new();
let frame0_fp = Label::new();
let frame1_fp = Label::new();
let frame2_fp = Label::new();
let authenticated_frame1_fp = Label::new();
let authenticated_frame2_fp = Label::new();
stack = stack
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame0_fp) .D64(&authenticated_frame1_fp) .D64(authenticated_return_address1) .mark(&frame1_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame1_fp)
.D64(&authenticated_frame2_fp)
.D64(authenticated_return_address2)
.mark(&frame2_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame2_fp) .D64(0)
.D64(0);
authenticated_frame1_fp.set_const(frame1_fp.value().unwrap() | 0xa310_0000_0000_0000);
authenticated_frame2_fp.set_const(frame2_fp.value().unwrap() | 0xf31e_8000_0000_0000);
f.raw.set_register("pc", 0x40005510);
f.raw.set_register("lr", 0x1fe0fe10);
f.raw.set_register("fp", frame0_fp.value().unwrap());
f.raw.set_register("sp", stack.start().value().unwrap());
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 3);
{
let frame = &s.frames[0];
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
}
{
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::FramePointer);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 3);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame1_sp.value().unwrap()
);
assert_eq!(
ctx.get_register("fp", valid).unwrap(),
frame1_fp.value().unwrap()
);
} else {
unreachable!();
}
}
{
let frame = &s.frames[2];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::FramePointer);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 3);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame2_sp.value().unwrap()
);
assert_eq!(
ctx.get_register("fp", valid).unwrap(),
frame2_fp.value().unwrap()
);
} else {
unreachable!();
}
}
}
const CALLEE_SAVE_REGS: &[&str] = &[
"pc", "sp", "fp", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28",
];
fn init_cfi_state_high_module() -> (TestFixture, Section, Context, MinidumpContextValidity) {
init_cfi_state_common(TestFixture::high_module())
}
fn init_cfi_state() -> (TestFixture, Section, Context, MinidumpContextValidity) {
init_cfi_state_common(TestFixture::new())
}
fn init_cfi_state_common(
mut f: TestFixture,
) -> (TestFixture, Section, Context, MinidumpContextValidity) {
let symbols = [
"FUNC 4000 1000 10 enchiridion\n",
"STACK CFI INIT 4000 100 .cfa: sp 0 + .ra: x30\n",
"STACK CFI 4001 .cfa: sp 32 + .ra: .cfa -8 + ^",
" x19: .cfa -32 + ^ x20: .cfa -24 + ^ ",
" x29: .cfa -16 + ^\n",
"STACK CFI 4002 x19: x0 x20: x1 x21: x2 x22: x3\n",
"STACK CFI 4003 .cfa: sp 40 + x1: .cfa 40 - ^",
" x19: x19 x20: x20 x21: x21 x22: x22\n",
"STACK CFI 4005 .cfa: sp 32 + x1: .cfa 32 - ^",
" x29: .cfa 8 - ^ .ra: .cfa ^ sp: .cfa 8 +\n",
"STACK CFI 4006 .cfa: sp 40 + pc: .cfa 40 - ^\n",
"FUNC 5000 1000 10 epictetus\n",
"STACK CFI INIT 5000 1000 .cfa: 0 .ra: 0\n",
"FUNC 6000 1000 20 palinal\n",
"STACK CFI INIT 6000 1000 .cfa: sp 8 - .ra: x30\n",
"FUNC 7000 1000 20 rhetorical\n",
"STACK CFI INIT 7000 1000 .cfa: moot .ra: ambiguous\n",
];
f.add_symbols(String::from("module1"), symbols.concat());
f.raw.set_register("pc", 0x0000_0000_4000_5510);
f.raw.set_register("sp", 0x0000_0000_8000_0000);
f.raw.set_register("fp", 0x0000_00a2_8112_e110);
f.raw.set_register("x19", 0x5e68b5d5b5d55e68);
f.raw.set_register("x20", 0x34f3ebd1ebd134f3);
f.raw.set_register("x21", 0x74bca31ea31e74bc);
f.raw.set_register("x22", 0x16b32dcb2dcb16b3);
f.raw.set_register("x23", 0x21372ada2ada2137);
f.raw.set_register("x24", 0x557dbbbbbbbb557d);
f.raw.set_register("x25", 0x8ca748bf48bf8ca7);
f.raw.set_register("x26", 0x21f0ab46ab4621f0);
f.raw.set_register("x27", 0x146732b732b71467);
f.raw.set_register("x28", 0xa673645fa673645f);
let raw_valid = MinidumpContextValidity::All;
let expected = f.raw.clone();
let expected_regs = CALLEE_SAVE_REGS;
let expected_valid = MinidumpContextValidity::Some(expected_regs.iter().copied().collect());
let stack = Section::new();
stack
.start()
.set_const(f.raw.get_register("sp", &raw_valid).unwrap());
(f, stack, expected, expected_valid)
}
async fn check_cfi(
f: TestFixture,
stack: Section,
expected: Context,
expected_valid: MinidumpContextValidity,
) {
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{
let frame = &s.frames[0];
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
}
{
if let MinidumpContextValidity::Some(ref expected_regs) = expected_valid {
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::CallFrameInfo);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), expected_regs.len());
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
for reg in expected_regs {
assert_eq!(
ctx.get_register(reg, valid),
expected.get_register(reg, &expected_valid),
"{reg} registers didn't match!"
);
}
return;
} else {
unreachable!()
}
}
}
unreachable!();
}
#[tokio::test]
async fn test_cfi_at_4000() {
let (mut f, mut stack, expected, expected_valid) = init_cfi_state();
stack = stack.append_repeated(0, 120);
f.raw.set_register("pc", 0x0000000040004000);
f.raw.set_register("lr", 0x0000000040005510);
check_cfi(f, stack, expected, expected_valid).await;
}
#[tokio::test]
async fn test_cfi_at_4001() {
let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state();
let frame1_sp = Label::new();
stack = stack
.D64(0x5e68b5d5b5d55e68) .D64(0x34f3ebd1ebd134f3) .D64(0x0000_00a2_8112_e110) .D64(0x0000_0000_4000_5510) .mark(&frame1_sp)
.append_repeated(0, 120);
expected.set_register("sp", frame1_sp.value().unwrap());
f.raw.set_register("pc", 0x0000000040004001);
f.raw.set_register("x19", 0xadc9f635a635adc9);
f.raw.set_register("x20", 0x623135ac35ac6231);
f.raw.set_register("fp", 0x5fc4be14be145fc4);
check_cfi(f, stack, expected, expected_valid).await;
}
#[tokio::test]
async fn test_cfi_at_4002() {
let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state();
let frame1_sp = Label::new();
stack = stack
.D64(0xff3dfb81fb81ff3d) .D64(0x34f3ebd1ebd134f3) .D64(0x0000_00a2_8112_e110) .D64(0x0000_0000_4000_5510) .mark(&frame1_sp)
.append_repeated(0, 120);
expected.set_register("sp", frame1_sp.value().unwrap());
f.raw.set_register("pc", 0x0000000040004002);
f.raw.iregs[0] = 0x5e68b5d5b5d55e68; f.raw.iregs[1] = 0x34f3ebd1ebd134f3; f.raw.iregs[2] = 0x74bca31ea31e74bc; f.raw.iregs[3] = 0x16b32dcb2dcb16b3; f.raw.iregs[19] = 0xadc9f635a635adc9; f.raw.iregs[20] = 0x623135ac35ac6231; f.raw.iregs[21] = 0xac4543564356ac45; f.raw.iregs[22] = 0x2561562f562f2561; f.raw.set_register("fp", 0x5fc4be14be145fc4);
check_cfi(f, stack, expected, expected_valid).await;
}
#[tokio::test]
async fn test_cfi_at_4003() {
let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state();
let frame1_sp = Label::new();
stack = stack
.D64(0xdd5a48c848c8dd5a) .D64(0xff3dfb81fb81ff3d) .D64(0x34f3ebd1ebd134f3) .D64(0x0000_00a2_8112_e110) .D64(0x0000_0000_4000_5510) .mark(&frame1_sp)
.append_repeated(0, 120);
expected.set_register("sp", frame1_sp.value().unwrap());
expected.iregs[1] = 0xdd5a48c848c8dd5a;
if let MinidumpContextValidity::Some(ref mut which) = expected_valid {
which.insert("x1");
} else {
unreachable!();
}
f.raw.set_register("pc", 0x0000000040004003);
f.raw.iregs[1] = 0xfb756319fb756319;
f.raw.set_register("fp", 0x5fc4be14be145fc4);
check_cfi(f, stack, expected, expected_valid).await;
}
#[tokio::test]
async fn test_cfi_at_4004() {
let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state();
let frame1_sp = Label::new();
stack = stack
.D64(0xdd5a48c848c8dd5a) .D64(0xff3dfb81fb81ff3d) .D64(0x34f3ebd1ebd134f3) .D64(0x0000_00a2_8112_e110) .D64(0x0000_0000_4000_5510) .mark(&frame1_sp)
.append_repeated(0, 120);
expected.set_register("sp", frame1_sp.value().unwrap());
expected.iregs[1] = 0xdd5a48c848c8dd5a;
if let MinidumpContextValidity::Some(ref mut which) = expected_valid {
which.insert("x1");
} else {
unreachable!();
}
f.raw.set_register("pc", 0x0000000040004004);
f.raw.iregs[1] = 0xfb756319fb756319;
f.raw.set_register("fp", 0x5fc4be14be145fc4);
check_cfi(f, stack, expected, expected_valid).await;
}
#[tokio::test]
async fn test_cfi_at_4005_ptr_auth_strip_apple() {
let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state();
let frame1_sp = Label::new();
stack = stack
.D64(0xdd5a48c848c8dd5a) .D64(0xff3dfb81fb81ff3d) .D64(0x34f3ebd1ebd134f3) .D64(0xae23_80a2_8112_e110) .D64(0xae1d_0000_4000_5510) .mark(&frame1_sp)
.append_repeated(0, 120);
expected.set_register("sp", frame1_sp.value().unwrap());
expected.iregs[1] = 0xdd5a48c848c8dd5a;
if let MinidumpContextValidity::Some(ref mut which) = expected_valid {
which.insert("x1");
} else {
unreachable!();
}
f.raw.set_register("pc", 0x0000000040004005);
f.raw.iregs[1] = 0xfb756319fb756319;
check_cfi(f, stack, expected, expected_valid).await;
}
#[tokio::test]
async fn test_cfi_at_4005_ptr_auth_strip_high() {
let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state_high_module();
let frame1_sp = Label::new();
stack = stack
.D64(0xdd5a48c848c8dd5a) .D64(0xff3dfb81fb81ff3d) .D64(0x34f3ebd1ebd134f3) .D64(0x1003_45a2_8112_e110) .D64(0x100d_f700_4000_5510) .mark(&frame1_sp)
.append_repeated(0, 120);
expected.set_register("sp", frame1_sp.value().unwrap());
expected.set_register("fp", 0x0003_45a2_8112_e110);
expected.set_register("pc", 0x000d_f700_4000_5510);
expected.iregs[1] = 0xdd5a48c848c8dd5a;
if let MinidumpContextValidity::Some(ref mut which) = expected_valid {
which.insert("x1");
} else {
unreachable!();
}
f.raw.set_register("pc", 0x0000000040004005);
f.raw.iregs[1] = 0xfb756319fb756319;
check_cfi(f, stack, expected, expected_valid).await;
}
#[tokio::test]
async fn test_cfi_at_4005() {
let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state();
let frame1_sp = Label::new();
stack = stack
.D64(0xdd5a48c848c8dd5a) .D64(0xff3dfb81fb81ff3d) .D64(0x34f3ebd1ebd134f3) .D64(0x0000_00a2_8112_e110) .D64(0x0000_0000_4000_5510) .mark(&frame1_sp)
.append_repeated(0, 120);
expected.set_register("sp", frame1_sp.value().unwrap());
expected.iregs[1] = 0xdd5a48c848c8dd5a;
if let MinidumpContextValidity::Some(ref mut which) = expected_valid {
which.insert("x1");
} else {
unreachable!();
}
f.raw.set_register("pc", 0x0000000040004005);
f.raw.iregs[1] = 0xfb756319fb756319;
check_cfi(f, stack, expected, expected_valid).await;
}
#[tokio::test]
async fn test_cfi_at_4006() {
let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state();
let frame1_sp = Label::new();
stack = stack
.D64(0x0000000040005510) .D64(0xdd5a48c848c8dd5a) .D64(0xff3dfb81fb81ff3d) .D64(0x34f3ebd1ebd134f3) .D64(0x0000_00a2_8112_e110) .D64(0xf8d157835783f8d1) .mark(&frame1_sp)
.append_repeated(0, 120);
expected.set_register("sp", frame1_sp.value().unwrap());
expected.iregs[1] = 0xdd5a48c848c8dd5a;
if let MinidumpContextValidity::Some(ref mut which) = expected_valid {
which.insert("x1");
} else {
unreachable!();
}
f.raw.set_register("pc", 0x0000000040004006);
f.raw.iregs[1] = 0xfb756319fb756319;
check_cfi(f, stack, expected, expected_valid).await;
}
#[tokio::test]
async fn test_cfi_reject_backwards() {
let (mut f, mut stack, _expected, _expected_valid) = init_cfi_state();
stack = stack.append_repeated(0, 120);
f.raw.set_register("pc", 0x0000000040006000);
f.raw.set_register("sp", 0x0000000080000000);
f.raw.set_register("lr", 0x0000000040005510);
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 1);
}
#[tokio::test]
async fn test_cfi_reject_bad_exprs() {
let (mut f, mut stack, _expected, _expected_valid) = init_cfi_state();
stack = stack.append_repeated(0, 120);
f.raw.set_register("pc", 0x0000000040007000);
f.raw.set_register("sp", 0x0000000080000000);
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 1);
}
#[tokio::test]
async fn test_frame_pointer_overflow() {
type Pointer = u64;
let stack_max: Pointer = Pointer::MAX;
let stack_size: Pointer = 1000;
let bad_frame_ptr: Pointer = stack_max;
let mut f = TestFixture::new();
let mut stack = Section::new();
let stack_start: Pointer = stack_max - stack_size;
stack.start().set_const(stack_start);
stack = stack
.append_repeated(0, stack_size as usize);
f.raw.set_register("pc", 0x00007400c0000200);
f.raw.set_register("fp", bad_frame_ptr);
f.raw
.set_register("sp", stack.start().value().unwrap() as Pointer);
f.raw.set_register("lr", 0x00007500b0000110);
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 1);
}
#[tokio::test]
async fn test_frame_pointer_barely_no_overflow() {
let mut f = TestFixture::highest_module();
let mut stack = Section::new();
type Pointer = u64;
let stack_max: Pointer = Pointer::MAX;
let pointer_size: Pointer = std::mem::size_of::<Pointer>() as Pointer;
let stack_size: Pointer = pointer_size * 3;
let stack_start: Pointer = stack_max - stack_size;
let return_address: Pointer = 0x00007500b0000110;
stack.start().set_const(stack_start);
let frame0_fp = Label::new();
let frame1_sp = Label::new();
let frame1_fp = Label::new();
stack = stack
.mark(&frame0_fp)
.D64(&frame1_fp) .D64(return_address) .mark(&frame1_sp)
.mark(&frame1_fp) .D64(0);
f.raw.set_register("pc", 0x00007400c0000200);
f.raw
.set_register("fp", frame0_fp.value().unwrap() as Pointer);
f.raw
.set_register("sp", stack.start().value().unwrap() as Pointer);
f.raw.set_register("lr", return_address);
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{
let frame = &s.frames[0];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(
ctx.get_register("fp", valid).unwrap(),
frame0_fp.value().unwrap() as Pointer
);
} else {
unreachable!();
}
}
{
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::FramePointer);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 3);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame1_sp.value().unwrap() as Pointer
);
assert_eq!(
ctx.get_register("fp", valid).unwrap(),
frame1_fp.value().unwrap() as Pointer
);
} else {
unreachable!();
}
}
}
#[tokio::test]
async fn test_frame_pointer_infinite_equality() {
let mut f = TestFixture::new();
let mut stack = Section::new();
stack.start().set_const(0x80000000);
let return_address1 = 0x50000100u64;
let return_address2 = 0x50000900u64;
let frame1_sp = Label::new();
let frame2_sp = Label::new();
let frame0_fp = Label::new();
let frame1_fp = Label::new();
let frame2_fp = Label::new();
stack = stack
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame0_fp) .D64(&frame0_fp) .D64(return_address1) .mark(&frame1_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame1_fp)
.D64(&frame2_fp)
.D64(return_address2)
.mark(&frame2_sp)
.append_repeated(0, 64) .D64(0x0000000D) .D64(0xF0000000) .mark(&frame2_fp) .D64(0)
.D64(0);
f.raw.set_register("pc", 0x40005510);
f.raw.set_register("lr", 0x1fe0fe10);
f.raw.set_register("fp", frame0_fp.value().unwrap());
f.raw.set_register("sp", stack.start().value().unwrap());
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{
let frame = &s.frames[0];
assert_eq!(frame.trust, FrameTrust::Context);
assert_eq!(frame.context.valid, MinidumpContextValidity::All);
}
{
let frame = &s.frames[1];
let valid = &frame.context.valid;
assert_eq!(frame.trust, FrameTrust::FramePointer);
if let MinidumpContextValidity::Some(ref which) = valid {
assert_eq!(which.len(), 3);
} else {
unreachable!();
}
if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw {
assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1);
assert_eq!(
ctx.get_register("sp", valid).unwrap(),
frame1_sp.value().unwrap()
);
assert_eq!(
ctx.get_register("fp", valid).unwrap(),
frame0_fp.value().unwrap()
);
} else {
unreachable!();
}
}
}