我目前正在尝试编写一个将 x86-64 指令解码为汇编的程序,但是在处理操作数不明确的指令时(即它们根据当前操作模式)。
我知道每个内存段都有自己的描述符表,其中描述了
EFER.LMA
、CS.L
和 CS.D
字段。这些信号的不同组合表明特定段所处的操作模式。
我知道每种操作模式都需要自己的数据/地址默认大小,这些默认大小可能会根据指令编码中的操作数/地址覆盖前缀而发生变化。
我的问题是如何知道要检查哪个段的操作模式以确定给定指令的操作数大小?
我举个例子:
带有
add
的 opcode = 0x03
指令变体具有符号 Gv, Ev
。
根据 AMD x64 手册第 3 卷,G/E 表示通用寄存器,v 符号表示“一个字、双字或四字(64 位模式下),具体取决于有效操作数大小。”
我知道有效操作数大小是由some段的操作模式指定的默认操作数大小(忽略大小覆盖),但是我在这里检查哪个段?详细描述添加指令的页面没有提到特定的段,因此我假设这是逐个指令未提供的内容(边缘情况除外)。
这是我到目前为止所拼凑的关于如何解决这个问题的内容:
假设指令针对默认段。也许 DS 代表寄存器操作数,SS 代表内存操作数。如果存在段覆盖前缀,请改用此段。
获取该段的描述符条目。
使用描述符条目中的字段来确定有效操作数大小。有效操作数大小将是默认值,除非有大小覆盖前缀。
4)这将是用于操作数的大小。
这种做法正确吗?请随意纠正我的方法/理解中的任何内容。非常感谢。
仅 CS 段影响操作数和地址大小。 DS、SS、ES、FS 和 GS 对两者都没有影响。
有 3 种不同的模式:16 位、32 位和 64 位。它们是在加载到 CS 的描述符中的 D 和 L 位上选择的:
66
将操作数大小更改为 32 位,前缀 67
将地址大小更改为 32 位。 64 位大小不可用。66
将操作数大小更改为 16 位,前缀 67
将地址大小更改为 16 位。 64 位大小不可用。66
将操作数大小更改为 16 位,REX.W 前缀将操作数大小更改为 64 位,前缀 67
将地址大小更改为 32 位。 16 位地址大小不可用。EFER.LMA 是一个全局值(不是每个段),它会影响 CPU 是否关注 L 位。如果 EFER.LMA=0,L 位被忽略(假设为 0),并且无法访问 64 位模式。
数据段中的D位不影响默认操作数大小。它只影响向下增长段中的最大地址(是0xFFFF还是0xFFFFFFFF),对于SS段,它影响
push
和pop
是否使用SP或ESP寄存器。在 64 位模式下,该寄存器始终为 RSP。
注意,如果代码在操作系统下运行,用户程序通常无法访问描述符表。您必须使用某种特定于操作系统的方式来询问其他进程正在运行哪种模式 - 如果操作系统甚至提供了这种方式。然而,大多数程序都以一种模式启动并且从不改变它。启动模式通常在可执行文件的标头中标识。