我在C中编写了一个用于x86_64 linux的JIT编译器。
目前的想法是在可执行内存的缓冲区中生成一些字节码(例如通过mmap调用获得)并使用函数指针跳转到它。
我希望能够将多个可执行内存块链接在一起,以便它们可以仅使用本机指令在彼此之间跳转。
理想情况下,可执行块的C级指针可以作为绝对跳转地址写入另一个块,如下所示:
unsigned char *code_1 = { 0xAB, 0xCD, ... };
void *exec_block_1 = mmap(code1, ... );
write_bytecode(code_1, code_block_1);
...
unsigned char *code_2 = { 0xAB, 0xCD, ... , exec_block_1, ... };
void *exec_block_2 = mmap(code2, ... );
write_bytecode(code_2, exec_block_2); // bytecode contains code_block_1 as a jump
// address so that the code in the second block
// can jump to the code in the first block
然而,我发现x86_64的局限性是一个很大的障碍。由于所有可用的64位跳转操作都与指令指针相关,因此无法跳转到x86_64中的绝对64位地址。这意味着我不能将C指针用作生成代码的跳转目标。
是否有解决此问题的方法可以让我以我所描述的方式将块链接在一起?也许是我不知道的x86_64指令?
答案 0 :(得分:3)
如果您在发出跳转指令时知道块的地址,则可以检查从跳转指令的地址到目标块的地址的距离是否符合jXX
指令族的32位有符号偏移量。
即使你单独mmap
每个块,很可能你不会得到两个相邻(在控制流感中)块超过±2GiB的块。话虽如此,不有几个很好的理由来分别映射每个块。首先,mmap
的最小分配单位(几乎是定义)是一个页面,可能至少为4KiB。这意味着每个块的代码之后的未使用空间被浪费了。其次,更紧密地打包基本块会增加指令缓存的利用率,并且缩短跳转编码的可能性也会有效。
也许是我不知道的x86_64指令?
顺便提一下,有一条指令用于将64位立即数加载到rax
。 GNU工具链将其称为movabs
:
0000000000000000 <.text>:
0: 49 b8 ff ff ff ff ff movabs rax,0x7fffffffffffffff
7: ff ff 7f
因此,如果你真的想要,你只需将指针加载到rax
并使用跳转进行注册。
答案 1 :(得分:0)
嗯,我不确定我是否清楚地理解了你的问题以及是否是一个正确的答案。实现这一目标是一种非常复杂的方式:
;instr ; opcodes [op size] (comment)
call next ; e8 00 00 00 00 [4] (call to get current location)
next:
pop rax ; 58 [1] (next label address in rax)
add rax, 12h ; 48 83 c0 12 [4] (adjust rax to fall on landing label)
push rax ; 50 [1] (push adjusted value)
mov rax, code_block ; 48 b8 XX XX XX XX XX XX XX XX [10] (load target address)
push rax ; 50 [1] (push to ret to code_block)
ret ; c3 [1] (go to code_block)
landing:
nop
nop
e8 00 00 00 00
就是将当前指针放在堆栈顶部。然后代码会调整rax
以便稍后落在着陆标签上。您需要将XX
(mov rax, code_block
)替换为code block
的虚拟地址。 ret
指令用作呼叫。当调用者返回时,代码应该落在landing
上。
这是你想要实现的这种事吗?