【问题标题】:In yasm how do I specify a 16-bit near jmp when targeting 32-bit code?在 yasm 中,当针对 32 位代码时,如何在 jmp 附近指定 16 位?
【发布时间】:2019-07-24 23:04:39
【问题描述】:

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

特别是我正在使用bits 32cpu i686

【问题讨论】:

  • 试试jmp near label
  • 试试jmp word label
  • @fuz:这将在 16 位模式下为您提供 rel16。在 32/64 位模式下jmp near means rel32`。

标签: assembly x86 yasm


【解决方案1】:

请注意,这会将 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,但可以使用jajle 或任何其他jcc。请注意call rel8 不存在,只有rel16rel32(和间接)使用相同的覆盖语法。

来自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 &lt;foo&gt; 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 一致。

【讨论】:

  • 啊,你使用jmp word foo 似乎很奇怪,而jmp byte foo 会出错。无论如何,很高兴了解 rel16 的 EIP 截断。我对该文档的阅读是绝对目标会截断,但不会截断相对目标。老实说,虽然可能必须做一些测试。似乎与org 0x10000 一样,yasm 应该给出错误或警告,说明曾经使用过这样的 rel16
  • @Earlz:应用截断的伪代码块在 if abs else rel 块之后并与它分开。只是为了好玩,我测试了工具会说什么(并确保我的 Skylake 与文档匹配)
猜你喜欢
  • 1970-01-01
  • 2013-02-07
  • 2016-09-15
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 2012-04-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多