Ftrace是Function Trace的簡寫,由 Steven Rostedt 開發的,從 2008 年發布的內核 2.6.27 中開始就內置了。
Ftrace是一個系統內部提供的追蹤工具,旨在幫助內核設計和開發人員去追蹤系統內部的函數調用流程。
隨著Ftrace的不斷完善,除了追蹤函數調用流程的作用外,還可以用來調試和分析系統的延遲和性能問題,并發展成為一個追蹤類調試工具的框架。
除了Ftrace外,追蹤類調試工具還包括:

為了幫助我們更好的使用Ftrace,我們有必要簡單了解Ftrace的實現原理。
Ftrace的框架圖如下:

由框架圖我們可以知道:
ftrace包括多種類型的tracers,每個tracer完成不同的功能tracers注冊進入ftrace frameworktracers收集不同的信息,并放入到Ring buffer緩沖區以供調用。?
Ftrace采用了靜態插樁和動態插樁兩種方式來實現。
靜態插樁:
我們在Kernel中打開了CONFIG_FUNCTION_TRACER功能后,會增加一個-pg的一個編譯選項,這個編譯選項的作用就是為每個函數入口處,都會插入bl mcount跳轉指令,使得每個函數運行時都會進入mcount函數。
Ftrace一旦使能,對kernel中所有的函數插樁,這帶來的性能開銷是驚人的,有可能導致人們棄用Ftrace功能。
為了解決這個問題,開發者推出了Dynamic ftrace,以此來優化整體的性能。
動態插樁:
這里的動態,是指的動態修改函數指令。
nop指令,以實現非調試狀態性能零損失。function tracer 設置,動態將被調試函數的nop指令,替換為跳轉指令,以實現追蹤。?
總而言之,Ftrace記錄數據可以總結為以下幾個步驟:
-pg,為每個函數都增加跳轉指令nop指令flag標志位來動態管理,將需要追蹤的函數預留的nop指令替換回追蹤指令,記錄調試信息。?
CONFIG_FTRACE=y????????#?啟用了?Ftrace
CONFIG_FUNCTION_TRACER=y?????#?啟用函數級別的追蹤器
CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y???#?表示內核支持圖形顯示
CONFIG_FUNCTION_GRAPH_TRACER=y????#?以圖形的方式顯示函數追蹤過程
CONFIG_STACK_TRACER=y??????#?啟用堆棧追蹤器,用于跟蹤內核函數調用的堆棧信息。
CONFIG_DYNAMIC_FTRACE=y??????#?啟用動態 Ftrace,允許在運行時啟用和禁用 Ftrace 功能。
CONFIG_HAVE_FTRACE_NMI_ENTER=y????#?表示內核支持非屏蔽中斷(NMI)時進入?Ftrace?的功能
CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y???#?表示內核支持通過 mcount 記錄函數調用關系。
CONFIG_FTRACE_NMI_ENTER=y???????????????????#?表示內核支持通過 mcount 記錄函數調用關系。???
CONFIG_FTRACE_SYSCALLS=y?????#?系統調用的追蹤
CONFIG_FTRACE_MCOUNT_RECORD=y????#?啟用 mcount 記錄函數調用關系。
CONFIG_SCHED_TRACER=y??????#?支持調度追蹤
CONFIG_FUNCTION_PROFILER=y?????#?啟用函數分析器,主要用于記錄函數的執行時間和調用次數
CONFIG_DEBUG_FS=y???????#?啟用?Debug?文件系統支持
上面只是介紹了部分配置,更多詳細配置可自行了解。
并且上述配置不一定全部打開,勾選自己需要的即可,通常我們選擇
CONFIG_FUNCTION_TRACER和CONFIG_HAVE_FUNCTION_GRAPH_TRACER即可,然后編譯燒錄到開發板。
?
Ftrace是基于debugfs調試文件系統的,所以我們的第一步就是先掛載debugfs。
mount?-t?debugfs?none?/sys/kernel/debug
此時我們能夠在/sys/kernel/debug下看到內核支持的所有的調試信息了。
#?cd?/sys/kernel/debug/
#?ls
asoc????????????????gpio????????????????regmap
bdi?????????????????ieee80211???????????sched_debug
block???????????????memblock????????????sched_features
clk?????????????????mmc0????????????????sleep_time
device_component????mmc1????????????????suspend_stats
devices_deferred????mtd?????????????????tracing
dma_buf?????????????opp?????????????????ubi
extfrag?????????????pinctrl?????????????ubifs
fault_around_bytes??pm_qos??????????????wakeup_sources
?
在/sys/kernel/debug目錄下,包含的是kernel所有的調試信息,本章只關注與tracing目錄,下面挑選一些比較重要的屬性文件來分析。
?
萬變不離其宗,如此復雜的框架,設計人員已經提供了
README文件,里面詳解了各個屬性文件的含義,我建議拋棄本文,看README吧:)
trace :包含當前追蹤的內容,以人類可讀的格式展現,通過echo > trace來清除。
?
trace_pipe 和 trace 一樣,都是記錄當前的追蹤內容,但它和 trace 不一樣的是:
trace_pipe 的讀操作將會阻塞,直到有新的追蹤數據進來為止;trace_pipe 讀取的內容將被消耗掉,再次讀 trace_pipe 又會阻塞到新數據進來為止。簡單的來說,
cat trace_pipe是堵塞讀取,有數據就讀,沒數據就等待;而cat trace有沒有數據都是直接返回的
?
tracing_on:向 tracing_on 寫入 1,啟用追蹤;向 tracing_on 寫入 0,停止追蹤。
追蹤使用
ring buffer記錄追蹤數據。修改tracing_on不會影響ring buffer當前記錄的內容。
?
current_tracer 表示當前啟用的 tracer ,默認為 nop ,即不做任何追蹤工作:
#?cat?current_tracer
nop
?
available_filter_functions:可以被追蹤的函數列表,即可以寫到 set_ftrace_filter,set_ftrace_notrace,set_graph_function,set_graph_notrace 文件的函數列表。
?
available_tracers 文件中包含的是當前編譯到內核的 tracer 列表,也表示當前內核支持的tracer列表。
該列表的內容,就是可以寫到 current_tracer 的 tracer 名。
#?cat?available_tracers
function_graph?function?nop
nop:表示為空,不追蹤function:追蹤函數調用function_graph:以圖形形式追蹤函數調用?
buffer_size_kb 記錄 CPU buffer 的大小,單位為 KB 。
per_cpu/cpuX/buffer_size_kb 記錄 每個CPU buffer 大小,單位為 KB 。可通過寫 buffer_size_kb 來改變 CPU buffer 的大小。
?
buffer_total_size_kb 記錄所有 CPU buffer 的總大小,即所有 CPU buffer 大小總和。
如有 128 個 CPU buffer ,每個大小 7KB,則
buffer_total_size_kb記錄的總大小為 128 * 7KB = 896。
buffer_total_size_kb 文件是只讀的。
?
set_ftrace_filter :過濾函數追蹤,僅僅追蹤寫入該文件的函數名。
可填入的參數,可以通過available_filter_functions文件查看當前支持的函數名。
該過濾功能,也有很多其他變體,如追蹤某個模塊的函數調用等。
官方給的示例:
Format:?:mod:
example:?echo?:mod:ext3?>?set_ftrace_filter??#?該模塊必須是已經加載進去的模塊
?
set_ftrace_notrace:和 set_ftrace_filter 剛好相反,系統禁用對其中列舉函數的追蹤。
?
系統對 set_ftrace_pid 文件中指定的 PID進程進行追蹤。
如果開啟了 options/function-fork 選項,fork 的子進程的 PID 也會自動加入文件,同時該選項也會引起系統自動將退出進程的 PID 從文件中移除。
?
此文件中列出的函數將導致函數圖跟蹤器僅跟蹤這些函數以及它們調用的函數。
但是該跟蹤的記錄,仍然受set_ftrace_filter 和 set_ftrace_notrace 的影響。
?
與 set_graph_function 類似,但當函數被命中時,將禁用函數圖跟蹤,直到退出函數。
?
更多干貨可見:高級工程師聚集地,助力大家更上一層樓!
?
一般我們掛載上
debugfs后,tracing_on是處于打開狀態的。

?

?

?
echo?0?>?tracing_on???#?關閉trace
echo?>?trace????#?清空當前trace記錄
cat?available_tracers???#?查看當前支持的追蹤類型
echo?function_graph?>?current_tracer??#?設置當前的追蹤類型
echo?1?>?tracing_on???#?開啟追蹤
cat?trace?????#?查看追蹤結果
?
上述章節,只是介紹了Ftrace最基本的命令,下面來看一下Ftrace在具體問題中的用法!
如何追蹤我們執行的命令呢?
Ftrace支持追蹤特定進程,通過set_ftrace_pid屬性來設置指定進程。然后在該進程中,執行特定的命令。
首先我們需要設置好我們的追蹤器
mount?-t?debugfs?none?/sys/kernel/debug
cd?/sys/kernel/debug/tracing
echo?0?>?tracing_on?????????#?關閉追蹤器
echo?function?>?current_tracer??????#?設置當前追蹤類別
在我們設置好追蹤器后,使用如下命令,即可追蹤我們執行的命令your_command
echo?>?trace;?echo?$$?>?set_ftrace_pid;?echo?1?>?tracing_on;?your_command;?echo?0?>?tracing_on
為什么要寫成一條語句?
因為ftrace當打開時,在沒有過濾的情況下,瞬間會抓取到內核所有的函數調用,為了更準確的抓取我們執行的命令,所以需要打開trace,執行完命令后,馬上關閉。
?
跟蹤函數的時候,設置 echo 1 > options/func_stack_trace 即可在 trace 結果中獲取追蹤函數的調用棧。
mount?-t?debugfs?none?/sys/kernel/debug
cd?/sys/kernel/debug/tracing
echo?0?>?tracing_on?????????#?關閉追蹤器
cat?available_filter_functions?|?grep?"xxxxxx"??#?搜索函數是否存在
echo?xxxxxx?>?set_ftrace_filter??????#?設定追蹤的函數
echo?function?>?current_tracer??????#?設置當前追蹤類別
echo?1?>?options/func_stack_trace?????#?記錄堆棧信息
echo?>?trace??????????#?清空緩存
echo?1?>?tracing_on?????????#?開始追蹤
效果如下:
#?cat?trace
#?tracer:?function
#
#?entries-in-buffer/entries-written:?2/2???#P:3
#
#??????????????????????????????_-----=>?irqs-off
#?????????????????????????????/?_----=>?need-resched
#????????????????????????????|?/?_---=>?hardirq/softirq
#????????????????????????????||?/?_--=>?preempt-depth
#????????????????????????????|||?/?????delay
#???????????TASK-PID???CPU#??||||????TIMESTAMP??FUNCTION
#??????????????|?|???????|???||||???????|?????????|
?????kworker/1:1-59????[001]?....???168.954199:?mmc_rescan?<-process_one_work
?????kworker/1:1-59????[001]?....???168.954248:?
?=>?mmc_rescan
?=>?process_one_work
?=>?worker_thread
?=>?kthread
?=>?ret_from_fork
?=>?0
?
要想我們的ko文件能夠被Ftrace記錄到,**我們需要在編譯模塊的時候,加上編譯參數-pg**,這點很重要,否則你在available_filter_functions列表中,查找不到你想要的函數。
然后,需要我們設置過濾器,設置方法有以下幾種:
#?示例
Format:?:mod:
example:?echo?:mod:ext3?>?set_ftrace_filter
追蹤
ext3模塊內的所有函數
?
如果該模塊內的函數,命名都有一定的規則,可以按照正則表達式來過濾
#?示例
echo?"mmc*"?>?set_ftrace_filter
過濾包含
mmc字符的所有函數
?
如果函數命名沒有規律,又想過濾該模塊所有函數,該怎么辦?
按照加載模塊前后的函數差異,寫入到文件中來過濾
cat?available_filter_functions?>?/tmp/1.txt
cat?available_filter_functions?>?/tmp/2.txt
diff?/tmp/1.txt?/tmp/2.txt?>?/tmp/3.txt
cat?/tmp/3.txt?|?sed?'s/^+//'?|?awk?'{print?$1}'?#?如果diff出來格式前帶有+-號,需要手動去掉
cat?/tmp/3.txt?>?set_ftrace_filter
?
Ftrace功能很強大,在內核層面我們通過echo和cat即可獲取我們想要的所有信息,但是通過一次一次敲命令顯得有些繁瑣,自己也對常用的功能整合了一個自動化腳本,能夠通過命令行,直接追蹤特定模塊、函數、命令,極大提高了調試效率。
PS:自動化腳本可在本公眾號獲取
#?/root/common_trace.sh?
Usage:?/root/common_trace.sh?{module|funcs|funcs_stack|command|clear}
???????/root/common_trace.sh?module?ext4?
???????/root/common_trace.sh?funcs?sysfs?
???????/root/common_trace.sh?funcs_stack?sysfs?
???????/root/common_trace.sh?command?sysfs?[functions]?
???????/root/common_trace.sh?clear
腳本主要實現的功能有:
腳本除了
command功能外,其他功能都需要手動調用common_trace.sh clear來停止追蹤。
?
以上,介紹了Ftrace的由來,實現原理,以及如何使用Ftrace,并最終提供了自動化測試腳本,希望對大家有所幫助。