我有一些工具生成的x86_64架构的gnu汇编代码,其中有以下指令:
movq %rsp, %rbp
leaq str(%rip), %rdi
callq puts
movl $0, %eax
我找不到有关“callq”指令的实际文档。
我看过http://support.amd.com/TechDocs/24594.pdf,它是“AMD64架构程序员手册第3卷:通用和系统指令”,但它们只描述CALL近指令和远指令。
我看过GNU汇编程序https://sourceware.org/binutils/docs/as/index.html的文档,但找不到详细说明它支持的指令的部分。
我知道这是一个函数调用,但我想知道细节。我在哪里可以找到它们?
2条答案
按热度按时间ycl3bljg1#
它只是
call
。如果您希望能够在Intel/AMD手册中查找指令,请使用Intel语法反汇编。(objdump -drwC -Mintel
,GBDset disassembly-flavor intel
,GCC-masm=intel
)从技术上讲,
q
操作数大小后缀确实适用(它推送64位返回地址,并将RIP视为64位寄存器),但无法使用指令前缀覆盖它。例如,根据英特尔手册,calll
和callw
在64位模式下不可编码,所以有些AT&T的语法工具将其显示为callq
而不是call
,这很烦人。不同的工具在32位和64位模式下是不同的。(Godbolt)
call
/ret
。不错。callq
/retq
和calll
/retl
。至少它一直很烦人。callq
/retq
(显式64位)和call
/ret
(隐式32位)。不一致并且有点愚蠢,因为64位没有操作数大小的选择,但32位有。(尽管不是一个 * 有用 * 的选择:callw
将EIP截断为16位。)另一方面,尽管64位模式下 * 大多数 * 指令的默认操作数大小(不带雷克斯.W前缀)仍然是32。但是
add $1, (%rdi)
需要操作数大小后缀;即使pushw $1
和pushq $1
在64位模式下都是可编码的(实际上也是可用的),但是push
隐含地是pushq
。64位模式下的GAS会将
callw foo
/foo:
组装为66 e8 00 00
,但我的Skylake CPU将其单步执行为6字节指令,在其后面消耗了2字节的00。并将RSP更改为8。因此,它将其解码为callq
,带有rel32=0
。忽略66
操作数大小前缀。因此,即使没有操作数大小选项,GNU Binutils也认为有。(使用GAS 2.38测试)。因此,它在64位模式下使用后缀而不是32位模式仍然很奇怪,因为它认为两种模式下的情况是相同的。Clang和
llvm-objdump -d
有相同的bug,在64位模式下组装/反汇编callw
。AMD's manual表示64位模式不能使用32位操作数大小,但 * 没有 * 提到对使用16位操作数大小的任何限制。因此GAS和LLVM对于AMD CPU可能是正确的,并且
66
前缀的选择还是一样的,(您可以通过在静态可执行文件中单步执行callw foo
/foo:
(而不是0x401006
)之后查看RIP是否=0x1004
来进行测试,其中.text部分从0x401000
开始。)NASM的
ndisasm -b64
假设66
前缀在64位模式下将被忽略,将66E800000000
反汇编为call qword 0x18c
(它不理解ELF元数据,所以我只是用nops填充,并在a .o的反汇编中发现它,就好像它是一个平面二进制文件一样,因此有了不寻常的地址。)来自英特尔的指令集参考手册(上面链接):
对于近调用绝对值,绝对偏移量是在通用寄存器或内存位置(r/m16,r/m32或r/m64)中间接指定得.操作数大小属性确定目的操作数得大小(16,32或64位).在64位模式下,近调用(及所有近分支)得操作数大小强制为64位.
for rel 32...与绝对偏移量一样,操作数大小属性确定目的操作数得大小(16,32或64位).在64位模式中,目的操作数始终为64位,因为近分支得操作数大小强制为64位.
在32位模式下,您可以对将EIP截断为16位的16位
call rel16
进行编码,也可以对使用绝对16位地址的call r/m16
进行编码。但正如手册中所述,在64位模式下操作数大小是固定的。这与
push
得情况不同,push
在64位模式下默认为64位,但可以使用操作数大小前缀将其覆盖为16(但雷克斯.W=0时不能覆盖为32).因此,pushq
与pushw
都可用,但只有callq
可用.deyfvvtc2#
callq指的是共享库/动态库中的可重定位调用。其思想是push 0,然后push要搜索的符号,然后调用一个函数,以便在第一次调用时搜索它。在程序的可重定位表中,它在第一次调用函数时将调用替换为函数的实际位置。随后的调用将引用在运行时创建的重定位表。