X86_64 load from variable address

I am using https://crates.io/crates/dynasm and struggling with figuring out the mov syntax.

use std::{
    io,
    io::Write,
    mem,
    ops::{Index, IndexMut},
    slice,
};

use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi};

#[test]
fn test_00() {
    let mut ops = dynasmrt::x64::Assembler::new().unwrap();

    let instrs = ops.offset();

    let mut x: i64 = 23;

    dynasm!(ops
        ; .arch x64
        ; mov rax, QWORD x
        ; ret
    );

    x = 24;

    let buf = ops.finalize().unwrap();

    let hello_fn: extern "sysv64" fn() -> u64 = unsafe { mem::transmute(buf.ptr(instrs)) };

    assert_eq!(hello_fn(), 23);}

So the answer here is 23, showing that it compiled in the value of x. I am trying to figure out if there is a way to have the mov load from the address of x (rather than the literal value of x at the time of invoking the macro).

looking at the main page of dynasm, the syntax appears to be mov rax, QWORD [x].
Note that you also have to pass that pointer to x to the call.

I can do this:

#[test]
fn test_00() {
    let mut ops = dynasmrt::x64::Assembler::new().unwrap();

    let instrs = ops.offset();

    let mut x: i64 = 23;

    dynasm!(ops
        ; .arch x64
        ; mov rax, QWORD (&mut x as *mut i64 as u64 as i64)
        ; mov rax, [rax]
        ; ret
    );

    x = 24;
    let buf = ops.finalize().unwrap();

    let hello_fn: extern "sysv64" fn() -> u64 = unsafe { mem::transmute(buf.ptr(instrs)) };

    assert_eq!(hello_fn(), 24);}

but I am having problem trying to convert it to a single instruction. In particular, the inside of '[x] seems to want a i32 instead of an i64.

In particular,

#[test]
fn test_00() {
    let mut ops = dynasmrt::x64::Assembler::new().unwrap();

    let instrs = ops.offset();

    let mut x: i64 = 23;

    /*
       ; mov rax, QWORD (&mut x as *mut i64 as u64 as i64)
       ; mov rax, [rax]
    */

    dynasm!(ops
        ; .arch x64
        ; mov rax, QWORD [&mut x as *mut i64 as u64 as i64]
        ; ret
    );

    x = 24;
    let buf = ops.finalize().unwrap();

    let hello_fn: extern "sysv64" fn() -> u64 = unsafe { mem::transmute(buf.ptr(instrs)) };

    assert_eq!(hello_fn(), 24);}

gives the compiler error of:

   |
29 |         ; mov rax, QWORD [&mut x as *mut i64 as u64 as i64]
   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `i64`
   |
help: you can convert an `i64` to an `i32` and panic if the converted value doesn't fit
   |
29 |         ; mov rax, QWORD [(&mut x as *mut i64 as u64 as i64).try_into().unwrap()]

This compiles and works for me:

use dynasmrt::{dynasm, DynasmApi};

fn main() {
    let mut ops = dynasmrt::x64::Assembler::new().unwrap();

    let instrs = ops.offset();

    let mut x: i64 = 23;

    dynasm!(ops
        ; .arch x64
        ; mov rax, QWORD &mut x as *mut i64 as i64
        ; mov rax, [rax]
        ; ret
    );

    x = 24;
    let buf = ops.finalize().unwrap();

    let hello_fn: extern "sysv64" fn() -> u64 = unsafe { std::mem::transmute(buf.ptr(instrs)) };

    assert_eq!(hello_fn(), 24);
}
  1. Thank you for taking time to look into this.

  2. Is your solution nearly the same as X86_64 load from variable address - #3 by zeroexcuses ? I think you shaved off an "as u64"

  3. The main issue I have with this solution is -- and I'm not an x86_64 asm expert -- is this the right way to do this? It seems if we are loading directly from an address, the CPU can start pre-fetching, but if we load a value into a register than load from the reg, it seems the CPU can not pre-fetch while a mile away.

Oh, I totally missed that you first load the address. Lets see.

#[test]
fn test_00() {
    let mut ops = dynasmrt::x64::Assembler::new().unwrap();
    let instrs = ops.offset();
    let mut x: i64 = 23;

    dynasm!(ops
        ; .arch x64
        ; movabs rax, &mut x as *mut i64 as u64 as i64
        ; ret
    );

    x = 24;
    let buf = ops.finalize().unwrap();
    let hello_fn: extern "sysv64" fn() -> u64 = unsafe { mem::transmute(buf.ptr(instrs)) };
    assert_eq!(hello_fn(), 24);}

appears to work (note change of mov to movabs).

Downside: this apperas to only work on register rax as destination.

This is what the rust code would look like:

mov rax, qword ptr [rip + example::X]

1 Like

Maybe the REX.W extension isn't implemented in dyasmrt?

  1. Regarding "REX.W" extension -- no idea what it is. I suspect the answer lies in https://github.com/CensoredUsername/dynasm-rs/blob/master/plugin/src/arch/x64/gen_opmap.rs#L5187-L5224 but I have not managed to decipher how to read those lines yet.

  2. I think the case I care about (load/store to arbitrary reg from an absolute address) is not a very common case anyway.

  3. Thanks for the rip example above. Position-independent-code makes so much more sense. (Pure speculation) I would not be surprised if modern CPUs had silicon dedicated to find blocks of instrs where rip is not written to, and thus can pre-fetch based on offsets to rip.

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.