这阵子一直在研究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. xtu summer individual 4 C - Dancing Lessons

    Dancing Lessons Time Limit: 5000ms Memory Limit: 262144KB This problem will be judged on CodeForces. ...

  2. 两个很实用很方便的函数核心及用法{(lower_bound)+(max_element))~~

    (1)            关于 lower_bound(a,a+n,x)-a的用法:                                                求x在数组a中的 ...

  3. 虚拟机(Virtual Machine)和容器(Container)的对比

    目前云计算平台常用的虚拟化技术有虚拟机(Virtual Machine)和容器(Container)两种.虚拟机已经是比较成熟的技术,容器技术作为下一代虚拟化技术,国内的各厂商应用还不广,但似乎其代表 ...

  4. 【组合 数学】codeforces C. Do you want a date?

    codeforces.com/contest/810/problem/C [题意] 给定一个集合A,求 , 输入: [思路] 基数为n的集合有2^n-1个非空子集. 首先n个数要从小到大排序,枚举最后 ...

  5. THUWC2018 暴力+爆炸记

    Day 0 没有Day0. Day 1 签到然后去宿舍,环境还行,比某偏远山区要强多了,不过这热水有点难拿??看RP有遇到煮好水的饮水机就拿,没有就苟矿泉水. 中午,那个餐还是挺好吃的,不过餐费40就 ...

  6. mybatis连接mysql

    配置web.xml 1. <context-param> 参考文章 <context-param>    <param-name>contextConfigLoca ...

  7. 匿名函数--lambda函数

    匿名函数 匿名函数:为了解决一些功能很简单的需求而设计的一句话函数 (python对匿名函数支持有限,只有一些简单的条件下可以用匿名函数) 匿名函数固定格式: func = lambda *args: ...

  8. I NEED A OFFER!---hdu1203(01背包)

     http://acm.hdu.edu.cn/showproblem.php?pid=1203   Problem Description Speakless很早就想出国,现在他已经考完了所有需要的考 ...

  9. 【Perl】perl正则表达式中的元字符、转义字符、量词及匹配方式

    Linux平台上被广泛使用的正则表达式库PCRE - Perl-compatible regular expressions,从其名字即可知道,PCRE提供的是一套与Perl中相兼容的正则表达式. 元 ...

  10. Java的基本运算符

    以下内容引用自http://wiki.jikexueyuan.com/project/java/basic-operators.html: Java针对操控变量提供了一组丰富的运算符.可以将所有的Ja ...