这阵子一直在研究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 函数了:

 #include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
/*----------------DEBUG_FUNCTION--------------*/
void printf_debug(char *Path, char* functionName,
int NeedData[])
{
struct tm* p;
time_t timep;
time(&timep);
p = gmtime(&timep);
char s[] = "";
int fd = open(Path,O_RDWR | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR );
sprintf(s+strlen(s), "%s %d/%d/%d/ %d:%d:%d \n",
  functionName,(+p->tm_year),(+p->tm_mon),p->tm_mday,
p->tm_hour, p->tm_min, p->tm_sec);
sprintf(s+strlen(s), "CONFIG_LINUX_AIO is %d \n use_aio is %d \n",NeedData[],NeedData[]);
write(fd,s,sizeof(s));
close(fd);
}

写到这里我已经迫不及待地将这个函数扔到qemu的源码里面进行调试了,老大一开始叫我追踪好几个函数,我就迫不及待地将这个函数的定义放到了制定的源码文件里面,哗啦啦地将这几个函数放在了追踪函数的前面。。。。。随之而来的是。。。。

  老大时不时叫我关掉这个函数,独立开启另一个个函数,时不时叫我还原源码重新调整。。。。

卧槽卧槽卧槽卧槽!!!!,

  那个源码文件将近3000行的代码,并且每个需要追踪的函数之间的间隔也比较大,每次需要屏蔽或者删除修改的时候都极度蛋疼,为了解决这个比较蛋疼的问题,我便开始重新构思这个debug函数的结构,由于我只能在vim这一类的命令行式的文本编辑器下面进行Coding和Reading,不方便进行可视化的快速复制和黏贴

所以每次需要进行debug函数的大规模的修改和删除的时候,最好能将操作区域集中在一块相对较小的区域里面进行,再次深度构思了一下之后,遂决定使用C语言里面的宏定义来满足我的需求,又胡思乱想地修改了之后,得到了如下的思路:

  在这里我为printf_debug函数引入多了一个DebugAllow参数,如果DebugAllow为0的话就代表这个printf_debug被禁止掉了,

 void printf_debug(char *Path,     char* functionName,
int DebugAllow, int NeedData[])
{
if (DebugAllow == )
return ;
struct tm* p;
time_t timep;
time(&timep);
p = gmtime(&timep);
char s[] = "";
int fd = open(Path,O_RDWR | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR );
sprintf(s+strlen(s), "%s %d/%d/%d/ %d:%d:%d \n",
functionName,(+p->tm_year),(+p->tm_mon),p->tm_mday,
p->tm_hour, p->tm_min, p->tm_sec);
sprintf(s+strlen(s), "CONFIG_LINUX_AIO is %d \n use_aio is %d \n",NeedData[],NeedData[]);
write(fd,s,sizeof(s));
close(fd);
}

然后我再为每个所需要追踪的函数单独定义了一个宏:

  #define  ALLOW_RAW_OPEN                 1
#define ALLOW_RAW_REOPEN_PREPARE 1
#define ALLOW_HANDLE_AIOCB_RW_VECTOR 1
#define ALLOW_HANDLE_AIOCB_RW_LINEAR 1
#define ALLOW_LAIO_SUBMIT 1
#define ALLOW_PAIO_SUBMIT 1

我们只需要将这些宏插入到对应的追踪函数的printf_debug里的DebugAllow即可

比如我们需要追踪如下的函数

 return paio_submit(bs, s->fd, sector_num, qiov, nb_sectors,
cb, opaque, type);

只需在前面添加对应的printf_debug函数:

     printf_debug(PATH_PAIO_SUBMIT ,  "raw-posix.c-raw_aio_submit-paio_submit",
ALLOW_PAIO_SUBMIT, );
return paio_submit(bs, s->fd, sector_num, qiov, nb_sectors,
cb, opaque, type);

当我们需要屏蔽掉paio_submit的printf_debug函数的时候,只要在前面的宏定义里面的 ALLOW_PAIO_SUBMIT设置为0即可。当需要修改多个printf_debug函数的屏蔽与否时,只需要集中在前面所定义的宏的代码块里面操作就可以了。这样就可以将操作范围从3000行缩短到6行了。

当我们需要集中地清楚掉所有的debug函数的时候,我们不妨在定义多一个宏

  #define DEBUG_QEMU_IO_MODE

我们可以利用这个宏来一次性地掌控所有的printf_debug函数的存在,比如

 #ifdef DEBUG_QEMU_IO_MODE
printf_debug(PATH_PAIO_SUBMIT , "raw-posix.c-raw_aio_submit-paio_submit",
ALLOW_PAIO_SUBMIT, needdata);
#endif
return paio_submit(bs, s->fd, sector_num, qiov, nb_sectors,
cb, opaque, type);

 当我们需要清除掉所有的printf_debug函数的时候,只需除掉一开始的对于DEBUG_QEMU_IO_MODE的定义即可。

以上便是今天所用到的所有用来调试追踪qemu磁盘io源码的方案了,下面便是所用的所有源码

 /*---------------------------------*/
/*------DEBUG_QEMU_IO_MODE---------*/ #define DEBUG_QEMU_IO_MODE /*---open or close the debug mode*/ #ifdef DEBUG_QEMU_IO_MODE #define ALLOW_RAW_OPEN 1
#define ALLOW_RAW_REOPEN_PREPARE 1
#define ALLOW_HANDLE_AIOCB_RW_VECTOR 1
#define ALLOW_HANDLE_AIOCB_RW_LINEAR 1
#define ALLOW_LAIO_SUBMIT 1
#define ALLOW_PAIO_SUBMIT 1 char *PATH_RAW_REOPEN_PREPARE ="/tmp/raw_reopen_prepare.log";
#define PATH_HANDLE_AIOCB_RW_VECTOR
#define PATH_RAW_OPEN
#define PATH_HANDLE_ATIOCB_RW_RW_LINEAR
char *PATH_LAIO_SUBMIT = "/tmp/laio-submit.log";
char *PATH_PAIO_SUBMIT = "/tmp/paio-submit.log";
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
/*----------------DEBUG_FUNCTION--------------*/
void printf_debug(char *Path, char* functionName,
int DebugAllow, int NeedData[])
{
if (DebugAllow == )
return ;
struct tm* p;
time_t timep;
time(&timep);
p = gmtime(&timep);
char s[] = "";
int fd = open(Path,O_RDWR | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR );
sprintf(s+strlen(s), "%s %d/%d/%d/ %d:%d:%d \n",
functionName,(+p->tm_year),(+p->tm_mon),p->tm_mday,
p->tm_hour, p->tm_min, p->tm_sec);
sprintf(s+strlen(s), "CONFIG_LINUX_AIO is %d \n use_aio is %d \n",NeedData[],NeedData[]);
write(fd,s,sizeof(s));
close(fd);
}
/*----------------------------------------*/
/*----------------------------------------*/
  #endif

关于追踪qemu 源码函数路径的一个方法的更多相关文章

  1. GDB调试qemu源码纪录

    今天介绍下如何利用gdb调试qemu 1.首先获取qemu源码 获取地址:https://www.qemu.org/ 2.编译安装qemu 进入qemu目录 ./configure --enable- ...

  2. qemu 源码调试

    1:下载最新的QEMU源码 git clone https://github.com/qemu/qemu.git 2:对qemu进行编译 ./configure --prefix=/usr --lib ...

  3. 源码编译路径错误导致的Apache 无法重启问题解决方法

    问题现象: 第一次源码编译安装Apache设置路径错误,安装到/usr/local/src/ 目录下了. 删掉该目录下的安装文件,重新编译安装到/usr/local/目录下 重启apache服务时报这 ...

  4. ubuntu下linux内核源码阅读工具和调试方法总结

    http://blog.chinaunix.net/uid-20940095-id-66148.html 一 linux内核源码阅读工具 windows下当然首选source insight, 但是l ...

  5. 痞子衡嵌入式:MCUXpresso IDE下将源码制作成Lib库方法及其与IAR,MDK差异

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是MCUXpresso IDE下将源码制作成Lib库方法及其与IAR,MDK差异. 程序函数库是一个包含已经编译好代码和数据的函数集合,这 ...

  6. 框架源码系列五:学习源码的方法(学习源码的目的、 学习源码的方法、Eclipse里面查看源码的常用快捷键和方法)

    一. 学习源码的目的 1. 为了扩展和调优:掌握框架的工作流程和原理 2. 为了提升自己的编程技能:学习他人的设计思想.编程技巧 二. 学习源码的方法 方法一: 1)掌握研究的对象和研究对象的核心概念 ...

  7. 你与优秀源码之间只差一个 Star

    fir.im Weekly - 你与优秀源码之间只差一个 Star   说起开源社区,Github 是一个不可缺少的存在.作为全球最大的同性交友网站,上面有太多优秀的开源代码库和编程大神,让无数开发者 ...

  8. Netty 源码剖析之 unSafe.write 方法

    前言 在 Netty 源码剖析之 unSafe.read 方法 一文中,我们研究了 read 方法的实现,这是读取内容到容器,再看看 Netty 是如何将内容从容器输出 Channel 的吧. 1. ...

  9. 从源码看commit和commitAllowingStateLoss方法区别

    Fragment介绍 在很久以前,也就是我刚开始写Android时(大约在2012年的冬天--),那时候如果要实现像下面微信一样的Tab切换页面,需要继承TabActivity,然后使用TabHost ...

随机推荐

  1. java常见问题集锦

    Eclipse 编译错误 Access restriction:The type *** is not accessible due to restriction on... 解决方案 Eclipse ...

  2. 腾讯云CVM使用记录--使用root权限

    1.su root 指令 ,执行下列命令获取root权限: sudo /bin/su - root 注意:严禁执行password命令,root密码默认不能被修改.

  3. hihoCoder #1032 : 最长回文子串 [ Manacher算法--O(n)回文子串算法 ]

    传送门 #1032 : 最长回文子串 时间限制:1000ms 单点时限:1000ms 内存限制:64MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相 ...

  4. django学习之- Form

    参考:http://www.cnblogs.com/wupeiqi/articles/6144178.htmlFORM中的字段只对post上来的数据进行form验证,主要涉及:字段 和 插件字段:对用 ...

  5. csu - 1659 Graph Center(最短路)

    http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1659 题意是找一个图的中心,图的中心定义是某一个点到其他点的最大距离最小,如果有多个排序输出. 注 ...

  6. RabbitMQ最佳实践

    在使用消息机制时,我们通常需要考虑以下几个问题: 消息不能丢失 保证消息一定能投递到目的地 保证业务处理和消息发送/消费的一致性 本文以RabbitMQ为例,讨论如何解决以上问题. 消息持久化 如果希 ...

  7. 通过简单的两数相加体会hashmap的好处

    目录 引入题目:两数相加 HashMap相关知识: Map集合 Map集合的特点 Map常用子类 HashMap集合 LinkedHashMap集合 Map集合的常用方法 Map集合的第一种遍历方式: ...

  8. Could not find leader nimbus

    运行storm ui, 然后访问storm ui 的网页的时候,死活跑不起来.后面,根据下面这篇文章的说法, 停止zookeeper 之后,删掉zookeeper 上面的storm 节点, 然后再重启 ...

  9. 用"再生龙"Clonezilla 来克隆Linux系统

      上周公司买了5套高配置PC机来做测试用.上面要装好CentOS 加上一堆工具,有web的,数据库的,还有一些自己开发的工具.有些朋友肯定想,直接用kickstart不就行了,确实.kickstar ...

  10. hdu 4950 Monster(数学题,多校8)

    题目链接:pid=4950http://acm.hdu.edu.cn/showproblem.php?pid=4950">http://acm.hdu.edu.cn/showprobl ...