I just found out that call instructions that we usually are actually program-counter relative. Yet the x86 instruction uses a 32-bit wide offset to indicate a relative number.
What if I want to jump > 4GB away?
I just found out that call instructions that we usually are actually program-counter relative. Yet the x86 instruction uses a 32-bit wide offset to indicate a relative number.
What if I want to jump > 4GB away?
I guess this could come up if you JIT some code into a buffer allocated more than 2^32 away from some functions it needs to call. The simple answer is: don't do that.
On Linux, for example, use mmap(MAP_32BIT)
to allocate memory in the low 2GiB of virtual address space, if you want the JITed code to call
function in the main executable. (Assuming a position-dependent executable).
In a PIE executable or a shared library (which typically won't be mapped in the low 32 bits of virtual address space), you might try to allocate memory near your own code by trying mmap
without MAP_FIXED
, and trying different addresses in range if that doesn't work the first time. mmap(hint_address, ...)
/ check if it's within +-2GiB of the code and/or data it needs to reach / munmap
and retry with a different hint.
The reason is that the only workaround is to use an absolute address indirect call. See Call an absolute pointer in x86 machine code. You'd need to load the target address into a register, or have the address stored in memory as a pointer, and jump to that. See Intel's insn ref manual, where all the available encodings of call
are listed.
Also the x86 tag wiki links to https://www-ssl.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
If you don't need it to be super-efficient, one way to actually JIT the absolute-indirect calls would be to put a table of pointers at a known location relative to the JITed code so it can use indirect call [rel pointer_to_func1]
(RIP-relative addressing). This is like the global offset table used by Unix shared libs, and how compiler-generated code calls shared library functions if compiled with gcc -fno-plt
.
I just happened to have this very need: To emit a call to an absolute 64-bit address into some dynamically created code, and it turns out not to be hard at all albeit slightly kludgy compared to the direct absolute jump we have in 32-bit mode (your exact asm syntax may vary depending on which assembler you happen to be using) e.g.:
call qword ptr [rel @1]
jmp @2
@1:
dq <64-bit address>
@2: