我应该如何使用 Intel 的文档获取与 x86 中的 `call dword ptr` 指令对应的 `ModeR/M` 字节?
How should I obtain the `ModeR/M` byte corresponding to a `call dword ptr` instruction in x86, using Intel's docs?
我在VS2017中调试了如下代码(注意下面的断点):
下面你会找到上面提到的断点的反汇编:
如上图所示,编译器为指令call dword ptr [fp]
生成的机器码是FF 55 F8
,其中FF
是调用指令的操作码, 55
是ModeR/M
字节的值,F8
是一个8位位移,值为-8,下面我会解释。
现在,如果您查看下面的 "Table 2-2: 32 bit addressing forms with the ModeR/M" 字节,它取自英特尔“64 位 IA-32 架构软件开发人员手册”第 2A 卷,您会注意到我突出显示了上面提到的数字 55,它对应一个有效地址[EBP]+disp8
。即汇编器中的CALL
指令会跳转到地址为寄存器EBP中的地址加上上述8位位移值为-8得到的指令。这个地址是正确的。它对应于最终将代码传输到函数 f
.
的 JMP 指令的地址
因此一切看起来都很好。但是我遗漏了重要的一点:我应该使用英特尔手册中的 CALL 指令参考和上面显示的 Table 2.2 获得 ModeR/M
字节。但我仍然不知道该怎么做。任何提示都将不胜感激,因为我已经为此工作了好几天,但我对此仍然一无所知。
您缺少的是操作码的一部分编码在 ModR/M 字节中。通常 ModR/M 字节编码两个操作数。第一个操作数是 table 左侧行标签中给出的寄存器或内存操作数,而第二个操作数是 table 顶部列标题中给出的寄存器。对于只有一个操作数的指令,如 CALL 指令,第二个操作数用于提供额外的操作码位。
如果您查看 CALL 指令的文档,您会看到 "Call near, absolute indirect, address given in r/m32" 指令的操作码列为 FF /2
。 /2
表示 ModR/M 字节中的附加操作码位在此指令中的值为 2。如果您随后查看“(十进制)/数字(操作码)”列标题,您会看到数字 2 出现在列的开头。如果您向下看该列,您会在标有“[EBP] + disp8”的行上看到 55
。
这在英特尔软件开发人员手册第 2 卷的指令摘要中第 3.1.1.1 节操作码列中有记载Table(没有 VEX 前缀的指令):
- /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.
您唯一缺少的另一件事是 call dword ptr [fp]
是由反汇编程序生成的文本。它从未组装过,组装方式取决于 fp
的定义方式。反汇编程序从调试信息中知道编译器生成的局部变量 fp
在堆栈中的位置,并且知道 [ebp - 8]
可以用来访问它。它显示 fp
而不是 [ebp - 8]
因为前者在大多数情况下更有意义。您稍后应该可以通过取消选中 "Show symbol names".
来查看
请注意,无论 std
、operator
等如何定义,call std::operator<<std:char_traits<char> >
行都无法汇编,因此表明您正在查看的反汇编是实际上并不意味着要组装。反汇编程序通常就是这种情况。输出旨在提供信息,这是比查看十六进制字节序列更好的替代方法。这并不意味着是一个可逆过程。
我在VS2017中调试了如下代码(注意下面的断点):
下面你会找到上面提到的断点的反汇编:
如上图所示,编译器为指令call dword ptr [fp]
生成的机器码是FF 55 F8
,其中FF
是调用指令的操作码, 55
是ModeR/M
字节的值,F8
是一个8位位移,值为-8,下面我会解释。
现在,如果您查看下面的 "Table 2-2: 32 bit addressing forms with the ModeR/M" 字节,它取自英特尔“64 位 IA-32 架构软件开发人员手册”第 2A 卷,您会注意到我突出显示了上面提到的数字 55,它对应一个有效地址[EBP]+disp8
。即汇编器中的CALL
指令会跳转到地址为寄存器EBP中的地址加上上述8位位移值为-8得到的指令。这个地址是正确的。它对应于最终将代码传输到函数 f
.
因此一切看起来都很好。但是我遗漏了重要的一点:我应该使用英特尔手册中的 CALL 指令参考和上面显示的 Table 2.2 获得 ModeR/M
字节。但我仍然不知道该怎么做。任何提示都将不胜感激,因为我已经为此工作了好几天,但我对此仍然一无所知。
您缺少的是操作码的一部分编码在 ModR/M 字节中。通常 ModR/M 字节编码两个操作数。第一个操作数是 table 左侧行标签中给出的寄存器或内存操作数,而第二个操作数是 table 顶部列标题中给出的寄存器。对于只有一个操作数的指令,如 CALL 指令,第二个操作数用于提供额外的操作码位。
如果您查看 CALL 指令的文档,您会看到 "Call near, absolute indirect, address given in r/m32" 指令的操作码列为 FF /2
。 /2
表示 ModR/M 字节中的附加操作码位在此指令中的值为 2。如果您随后查看“(十进制)/数字(操作码)”列标题,您会看到数字 2 出现在列的开头。如果您向下看该列,您会在标有“[EBP] + disp8”的行上看到 55
。
这在英特尔软件开发人员手册第 2 卷的指令摘要中第 3.1.1.1 节操作码列中有记载Table(没有 VEX 前缀的指令):
- /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.
您唯一缺少的另一件事是 call dword ptr [fp]
是由反汇编程序生成的文本。它从未组装过,组装方式取决于 fp
的定义方式。反汇编程序从调试信息中知道编译器生成的局部变量 fp
在堆栈中的位置,并且知道 [ebp - 8]
可以用来访问它。它显示 fp
而不是 [ebp - 8]
因为前者在大多数情况下更有意义。您稍后应该可以通过取消选中 "Show symbol names".
请注意,无论 std
、operator
等如何定义,call std::operator<<std:char_traits<char> >
行都无法汇编,因此表明您正在查看的反汇编是实际上并不意味着要组装。反汇编程序通常就是这种情况。输出旨在提供信息,这是比查看十六进制字节序列更好的替代方法。这并不意味着是一个可逆过程。