use crate::*;
const DATA_ADDR: u64 = 0x7000;
#[test]
fn test_leave_basic() {
let code = [
0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x2FF8-(0x2FF8 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x2FF8; emu.regs_mut().rbp = 0x3000;
emu.maps.write_qword(0x3000, 0x4000);
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x3008, "RSP = old RBP + 8");
assert_eq!(emu.regs().rbp, 0x4000, "RBP restored from stack");
}
#[test]
fn test_leave_after_enter() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "RSP restored to original");
assert_eq!(emu.regs().rbp, 0x2000, "RBP restored to original");
}
#[test]
fn test_leave_with_local_variables() {
let code = [
0xc8, 0x20, 0x00, 0x00, 0x48, 0xc7, 0x45, 0xf8, 0xaa, 0x00, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "RSP restored despite local var usage");
assert_eq!(emu.regs().rbp, 0x2000, "RBP restored");
}
#[test]
fn test_leave_small_frame() {
let code = [
0xc8, 0x08, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "RSP restored");
assert_eq!(emu.regs().rbp, 0x2000, "RBP restored");
}
#[test]
fn test_leave_large_frame() {
let code = [
0xc8, 0x00, 0x04, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x2000-(0x2000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x2000;
emu.regs_mut().rbp = 0x3000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x2000, "RSP restored after large allocation");
assert_eq!(emu.regs().rbp, 0x3000, "RBP restored");
}
#[test]
fn test_leave_nested_functions() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0xc8, 0x08, 0x00, 0x00, 0xc9, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "RSP fully restored after nested calls");
assert_eq!(emu.regs().rbp, 0x2000, "RBP fully restored after nested calls");
}
#[test]
fn test_leave_triple_nested() {
let code = [
0xc8, 0x20, 0x00, 0x00, 0xc8, 0x10, 0x00, 0x00, 0xc8, 0x08, 0x00, 0x00, 0xc9, 0xc9, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x2000-(0x2000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x2000;
emu.regs_mut().rbp = 0x3000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x2000, "RSP restored through 3 levels");
assert_eq!(emu.regs().rbp, 0x3000, "RBP restored through 3 levels");
}
#[test]
fn test_leave_deep_nesting() {
let code = [
0xc8, 0x08, 0x00, 0x00, 0xc8, 0x08, 0x00, 0x00, 0xc8, 0x08, 0x00, 0x00, 0xc8, 0x08, 0x00, 0x00, 0xc8, 0x08, 0x00, 0x00, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x3000-(0x3000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x3000;
emu.regs_mut().rbp = 0x4000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x3000, "RSP restored through deep nesting");
assert_eq!(emu.regs().rbp, 0x4000, "RBP restored through deep nesting");
}
#[test]
fn test_leave_preserves_registers() {
let code = [
0x48, 0xc7, 0xc0, 0x11, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc3, 0x22, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc1, 0x33, 0x00, 0x00, 0x00, 0xc8, 0x10, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rax, 0x11, "RAX unchanged");
assert_eq!(emu.regs().rbx, 0x22, "RBX unchanged");
assert_eq!(emu.regs().rcx, 0x33, "RCX unchanged");
}
#[test]
fn test_leave_preserves_flags() {
let code = [
0xf9, 0xc8, 0x08, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.run(None).unwrap();
assert_ne!(emu.flags().dump() & 0x01, 0, "CF should be preserved");
}
#[test]
fn test_leave_manual_frame() {
let code = [
0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x10, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "RSP restored");
assert_eq!(emu.regs().rbp, 0x2000, "RBP restored");
}
#[test]
fn test_leave_with_push_pop_in_function() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0x50, 0x53, 0x5b, 0x58, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.regs_mut().rax = 0xAAAA;
emu.regs_mut().rbx = 0xBBBB;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "RSP restored");
assert_eq!(emu.regs().rbp, 0x2000, "RBP restored");
assert_eq!(emu.regs().rax, 0xAAAA, "RAX restored via PUSH/POP");
assert_eq!(emu.regs().rbx, 0xBBBB, "RBX restored via PUSH/POP");
}
#[test]
fn test_leave_standalone() {
let code = [
0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x2FF8-(0x2FF8 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x2FF8;
emu.regs_mut().rbp = 0x3000;
emu.maps.write_qword(0x3000, 0x4000);
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x3008, "RSP = RBP + 8");
assert_eq!(emu.regs().rbp, 0x4000, "RBP from stack");
}
#[test]
fn test_leave_with_modified_stack() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0x48, 0x83, 0xec, 0x08, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "RSP restored via RBP");
assert_eq!(emu.regs().rbp, 0x2000, "RBP restored");
}
#[test]
fn test_leave_high_stack_address() {
let code = [
0xc8, 0x20, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x100000-(0x100000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x100000;
emu.regs_mut().rbp = 0x200000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x100000, "RSP restored at high address");
assert_eq!(emu.regs().rbp, 0x200000, "RBP restored");
}
#[test]
fn test_leave_low_stack_address() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x100-(0x100 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x100;
emu.regs_mut().rbp = 0x200;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x100, "RSP restored at low address");
assert_eq!(emu.regs().rbp, 0x200, "RBP restored");
}
#[test]
fn test_leave_zero_sized_frame() {
let code = [
0xc8, 0x00, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "RSP restored");
assert_eq!(emu.regs().rbp, 0x2000, "RBP restored");
}
#[test]
fn test_leave_typical_epilogue() {
let code = [
0xc8, 0x18, 0x00, 0x00, 0x48, 0xc7, 0xc0, 0x42, 0x00, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rax, 0x42, "Return value set");
assert_eq!(emu.regs().rsp, 0x1000, "Stack cleaned up");
assert_eq!(emu.regs().rbp, 0x2000, "Frame pointer restored");
}
#[test]
fn test_leave_with_saved_registers() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0x50, 0x53, 0x48, 0xc7, 0xc0, 0x11, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc3, 0x22, 0x00, 0x00, 0x00, 0x5b, 0x58, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.regs_mut().rax = 0xAAAA;
emu.regs_mut().rbx = 0xBBBB;
emu.run(None).unwrap();
assert_eq!(emu.regs().rax, 0xAAAA, "RAX restored");
assert_eq!(emu.regs().rbx, 0xBBBB, "RBX restored");
assert_eq!(emu.regs().rsp, 0x1000, "Stack balanced");
}
#[test]
fn test_leave_sequence() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0xc9, 0xc8, 0x10, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "RSP restored after sequence");
assert_eq!(emu.regs().rbp, 0x2000, "RBP restored after sequence");
}
#[test]
fn test_leave_frame_chain() {
let code = [
0xc8, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0xc9, 0xc9, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x2000-(0x2000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x2000;
emu.regs_mut().rbp = 0x3000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x2000, "Full chain unwound");
assert_eq!(emu.regs().rbp, 0x3000, "Original frame pointer restored");
}
#[test]
fn test_leave_with_varying_frames() {
let code = [
0xc8, 0x40, 0x00, 0x00, 0xc8, 0x20, 0x00, 0x00, 0xc8, 0x10, 0x00, 0x00, 0xc9, 0xc9, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x2000-(0x2000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x2000;
emu.regs_mut().rbp = 0x3000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x2000, "All frames unwound");
assert_eq!(emu.regs().rbp, 0x3000, "Frame pointer fully restored");
}
#[test]
fn test_leave_rbp_equals_rsp() {
let code = [
0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x3000-(0x3000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x3000;
emu.regs_mut().rbp = 0x3000;
emu.maps.write_qword(0x3000, 0x4000);
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x3008, "RSP incremented");
assert_eq!(emu.regs().rbp, 0x4000, "RBP loaded from stack");
}
#[test]
fn test_leave_with_zero_saved_rbp() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x0000; emu.run(None).unwrap();
assert_eq!(emu.regs().rbp, 0x0000, "Zero RBP restored");
}
#[test]
fn test_leave_stack_grows_correctly() {
let code = [
0xc8, 0x00, 0x01, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x2000-(0x2000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x2000;
emu.regs_mut().rbp = 0x3000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x2000, "Large frame properly unwound");
}
#[test]
fn test_leave_after_stack_arithmetic() {
let code = [
0xc8, 0x20, 0x00, 0x00, 0x48, 0x83, 0xec, 0x10, 0x48, 0x83, 0xc4, 0x10, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "LEAVE restores correctly despite SUB/ADD");
}
#[test]
fn test_leave_after_many_pushes() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0x50, 0x50, 0x50, 0x50, 0x58, 0x58, 0x58, 0x58, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "Stack balanced after PUSH/POP");
}
#[test]
fn test_leave_recursive_function_simulation() {
let code = [
0xc8, 0x08, 0x00, 0x00, 0xc8, 0x08, 0x00, 0x00, 0xc8, 0x08, 0x00, 0x00, 0xc9, 0xc9, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x3000-(0x3000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x3000;
emu.regs_mut().rbp = 0x4000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x3000, "Recursion fully unwound");
assert_eq!(emu.regs().rbp, 0x4000, "Base frame restored");
}
#[test]
fn test_leave_with_mixed_operations() {
let code = [
0xc8, 0x20, 0x00, 0x00, 0x50, 0x53, 0x48, 0xc7, 0xc0, 0x99, 0x00, 0x00, 0x00, 0x5b, 0x58, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.regs_mut().rax = 0x1111;
emu.regs_mut().rbx = 0x2222;
emu.run(None).unwrap();
assert_eq!(emu.regs().rax, 0x1111, "RAX restored by POP");
assert_eq!(emu.regs().rbx, 0x2222, "RBX restored by POP");
assert_eq!(emu.regs().rsp, 0x1000, "Stack cleaned by LEAVE");
}
#[test]
fn test_leave_exception_handler_pattern() {
let code = [
0xc8, 0x10, 0x00, 0x00, 0x48, 0xc7, 0x45, 0xf8, 0xff, 0x00, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "Handler frame unwound");
}
#[test]
fn test_leave_tail_call_preparation() {
let code = [
0xc8, 0x08, 0x00, 0x00, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x1000-(0x1000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x1000;
emu.regs_mut().rbp = 0x2000;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x1000, "Frame cleaned for tail call");
assert_eq!(emu.regs().rbp, 0x2000, "RBP restored for tail call");
}
#[test]
fn test_leave_coroutine_switch_pattern() {
let code = [
0xc8, 0x00, 0x00, 0x00, 0x50, 0x53, 0x51, 0x52, 0x5a, 0x59, 0x5b, 0x58, 0xc9, 0xf4, ];
let mut emu = emu64();
emu.load_code_bytes(&code);
emu.maps.create_map("stack_test", 0x2000-(0x2000 / 2), 0x1000, crate::maps::mem64::Permission::READ_WRITE_EXECUTE).unwrap();
emu.regs_mut().rsp = 0x2000;
emu.regs_mut().rbp = 0x3000;
emu.regs_mut().rax = 0xAA;
emu.regs_mut().rbx = 0xBB;
emu.regs_mut().rcx = 0xCC;
emu.regs_mut().rdx = 0xDD;
emu.run(None).unwrap();
assert_eq!(emu.regs().rsp, 0x2000, "Context switch cleaned up");
assert_eq!(emu.regs().rax, 0xAA, "Registers preserved");
}