我想要解释 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;
}
我在源文件 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?
正如 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”只是一个任意值。编译器将根据一些实现细节生成唯一的标签名称,例如基本块的内部编号。)
我想要解释 GCC 生成的程序集中与
指令一起使用的值。.cfi_def_cfa_offset
马修提供了很好的解释。以下是 GAS 手册中第 7.10 节 CFI 指令的定义:
修改计算CFA的规则。寄存器保持不变,但偏移量是新的。请注意,这是将添加到定义的寄存器以计算 CFA 地址的绝对偏移量。.cfi_def_cfa_offset
和
.cfi_adjust_cfa_offset
:
与
相同,但偏移量是一个相对值,与之前的偏移量相加/相减。.cfi_def_cfa_offset