在x86机器代码中调用绝对指针
在x86机器代码中调用绝对指针的“正确”方法是什么?有没有一个好方法可以在一个指令中完成 我想做的是: 我试图构建一种基于“子程序线程”的简化迷你JIT(still)。这基本上是字节码解释器最短的升级:每个操作码都作为一个单独的函数实现,因此字节码的每个基本块都可以“jitte”到一个新的过程中,它看起来像这样:在x86机器代码中调用绝对指针,x86,jit,machine-code,X86,Jit,Machine Code,在x86机器代码中调用绝对指针的“正确”方法是什么?有没有一个好方法可以在一个指令中完成 我想做的是: 我试图构建一种基于“子程序线程”的简化迷你JIT(still)。这基本上是字节码解释器最短的升级:每个操作码都作为一个单独的函数实现,因此字节码的每个基本块都可以“jitte”到一个新的过程中,它看起来像这样: {prologue} call {opcode procedure 1} call {opcode procedure 2} call {opcode procedure 3} ...
{prologue}
call {opcode procedure 1}
call {opcode procedure 2}
call {opcode procedure 3}
...etc
{epilogue}
因此,我们的想法是,每个块的实际机器代码都可以从模板中粘贴出来(根据需要扩展中间部分),唯一需要“动态”处理的是将每个操作码的函数指针复制到正确的位置,作为每个调用指令的一部分
我遇到的问题是理解模板的调用…
部分使用什么。x86似乎并没有考虑到这种用法,它支持相对调用和间接调用
看起来我可以使用FF 15 efbeade
或2E FF 15 efbeade
在DEADBEEF
处假设调用函数(基本上是通过将东西放入汇编程序和反汇编程序并查看产生的有效结果,而不是通过理解它们的功能发现的),但我对段、特权和相关信息的理解不够透彻,无法看出它们之间的区别,也无法看出它们与更常见的调用指令的行为方式有何不同。《英特尔体系结构手册》还指出,它们仅在32位模式下有效,在64位模式下“无效”
有人能解释一下这些操作码,以及我将如何或是否为此目的使用它们或其他操作码吗
(使用通过寄存器的间接调用也有一个明显的答案,但这似乎是“错误”的方法——假设直接调用指令确实存在。)您不能只使用一条指令。使用MOV+CALL是一种不错的方法:
0000000002347490: 48b83412000000000000 mov rax, 0x1234
000000000234749a: 48ffd0 call rax
如果要调用的过程的地址更改,请更改从偏移量2开始的八个字节。如果调用0x1234的代码的地址发生更改,则无需执行任何操作,因为该地址是绝对地址。此处的所有内容也适用于绝对地址,并且指定目标的语法相同。这个问题是关于JIT的,但我也加入了NASM和AT&T语法来扩大范围
有关分配“附近”内存的方法,请参见,以便您可以使用rel32
从JIT代码中提前调用已编译函数
x86没有针对指令中编码的绝对地址的正常(近)调用或jmp
的编码
没有绝对直接调用/jmp编码,除了您不想要的jmp far
之外。看见(另请参阅文档和指南的其他链接。)顺便说一句,大多数计算机体系结构,如x86
最好的选择(如果您可以生成知道自己地址的位置相关代码)是使用正常的call rel32
,即E8 rel32
直接近调用编码,其中rel32
字段是target-end\u of_call\u insn
(2的补码二进制整数)
有关手动编码调用
指令的示例,请参阅;在JITing的时候做应该同样容易
在AT&T语法中:调用0x1234567
在NASM语法中:调用0x1234567
也适用于具有绝对地址的命名符号(例如,使用eq
或.set
创建)。MASM没有等价物,它显然只接受一个标签作为目的地,所以人们有时会使用低效的变通方法来解决工具链(和/或对象文件格式重定位类型)的限制
这些程序在位置相关的代码(不是共享库或饼图可执行文件)中可以很好地组装和链接。但在x86-64 OS X中,文本部分映射到4GiB以上,因此无法使用rel32
到达较低的地址
在要调用的绝对地址范围内分配JIT缓冲区例如,在Linux上使用mmap(MAP_32BIT)
在低2GB内存中分配内存,+-2GB可以到达该区域的任何其他地址,或者在跳转目标附近提供非空提示地址。(不过,不要使用MAP\u FIXED
;如果提示与任何现有映射重叠,最好让内核选择不同的地址。)
(Linux非饼图可执行文件映射在低2GB的虚拟地址空间中,因此它们可以使用[disp32+reg]
使用符号扩展32位绝对地址进行数组索引,或将静态地址放入带有mov eax、imm32
的寄存器中,以获得零扩展绝对值。因此,2GB较低,而不是4GB较低。因此,不要假设主可执行文件中的静态地址位于32位低位,除非确保使用无饼图-fno饼图构建+链接。而其他操作系统,如OSX,总是将可执行文件放在4GB以上。)
如果无法使调用rel32
可用
但是如果您需要生成不知道其自身绝对地址的位置独立代码,或者如果您需要调用的地址距离调用方超过+-2GiB(可能是64位,但最好将代码放得足够近),您应该使用寄存器间接调用
; use any register you like as a scratch
mov eax, 0xdeadbeef ; 5 byte mov r32, imm32
; or mov rax, 0x7fffdeadbeef ; for addresses that don't fit in 32 bits
call rax ; 2 byte FF D0
或AT&T语法
mov $0xdeadbeef, %eax
# movabs $0x7fffdeadbeef, %rax # mov r64, imm64
call *%rax
显然,您可以使用任何寄存器,如r10
或r11
,它们被调用关闭,但不用于在x86-64系统V中传递参数。AL=可变函数的XMM参数数,因此在调用x86-64系统V调用约定中的可变函数之前,需要AL=0中的固定值
如果你真的需要
foo.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: e8 00 00 00 00 call 0x5 1: R_X86_64_PC32 *ABS*+0xdeadbeeb