Unit testing x86_64 instructions

Let REGISTERS = { rax, rbx, rcx, rdx, rbp, rsi, rdi, rsp, r9-r15, xmm0-xmm15}.

Is there a way to (1) specify the values of REGISTERS, (2) run a block of x86_64 instructions, and (3) read back the values of REGISTERS.

I'm not executing any exotic instructions. Just load/store, arith, jump. Definitely not anything privileged.

I'm okay with massive slowdowns.

The XY problem here is: dynamically generating x86_64 instructions in Rust, and want to unit test the effects on registers.

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.

rfcs/1201-naked-fns.md at master · rust-lang/rfcs · GitHub looks interesting, but I do not know enough about the interaction of Rust/assembly to see how to use this.

One idea I had in mind was to write a "x86_64 wrapper" of the following form:

  1. push all the callee saved registers to LOC_0 in memory.
  2. copy from LOC_1 in memory to rax, rbx, ..., r15, xmm0, ..., xmm15
  3. execute the x86_64 block we care about
  4. write rax, rbx, ..., r15, xmm0, ..., xmm15 to LOC_2 in memory
  5. restore the callee saved registers from LOC_0 in memory

Here, 1, 2, 4, 5, are part of a "x86_64 wrapper" that gets pasted around the actual x86_64 instrs we care about in (3).

Is this the general idea you were pushing towards ?

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);
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.