请注意,这会将 EIP 截断为 16 位 IP。
YASM/NASM 语法是jmp word label。两者都经过测试。
YASM 也错误地允许它在 64 位模式下1,但 NASM 只允许它在 32 位和 16 位模式下。
您在 32 位代码中的选择(默认地址/操作数大小 = 32)是:
- 普通 2 字节
jmp/jcc rel8。使用 jmp short 强制执行
- 普通 5 字节
jmp rel32 或 6 字节 jcc rel32(2 字节操作码 + 4 字节 rel32)。 jmp dword 在任何模式下,jmp near 在 32/64 位代码中。
- 4 字节
jmp rel16 / 5 字节 jcc rel16 的操作数大小前缀,将 EIP 的高 16 位归零。 (在 64 位模式下不可编码,只能在 16 或 32 位模式下进行编码。)
jmp word 在 16/32 位模式下,jmp near 在 16 位模式下。李>
strict 是可选的,例如jmp strict short foo,在所有这些覆盖中。即使没有严格,如果 rel8 无法同时访问 NASM 和 YASM,这不仅仅是一个警告,这也是一个错误。我的示例也使用了jmp,但可以使用ja、jle 或任何其他jcc。请注意call rel8 不存在,只有rel16 和rel32(和间接)使用相同的覆盖语法。
来自Intel's jmp documentation的操作部分:
# near jump
... EIP <- EIP + DEST for non-64-bit mode relative jumps
IF OperandSize = 32
THEN
EIP ← tempEIP; # in 64-bit mode, this truncates RIP to EIP
ELSE
IF OperandSize = 16
THEN (* OperandSize = 16 *)
EIP ← tempEIP AND 0000FFFFH; #### This line
ELSE (* OperandSize = 64)
RIP ← tempRIP;
FI;
FI;
因此,您无法通过添加操作数大小前缀来获得rel16 来有效地节省 1 个字节,除非您的代码是从低 64kiB 的虚拟地址空间执行的。 (或使用非零 CS 基数,IP=EIP。)
只是为了好玩,我在我的 Skylake CPU 上验证了这是一个真实的东西:在一个 32 位 Linux 静态可执行文件中,在 GDB 中单步执行 0x8049000 <foo> jmpw 0x9000 会得到 Cannot access memory at address 0x9000。 Binutilsobjdump将指令反汇编为:
# objdump -d -Mintel output from a 32-bit ELF executable
08049000 <foo>:
8049000: 66 e9 fc ff jmpw 9000 <foo-0x8040000>
所以真正的执行匹配这个反汇编,将 EIP 截断为 IP。
脚注 1:在 64 位模式下:YASM 和 binutils 错误与真实 CPU 的对比
YASM 在 64 位模式下错误地允许它,而 GNU Binutils 在 64 位模式下错误地将其解码为 jmp rel16。 NASM 在 64 位模式下正确拒绝 jmp word。
但实际上(在 Skylake 上)运行它会将其解码为 jmp rel32,正如英特尔所记录的那样。 (rel16 编码标记为 N.E. 在长模式下不可编码)。
例如
foo: jmp word foo
db 1, 1, 1, 1
用 GNU Binutils 2.31.1 objdump 组装 + 链接到 Linux 静态可执行文件反汇编:
0000000000401000 <foo>:
401000: 66 e9 fc ff jmp 1000 <foo-0x400000>
401004: 01 01 add DWORD PTR [rcx],eax
401006: 01 01 add DWORD PTR [rcx],eax
实际上在 GDB (starti / si) 中运行它表明我们在从 0x1421002(即从 0x401004 + 0x0101fffc)获取代码时出错。
这与 (66 e9 fc ff 01 01) 忽略无意义的操作数大小前缀并解码为 jmp +0x0101fffc 一致。