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相同,但偏移量是从上一个偏移量中添加/减去的相对值。

Interesting Posts