GAS:.cfi_def_cfa_offset的解释
我想要解释一下GCC生成的汇编中的.cfi_def_cfa_offset指令所使用的值。 我隐约知道.cfi指令涉及调用框架和堆栈展开,但是我想要更详细地解释为什么,例如,在GCC输出的程序集中使用值16和8来编译下面的C程序在我的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? 另外,为什么用于本地function的数字22开始和function结束标签?
正如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
指令在debugging信息中声明堆栈指针的更改,您可以看到CFA现在与当前堆栈指针.cfi_def_cfa_offset
16个字节。
在函数结尾处, addq $8, %rsp
.cfi_def_cfa_offset
指令再次改变堆栈指针,所以插入另一个.cfi_def_cfa_offset
指令来指示CFA现在与堆栈指针仅有8个字节的偏移量。
(标号中的数字“22”只是一个任意值,编译器会根据一些实现细节,如基本块的内部编号,生成唯一的标签名称。
我想要解释一下GCC生成的汇编中的
.cfi_def_cfa_offset
指令所使用的值。
马修提供了一个很好的解释。 以下是GAS手册中第7.10节CFI指令的定义:
.cfi_def_cfa_offset
修改计算CFA的规则。 注册保持不变,但抵消是新的。 请注意,这是将被添加到定义的寄存器来计算CFA地址的绝对偏移量。
和.cfi_adjust_cfa_offset
:
与
.cfi_def_cfa_offset
相同,但偏移量是从上一个偏移量中添加/减去的相对值。