在動態(tài)ftrace原理中已經介紹了內核通過gcc -pg -fentry為函數增加5 Byte的stub罚舱,系統(tǒng)啟動后這5 Byte被修改為NOP指令:66 66 66 66 90H。
開始trace時要將NOP指令修改為跳轉指令绎谦,去執(zhí)行各種trace對應的hook函數管闷。function trace對應的hook函數就是function_trace_call。
本文將會介紹內核是如何修改代碼段以控制函數去執(zhí)行指定的hook函數窃肠。
運行時修改代碼段
系統(tǒng)運行時修改代碼段是一個很危險的操作包个,因為被修改的5 Byte有可能跨兩個cache line,如果其它Core正在執(zhí)行铭拧,有可能取到被修改了一半的結果赃蛛,導致系統(tǒng)crash。
ftrace修改代碼段是在ftrace_replace_code中完成的搀菩,這個函數里有三個大循環(huán)
- add_breakpoints: 首先找到需要trace的函數呕臂,將第一個字節(jié)修改為0xCC,即int 3(也叫break指令)
- add_update: 修改為callq trampoline指令肪跋,但是第一個字節(jié)保留為0xCC
- finish_update: 將0xCC修改為0xE8歧蒋,即為call指令
# echo expand_files > set_ftrace_filter
# echo function > current_tracer
以上面的操作舉例,配置ftrace跟蹤expand_files函數州既,該函數前5 Byte變化如下面所示:
0xffffffff8114aae0 <expand_files>:
66 66 66 66 90H <-- NOP
|
|
V
CC 66 66 66 90H <-- int 3
|
|
V
CC 1b 55 eb 1eH <-- 跳轉的偏移已經修改好了谜洽,但opcode還是int 3
|
|
V
e8 1b 55 eb 1eH <-- callq 0xffffffffa0000000
內核在修改代碼段時先將第一個Byte修改為0xCC,如果有其它Core執(zhí)行到這里會觸發(fā)異常吴叶,但是在int 3異常處理程序中直接返回并再次觸發(fā)異常阐虚,直至int 3被修改為call指令后才跳出循環(huán)
dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)
{
#ifdef CONFIG_DYNAMIC_FTRACE
/*
* ftrace must be first, everything else may cause a recursive crash.
* See note by declaration of modifying_ftrace_code in ftrace.c
*/
if (unlikely(atomic_read(&modifying_ftrace_code)) &&
ftrace_int3_handler(regs))
return;
#endif
...
跳轉目標
前面說到trace的原理是修改函數開始的5 Byte,讓其先去執(zhí)行指定的hook函數蚌卤。不同的tracer有不同的hook函數实束,function tracer的hook函數是function_trace_call奥秆,這個函數的功能比較簡單,只是向ring buffer中記錄了ip和parent_ip
內核提供了<font color=cornflowerblue>.ftrace_caller</font>和<font color=cornflowerblue>.ftrace_regs_caller</font>兩段匯編代碼作為wrapper咸灿,用來完成保存/恢復寄存器等通用的工作疫向,其中的<font color=cornflowerblue>call ftrace_stub</font >會被修改為各種tracer對應的hook function
ENTRY(ftrace_caller)
/* save_mcount_regs fills in first two parameters */
save_mcount_regs
GLOBAL(ftrace_caller_op_ptr)
/* Load the ftrace_ops into the 3rd parameter */
movq function_trace_op(%rip), %rdx
/* regs go into 4th parameter (but make it NULL) */
movq $0, %rcx
GLOBAL(ftrace_call)
call ftrace_stub
restore_mcount_regs
GLOBAL(ftrace_caller_end)
GLOBAL(ftrace_return)
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
GLOBAL(ftrace_graph_call)
jmp ftrace_stub
#endif
GLOBAL(ftrace_stub)
retq
END(ftrace_caller)
但是內核也沒有直接調用<font color=cornflowerblue>.ftrace_caller</font>和<font color=cornflowerblue>.ftrace_regs_caller</font>纬凤,而是在內存中構造了一個trampoline嚎花,將<font color=cornflowerblue>.ftrace_caller</font>拷貝到這段trampoline中龟再,并修改其中的相對偏移。
多個tracer同時工作
未完待續(xù)