在 yasm 中,当目标为 32 位代码时,如何指定 16 位近 jmp?

Ear*_*rlz 3 x86 assembly yasm

我试图让 yasm 输出一个 16 位近相对 jmp。具体来说,它将是带有操作数大小覆盖前缀的 rel16/rel32 jmp 操作码。我知道jmp short labelwill 发出一个 8 位近相对 jmp,ajmp long label将发出一个 32 位近相对 jmp,但如何让它发出 16 位近相对 jmp?

具体来说我正在使用bits 32cpu i686

Pet*_*des 5

请注意,这会将 EIP 截断为 16 位 IP。

\n\n

YASM/NASM 语法是jmp word label. 两者都经过测试。

\n\n

YASM 也错误地允许它处于 64 位模式1,但 NASM 只允许它处于 32 和 16 位模式。

\n\n
\n\n

您在 32 位代码(默认地址/操作数大小 = 32)中的选择是:

\n\n
    \n
  • 正常2字节jmp/jcc rel8。强制执行jmp short
  • \n
  • 正常 5 字节jmp rel32或 6 字节jcc rel32(2 字节操作码 + 4 字节 rel32)。 jmp dword在任何模式下,并且jmp near在 32/64 位代码中。
  • \n
  • 4 字节jmp rel16/5 字节的操作数大小前缀jcc rel16,将 EIP 的高 16 位清零。(在 64 位模式下不可编码,只能在 16 或 32 位模式下编码。)
    \njmp word在 16/32 位模式下,jmp near在 16 位模式下。
  • \n
\n\n

strict是可选的,例如jmp strict short foo,在所有这些覆盖中。即使没有 strict,如果 rel8 无法同时到达 NASM 和 YASM,这也是一个错误,而不仅仅是一个警告。我的例子也使用了jmp,但可以使用ja,,jle或任何其他jcc。请注意,call rel8不存在,只有rel16and rel32(和间接),使用相同的覆盖语法。

\n\n

来自英特尔jmp文档的操作部分:

\n\n
# near jump\n\n   ...  EIP <- EIP + DEST   for non-64-bit mode relative jumps\n\n   IF OperandSize = 32\n        THEN\n             EIP \xe2\x86\x90 tempEIP;                         # in 64-bit mode, this truncates RIP to EIP\n        ELSE\n             IF OperandSize = 16\n                     THEN (* OperandSize = 16 *)\n                             EIP \xe2\x86\x90 tempEIP AND 0000FFFFH;     #### This line\n                     ELSE (* OperandSize = 64)\n                             RIP \xe2\x86\x90 tempRIP;\n             FI;\n   FI;\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,通过添加操作数大小前缀来获取 a 并不能有效地节省 1 个字节rel16,除非您的代码是从低 64kiB 的虚拟地址空间执行的。(或者使用非零 CS 基数,IP=EIP。)

\n\n

只是为了好玩,我验证了这在我的 Skylake CPU 上是真实存在的:在 32 位 Linux 静态可执行文件中,0x8049000 <foo> jmpw 0x9000GDB 中的单步执行给出了Cannot access memory at address 0x9000. Binutilsobjdump将指令反汇编为:

\n\n
# objdump -d -Mintel output from a 32-bit ELF executable\n08049000 <foo>:\n 8049000:       66 e9 fc ff             jmpw   9000 <foo-0x8040000>\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以真正的执行与这个反汇编相匹配,将EIP截断为IP。

\n\n
\n\n

脚注 1:64 位模式下:YASM 和 binutils 错误与真实 CPU

\n\n

YASM 错误地允许它处于 64 位模式,并且 GNU Binutils 错误地将其解码为jmp rel1664 位模式。jmp wordNASM在 64 位模式下正确拒绝。

\n\n

但实际运行它(在 Skylake 上)会将其解码为jmp rel32,如英特尔所述。(rel16在长模式下编码被标记为 NE 不可编码)。

\n\n

例如

\n\n
   foo: jmp word foo\n   db   1, 1, 1, 1\n
Run Code Online (Sandbox Code Playgroud)\n\n

组装 + 链接到 Linux 静态可执行文件中,使用 GNU Binutils 2.31.1 进行反汇编objdump

\n\n
0000000000401000 <foo>:\n  401000:       66 e9 fc ff             jmp    1000 <foo-0x400000>\n  401004:       01 01                   add    DWORD PTR [rcx],eax\n  401006:       01 01                   add    DWORD PTR [rcx],eax\n
Run Code Online (Sandbox Code Playgroud)\n\n

starti实际上在 GDB ( / )中运行它表明我们在从,即从si获取代码时出错。0x14210020x401004 + 0x0101fffc

\n\n

这与 ( 66 e9 fc ff 01 01) 忽略无意义的操作数大小前缀并将其解码为jmp +0x0101fffc.

\n