以下內容總結自debug hacks一書的高手們的調試技術一章
1.strace的使用技巧
strace name,以這樣的方式運行程序,可以查看到程序運行時的系統調用,僅僅是系統調用。可以看到系統調用失敗時的傳參,或者卡在哪個函數位置等等。
-i選項可以看到每個系統調用的地址,那樣在使用gdb調試時可以加斷點。
-p選項可以attach上已經正在運行的程序
-o可以指定輸出文件
-t和-tt可以指定系統調用時間,分別以秒和毫秒為單位。
2.objdump的使用技巧
objdump反匯編之後的文件往往很難看出來對應於c程序中的哪一行代碼,這個時候可以指定-S和-l選項分別顯示出源文件中的代碼和行號,程序需要包含有調試信息,最好是沒有優化選項的文件,但不一定完全對應,可以作為參考。
3.valgrind的使用技巧
valgrind可以對緩存,堆進行評測,檢測POSIX線程沖突等。
最常用的內存檢測,--tool=memcheck這個工具是valgrind的默認工具,可以不指定。
可以檢測的內容有,內存洩露,非法內存訪問,讀取未初始化區域,訪問已釋放區域,內存雙重釋放,非法棧操作等等。但是valgrind對於棧上的空間檢測不是很好。
4.kprobe的使用
這個屬於內核調試技術,可以在不重新編譯內核的基礎上,在任何一個函數內加打印,或者做其他任何處理,當然需要有內核源碼,做一個合適的操作。
比較好的一點是可以顯示棧跟蹤。這在調試中屬於很好的技術。
5.jprobe的使用
與kprobe相同,可以檢測任何一個內核函數的使用情況,但是jprobe的優點在於偵測函數的參數和被偵測函數的參數一樣,可以很方便的打印出傳參,而不像kprobe需要通過堆棧或者寄存器推理。
其實我覺得以上兩個工具對應於gdb就是斷點。
6.kprobe的強大之處
kproble強大他可以插入內核任意位置,而不像jprobe只能插入在函數的開頭處,包括他還在可以插在某條指令執行後還是某條指令執行前。
7.kprobe替換內核函數
kprobe可以替換內核中的某個函數,這樣就可以在內核不重新編譯的情況下,調試某個函數的情況。
8.KAHO替換應用程序函數
類似於上一個kprobe的功能,這樣可以省的再次編譯大型的應用程序。
9.systemtap的使用
這個工具是利用kprobe實現的一個工具,但是他是類似於腳本語言的方式來使用的,更加方便。功能有,查看堆棧,內部數據,等等。在應用程序的調試中就是gdb工具。
10./proc/meminfo中的寶藏
這個可以用作內存檢測,他與valgrind相比,valgrind必須在程序運行結束時才給出測試結果,但這個可以直接實時看到。
11./proc/<pid>/mem快速讀取進程的內容
和gdb或者ptrace一樣,是查看內存的功能,但是速度上要快。
12.oom killer
當內存不足時,系統會對每個應用進程進行評分,評分最高者被關閉。
13.錯誤注入
一般來講,malloc都會是成功的,但是這樣就很難檢測一些如果分配失敗時導致的錯誤。那麼這個功能就是提高分配失敗的概率,或者說指定分配失敗。
需要連接一個failmalloc的庫。方便測試失敗情況。
這個的使用非常方便,首先到failmalloc的官網下載他的代碼,並編譯和安裝他,
在每次運行時指定env的LD_PRELOAD參數為庫所在目錄及庫名稱,
另外一個這個庫支持指定選項,有四個
FAILMALLOC_PROBABILITY
specifies how often it should fail between 0.0 and 1.0.
這個選項為失敗的概率
FAILMALLOC_INTERVAL
specifies the interval of failures.
這個選項為每幾次malloc出現一次失敗。
FAILMALLOC_TIMES
specifies how many times failures may happen at most.
指定失敗次數的上限
FAILMALLOC_SPACE
specifies the size of free space where memory can be allocated safely in bytes.
指定申請內存失敗的上限,即低於或者等於該值才會申請失敗,超過該值必定成功。
14.oprofile的使用
這個工具可以查看一個程序的性能,比如l2級緩存的命中,各個函數的運行時間等等,並且這個工具可以生成圖表。
最常用的是各個函數的運行時間。
類似的工具還有gprof,但是功能上差很多
另外一個要注意的是,oprofile在虛擬機下不支持按事件計數。比較明顯的是各個函數的運行時間檢測不支持。
下面詳細描述一次oprofile的使用過程:
源碼:
#include <stdio.h>
int fun(int s,int i)
{
printf("s = %d\n , i = %d\n",s,i);
s = s+i;
return s;
}
int main()
{
int i = 0;
int sum = 0;
for(;i<0x10000;i++)
sum = fun(sum,i);
printf("sum = %x\n",sum);
return 0;
}
接著是初始化oprofile
[root@localhost oprofile-1.1.0]# opcontrol --init
指定監聽事件,這裡使用默認事件,在cpu的時鐘下采樣,每10000個時鐘采一次,不記錄內核,只記錄應用程序
[root@localhost oprofile-1.1.0]# opcontrol --event=CPU_CLK_UNHALTED:10000:0:0:1
開始分析
[root@localhost oprofile-1.1.0]# opcontrol --start
Using 2.6+ OProfile kernel interface.
Using log file /var/lib/oprofile/samples/oprofiled.log
Daemon started.
Profiler running.
運行程序,結束之後,停止分析
[root@localhost oprofile-1.1.0]# opcontrol --stop
查看結果
[root@localhost oprofile-1.1.0]# opreport --merge=cpu -d a.out
CPU: Core 2, speed 2666.13 MHz (estimated)
Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (Unhalted core cycles) count 10000
Processes with a thread ID of 32720
Processes with a thread ID of all
vma samples % samples % symbol name
004004c4 226 61.9178 756 59.9524 fun
004004c4 12 5.3097 34 4.4974
004004cc 4 1.7699 25 3.3069
004004da 2 0.8850 17 2.2487
004004e7 4 1.7699 20 2.6455
004004ec 23 10.1770 97 12.8307
004004ef 130 57.5221 378 50.0000
004004f2 32 14.1593 96 12.6984
004004f5 19 8.4071 89 11.7725
004004f7 139 38.0822 505 40.0476 main
0040050f 3 2.1583 18 3.5644
00400519 3 2.1583 23 4.5545
0040051e 7 5.0360 25 4.9505
00400521 92 66.1871 299 59.2079
00400525 9 6.4748 33 6.5347
0040052c 25 17.9856 107 21.1881
這裡的a.out是指定鏡像,只查看該程序的函數。可以看出,main函數和fun函數各占了本次運行的比例。這只是一個簡單的例子,如果對於一個大型的程序,就可以針對這個結果,優化函數,
這裡還可以查看代碼級的分析結果。
[root@localhost oprofile-1.1.0]# opannotate --merge=cpu -s a.out
/*
* Command line: opannotate --merge=cpu -s a.out
*
* Interpretation of command line:
* Output annotated source file with samples
* Output all files
*
* CPU: Core 2, speed 2666.13 MHz (estimated)
* Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (Unhalted core cycles) count 10000
* Processes with a thread ID of 32720
* Processes with a thread ID of all
*/
/*
* Total samples for file : "/root/czh/oprofile-1.1.0/test.c"
*
* 365 100.000 1261 100.000
*/
:#include <stdio.h>
:
:int fun(int s,int i)
16 4.3836 59 4.6788 :{ /* fun total: 226 61.9178 756 59.9524 */
6 1.6438 37 2.9342 : printf("s = %d\n , i = %d\n",s,i);
153 41.9178 475 37.6685 : s = s+i;
32 8.7671 96 7.6130 : return s;
19 5.2055 89 7.0579 :}
:
:
:int main()
:{ /* main total: 139 38.0822 505 40.0476 */
: int i = 0;
: int sum = 0;
126 34.5205 439 34.8136 : for(;i<0x10000;i++)
13 3.5616 66 5.2339 : sum = fun(sum,i);
: printf("sum = %x\n",sum);
: return 0;
:}
可以清楚的看到哪一行的代碼占用的時間最多。
注意,如果在虛擬機下運行,是不支持基於事件采樣的,只能基於時間采用,但是這個采樣率太低,效果很差。
加載模塊前先運行modprobe oprofile timer=1
可以通過dmesg查看是否是以timer運行的
15.vprobe
找不到相關資料
16.查看x86機器是否支持64位
這一點可以通過查看cpu自帶的寄存器內容或者/proc/cpuinfo中的內容