摘要:[root@u-jeff ~]# cat /sys/kernel/debug/tracing/kprobe_events。因爲實現這些功能的原理就是在代碼段函數頭中插上預先設定好的函數調用,如果剛剛插入代碼時,正好有程序跑到這塊代碼就好玩了,系統很可能會崩掉,在x86系統上採用的就是先在函數頭中插入一個字節指令'cc',當其它進程執行此指令時,系統進入int3中斷流程,。

本文介紹linux內核中幾種常用的動態調試手段,也都是我常用的,都是在生產環境中直接使用, 不需要藉助工具, 依照我的經驗,去客戶生產環境中解決問題,很多都不會預裝perf、BPF工具,有的即使有perf這樣的工具,也因爲工具性能影響客戶幾千億美金的正常業務,也會不讓你使用。

最常見的就是使用kprobe,kprobe具體的原理可以參考我以前寫的這篇文章kprobe原理

使用kprobe最常用的就是查詢函數調用的參數和返回值:

cd /sys/kernel/debug/tracing

echo ‘p:myprobe do_sys_open fname_str=+0(%si):string' > kprobe_events

echo 'r:myretprobe do_sys_open $retval' >> kprobe_events

ls  ./events/kprobes/

enable  filter myprobe  myretprobe

使能kprobe event:

echo 1 >  ./events/kprobes/myprobe/enable

echo 1 >  ./events/kprobes/myretprobe/enable

cat  ./trace (可以看到系統中調用open的文件名和返回值)

至於在kprobe_events文件中指定的寄存器(%si),基於具體平臺的不同會不一樣,可以預先使用perf工具查詢到當前平臺上獲取函數參數的寄存器。

[root@u-jeff ~]# perf probe  'do_sys_open filename:string flags:u32'

Added new event:

...

[root@u-jeff ~]# cat /sys/kernel/debug/tracing/kprobe_events

p:probe/do_sys_open _text+2391184 filename_string=+0( %si ):string flags_u32= %dx :u32

第二類就是使用trace event:

比如像linux內核中最著名的函數__schedule()

static void __sched notrace __schedule(bool preempt)

{

...

trace_sched_switch(preempt, prev, next);

...

}

這種原理不用多說,是內核代碼編譯的時候就已經內嵌於函數中的,只需打開對應開關就可以看到固定格式的打印信息。

#pwd

/sys/kernel/debug/tracing/events/sched/sched_switch

#cat /sys/kernel/debug/tracing/trace

系統中大量子系統中的函數都嵌入了這種trace event,只需針對性打開開關便可。

#pwd

/sys/kernel/debug/tracing/events

還有一些函數里面沒有內嵌trace event,或者有時想查看函數的調用堆棧,可以使用trace function + stack_trace

cd /sys/kernel/debug/tracing/

echo 0 > ./tracing_on

echo function > current_tracer

echo 1 > options/func_stack_trace

echo  "your function" > set_ftrace_filter

echo 1 > tracing_on

比如把 ”your function" 改成 schedule

cat ./trace   可以看到調用schedule時的函數堆棧,而不需要在函數中加上WARN_ON(1)這樣的語句再重新編譯內核。

不管是function tracer還是 function_graph 這樣的tracer,

cat ./available_tracers

hwlat blk function_graph wakeup_dl wakeup_rt wakeup function nop

我最佩服的就是實現此功能時同步代碼段的方法,其實就是怎樣從下面第一幅圖變成第二幅圖:

圖一:

圖二:

因爲實現這些功能的原理就是在代碼段函數頭中插上預先設定好的函數調用,如果剛剛插入代碼時,正好有程序跑到這塊代碼就好玩了,系統很可能會崩掉,在x86系統上採用的就是先在函數頭中插入一個字節指令'cc',當其它進程執行此指令時,系統進入int3中斷流程,

所以在do_int3中可以看到下面類似的代碼:

當插入‘cc'之後,把'cc'指令同步到所有的cpu,然後把’cc‘之後的代碼換成函數調用代碼,最後把’cc'換掉,一切都ok.

相關文章