关于追踪qemu 源码函数路径的一个方法
这阵子一直在研究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