Gnu Assembler(GAS)中的CFI指令用于什么?

137

每行似乎都有一个.CFI指令,并且这些指令有很多种,例如.cfi_startproc.cfi_endproc等。 在这里了解更多

    .file   "temp.c"
    .text
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    movl    $0, %eax
    leave
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl func
    .type   func, @function
func:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movl    %esi, %eax
    movb    %al, -8(%rbp)
    leave
    ret
    .cfi_endproc
.LFE1:
    .size   func, .-func
    .ident  "GCC: (Ubuntu 4.4.1-4ubuntu9) 4.4.1"
    .section    .note.GNU-stack,"",@progbits

我不明白这些的目的是什么。


3
这里是GNU AScfi指令的说明。 - Paschalis
1
相关:如何从GCC/clang汇编输出中删除“噪音”?,如果您只想要指令而不是指示符。一种好的方法是将您的代码放在http://gcc.godbolt.org/上,以查看来自各种编译器版本(包括非x86)的漂亮过滤的汇编输出,并使用颜色突出显示将源行与asm块匹配。 - Peter Cordes
5个回答

154

要禁用这些功能,请使用gcc选项。

-fno-asynchronous-unwind-tables

-fno-dwarf2-cfi-asm 也可能需要。


14
可能需要使用-fno-dwarf2-cfi-asm - technosaurus
1
如果您要禁用它以获得人类可读的汇编输出,请参阅如何从GCC / clang汇编输出中删除“噪音”?以获取其他有用的选项和技巧。 - Peter Cordes
5
有趣的是,关于如何禁用它们的回答获得的赞数比描述它们是什么的回答更多 :) - mathway

78
我有一种感觉,它代表着调用帧信息,是GNU AS的扩展,用于管理调用帧。来自DeveloperWorks
在某些架构上,异常处理必须使用调用帧信息指令进行管理。这些指令用于汇编语言中以指导异常处理。如果由于任何原因(例如代码基础的可移植性),GCC生成的异常处理信息不足够,在Linux on POWER上可以使用这些指令。
看起来这些指令在某些平台上根据对异常处理的需求而生成。
如果您想禁用这些指令,请参阅David's answer

5
还有,您能否简单介绍一下.LFB0、.LFB1、.LFE0和.LFE1? - claws
@claws - 这些是编译器生成的标签(正如您可以从“:”中看到的那样)。请参阅https://dev59.com/dGUp5IYBdhLWcg3wbXJf#15285058。 - General Grievance
死亡的DeveloperWorks链接的存档URL:https://web.archive.org/web/20121010045618/http://www.ibm.com/developerworks/systems/library/es-gnutool/ - AJM

36

CFI指令用于调试,它允许调试器展开堆栈。例如:如果过程A调用过程B,然后调用了一个共同的过程C。过程C失败了。您现在想知道实际调用C的人,然后您可能想知道谁调用了B。

调试器可以使用堆栈指针(%rsp)和寄存器%rbp来展开这个堆栈,但是它需要知道如何找到它们。这就是CFI指令发挥作用的地方。

movq    %rsp, %rbp
.cfi_def_cfa_register 6

所以这里的最后一行告诉我们 "调用帧地址" 现在存储在寄存器6(%rbp)中。


2
但我认为,CFI 的异常处理使用应该比调试更频繁。 - osgx
6
实际上,CFA代表"规范帧地址"。请参见这里 - Cameron
ImperialViolet - 汇编文件中的CFI指令 - Mark Simon
1
CFI指令允许堆栈展开,即使使用-fomit-frame-pointer编译的代码,作为RBP的替代方案(gcc或clang -O1及更高版本默认启用)。它被C++异常处理以及调试器/分析器使用。在具有传统RBP帧指针的代码中,当前RBP值始终指向保存的RBP值,并且该值指向前一个值形成一个链接列表。在这种情况下,不需要CFI。 (尽管在使用帧指针的函数中,CFI cfa_register避免了需要每个RSP更改的更多元数据,就像您所展示的那样。) - Peter Cordes

4
为了禁用这些功能,g++需要使用-fno-exceptions,以及之前提到的-fno-asynchronous-unwind-tables(前提是您不使用异常)。

-2

这个词其实指的是控制流完整性。 它们本质上是传递给调试器和其他工具的信息项,用于描述程序的预期流程。


不,这是调用帧信息。控制流完整性是一类通用技术(https://en.wikipedia.org/wiki/Control-flow_integrity),包括使用CPU指令如`endbr`的Intel CFE(控制流执行)。我认为任何控制流完整性相关的东西都不会使用由.cfi_*指令生成的堆栈展开元数据。 - Peter Cordes
在《使用汇编语言学习编程:新手的基础学习》第13.6章“代码注释”中,"在函数内部,一组称为CFI(控制流完整性)指令的指令告诉调试器您在函数内的位置。这些指令相当复杂,但如果您看到以.cfi_开头的指令,则它们实质上是传递给调试器和其他工具的信息项,用于描述程序的预期流程。" - kingkong
那本书是错的。GAS:.cfi_def_cfa_offset的解释 引用了DWARF规范,这是调试信息的格式,以及这些指令创建的.eh_frame展开信息。另请参见GNU汇编器(GAS)中的CFI指令用于什么? / CFI指令是什么意思?(还有一些问题) (不幸的是,GAS手册关于它们的章节没有扩展该首字母缩略词)。 - Peter Cordes
你的书对指令的作用只是部分正确的。它们创建堆栈展开信息,使调试器能够找到给定当前 RIP 的返回地址。它们不会告诉调试器程序的预期流程,只会告诉改变 RSP 或函数使用 RBP 作为帧指针的情况。可能作者在谷歌上搜索 CFI 后,找到了 https://en.wikipedia.org/wiki/Control-flow_integrity,这是另一个使用相同首字母缩写的 CS / 工程概念。 - Peter Cordes
好的,我现在相信你了。感谢纠正。 - kingkong
实际上,这只是一个吹毛求疵的小问题。'控制流'和'调用帧'是高度相关的概念。请参见vmlinux.lds.h。此外,我认为这些指令也适用于COFF和ELF。尽管如此,这个答案并不是太有帮助。'CFI代表什么'这个字面上的问题可能是含糊不清的。它还具有'异常'机制,这是在堆栈跟踪之上的另一层次。如果基本块创建本地存储,则信号处理程序可能需要此信息。 - artless noise

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