【问题标题】:what is jmpl instruction in x86?x86中的jmpl指令是什么?
【发布时间】:2019-06-20 13:19:21
【问题描述】:

x86汇编设计有指令后缀,如l(long)w(word)b(byte)
所以我认为jmpllong jmp

但是当我组装它时它的工作很奇怪:

Test1 jmp: 汇编源码和反汇编

main:
  jmp main

eb fe     jmp 0x0804839b <main> 

Test2 jmpl: 汇编源码和反汇编

main:
  jmpl main       # added l suffix

ff 25 9b 83 04 08   jmp *0x0804839b

与Test1相比,Test2的结果出乎意料。
我觉得应该和Test1一样组装。


问题:
jmpl 是 8086 设计中的一些不同指令吗?
(根据here,SPARC 中的jmpl 表示jmp 链接。是这样的吗?)

...或者这只是 GNU 汇编器中的一个错误?

【问题讨论】:

  • 在 32 位代码中使用 AT&T jmp main 是到标签 main 的相对跳转。 jmpl main 是间接近跳转到存储在标签main 处的地址。
  • 您应该在第二次测试中收到关于组装时缺少星号的警告。
  • 相关:What is callq instruction?What is the difference between retq and ret?。在这些情况下,它只是操作数大小的后缀。

标签: assembly x86 gnu-assembler att


【解决方案1】:

您已经成为 AT&T 语法糟糕的受害者。

x86汇编设计有指令后缀,如l(long)、w(word)、b(byte)。

不,它没有。可憎的是 AT&T 语法有这个。
在理智的英特尔语法中,没有这样的后缀。

jmpl 有什么不同吗?

是的,这是到绝对地址的间接跳转。 -near- 跳转到 -long- 地址。
ljmp 在 gnu 语法中是一个 -far- 跳转,但这完全不同,设置一个新的 CS:EIP。)
跳转的默认设置是近跳转,到相对地址。
请注意,此跳转的 Intel 语法是:

jmp dword [ds:0x0804839b]  //note the [] specifying the indirectness.
//or, this is the same
jmp [0x0804839b]
//or
jmp [main]
//or
jmp DWORD PTR ds:0x804839f  //the PTR makes it indirect.

我更喜欢[],以突出间接性。

它确实跳转到0x0804839b,而是从指定的地址读取一个dword,然后跳转到这个dword中指定的地址。在 Intel 语法中,间接性是明确的。

当然你打算直接跳转到 0x0804839b (aka main:),这是由:

Hm, most assembler do not allow absolute far jumps!  
It cannot be done.

另请参阅:How to code a far absolute JMP/CALL instruction in MASM?

近/短相对跳转(几乎)总是更好,因为当您的代码更改时它仍然有效;跳远可能无效。 此外,较短的指令通常更好,因为它们在指令缓存中占用的空间更少。汇编器(在 Intel 模式下)会自动为您选择正确的 jmp 编码。

SPARC
这是与 x86 完全不同的处理器。来自不同的制造商,使用不同的范例。显然 SPARC 文档与 x86 文档无关。

jmp 的英特尔官方文档在这里。

https://www.felixcloutier.com/x86/jmp

请注意,英特尔没有为 jmp 的相对形式和绝对形​​式指定不同的助记符。这是因为英特尔希望汇编程序始终使用短(相对)跳转,除非目标太远,在这种情况下使用近 jmp rel32 编码。 (或者在 16 位模式下,jmp foo 可以组合成一个远绝对跳转到不同的 CS 值,也就是段。在 32 位模式下,相对 jmp rel32 可以从任何地方到达任何其他 EIP 值。)
这样做的美妙之处在于,汇编器会自动为您使用正确的跳转。
(在 64 位模式下跳转超过 +-2GiB 需要额外的指令或内存中的指针,没有 64 位绝对直接远跳转,因此汇编器无法自动为您执行此操作。)

迫使 gnu 恢复正常
你可以使用

 .intel_syntax noprefix    <<-- as the first line in your assembly
 mov eax,[eax+100+ebx*2] 
 ....

为了使 gnu 使用 Intel 语法,这将使事情回到 Intel 设计的方式,并远离 gnu 使用的PDP7 syntax

【讨论】:

  • 我认为值得注意的是jmp *0x0804839b不会跳转到内存地址0x0804839b。它检索内存位置 ds:0x0804839b 的 32 位地址,并将其用作跳转目标。在 Intel 语法中它是jmp DWORD ds:0x0804839b。这实际上是一个间接的、绝对的near 跳转。用于 JMP 的 CS 是 CS 的当前值。这不是 FAR JMP。
  • jmp *mainjmpl *main 也是一样的,因为在 32 位保护模式下,操作数默认为 32 位宽。实际上,dojmpw *main 也是合法的。在 32 位保护模式下,它将读取 main 处的 16 位值并将其用作跳转目标。 wl 后缀表示在进行间接跳转时要从指定的内存位置读取的偏移量的大小。
  • @PeterCordes :我认为当您使用 JMPL 时,GAS 会发出警告 Warning: direct jmp without *' 是有原因的。更糟糕的是,如果您使用 YASM 组装相同的代码(解析为气体) - 没有警告,它实际上将 jmpl 编码为相对 JMP。我认为 YASM 存在错误,因为它不符合 GAS 的解释。
  • 所有这些对 GAS 和 AT&T 语法的厌恶似乎都是不必要的。它实际上有很多很棒的特性(它的伪操作是最好的,比如 nop-aligning)。它还要求您明确说明操作数的大小,并且没有任何这种令人困惑的DWORD PTR FLAT 东西。
  • @S.S.Anne:AT&T 语法有一些不错的特性,但也有一些主要缺陷,jmplcalll 之间的不一致就是其中之一。其他包括针对某些形式的 x87 语法设计错误,例如 fsubfsubr described in the GAS manual,以及 add $1, (%rdi) 默认为 32 位操作数大小而不是关于歧义的错误; mov 的 GAS 仅错误,并且仅在最近开始对其他指令发出警告。 (尽管 clang 的汇编程序在任何歧义上都会出错。)
【解决方案2】:

l 操作数大小后缀意味着间接jmp,与calll main 不同,后者仍然是一个相对接近的调用。 这种不一致在 AT&T 语法设计中是纯粹的疯狂。

(并且由于您将它与main 之类的操作数一起使用,它变成了内存间接跳转,从main 加载数据并将其用作新的EIP 值。)

您永远不需要使用jmpl 助记符,您可以并且应该在操作数上使用* 来指示间接跳转。如jmp *%eax 设置EIP = EAX,或jmp *4(%edi, %ecx, 4) 索引跳转表,或jmp *func_pointer。在所有这些中使用jmpl 是可选的。

您可以使用jmpw *%ax 将 EIP 截断为 16 位值。组装到66 ff e0 jmpw *%ax)


比较What is callq instruction?What is the difference between retq and ret?,这只是操作数大小的后缀,其行为与您预期的一样,与普通call 或普通ret 相同。但jmp 不同。


半相关:AT&T 语法中的 far jmp 或 call (to a new CS:[ER]IP) 是 ljmp / lcall。这些是非常不同的。


GAS 接受jmpl main 等同于jmpl *main 也很疯狂。它只会警告而不是出错

$ gcc -no-pie -fno-pie -m32 jmp.s 
jmp.s: Assembler messages:
jmp.s:3: Warning: indirect jmp without `*'

然后拆解看看我们得到了什么,objdump -drwC a.out:

08049156 <main>:                                          # corresponding source line (added by hand)
 8049156:       ff 25 56 91 04 08       jmp    *0x8049156    # jmpl main
 804915c:       ff 25 56 91 04 08       jmp    *0x8049156    # jmp  *main
 8049162:       ff 25 56 91 04 08       jmp    *0x8049156    # jmpl *main

08049168 <foo>:
 8049168:       e8 fb ff ff ff          call   8049168 <foo> # calll foo
 804916d:       ff 15 68 91 04 08       call   *0x8049168    # calll *foo
 8049173:       ff 15 68 91 04 08       call   *0x8049168    # call  *foo

如果我们将源代码中的l 替换为q,并且在没有-m32 的情况下构建(使用默认的-m64),我们会得到相同的结果。包括关于缺少* 的相同警告。但是反汇编在每条指令上都有一个明确的jmpqcallq。 (除了我添加的相对直接的jmp,它在反汇编中使用了jmp助记词。)

就像 objdump 认为 32 位是 32 位和 64 位模式下 jmp/call 的默认操作数大小,因此它希望始终在 64 位中使用 q 后缀,但将其隐含在32 位模式。无论如何,这只是隐式/显式大小后缀之间的反汇编选择,对于编写源代码的程序员来说并不奇怪。


其他 AT&T 语法汇编器:

  • Clang 的内置汇编器确实拒绝 jmpl main,需要 jmpl *main

    $ clang -m32 jmp.s
    jmp.s:3:8: error: invalid operand for instruction
      jmpl main
           ^~~~
    

    calll maincall main 相同。 call *maincalll *main 都被接受用于间接跳转。

  • YASM 的 GAS 语法模式将 jmpl main 组装成一个近乎相对的 jmp,例如 jmp main! 所以它不同意 gcc/clang 关于 jmpl 暗示间接。 (很少有人在 GAS 模式下使用 YASM;而且现在它的维护没有跟上 NASM 的新指令,如 AVX512。我喜欢 YASM 对长 NOP 的良好默认设置,但否则我会推荐 NASM。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-05
    • 2013-05-20
    • 2015-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多