使用gcc的-finstrument-functions選項進行函數跟蹤

    GCC Function instrumentation機制可以用來跟蹤函數的調用關係,在gcc中對應的選項爲“-finstrument-functions”。可查看gcc的man page來獲取更詳細信息。
編譯時如果爲gcc加上“-finstrument-functions”選項,那在每個函數的入口和出口處會各增加一個額外的hook函數的調用,增加的這兩個函數分別爲:
void __cyg_profile_func_enter (void *this_fn, void *call_site);void __cyg_profile_func_exit  (void *this_fn, void *call_site);
其中第一個參數爲當前函數的起始地址,第二個參數爲返回地址,即caller函數中的地址。
這是什麼意思呢?例如我們寫了一個函數func_test(),定義如下:
static void func_test(v){	/* your code... */}
那通過-finstrument-functions選項編譯後,這個函數的定義就變成了:
static void func_test(v){	__cyg_profile_func_enter(this_fn, call_site);	/* your code... */	__cyg_profile_func_exit(this_fn, call_site);}
我們可以按照自己的需要去實現這兩個hook函數,這樣我們就可以利用this_fn和call_site這兩個參數大做文章。
例如下面這段代碼:
instrfunc.c: #include <stdio.h>#define DUMP(func, call) /	printf("%s: func = %p, called by = %p/n", __FUNCTION__, func, call)void __attribute__((no_instrument_function))__cyg_profile_func_enter(void *this_func, void *call_site){	DUMP(this_func, call_site);}void __attribute__((no_instrument_function))__cyg_profile_func_exit(void *this_func, void *call_site){	DUMP(this_func, call_site);}int do_multi(int a, int b){	return a * b;}int do_calc(int a, int b){	return do_multi(a, b);}int main(){	int a = 4, b = 5;	printf("result: %d/n", do_calc(a, b));	return 0;}
這段代碼中實現了兩個hook函數,即打印出所在函數的函數地址以及返回地址。
編譯代碼:
[zhenfg@ubuntu]code:$ gcc -finstrument-functions instrfunc.c -o instrfunc[zhenfg@ubuntu]code:$ ./instrfunc __cyg_profile_func_enter: func = 0x8048521, called by = 0xb75554e3__cyg_profile_func_enter: func = 0x80484d8, called by = 0x8048562__cyg_profile_func_enter: func = 0x804849a, called by = 0x8048504__cyg_profile_func_exit: func = 0x804849a, called by = 0x8048504__cyg_profile_func_exit: func = 0x80484d8, called by = 0x8048562result: 20__cyg_profile_func_exit: func = 0x8048521, called by = 0xb75554e3
通過反彙編的代碼(objdump -D instrfunc)可以看到,這些地址和函數的對應關係爲:
__cyg_profile_func_enter: func = 0x8048521(main), called by = 0xb75554e3__cyg_profile_func_enter: func = 0x80484d8(do_calc), called by = 0x8048562(main)__cyg_profile_func_enter: func = 0x804849a(do_multi), called by = 0x8048504(do_calc)__cyg_profile_func_exit: func = 0x804849a(do_multi), called by = 0x8048504(do_calc)__cyg_profile_func_exit: func = 0x80484d8(do_calc), called by = 0x8048562(main)result: 20__cyg_profile_func_exit: func = 0x8048521(main), called by = 0xb75554e3
實際上這就給出了函數的調用關係。

如果不想跟蹤某個函數,可以給該函數指定“no_instrument_function”屬性。需要注意的是,__cyg_profile_func_enter()和__cyg_profile_func_exit()這兩個hook函數是一定要加上“no_instrument_function”屬性的,不然,自己跟蹤自己就會無限循環導致程序崩潰,當然,也不能在這兩個hook函數中調用其他需要被跟蹤的函數。

得到一系列的地址看起來不太直觀,我們更希望看到函數名,幸運的是,addr2line工具爲我們提供了這種可能。我們先看一下addr2line的使用方法:
[zhenfg@ubuntu]code:$ addr2line --helpUsage: addr2line [option(s)] [addr(s)] Convert addresses into line number/file name pairs. If no addresses are specified on the command line, they will be read from stdin The options are:  @<file>                Read options from <file>  -a --addresses         Show addresses  -b --target=<bfdname>  Set the binary file format  -e --exe=<executable>  Set the input file name (default is a.out)  -i --inlines           Unwind inlined functions  -j --section=<name>    Read section-relative offsets instead of addresses  -p --pretty-print      Make the output easier to read for humans  -s --basenames         Strip directory names  -f --functions         Show function names  -C --demangle[=style]  Demangle function names  -h --help              Display this information  -v --version           Display the program's version
首先要注意,使用addr2line工具時,需要用gcc的“-g”選項編譯程序增加調試信息。
同樣是上面的程序,我們加上-g選項再編譯一次:
[zhenfg@ubuntu]code:$ gcc -g -finstrument-functions instrfunc.c -o instrfunc[zhenfg@ubuntu]code:$ ./instrfunc __cyg_profile_func_enter: func = 0x8048521, called by = 0xb757d4e3__cyg_profile_func_enter: func = 0x80484d8, called by = 0x8048562__cyg_profile_func_enter: func = 0x804849a, called by = 0x8048504__cyg_profile_func_exit: func = 0x804849a, called by = 0x8048504__cyg_profile_func_exit: func = 0x80484d8, called by = 0x8048562result: 20__cyg_profile_func_exit: func = 0x8048521, called by = 0xb757d4e3
使用addr2line嘗試查找0x8048504地址所在的函數:
[zhenfg@ubuntu]code:$ addr2line -e instrfunc -a 0x8048504 -fp -s0x08048504: do_calc at instrfunc.c:25
這樣一來,就可以通過gcc的“-finstrument-functions”選項結合addr2line工具,方便的對一個程序中的函數進行跟蹤。並且既然我們可以自己實現hook函數,那不僅僅可以用來跟蹤函數調用關係,你可以在hook函數中添加自己想做的事情,例如添加一些統計信息。
另外,我們知道__builtin_return_address(level)宏可以獲得不同層級的函數返回地址,但是在某些體系架構(如mips)中,__builtin_return_address(level)只能獲得當前函數的直接調用者的地址,即level只能是0,那這時,就可使用上述方法來跟蹤函數調用關係(mips中竟然能用,確實有些小吃驚)。

接下來可以看一下gcc是如何將hook函數嵌入各個函數中的,以反彙編代碼中的do_multi()函數爲例(這是mips的彙編代碼),在mips中,ra寄存器用來存儲返回地址,a0-a3用來做函數參數。
004006c8 <do_multi>:  4006c8:	27bdffd8 	addiu	sp,sp,-40  4006cc:	afbf0024 	sw	ra,36(sp)	;;存儲ra寄存器(返回地址)的值  4006d0:	afbe0020 	sw	s8,32(sp)  4006d4:	afb1001c 	sw	s1,28(sp)  4006d8:	afb00018 	sw	s0,24(sp)  4006dc:	03a0f021 	move	s8,sp  4006e0:	03e08021 	move	s0,ra	;;s0 = ra  4006e4:	afc40028 	sw	a0,40(s8)  4006e8:	afc5002c 	sw	a1,44(s8)  4006ec:	02001021 	move	v0,s0	;;v0 = s0  4006f0:	3c030040 	lui	v1,0x40  4006f4:	246406c8 	addiu	a0,v1,1736	;;將本函數的地址賦值給a0寄存器  4006f8:	00402821 	move	a1,v0		;;將返回地址ra的值賦值給a1寄存器  4006fc:	0c100188 	jal	400620 <__cyg_profile_func_enter> ;;調用hook函數  400700:	00000000 	nop  400704:	8fc30028 	lw	v1,40(s8)  400708:	8fc2002c 	lw	v0,44(s8)  40070c:	00000000 	nop  400710:	00620018 	mult	v1,v0  400714:	00008812 	mflo	s1  400718:	02001021 	move	v0,s0  40071c:	3c030040 	lui	v1,0x40  400720:	246406c8 	addiu	a0,v1,1736	;;將本函數的地址賦值給a0寄存器  400724:	00402821 	move	a1,v0		;;將返回地址ra的值賦值給a1寄存器  400728:	0c10019d 	jal	400674 <__cyg_profile_func_exit> ;;調用hook函數  40072c:	00000000 	nop  400730:	02201021 	move	v0,s1  400734:	03c0e821 	move	sp,s8  400738:	8fbf0024 	lw	ra,36(sp)	;;恢復ra寄存器(返回地址)的值  40073c:	8fbe0020 	lw	s8,32(sp)  400740:	8fb1001c 	lw	s1,28(sp)  400744:	8fb00018 	lw	s0,24(sp)  400748:	27bd0028 	addiu	sp,sp,40  40074c:	03e00008 	jr	ra  400750:	00000000 	nop

上述反彙編的代碼中,使用“-finstrument-functions”選項編譯程序所增加的指令都已註釋出來,實現沒什麼複雜的,在函數中獲得自己的地址和上一級caller的地址並不是什麼難事,然後將這兩個地址傳給__cyg_profile_func_enter和__cyg_profile_func_exit就好了。         

        

發佈了67 篇原創文章 · 獲贊 25 · 訪問量 45萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章