author: hjjdebug
date: 2025年 06月 12日 星期四 14:24:40 CST
descrip: c/c++ 匯編碼中的.cfi 指令有什么用途?
文章目錄
- 1. 幾個簡寫詞.
- 2. 看一個簡單的測試代碼:
- 3. 生成匯編代碼:
- 4. 分析.cfi 指令
- 5. 小結:
1. 幾個簡寫詞.
cfi(call frame info) 調用幀信息, 名詞. 描述的是調用棧的信息
cfa(call frame address) 調用幀地址, 就是當你執行 call xxx 時,堆棧esp的地址, 這個地址很重要.
它是一個固定的地址,該地址處,執行call時, 首先會存如一個8字節的返回地址.(x86_64)
cfi 作用: 異常時stack 回滾.
當程序崩潰或 C++ 異常拋出時,函數后面的代碼就不執行了, 那堆棧如何恢復?
如何找到異常接受代碼, 并恢復上一級甚至上上一級堆棧
CFI 信息就保留著各棧幀之間的信息.
eh_frame 節中的 CFI 指令, 記錄了各調用棧信息,處理異常代碼可據此正確回滾調用棧.
2. 看一個簡單的測試代碼:
這里不講回滾細節, 這里之間.cfi 指令是什么, 記錄了什么信息.
$ cat main.cpp
#include <stdio.h>
int Add(int i, int j)
{return i+j;
}int main()
{int i=Add(2,3);printf("i:%d\n",i);return 0;
}
3. 生成匯編代碼:
g++ -S -o 1.S main.cpp
分析一下Add 函數的匯編代碼.
Add(int, int):
.LFB0:.cfi_startprocendbr64pushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6movl %edi, -4(%rbp)movl %esi, -8(%rbp)movl -4(%rbp), %edxmovl -8(%rbp), %eaxaddl %edx, %eaxpopq %rbp.cfi_def_cfa 7, 8ret.cfi_endproc
4. 分析.cfi 指令
前面已經說過了,cfi 開頭的指令都是為了堆棧回滾而存在的,為了形成eh_frame節信息.
.cfi_startproc ;代表棧幀的開始
.cfi_endproc ;代表棧幀的結束
cfi_def_cfa_offset 16
.cfi_def_cfa_offset 16
def 是 define 的簡寫
定義調用幀地址偏移 為 16
就是說當你調用了pushq %rbp 后, 這個地方的堆棧地址比調用時的堆棧地址偏移了16bytes
這樣定義顯然是正確的, 因為此時堆棧中保存了8bytes 返回地址, 8bytes ebp值.
cfi 指令就是要跟蹤堆棧的變化.
.cfi_offset 6, -16
6號寄存器 值為CFA-16
.cfi_def_cfa_register 6
定義cfa寄存器是6號寄存器
在movq %rsp,%rbp 后調用定義該cfi指令, 說明6號寄存器是%rbp
.cfi_def_cfa 7, 8
7號寄存器 值為CFA+8
在popq %rbp 后調用該指令, 此時堆棧中還有8字節的返回地址,可知.
此處的7號寄存器對應esp
定義cfa 的幾號寄存器可能是保留cfi信息的一個必要手段,這里沒有具體分析其與eh_frame的對應關系.
5. 小結:
總之, cfi 指令是為了形成eh_frame節而定義的指令,用來跟蹤堆棧地址.以備隨時可以返回調用幀.
由于函數執行過程中也可能使用堆棧,所以無非就是跟蹤定義棧的偏移, 棧的偏移.
具體怎樣觸發恢復正確的棧幀,這個細節我們沒有分析,這應該分析eh_frame 中保留的數據.
這里只需要知道匯編中.cfi 的大體作用就可以了.
由此我們也可以斷定,刪除所有.cfi指令,不會影響代碼的執行. 如果代碼沒有異常的話.
只有代碼出現異常,才會使用到.cfi信息. 這樣就解除了對匯編碼中.cfi 指令的疑惑.
從另一個角度看,.cfi指令是偽指令,它并不對應cpu的指令代碼,但給編譯器提供了一些元信息,
就是輔助信息,能夠讓編譯器了解每個frame對應的堆棧信息. 以備不時之需.