一文搞懂 Ftrace 的實現原理

arm64 棧幀結構

arm64 有31個通用寄存器 r0-r30,用法分別如下:

寄存器 意義
SP Stack Pointer: 棧指針
r30 Link Register: 在調用函數時候,保存下一條要執行指令的地址
r29 Frame Pointer:保存函數棧的基地址
r28...r19
r18
r17
r16
r15...r9 臨時寄存器
r8 在一些情況下,返回值是通過 r8 返回的
r7...r0 在函數調用過程中傳遞參數和返回值
NZCV 狀態寄存器:N(Negative)負數 Z(Zero) 零 C(Carry) 進位 V(Overflow) 溢出

下面以如下代碼爲例,說明它的棧幀結構:

int fun2(int c,int d)
{
   return0;
}
 
int fun1(int a,int b)
{
   int c = 1;
   int d = 2;
  
   fun2(c, d);
   return0;
}
 
int main(int argc,char **argv)
{
   int a = 0;
   int b = 1;
   fun1(a,b);
}

其反彙編後的結果和對應棧幀結構爲:

image

其詳細步驟如下:

  1. 每個函數在入口處首先會分配棧空間,且一次分配,確定棧頂,之後sp將不再變化;
  2. 每個函數的棧頂部存放的是caller的棧頂指針,即fun1的棧頂存放的是main棧頂指針;
  3. 對於最後一級callee函數,由於x29保存了上一級caller的棧頂sp指針,因此不在需要入棧保存,如示例中fun2執行時,此時x29指向fun1的棧頂sp

編譯階段

以 blk_update_request 爲例,看下其開啓 Ftrace 前後的反彙編代碼:

image

可以看出,右圖中多插入了一段【3f3c: 94000000 bl 0 <_mcount>】,那麼是由誰何時插入的呢?

編譯選項 -pg -mrecord-mcoun 會在編譯時,在每個可 trace 的函數插入 bl 0 <_mcount>。

鏈接階段

image

可以看出編譯階段的【3f3c: 94000000 bl 0 <_mcount>】,在鏈接階段變成了【ffff8000104e43f4: 97ed1fde bl ffff80001002c36c <_mcount>】。

其中 _mcount 函數反彙編爲:

ffff80001002c36c <_mcount>:
ffff80001002c36c:       d65f03c0        ret

運行階段

ftrace_init 執行後的 blk_update_request

image

可見,內核在 start_kernel 執行時,會調用 ftrace_init,它會將所有可 trace 函數中的 _mcount 進行替換,如上可以看出鏈接階段的 【bl ffff80001002c36c <_mcount>】 已經被替換爲 【nop】 指令。

設定 trace 後的 blk_update_request

先執行如下命令來 trace 函數 blk_update_request:

$echo blk_update_request > /sys/kernel/debug/tracing/set_ftrace_filter

$echo function > /sys/kernel/debug/tracing/current_tracer

然後再來查看blk_update_request反彙編代碼:

image

可以看到之前在 blk_update_request 的 【nop】 指令被替換成 【bl 0xffff80001002c370 <ftrace_caller>】。函數 ftrace_caller 將調用用戶註冊的 trace 函數。

總結

  1. 編譯階段。通過編譯選項 -pg -mrecord-mcount 在每個支持 trace 的函數中插入 bl 0 <_mcount> 指令。
  2. 鏈接階段。會根據重定位段將 bl 0 <_mcount> 指令地址重定位爲 _mcount 函數地址。
  3. 運行階段。
(1). ftrace_init:會將可 trace 函數中的 【bl      0 <_mcount>】 替換爲 【nop】 指令;

(2). 執行 echo blk_update_request > set_ftrace_filter:會使能 blk_update_request 的鉤子函數替換標記(nop 替換爲 ftrace_caller);

(3). 執行 echo function > current_tracer:將 ftrace_caller 中 ftrace_call 被替換爲 ftrace_ops_no_ops,最終會調用到 function_trace_call。

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章