assemblyx86-64nasmjit

Making an absolute 64-bit jump in x64 assembler which can be copied as a JIT


I am trying to port some code from linux to windows. I need to assemble a jump to an absolute address, using nasm, such that the same bytes will jump to the same address, no matter where the code is placed in memory.

This is part of a Forth interpreter which will copy these bytes of machine code around to other locations, and I want the jmp to still target pushparam.

The original code is this

        jmp [.n+0]
.n:
        DQ pushparam
ALIGN 8
doestargetlen EQU $ - doestarget

pushparam:
        mov [r12], rbx
        add r12, 8

this assembles to (nasm on linux)

  1263 000018A0 FF2425[A7180000]                jmp [.n+0]
  1264                                  .n:
  1265 000018A7 [B018000000000000]              DQ pushparam
  1266 000018AF 90                      ALIGN 8
  1267                                  doestargetlen EQU $ - doestarget
  1268                                  
  1269                                  pushparam:

It has generated a 32-bit reference, however the linker (ld) is happy with it.

Assembling the same thing on windows and then linking using MSVC linker says

64th.obj : error LNK2017: 'ADDR32' relocation to '.rodata' invalid without /LARGEADDRESSAWARE:NO

I know this error from when I wrote a c compiler, it means that I have a 32-bit address instead of a 64 bit one. I can see in a dumpbin where it is

 000018A3  ADDR32                     000018A7         C  .rodata

Which is exactly the address in that jump.

I read around and saw first of all that there is no 64 bit absolute jump, but the nasm manual seems to disagree

JMP              imm64                    X86_64,LONG,BND 

and

JMP              mem64|far                X86_64,LONG 

But I could not find a syntax to do this so followed another piece of advice and tried

lea rax,[qword .n+0]
jmp rax

but that made no difference same linker error


Solution

  • If you can spare a register, a particularly compact way would be to write the address of pushparam into some fixed register (say r8) and then to jump to that register:

            lea  r8, [rel pushparam]
            ...
            jmp  r8
    

    This can be expanded to more than one snippet by means of a jump table

            lea  r8, [rel jmptab]
            ...
    jmptab: dq   pushparam
            dq   foo
            dq   bar
            ...
            jmp  qword [r8]     ; jump to pushparam
            jmp  qword [r8+8]   ; jump to foo
            jmp  qword [r8+16]  ; jump to bar
    

    at the cost of slightly reduced performance.