x86程序集设计具有指令后缀,例如l(long)
,w(word)
,b(byte)
。
所以我认为jmpl
是long 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汇编器上的错误?
答案 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 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
),则会得到相同的结果。包括有关丢失*
的相同警告。但是反汇编在每条指令上都有一个明确的jmpq
和callq
。 (除了我添加的相对直接jmp
之外,它在反汇编中使用了jmp
助记符。)
这就像objdump认为32位是32位和64位模式下jmp / call的默认操作数大小,因此它希望始终在64位中使用q
后缀,但保留它隐式在32位模式下。无论如何,那只是在隐式/显式大小后缀之间的反汇编选择,对于程序员编写源代码没有怪异。
Clang的内置汇编器确实拒绝jmpl main
,要求jmpl *main
。
$ clang -m32 jmp.s
jmp.s:3:8: error: invalid operand for instruction
jmpl main
^~~~
calll main
与call main
相同。 call *main
和calll *main
都可以用于间接跳转。
YASM的GAS语法模式将jmpl main
组装成一个近乎相对的jmp,例如jmp main
!,因此它与gcc / clang关于jmpl
的观点不同暗示间接的。 (很少有人在GAS模式下使用YASM;如今,对于像AVX512这样的新指令,它的维护还没有跟上NASM的步伐。我喜欢YASM对于长NOP的良好默认设置,但是否则,我建议使用NASM。)