【问题标题】:Function call in x86 Assembly Languagex86 汇编语言中的函数调用
【发布时间】: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 的独立定义,因为它不是staticinline。像lea eax, [rdi+rsi] / ret,因为这些输入是函数参数,而不是全局变量。
  • @PeterCordes 是的,它将发送到 objext 文件。但是在可执行文件中,这个函数不会被链接,所以在一天结束时它会以这两条指令结束(+所有启动、序言等)
  • @P__J__:它不会被执行,但会出现在最终的可执行文件中。我刚刚试了一下,objdump 输出中有一个0000000000000610 &lt;add&gt;:。 (在 Arch Linux 上,gcc7.3 加上来自 Binutils 2.29 的标准 ld)。无论如何,OP 正在查看编译器的 asm 输出,所以这就是他们所看到的。 (他们可以使用__attribute__((noinline)) 来查看函数调用的asm,而没有-O0 的噪音。How to remove "noise" from GCC/clang assembly output?

标签: assembly x86 att


【解决方案1】:

GNU 编译器以“AT&T 语法”而不是 Intel 语法生成程序集,如 here 所述:

GNU 汇编器 gas 使用的语法与您将要使用的语法不同 可能在任何 x86 参考手册和二操作数中都可以找到 指令的源和目的地顺序相反。 以下是气体指令的类型:

opcode                    (e.g., pushal)
opcode operand            (e.g., pushl %edx)
opcode source,dest        (e.g., movl %edx,%eax) (e.g., addl %edx,%eax)

如果有两个操作数,最右边的一个是目标。 最左边的是源。

【讨论】:

  • @Denis 有一些指令不同,例如 AT&T 语法中的 pushal 而 Intel 语法中的 pushad,而 AT&T 中的 if (eax &gt;= ecx) goto foo;cmpl %ecx, %eax / jge foo 因为 @ 987654322@(第二个要点)。
猜你喜欢
  • 2013-04-21
  • 2023-03-25
  • 1970-01-01
  • 2012-11-06
  • 1970-01-01
  • 2015-04-05
  • 1970-01-01
  • 1970-01-01
  • 2017-01-03
相关资源
最近更新 更多