gcc的-pg選項(xiàng)
ftrace 支持動態(tài)trace态兴,即可以跟蹤內(nèi)核和模塊中任意的全局函數(shù)狠持。它利用了gcc的-pg編譯選項(xiàng),在每個函數(shù)的開始增加一個stub瞻润,這樣在需要的時候可以控制函數(shù)跳轉(zhuǎn)到指定的代碼中去執(zhí)行喘垂。用過gprof工具應(yīng)該對gcc的-pg選項(xiàng)不陌生了献汗。
- 當(dāng)CONFIG_FUNCTION_TRACER打開時,編譯時會增加-pg編譯選項(xiàng)王污,gcc會在每個函數(shù)的入口處增加對mcount的調(diào)用罢吃。
- gcc 4.6新增加了-pg -mfentry支持,這樣可以在函數(shù)的最開始插入一條調(diào)用fentry的指令昭齐。
[root@localhost kernel-4.4.27]# echo 'void foo(){}' | gcc -x c -S -o - - -pg -mfentry
foo:
.LFB0:
.cfi_startproc
call __fentry__
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
通過nm可以看到多了一個未定義的符號fentry
U __fentry__
0000000000000000 T foo
對于動態(tài)ftrace尿招,有一個很重要的工作就是記錄這些被-pg影響的函數(shù),最終可以通過讀debugfs的文件/sys/kernel/debug/tracing/available_filter_functions來查看哪些函數(shù)是支持trace的阱驾。
編譯內(nèi)核
內(nèi)核在編譯代碼時就谜,先指定-pg -fentry選項(xiàng)編譯生成.o文件,然后通過scripts/recordmcount.pl腳本來處理.o文件
以一個簡單的foo.c文件舉例
static void foo() {}
static void foo2() {}
static void foo3() {}
經(jīng)過scripts/recordmcount.pl處理之后里覆,.o文件中新增了一個__mcount_loc段丧荐,在最終鏈接時被重定向,里面記錄了所有插入了mcount或者fentry的函數(shù)地址喧枷。
[root@localhost kernel-4.4.27]# objdump -s foo.o
Contents of section __mcount_loc:
0000 00000000 00000000 00000000 00000000 ................
0010 00000000 00000000 ........
[root@localhost kernel-4.4.27]# objdump -r foo.o
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000001 R_X86_64_PC32 __fentry__-0x0000000000000004
000000000000000c R_X86_64_PC32 __fentry__-0x0000000000000004
0000000000000017 R_X86_64_PC32 __fentry__-0x0000000000000004
RELOCATION RECORDS FOR [__mcount_loc]:
OFFSET TYPE VALUE
0000000000000000 R_X86_64_64 foo
0000000000000008 R_X86_64_64 foo+0x000000000000000b
0000000000000010 R_X86_64_64 foo+0x0000000000000016
最終內(nèi)核的鏈接腳本include/asm-generic/vmlinux.lds.h將__mcount_loc段的內(nèi)容放在.init.data段中虹统,并且通過__start_mcount_loc和__stop_mcount_loc兩個全局符號來訪問。
#define MCOUNT_REC() . = ALIGN(8); \
VMLINUX_SYMBOL(__start_mcount_loc) = .; \
*(__mcount_loc) \
VMLINUX_SYMBOL(__stop_mcount_loc) = .;
[root@localhost kernel-4.4.27] objdump -t vmlinux -j .init.data | egrep "__start_mcount_loc|__stop_mcount_loc"
ffffffff817109e0 g .init.data 0000000000000000 __stop_mcount_loc
ffffffff816fb0c0 g .init.data 0000000000000000 __start_mcount_loc
ftrace初始化
gcc的-pg -mfentry選項(xiàng)在每個函數(shù)開始處增加了一條callq指令隧甚,它和對應(yīng)的retq據(jù)統(tǒng)計(jì)會帶來13%的性能開銷车荔,因此在內(nèi)核的初始化階段將這些callq指令全部修改為5 Byte的NOP指令: 66 66 66 66 90H,同時將這些指令的地址記錄下來戚扳。
- scripts/recordmcount.pl過濾了kernel/trace/ftrace.o忧便,沒有為其增加__mcount_loc段,所以ftrace代碼不會修改其自身的代碼帽借。
- ftrace_init在start_kernel中調(diào)用珠增,早于kernel_init,此時不會有其它Core正在執(zhí)行代碼砍艾,因此也不用擔(dān)心修改指令導(dǎo)致其它Core出現(xiàn)crash(系統(tǒng)運(yùn)行時修改指令就要麻煩很多:被修改的指令正在其它Core上執(zhí)行蒂教,5個字節(jié)的指令有可能跨兩個cache line)。
- 由于ftrace_init執(zhí)行時間較早辐董,所以.initcall中的初始化函數(shù)都是可以被trace的(在cmdline中增加"ftrace_filter="參數(shù)來指定要trace的函數(shù))悴品。
void __init ftrace_init(void)
{
extern unsigned long __start_mcount_loc[];
extern unsigned long __stop_mcount_loc[];
unsigned long count;
count = __stop_mcount_loc - __start_mcount_loc;
ret = ftrace_process_locs(NULL,
__start_mcount_loc,
__stop_mcount_loc);
}
在ftrace_process_locs函數(shù)中禀综,內(nèi)核為__start_mcount_loc和__stop_mcount_loc之間的每個地址都創(chuàng)建一個struct dyn_ftrace結(jié)構(gòu)简烘,其中ip記錄著函數(shù)開始的stub地址,ftrace_code_disable函數(shù)會將這個地址的內(nèi)容替換為nop指令定枷,這樣在沒有trace時孤澎,系統(tǒng)的性能幾乎沒有影響。
struct dyn_ftrace {
unsigned long ip; /* address of mcount call-site */
unsigned long flags;
struct dyn_arch_ftrace arch;
};
當(dāng)開始trace時欠窒,內(nèi)核根據(jù)函數(shù)名找到ip覆旭,將該地址處的nop指令修改為call指令退子,以控制其跳轉(zhuǎn)到指定的位置。
模塊
編譯模塊時會用到內(nèi)核源碼樹中的Makefile和.config文件(實(shí)際上是根據(jù).config生成的include/config/auto.conf文件)型将,如果內(nèi)核源碼樹中的配置打開了CONFIG_FUNCTION_TRACER寂祥,那么在編譯模塊時也會增加-pg -mfentry,并將影響了的函數(shù)地址保存在__mcount_loc段中七兜。
在加載.ko時首先根據(jù)模塊放置的實(shí)際地址為__mcount_loc段重定向丸凭,并記錄在mod->ftrace_callsites中,最后同樣會調(diào)用ftrace_process_locs函數(shù)來處理腕铸。
如果當(dāng)前運(yùn)行的內(nèi)核打開了CONFIG_FUNCTION_TRACER惜犀,但編譯module時未打開,實(shí)際上編出來的.ko也能加載狠裹,只是其中的函數(shù)都不支持trace虽界。
附:scripts/recordmcount.pl實(shí)現(xiàn)
首先是逐行處理objdump -hdr foo.o, 將插入了mcount或者fentry的函數(shù)地址記錄到一個臨時的.s文件中涛菠,并將臨時.s文件編譯成.o文件并和原來的.o文件鏈接到一起
[root@localhost kernel-4.4.27]# cat .tmp_mc_foo.s
.section __mcount_loc,"a",@progbits
.align 8
.quad foo + 0
.quad foo + 11
需要注意的是如果.o文件中的第一個函數(shù)是static或者weak莉御,需要先通過objcopy --globalize-symbol將其轉(zhuǎn)換為全局符號,然后再和上面的.tmp_mc_foo.o一起鏈接
$cc -o $mcount_o -c $mcount_s
$objcopy $globallist $inputfile $globalobj
$ld -r $globalobj $mcount_o -o $globalmix
$objcopy $locallist $globalmix $inputfile