What about writing a #[naked] function (or macro) that uses inline assembly to save the state of your registers? Then you run the function/macro afterwards and compare the two.
Yeah, pretty much. In my suggestion, the "x86_64 wrapper" would just be written in Rust.
The reason I suggested using a #[naked] function is because it'll let you call a function but make sure the compiler doesn't emit the normal prelude for shuffling arguments around or adjusting the stack pointer, meaning your registers won't be changed.
#[derive(Default)]
struct Registers { rax: usize, ... }
#[naked]
unsafe fn load_registers(registers: &mut Registers) {
// TODO: load registers into the "registers" struct
asm!("...");
}
fn test_function(callable: impl FnOnce()) {
let mut before = Registers::default();
unsafe { load_registers(&mut before); }
callable();
let mut after = Registers::default();
unsafe { load_registers(&mut after); }
assert_eq!(before, after);
}