x86 asm 中调用指令的编码有什么区别?

What is the difference between the encodings for the call instruction in x86 asm?

供参考:英特尔 call instruction 文档的 HTML 摘录。


我知道第 3.1.1.3 节对此进行了解释,但我无法理解该手册,可能是因为它太技术化了。

  1. FF /2FF /3里面的/2/3是什么?

  2. r/m32m16:32有什么区别? r/m32 涵盖了 32 位寄存器和内存操作数,所以这不会使 m16:32 变得多余吗?

  3. 根据手册call ptr16:32是一个"Call far, absolute, address given in operand"。如果我理解正确,这将允许我在给定的绝对 32 位地址调用函数。如何调用绝对地址 0x717A60 处的函数(我在 MSVC 中使用内联汇编器)? call 0x717A60 给我一个错误,call ds:0x717A60 被组装成 call [0x717A60]

  4. 下面的机器码对应哪个操作码? FF 15 90 98 76 70

  1. /2 vs. /3:见3.1.1.1O Opcode Column in the Instruction Summary Table:

/digit — A digit between 0 and 7 indicates that the ModR/M byte of the instruction uses only the r/m (register or memory) operand. The reg field contains the digit that provides an extension to the instruction's opcode.

这里使用单操作数指令的 mod/rm 字节的 /r 字段与 inc r/m32 (FF /0).

x86 以这种方式用多个单操作数指令重载了几个操作码字节。 3 位 /r 字段成为另外 3 个操作码位而不是操作数。更多详情:

  • How to read the Intel Opcode notation
  • What does the /4 mean in FF /4?
  • x64 instruction encoding and the ModRM byte - 有关 modrm 字节中字段的详细信息。

r/m32 对比 m16:32 对比 ptr16:32

另见 x86 function call types

“far”call 加载 CS 段寄存器以及 IP/EIP/RIP。正常的“near”调用只需要一个 32 位(或 64 位)地址,并且不会修改 CS.

far call 从未在普通操作系统的“普通”32 位或 64 位用户代码中使用 space,因为它们都使用平面内存模型。

ptr16:32是一个立即数,16位的段值和32位的绝对地址编码到指令中(little-endian,32位偏移量在前,然后是新的CS值) . 这是一个far call。参见 this Q&A. Assemblers will generate this instruction encoding for far call some_symbol, taking the segment value from the segment some_symbol is defined in. Again, you're really unlikely to ever use this outside of 16-bit code. But if you do, see

m16:32是一个6字节的内存操作数,从ModR/M字节编码的有效地址加载。 这是另一个远程调用。所以 call far [eax]eax 中的地址加载 48 位。只有内存寻址模式对于 ModR/M 字节是合法的,因为指令需要的数据多于寄存器的宽度。 (即 call far eax 不合法)

r/m32 是内存或寄存器操作数 用于近调用。 你从 call eax 得到这个或 call [eax]。当然任何寻址方式都是合法的,例如call FS:[edi + esi*4 + some_table]。 FS 段覆盖前缀适用于函数指针加载的位置,而不是它跳转到的段。 (即它不会改变 CS,因为它仍然是一个接近的呼叫。)


How can I call the function at the absolute address 0x717A60

参见 Call an absolute pointer in x86 machine code

如果您的代码不必与位置无关,那么到目前为止,最好的选择是汇编成 call rel32 并具有正确的相对位移以到达该地址。在 NASM 和 AT&T 语法中,您可以简单地编写 call 0x717A60 ,汇编器 + 链接器会处理它。我不确定如何在 MASM 中编写它; MSVC 内联 asm 不接受 call 123456h.

call 附近没有绝对直接的编码,所以如果你确实需要 PIC,那么你应该做类似 mov eax, 0x717A60 / call eax.

的事情

您不想使用 call far 绝对直接调用,因为它可能 慢很多 ,并且推送 CS:EIP 而不是仅仅作为 EIP return 地址。您还必须知道要在段字段中输入什么。


FF 15 90 98 76 70

反汇编为 call DWORD PTR ds:0x70769890(在 GNU objdump -Mintel 语法中),这是一个 call r/m32,其中操作数是 [disp32] 绝对寻址模式。

(或者在 64 位代码中,它是一个具有 rel32 的 RIP 相对寻址模式,但仍然是一个从静态位置加载函数指针的内存间接 call。 )


另请参阅 标签 wiki 以获取更多文档链接。