GAS: Explanation of .cfi_def_cfa_offset

73
我希望能够解释GCC生成的汇编代码中使用.cfi_def_cfa_offset指令中的值。我大致知道.cfi指令与调用帧和堆栈展开有关,但我想更详细地了解为什么在我的64位Ubuntu机器上编译以下C程序时,GCC输出的汇编代码中会使用值16和8。
#include <stdio.h>

int main(int argc, char** argv)
{
        printf("%d", 0);
        return 0;
}

我按如下方式对源文件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

为什么生成汇编代码中要使用 .cfi_def_cfa_offset 指示符的值为 16 和 8?同时,为什么在本地函数开始和结束标签中要使用数字 22?

2个回答

105

DWARF规范第6.4节所述:

[...] 调用帧是由堆栈上的地址标识的。我们将这个地址称为规范帧地址或CFA。通常,CFA被定义为前一帧中调用站点处的堆栈指针的值(这可能与进入当前帧时的值不同)。

main()从其他地方(在libc C运行时支持代码中)被调用,并且在执行call指令时,%rsp将指向堆栈顶部(即最低地址-堆栈向下增长),无论它实际是什么(确切的值在此并不重要):

:                :                              ^
|    whatever    | <--- %rsp                    | increasing addresses
+----------------+                              |

此时%rsp的值是“调用点处堆栈指针的值”,即由规范定义的CFA。

当执行call指令时,它将在堆栈上推送一个64位(8字节)的返回地址:

:                :
|    whatever    | <--- CFA
+----------------+
| return address | <--- %rsp == CFA - 8
+----------------+

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

:                :
|    whatever    | <--- CFA
+----------------+
| return address |
+----------------+
| reserved space | <--- %rsp == CFA - 16
+----------------+

使用.cfi_def_cfa_offset指令在调试信息中声明了堆栈指针的变化,你可以看到CFA现在距离当前堆栈指针偏移了16个字节。

在函数末尾,addq $8, %rsp指令再次改变了堆栈指针,因此插入另一个.cfi_def_cfa_offset指令,表示CFA现在距离堆栈指针只有8个字节的偏移量。

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


5
确实是非常好的解释。通常标签是按顺序编号(关于函数作用域),这里我们可能只看到了这些,因为优化器删除了其他的标签。 - JohnTortugo
2
要理解 .cfi_* 指令,您还应该查看 https://sourceware.org/binutils/docs/as/CFI-directives.html。这很简洁,但它是官方的。 - David Wohlferd

2
我可以为您解释GCC生成的汇编中使用的.cfi_def_cfa_offset指令的值。Matthew提供了一个很好的解释。下面是GAS手册第7.10节CFI指令中的定义:

.cfi_def_cfa_offset修改计算CFA的规则。寄存器保持不变,但偏移量是新的。请注意,将添加到已定义寄存器以计算CFA地址的是绝对偏移量。

还有.cfi_adjust_cfa_offset

.cfi_def_cfa_offset相同,但偏移量是从先前的偏移量中添加/减去的相对值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接