【发布时间】:2019-02-13 18:03:48
【问题描述】:
我很难理解由 gcc 创建的简单 C 程序的汇编语言输出。
这是程序的 C 代码:
#include <stdio.h>
#include <stdlib.h>
int sum1=1;
int sum2=1;
int add(int s1, int s2){
return s1+s2;
}
int main(int argc,char** agrv){
int res=sum1+sum2;
return 0;
}
这是由 gcc 创建的汇编代码:
.file "main.c"
.globl sum1
.data
.align 4
sum1:
.long 1
.globl sum2
.align 4
sum2:
.long 1
.text
.globl add
.def add; .scl 2; .type 32; .endef
.seh_proc add
add:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
.seh_endprologue
movl %ecx, 16(%rbp)
movl %edx, 24(%rbp)
movl 16(%rbp), %edx
movl 24(%rbp), %eax
addl %edx, %eax
popq %rbp
ret
.seh_endproc
.def __main; .scl 2; .type 32; .endef
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $48, %rsp
.seh_stackalloc 48
.seh_endprologue
movl %ecx, 16(%rbp)
movq %rdx, 24(%rbp)
call __main
movl sum1(%rip), %edx
movl sum2(%rip), %eax
addl %edx, %eax
movl %eax, -4(%rbp)
movl $0, %eax
addq $48, %rsp
popq %rbp
ret
.seh_endproc
.ident "GCC: (x86_64-posix-seh-rev2, Built by MinGW-W64 project) 7.1.0"
我很难理解汇编代码中某些指令的操作数顺序(另请参阅内存布局图片以供参考Memory Layout)。首先,有说明
pushq %rbp
将调用者的基指针压入堆栈。该指令之后是以下指令:
movq %rsp, %rbp
这条指令应该将被调用者的基指针设置为当前堆栈指针的值。但是,两个操作数的顺序不应该是相反的吗(例如 movq %rbp, %rsp)?
指令出现类似的“问题”:
addl %edx, %eax
这里,操作的结果存储在寄存器 %edx 中,而不是 %eax(用于返回函数参数)。
到目前为止,我在 Internet 上查阅的几乎所有资料都声称指令的结果存储在指令的第一个参数中?
【问题讨论】:
-
默认情况下 GCC 输出 AT&T 语法。
-masm=intel用于 Intel 语法(我认为)。操作数在两种约定之间反转。 -
如果启用优化,代码实际上会缩减为
xor eax, eax ret或在您的约定中:xorl %eax, %eax ret -
@P__J__:gcc 还必须发出
add的独立定义,因为它不是static或inline。像lea eax, [rdi+rsi]/ret,因为这些输入是函数参数,而不是全局变量。 -
@PeterCordes 是的,它将发送到 objext 文件。但是在可执行文件中,这个函数不会被链接,所以在一天结束时它会以这两条指令结束(+所有启动、序言等)
-
@P__J__:它不会被执行,但会出现在最终的可执行文件中。我刚刚试了一下,
objdump输出中有一个0000000000000610 <add>:。 (在 Arch Linux 上,gcc7.3 加上来自 Binutils 2.29 的标准ld)。无论如何,OP 正在查看编译器的 asm 输出,所以这就是他们所看到的。 (他们可以使用__attribute__((noinline))来查看函数调用的asm,而没有-O0的噪音。How to remove "noise" from GCC/clang assembly output?)