如何解释英特尔 x86 汇编的特殊性?

How to interpret particularities of Intel x86 assembly?

提问人:DocDriven 提问时间:11/8/2023 更新时间:11/8/2023 访问量:95

问:

考虑以下简单的 C 代码:

#include <stdio.h>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    int var = 6;
    int var2 = 5;
    int var3 = add(var, var2);

    return 0;
}

编译此代码将生成以下汇编代码:gcc core.c -O0 -g

    .file   "core.c"
    .text
    .globl  add
    .type   add, @function
add:
.LFB0:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -4(%rbp), %edx
    movl    -8(%rbp), %eax
    addl    %edx, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   add, .-add
    .globl  main
    .type   main, @function
main:
.LFB1:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    $6, -12(%rbp)
    movl    $5, -8(%rbp)
    movl    -8(%rbp), %edx
    movl    -12(%rbp), %eax
    movl    %edx, %esi
    movl    %eax, %edi
    call    add
    movl    %eax, -4(%rbp)
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
    .section    .note.GNU-stack,"",@progbits
    .section    .note.gnu.property,"a"
    .align 8
    .long   1f - 0f
    .long   4f - 1f
    .long   5
0:
    .string "GNU"
1:
    .align 8
    .long   0xc0000002
    .long   3f - 2f
2:
    .long   0x3
3:
    .align 8
4:

在 gdb 中,我在输入 add 函数时设置了一个断点。看一下此时内存中的堆栈(和):$rbp = 0x7fffffffdb10$rsp = 0x7fffffffdb10

        0x7fffffffdb50: 0xffffdc30    0x00000001    0xffffdc48    0x00007fff
        0x7fffffffdb40: 0x00000000    0x00000000    0x55555141    0x00005555
        0x7fffffffdb30: 0x00000001    0x00000000    0xf7c29d90    0x00007fff    frame ptr, main RA 
        0x7fffffffdb20: 0x00001000    0x00000006    0x00000005    0x00005555    4096, var, var2, var3
rbp --> 0x7fffffffdb10: 0xffffdb30    0x00007fff    0x5555516a    0x00005555    main frame, add RA
        0x7fffffffdb00: 0x00000002    0x00000000    0x00000005    0x00000006    (8 rand byte), var2, var

我们可以看到几件事:

  1. 当前基指针 (RBP) 指向主帧地址。
  2. 上面是 main 函数的返回地址 (0x55555555516a)。
  3. 按照这个逻辑,主帧的基指针(0x...db30) 指向 0x1,返回地址为 0x7ffff7c29d90。
  4. 由于 main 有 3 个局部变量,它保留了 16 个字节 (0x...db20 到 0x...db2c),并将它们以相反的顺序放在那里。
  5. Add 函数在不移动堆栈指针的情况下推送堆栈上计算所需的值 (0x...db08 和 0x...db0c)。

对于问题:

Q1:返回地址和底架的值是否在主特殊值中?至少 RA 不能是恒定的,因为不能保证它是免费的。

问题 2:为局部变量分配内存时,为什么分配 16 个字节而不是预期的 12 个字节?(怀疑对齐?额外字节中的0x1000是什么意思?

Q3:add计算结果时,为什么堆栈指针在堆栈上明确放置数据时不调整?这难道不会导致信息丢失吗?

我将不胜感激任何帮助,也非常感谢资源指针。

C 汇编 x86 GNU 汇编程序

评论

4赞 Jester 11/8/2023
问题1. 由 C 启动代码调用。这是一个正确的退货地址。P2O(英语:P2O)是的,对齐。这只是以前使用该内存后留下的垃圾。问题3.SysV ABI 保证堆栈指针下有 128 字节的红色区域,无需分配即可使用。main0x1000
3赞 Peter Cordes 11/8/2023
Q3 重复:为什么这个函数序言中没有“sub rsp”指令,为什么函数参数存储在负 rbp 偏移量? / Q2 近乎重复: 为什么 x86-64 / AMD64 System V ABI 要求 16 字节堆栈对齐? (以及相关:为什么 GCC 在堆栈上分配的空间超出了对齐所需的空间? 但这里的情况并非如此, 未使用的空间小于 16 个字节。
0赞 Cem Polat 11/8/2023
问题 2:堆栈调整通常在函数为局部变量分配或释放空间时进行。由于 add 函数非常简单,并且不使用任何局部变量,因此它利用寄存器进行计算,而无需堆栈。因此,不会在函数中执行堆栈调整,因为没有必要为堆栈上的局部变量分配空间。
0赞 Jester 11/8/2023
@CemPolat除了生成的代码确实使用堆栈,即使只是因为优化处于关闭状态。add
1赞 Jester 11/8/2023
你看错了。启用优化后,这应该不会发生。 为我编译。addleal (%rdi,%rsi), %eax; ret

答: 暂无答案