x86 中的 jmpl 指令是什么?

what is jmpl instruction in x86?

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

但是当我 assemble 它时它工作起来很奇怪:

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

与测试1相比,测试2的结果出乎意料。
我觉得应该和Test1一样assembled.


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

...或者这只是 GNU assembler 中的错误?

你已经成为 AT&T 语法的可怕受害者。

x86 assembly design has instruction suffix, such as l(long), w(word), b(byte).

不,不是。这是 AT&T 语法的可憎之处。
在正常的 Intel 语法中没有这样的后缀。

Is jmpl something different.

是的,这是一个到绝对地址的间接跳转。 A -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指定的地址。在英特尔语法中,间接性是明确的。

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

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

另请参阅:

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

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

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

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

请注意,英特尔并未为 jmp 的相对和绝对形式指定不同的助记符。这是因为 Intel 希望 assembler 始终使用短(相对)跳转,除非目标距离太远,在这种情况下使用近 jmp rel32 编码。 (或者在 16 位模式下,jmp foo 可以 assemble 远绝对跳转到不同的 CS 值,也就是段。在 32 位模式下,相对 jmp rel32 可以到达任何其他来自任何地方的 EIP 值。)
这样做的好处是 assembler 会自动为您使用正确的跳转。
(在 64 位模式下跳转超过 +-2GiB 需要额外的指令或内存中的指针,没有 64 位绝对直接远跳转,所以 assembler 不能自动为你做这件事。) )

强制 gnu 恢复理智
您可以使用

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

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

l 操作数大小后缀表示间接 jmp,与 calll main 不同,后者仍然是相对接近调用。 这种不一致纯粹是 AT&T 语法设计中的错误。

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

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

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


比较 and ,这只是操作数大小后缀的行为与您预期的一样,与普通 call 或普通 ret 相同。但是jmp不一样。


semi-related: far jmp or call (to a new CS:[ER]IP) 在 AT&T 语法中是 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 main 等同于 call maincall *maincalll *main 都接受间接跳转。

  • YASM 的 GAS-syntax 模式将 jmpl main 组装成一个近乎相对的 jmp,比如 jmp main! 所以它不同意 gcc/clang 约 jmpl 暗示间接。 (很少有人在 GAS 模式下使用 YASM;现在它的维护跟不上 NASM 的新指令,如 AVX512。我喜欢 YASM 对长 NOP 的良好默认值,但除此之外我会推荐 NASM.)