這陣子一直在研究qemu 磁盤io路徑的源碼,發現直接看代碼是意見非常低效率的事情,qemu是一個比較龐大的家伙(源碼部分大概154MB,完全由C語言來完成),整個結構也都非常地復雜,所以從代碼上研究qemu最好的辦法只有debug之。不斷地收集更多的debug信息去獲取源碼所蘊含的道理。
很多人第一反應可能就是使用一些類似與Eclipse, gdb 這一類強大的debugger,我當然也不例外,在經過一個上午究竟該使用Eclipse還是gdb的思想斗爭的私人情緒之后,我才恍然明白,原來我兩個工具都不會用啊!! (大霧大霧
經過老大的前車之鑒的提醒之后,他說他以前弄Xen的時候使用gdb調試Xen的效果也是不太理想,並且由於我們使用的實驗環境一直都是沒有Xwindows的Centos7-miminal,所以使用Eclipse更是一種煎熬,他們以前是使用輸出調試信息產生函數調用的日志來進行函數追蹤和debug的,這或許真的是一種非常原生態,思路很簡潔的方法,有時候最有效的或許就是最簡單粗暴的方法了吧?
具體的調試方法我沒有再多過問了,我想自己去嘗試一下,於是便開始了自己的胡思亂想的debug方法構思。
一開始,我很理所當然地想到了 printf 函數,可是這個函數在單個源碼文件的程序里面是完全可行簡單的,一運行程序,便能夠在你所允許的shell里面打印出調試信息出來。然后才發現,使用prtintf真是naive 啊naive!!當我建這個思路用在一個需要讀取文件的多源碼文件里面的時候發現就不行了,C語言強大的地方便在與世界上最復雜的軟件系統幾乎只能用C語言來完成,可是當我們需要滿足日常使用的時候使用C語言便覺得有點殺雞用牛刀了,遠遠比不上shell script 以及Python一類的腳本語言了。
好吧,之前老大提到了說做一個類似與輸出函數調用日志文件的東西,既然如此,為了方便我們觀察輸出的日志消息,我們需要將函數調用的日志消息輸出到一個文件里面,ok,我們現在來分析一下這個日志文件究竟需要具備什么樣子的功能?借鑒了MySQL的輸出日志文件的特點,總結出了以下幾條
1.每一條的日志輸出都需要帶有時間戳的信息,包含年份,日,月, 時,分,秒。
2.每一條的日志輸出都一定要帶有所嗲用函數的精良精確的信息 file_name-fun1-fun2-fun3,其信息代表了 fun3被fun2調用 fun3 被fun1調用 fun1包含在file_name文件 里面。
3.每一條的日志輸出具備所追蹤的函數所攜帶的數據信息,如 數據量,數據值等等..
這樣子經過好幾次的修改和調試之后我就寫出了以下的printf_debug 函數了:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <fcntl.h> 4 #include <time.h> 5 #include <string.h> 6 /*----------------DEBUG_FUNCTION--------------*/ 7 void printf_debug(char *Path, char* functionName, 8 int NeedData[]) 9 { 10 struct tm* p; 11 time_t timep; 12 time(&timep); 13 p = gmtime(&timep); 14 char s[500] = ""; 15 int fd = open(Path,O_RDWR | O_CREAT | O_APPEND, 16 S_IRUSR | S_IWUSR ); 17 sprintf(s+strlen(s), "%s %d/%d/%d/ %d:%d:%d \n", 18 functionName,(1900+p->tm_year),(1+p->tm_mon),p->tm_mday, 19 p->tm_hour, p->tm_min, p->tm_sec); 20 sprintf(s+strlen(s), "CONFIG_LINUX_AIO is %d \n use_aio is %d \n",NeedData[0],NeedData[1]); 21 write(fd,s,sizeof(s)); 22 close(fd); 23 }
寫到這里我已經迫不及待地將這個函數扔到qemu的源碼里面進行調試了,老大一開始叫我追蹤好幾個函數,我就迫不及待地將這個函數的定義放到了制定的源碼文件里面,嘩啦啦地將這幾個函數放在了追蹤函數的前面。。。。。隨之而來的是。。。。
老大時不時叫我關掉這個函數,獨立開啟另一個個函數,時不時叫我還原源碼重新調整。。。。
卧槽卧槽卧槽卧槽!!!!,
那個源碼文件將近3000行的代碼,並且每個需要追蹤的函數之間的間隔也比較大,每次需要屏蔽或者刪除修改的時候都極度蛋疼,為了解決這個比較蛋疼的問題,我便開始重新構思這個debug函數的結構,由於我只能在vim這一類的命令行式的文本編輯器下面進行Coding和Reading,不方便進行可視化的快速復制和黏貼
所以每次需要進行debug函數的大規模的修改和刪除的時候,最好能將操作區域集中在一塊相對較小的區域里面進行,再次深度構思了一下之后,遂決定使用C語言里面的宏定義來滿足我的需求,又胡思亂想地修改了之后,得到了如下的思路:
在這里我為printf_debug函數引入多了一個DebugAllow參數,如果DebugAllow為0的話就代表這個printf_debug被禁止掉了,
1 void printf_debug(char *Path, char* functionName, 2 int DebugAllow, int NeedData[]) 3 { 4 if (DebugAllow == 0) 5 return ; 6 struct tm* p; 7 time_t timep; 8 time(&timep); 9 p = gmtime(&timep); 10 char s[500] = ""; 11 int fd = open(Path,O_RDWR | O_CREAT | O_APPEND, 12 S_IRUSR | S_IWUSR ); 13 sprintf(s+strlen(s), "%s %d/%d/%d/ %d:%d:%d \n", 14 functionName,(1900+p->tm_year),(1+p->tm_mon),p->tm_mday, 15 p->tm_hour, p->tm_min, p->tm_sec); 16 sprintf(s+strlen(s), "CONFIG_LINUX_AIO is %d \n use_aio is %d \n",NeedData[0],NeedData[1]); 17 write(fd,s,sizeof(s)); 18 close(fd); 19 }
然后我再為每個所需要追蹤的函數單獨定義了一個宏:
1 #define ALLOW_RAW_OPEN 1 2 #define ALLOW_RAW_REOPEN_PREPARE 1 3 #define ALLOW_HANDLE_AIOCB_RW_VECTOR 1 4 #define ALLOW_HANDLE_AIOCB_RW_LINEAR 1 5 #define ALLOW_LAIO_SUBMIT 1 6 #define ALLOW_PAIO_SUBMIT 1
我們只需要將這些宏插入到對應的追蹤函數的printf_debug里的DebugAllow即可
比如我們需要追蹤如下的函數
1 return paio_submit(bs, s->fd, sector_num, qiov, nb_sectors, 2 cb, opaque, type);
只需在前面添加對應的printf_debug函數:
1 printf_debug(PATH_PAIO_SUBMIT , "raw-posix.c-raw_aio_submit-paio_submit", 2 ALLOW_PAIO_SUBMIT, 0); 3 return paio_submit(bs, s->fd, sector_num, qiov, nb_sectors, 4 cb, opaque, type);
當我們需要屏蔽掉paio_submit的printf_debug函數的時候,只要在前面的宏定義里面的 ALLOW_PAIO_SUBMIT設置為0即可。當需要修改多個printf_debug函數的屏蔽與否時,只需要集中在前面所定義的宏的代碼塊里面操作就可以了。這樣就可以將操作范圍從3000行縮短到6行了。
當我們需要集中地清楚掉所有的debug函數的時候,我們不妨在定義多一個宏
1 #define DEBUG_QEMU_IO_MODE
我們可以利用這個宏來一次性地掌控所有的printf_debug函數的存在,比如
1 #ifdef DEBUG_QEMU_IO_MODE 2 printf_debug(PATH_PAIO_SUBMIT , "raw-posix.c-raw_aio_submit-paio_submit", 3 ALLOW_PAIO_SUBMIT, needdata); 4 #endif 5 return paio_submit(bs, s->fd, sector_num, qiov, nb_sectors, 6 cb, opaque, type);
當我們需要清除掉所有的printf_debug函數的時候,只需除掉一開始的對於DEBUG_QEMU_IO_MODE的定義即可。
以上便是今天所用到的所有用來調試追蹤qemu磁盤io源碼的方案了,下面便是所用的所有源碼
1 /*---------------------------------*/ 2 /*------DEBUG_QEMU_IO_MODE---------*/ 3 4 5 6 #define DEBUG_QEMU_IO_MODE /*---open or close the debug mode*/ 7 8 #ifdef DEBUG_QEMU_IO_MODE 9 10 #define ALLOW_RAW_OPEN 1 11 #define ALLOW_RAW_REOPEN_PREPARE 1 12 #define ALLOW_HANDLE_AIOCB_RW_VECTOR 1 13 #define ALLOW_HANDLE_AIOCB_RW_LINEAR 1 14 #define ALLOW_LAIO_SUBMIT 1 15 #define ALLOW_PAIO_SUBMIT 1 16 17 char *PATH_RAW_REOPEN_PREPARE ="/tmp/raw_reopen_prepare.log"; 18 #define PATH_HANDLE_AIOCB_RW_VECTOR 19 #define PATH_RAW_OPEN 20 #define PATH_HANDLE_ATIOCB_RW_RW_LINEAR 21 char *PATH_LAIO_SUBMIT = "/tmp/laio-submit.log"; 22 char *PATH_PAIO_SUBMIT = "/tmp/paio-submit.log"; 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <fcntl.h> 26 #include <time.h> 27 #include <string.h> 28 /*----------------DEBUG_FUNCTION--------------*/ 29 void printf_debug(char *Path, char* functionName, 30 int DebugAllow, int NeedData[]) 31 { 32 if (DebugAllow == 0) 33 return ; 34 struct tm* p; 35 time_t timep; 36 time(&timep); 37 p = gmtime(&timep); 38 char s[500] = ""; 39 int fd = open(Path,O_RDWR | O_CREAT | O_APPEND, 40 S_IRUSR | S_IWUSR ); 41 sprintf(s+strlen(s), "%s %d/%d/%d/ %d:%d:%d \n", 42 functionName,(1900+p->tm_year),(1+p->tm_mon),p->tm_mday, 43 p->tm_hour, p->tm_min, p->tm_sec); 44 sprintf(s+strlen(s), "CONFIG_LINUX_AIO is %d \n use_aio is %d \n",NeedData[0],NeedData[1]); 45 write(fd,s,sizeof(s)); 46 close(fd); 47 } 48 /*----------------------------------------*/ 49 /*----------------------------------------*/
#endif