5

我试图理解 C 函数的汇编代码。我不明白为什么andl -16主要是这样做的。是否为局部变量分配空间。如果是这样,为什么subl 32要为 main 完成。

我无法理解func1. 对于 8086 处理器,在读取时堆栈从高位地址增长到低位地址。所以这里为什么访问在 ebp 的正面(用于参数偏移),为什么不在 ebp 的负面。func1 中的局部变量是 3 + 返回地址 + 保存的寄存器 - 所以它必须是 20,但为什么是 24?( subl $24,esp)

#include<stdio.h>
int add(int a, int b){
 int res = 0;
 res = a + b;
 return res;
}
int func1(int a){
 int s1,s2,s3;
 s1 = add(a,a);
 s2 = add(s1,a);
 s3 = add(s1,s2);
 return s3;
}
int main(){
 int a,b;
 a = 1;b = 2;
 b = func1(a);
 printf("\n a : %d b : %d \n",a,b);
 return 0;
}

汇编代码:

       .file   "sample.c"
        .text
.globl add
        .type   add, @function
add:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $16, %esp
        movl    $0, -4(%ebp)
        movl    12(%ebp), %eax
        movl    8(%ebp), %edx
        leal    (%edx,%eax), %eax
        movl    %eax, -4(%ebp)
        movl    -4(%ebp), %eax
        leave
        ret
        .size   add, .-add
.globl func1
        .type   func1, @function
func1:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        movl    8(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    8(%ebp), %eax
        movl    %eax, (%esp)
        call    add
        movl    %eax, -4(%ebp)
        movl    8(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    -4(%ebp), %eax
        movl    %eax, (%esp)
        call    add
        movl    %eax, -8(%ebp)
        movl    -8(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    -4(%ebp), %eax
        movl    %eax, (%esp)
                                      call    add
        movl    %eax, -12(%ebp)
        movl    -12(%ebp), %eax
        leave
        ret
        .size   func1, .-func1
        .section        .rodata
.LC0:
        .string "\n a : %d b : %d \n"
        .text
.globl main
        .type   main, @function
main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $32, %esp
        movl    $1, 28(%esp)
        movl    $2, 24(%esp)
        movl    28(%esp), %eax
        movl    %eax, (%esp)
        call    func1
        movl    %eax, 24(%esp)
        movl    $.LC0, %eax
        movl    24(%esp), %edx
        movl    %edx, 8(%esp)
        movl    28(%esp), %edx
        movl    %edx, 4(%esp)
        movl    %eax, (%esp)
        call    printf
        movl    $0, %eax
        leave
        ret
        .size   main, .-main
        .ident  "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5"
        .section        .note.GNU-stack,"",@progbits
4

2 回答 2

5

通过andl $-16, %esp清除低四位,将堆栈指针对齐到 16 字节的倍数。

唯一使用正偏移的地方(%ebp)是参数访问。

您没有说明您的目标平台是什么或您用于编译的开关。汇编代码显示已插入一些 Ubuntu 标识符,但我不熟悉它使用的 ABI,除此之外它可能类似于通常用于 Intel x86 架构的 ABI。所以我猜测 ABI 在例程调用中需要 8 字节对齐,因此编译器将堆栈帧设为func124 字节而不是 20 字节,以保持 8 字节对齐。

我将进一步猜测编译器在开始时将堆栈对齐为 16 字节main作为编译器中的一种“首选项”,以防它使用更喜欢 16 字节对齐的 SSE 指令,或其他更喜欢 16 字节对齐的操作.

所以,我们有:

main中,andl $-16, %esp作为编译器首选项,将堆栈对齐为 16 字节的倍数。内部main,28(%esp)24(%esp)引用编译器保存在堆栈中的临时值,而8(%esp),4(%esp)(%esp)用于将参数传递给func1printf。我们从汇编代码调用printf但在您的代码中注释掉的事实中看到,您粘贴的 C 源代码与用于生成汇编代码的 C 源代码不同:这不是从C 源代码。

func1中,堆栈上分配了 24 个字节而不是 20 个,以保持 8 字节对齐。在内部func1,通过8(%ebp)和访问参数4(%ebp)。从-12(%ebp)-4(%ebp)的位置用于保存变量的值。4(%esp)(%esp)用于将参数传递给add.

这是堆栈帧func1

    - 4(%ebp) = 20(%esp): s1。
    - 8(%ebp) = 16(%esp): s2。
    -12(%ebp) = 12(%esp): s3。
    -16(%ebp) = 8(%esp):未使用的填充。
    -20(%ebp) = 4(%esp):传递 add 的第二个参数。
    -24(%ebp) = 0(%esp):传递 add 的第一个参数。
于 2013-04-01T23:55:50.017 回答
3

我建议通过它的输出来解决这个问题,objdump -S它会给你与 C 源代码的相互列表。

于 2013-04-01T23:53:32.053 回答