x86中的jmpl指令是什么?

时间:2019-01-27 09:28:46

标签: assembly x86 gas att

x86程序集设计具有指令后缀,例如l(long)w(word)b(byte)
所以我认为jmpllong jmp

但是当我编译它时,效果却很奇怪。
请参见以下示例。

Test1 :程序集

main:
  jmp main

Test1 :编译结果

eb fe     jmp 0x0804839b <main> 


Test2 :程序集

main:
  jmpl main # added l suffix

Test2 :编译结果

ff 25 9b 83 04 08   jmp *0x0804839b


与Test1相比, Test2 结果出乎意料。
我认为应该与Test1一样编译。


问题:
jmpl在8086设计中是否有不同的说明?
(根据here,SPARK中的jmpl表示jmp链接。是这样吗?)

...或者这仅仅是gnu汇编器上的错误?

2 个答案:

答案 0 :(得分:5)

您已成为AT&T语法糟糕的牺牲品。

  

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

不,不是。 AT&T语法令人讨厌。
在理智的Intel语法中,没有这样的后缀。

  

是不一样的东西。

是的,这是间接跳转到绝对地址。用gnu语法将-near-跳转到-long-地址ljmp是-far-跳转。
跳转的默认值是接近跳转到相对地址。
请注意,此跳转的英特尔语法为:

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,但是会从指定的地址读取一个双字,然后跳转到此双字中指定的地址。在Intel语法中,间接性是明确的。

当然,您打算直接跳至0x0804839b(又称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的相对形式和绝对形​​式指定不同的语法。这是因为Intel希望汇编程序始终使用短(相对)跳转,除非目标距离太远,在这种情况下,将使用远跳转(AT&T语法中为jmpl)。
这样做的好处是,汇编器会自动为您使用适当的跳转。

迫使GNU恢复理智
您可以使用

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

要使gnu使用Intel语法,这会使事情重新回到Intel设计的方式,而不是gnu使用的PDP7 syntax

答案 1 :(得分:2)

l 不同的是,jmp操作数大小的后缀表示间接的calll main这种不一致纯粹是AT&T语法设计中的精神错乱。

(由于您将其与main之类的操作数一起使用,因此它将成为内存间接跳转,从main进行数据加载,并将其用作新的EIP值。)< / p>

您永远不需要使用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语法中的远jmp或呼叫(到新的CS:[ER] IP)是ljmp / lcall。这些是非常不同的。


GAS接受jmpl mainjmpl *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;如今,对于像AVX512这样的新指令,它的维护还没有跟上NASM的步伐。我喜欢YASM对于长NOP的良好默认设置,但是否则,我建议使用NASM。)