插樁法是內核常用的一種調試手段,利用代碼中插樁,執行到此時執行對應的鉤子函數(hook)來達到調試的目的,從實現來說,它不可避免的會帶來一些性能上的開銷,不過隨着實現的不斷優化,這部分的開銷已經越來越小了。比如使能了 dynamic ftrace
後的內核,在關閉 ftrace
開關的情況下,實際上只是多了個幾個 nop
指令,並不會帶來很大的性能開銷。下面就來看下內核中都有哪幾種常用的插樁調試機制。
ftrace
我在其他博客中有介紹過,它的原理是使用 gcc
編譯器的 -pg
選項,達到在內核代碼中插樁的目的。該選項會自動在函數的入口處加上對 mcount
的調用指令。這樣每次進入一個函數時都會調用對應的鉤子函數來打印函數信息。
tracepoint
的原理和 ftrace
很類似,只不過它並不是利用 gcc
的特性來實現的,而是在內核代碼中提前做了插樁,代碼運行到跟蹤點(樁)上註冊了鉤子函數(hook),那麼就會執行鉤子函數達到調試的目的。和 ftrace
相比,它的劣勢是實現更加複雜,需要程序員按需要在特定位置加入跟蹤點代碼,ftrace
可以實現所有函數的跟蹤,但是 tracepoint
就只會跟蹤添加了代碼的跟蹤點。除了劣勢,自然 tracepoint
也有它的優點,它的靈活性更加好,我們可以自己定製hook函數,添加想要獲取的信息,而 ftrace
默認只會打印函數名。
eventtrace
這種機制實際上是相當於 tracepoint
+ ftrace
的組合,它的前端使用了 tracepoint
來實現跟蹤點,後端使用 ftrace
的架構,把調試信息輸出到 ftrace ring buffer
中,這樣在內核中可以通過 cat /sys/kernel/debug/tracing/trace
來獲取 eventtrace
中的調試信息。
kprobe
是另外一種基於打樁機制實現的調試手法,在註冊一個kprobe時會先拷貝出內核函數指令,然後在該函數前插入一條指令,以i386/x86_64爲例,它是一個 int3
指令,相當於是插入樁位,在執行到此時會觸發內核進入 int3
中斷,然後跳轉執行註冊的hook和已經拷貝的代碼段,最後再返回到函數後面的地址處繼續運行,它支持在監測的函數前後分別插入hook函數。在使用時可以實現一個kernel module的方式註冊 kprobe
,並且可以對所有的內核函數進行動態插樁,只要我們能夠找到對應的地址。
jprobe
和 kretprobe
這兩種是和 kprobe
類似的實現機制,也是通過注入中斷指令執行hook來添加調試信息的,和kprobe的區別是,jprobe只在函數入口處被調用,而kretprobe只在函數return處被調用。