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
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.