GAS:.cfi_def_cfa_offset的说明

voi*_*ter 50 assembly callstack gnu-assembler dwarf

我想解释一下GCC生成的汇编中.cfi_def_cfa_offset指令使用的值.我隐约知道.cfi指令涉及调用帧和堆栈展开,但我想更详细地解释为什么,例如,在编译以下C程序时GCC输出的汇编中使用值16和8的原因在我的64位Ubuntu机器上.

C程序:

#include <stdio.h>

int main(int argc, char** argv)
{
        printf("%d", 0);
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

我在源文件test.c上调用了GCC,如下所示:gcc -S -O3 test.c.我知道-O3可以实现非标准优化,但为了简洁起见,我想限制生成的程序集的大小.

生成的程序集:

        .file   "test.c"
        .section        .rodata.str1.1,"aMS",@progbits,1
.LC0:
        .string "%d"
        .text
        .p2align 4,,15
.globl main
        .type   main, @function
main:
.LFB22:
        .cfi_startproc
        subq    $8, %rsp
        .cfi_def_cfa_offset 16
        xorl    %edx, %edx
        movl    $.LC0, %esi
        movl    $1, %edi
        xorl    %eax, %eax
        call    __printf_chk
        xorl    %eax, %eax
        addq    $8, %rsp
        .cfi_def_cfa_offset 8
        ret
            .cfi_endproc
.LFE22:
        .size   main, .-main
        .ident  "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
        .section        .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)

为什么值16和8用于生成的程序集中的.cfi_def_cfa_offset指令?另外,为什么用于本地功能的数字22和功能结束标签?

Mat*_*ery 78

正如DWARF规范在第6.4节中所述:

[...]呼叫帧由堆栈上的地址标识.我们将此地址称为规范帧地址或CFA.通常,CFA被定义为前一帧中呼叫站点处的堆栈指针的值(其可以与其在进入当前帧时的值不同).

main()从其他地方调用(在libcC运行时支持代码中),并且在call执行指令时,%rsp将指向堆栈的顶部(这是最低的地址 - 堆栈向下增长),无论可能是什么(究竟是什么并不重要):

:                :                              ^
|    whatever    | <--- %rsp                    | increasing addresses
+----------------+                              |
Run Code Online (Sandbox Code Playgroud)

此时的值%rsp是"调用站点处的堆栈指针的值",即由规范定义的CFA.

call执行指令时,它会将64位(8字节)返回地址压入堆栈:

:                :
|    whatever    | <--- CFA
+----------------+
| return address | <--- %rsp == CFA - 8
+----------------+
Run Code Online (Sandbox Code Playgroud)

现在我们正在运行代码main,执行该代码subq $8, %rsp为自己保留另外8个字节的堆栈:

:                :
|    whatever    | <--- CFA
+----------------+
| return address |
+----------------+
| reserved space | <--- %rsp == CFA - 16
+----------------+
Run Code Online (Sandbox Code Playgroud)

使用.cfi_def_cfa_offset指令在调试信息中声明堆栈指针的更改,您可以看到CFA现在与当前堆栈指针的偏移量为16个字节.

在函数结束时,addq $8, %rsp指令再次更改堆栈指针,因此.cfi_def_cfa_offset插入另一个指令以指示CFA现在处于距堆栈指针仅8字节的偏移处.

(标签中的数字"22"只是一个任意值.编译器将根据一些实现细节生成唯一的标签名称,例如基本块的内部编号.)

  • 确实很好的解释.通常标签按顺序编号(关于功能范围),这里我们可能只看到那些因为优化器删除了其他标签. (3认同)
  • 要了解.cfi_*指令,还应该查看https://sourceware.org/binutils/docs/as/CFI-directives.html.它很薄,但它是官方的. (2认同)