嵌入式linux应用程序调试方法

2019-07-12 18:42发布

嵌入式linux应用程序调试方法 四 内存工具 17 4.1 MEMWATCH 17 4.2 YAMD 22 4.3 Electric Fence 24 五 C/C++代码覆盖、性能profiling工具 24 5.1 用gcov来测试代码覆盖率 25 5.2 使用gprof来优化你的C/C++程序 35 四 内存工具 您肯定不想陷入类似在几千次调用之后发生分配溢出这样的情形。 许多小组花了许许多多时间来跟踪稀奇古怪的内存错误问题。应用程序在有的开发工作站上能运行,但在新的产品工作站上,这个应用程序在调用 malloc() 两百万次之后就不能运行了。真正的问题是在大约一百万次调用之后发生了溢出。新系统之所有存在这个问题,是因为被保留的 malloc() 区域的布局有所不同,从而这些零散内存被放置在了不同的地方,在发生溢出时破坏了一些不同的内容。 我们用多种不同技术来解决这个问题,其中一种是使用调试器,另一种是在源代码中添加跟踪功能。在我职业生涯的大概也是这个时候,我便开始关注内存调试工具,希望能更快更有效地解决这些类型的问题。在开始一个新项目时,我最先做的事情之一就是运行 MEMWATCH 和 YAMD,看看它们是不是会指出内存管理方面的问题。 内存泄漏是应用程序中常见的问题,不过您可以使用本文所讲述的工具来解决这些问题。 内存调试工具 C 语言作为 Linux 系统上标准的编程语言给予了我们对动态内存分配很大的控制权。然而,这种自由可能会导致严重的内存管理问题,而这些问题可能导致程序崩溃或随时间的推移导致性能降级。 内存泄漏(即 malloc() 内存在对应的 free() 调用执行后永不被释放)和缓冲区溢出(例如对以前分配到某数组的内存进行写操作)是一些常见的问题,它们可能很难检测到。这一部分将讨论几个调试工具,它们极大地简化了检测和找出内存问题的过程。 4.1 MEMWATCH MEMWATCH 由 Johan Lindh 编写,是一个开放源代码 C 语言内存错误检测工具,您可以自己下载它(请参阅本文后面部分的参考资料)。只要在代码中添加一个头文件并在 gcc 语句中定义了 MEMWATCH 之后,您就可以跟踪程序中的内存泄漏和错误了。MEMWATCH 支持 ANSI C,它提供结果日志纪录,能检测双重释放(double-free)、错误释放(erroneous free)、没有释放的内存(unfreed memory)、溢出和下溢等等。  快速上手: 在MEMWATCH的源代码包中包含有三个程序文件:memwatch.c, memwatch.h, test.c; 可以利用这三个程序,按照你的编译环境对makefile修改后,就可是简单使用memwatch来检测test.c这个文件中的内存问题; 当然你可以根据自己的需要修改test.c文件,不过需要注意的是: 1)如果你的程序是永远不会主动退出的话,建议深入看看memwatch,因为memwatch默认情况下,在程序退出的时,将有关内存检测的结果写入到某个日志文件中;所以针对你永远不会退出的程序,你需要自己动手调用MEMWATCH的结果函数。 2)如果你要使用memwatch来检测内存问题的话,必须将memwatch.h头文件包含在你需要检测文件中;并且把memwatch.c加入编译。在编译时记得加-DMEMWATCH -DMW_STDIO选项;在主文件中最好包含以下信息, //在linux下需要包含signal.h这个头文件 #ifndef SIGSEGV #error "SIGNAL.H does not define SIGSEGV; running this program WILL cause a core dump/crash!" #endif //防止你在编译时没有加 -DMEMWATCH #ifndef MEMWATCH #error "You really, really don't want to run this without memwatch. Trust me." #endif //防止你在编译时候没有加 -DMEMWATCH_STDIO,在一些指针错误的时候,会弹出 //Abort/Retry/Ignore 让你选择处理方法。 #if !defined(MW_STDIO) && !defined(MEMWATCH_STDIO) #error "Define MW_STDIO and try again, please." #endif //多线程程序需要定义MW_PTHREADS,虽然它并不一定能够保证多线程安全,zpf #if !defined(MW_PTHREADS) && !defined(HAVE_PTHREAD_H) #error "if ur program is multithreads, please define MW_PTHREADS; " #error "otherwise, Comment out the following line." #endif 3)刚开始还是找一个简单的单线程程序来上手吧.  样例讲解 清单 1. 内存样本(test1.c) #include #include #include "memwatch.h" int main(void) { char *ptr1; char *ptr2; ptr1 = malloc(512); ptr2 = malloc(512); ptr2 = ptr1; free(ptr2); free(ptr1); } 清单 1 中的代码将分配两个 512 字节的内存块,然后指向第一个内存块的指针被设定为指向第二个内存块。结果,第二个内存块的地址丢失,从而产生了内存泄漏。 现在我们编译清单 1 的 memwatch.c。下面是一个 makefile 示例: test1 gcc -DMEMWATCH -DMW_STDIO test1.c memwatch c -o test1 当您运行 test1 程序后,它会生成一个关于泄漏的内存的报告。清单 2 展示了示例 memwatch.log 输出文件。 清单 2. test1 memwatch.log 文件 MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh ... double-free: <4> test1.c(15), 0x80517b4 was freed from test1.c(14) ... unfreed: <2> test1.c(11), 512 bytes at 0x80519e4 {FE FE FE FE FE FE FE FE FE FE FE FE ..............} Memory usage statistics (global): N)umber of allocations made: 2 L)argest memory usage : 1024 T)otal of all alloc() calls: 1024 U)nfreed bytes totals : 512 MEMWATCH 为您显示真正导致问题的行。如果您释放一个已经释放过的指针,它会告诉您。对于没有释放的内存也一样。日志结尾部分显示统计信息,包括泄漏了多少内存,使用了多少内存,以及总共分配了多少内存。 建议: 建议在你需要测试的主文件中加入以下函数信息 //在linux下需要包含signal.h这个头文件 #ifndef SIGSEGV #error "SIGNAL.H does not define SIGSEGV; running this program WILL cause a core dump/crash!" #endif //防止你在编译时没有加 -DMEMWATCH #ifndef MEMWATCH #error "You really, really don't want to run this without memwatch. Trust me." #endif //防止你在编译时候没有加 -DMEMWATCH_STDIO,在一些指针错误的时候,会弹出 //Abort/Retry/Ignore 让你选择处理方法。 #if !defined(MW_STDIO) && !defined(MEMWATCH_STDIO) #error "Define MW_STDIO and try again, please." #endif //多线程程序需要定义MW_PTHREADS,虽然它并不一定能够保证多线程安全,zpf #if !defined(MW_PTHREADS) && !defined(HAVE_PTHREAD_H) #error "if ur program is multithreads, please define MW_PTHREADS; " #error "otherwise, Comment out the following line." #endif 另外需要说明的是,在源代码附带的<>文档中,作者说不能保证代码是绝对多线程安全的,同时如果你如果要使用它来检测linux下多线程程序的内存情况时,请定义MW_PTHREADS宏(可以通过在makefile中加 –DMW_PTHREADS),这样memwatch将引入互斥量操作。 MEMWATCH程序使用中通过简单的加入memwatch.h头文件,并且将memwatch.c加入编译,就可以在程序正常结束的时候,输出日志文件,在日志文件中给出系统中的内存问题;但是有一个问题,在我们的终端中应用程序是永远不会退出的,并且可能系统根本就没有充分考虑良好的收尾工作;那么如果使用MEMWATCH来检测我们的程序是否存在内存问题哪?  仔细阅读memwatch.h文件会发现,MEMWATCH提供了两个比较有用的函数: void mwInit( void ); void mwTerm( void ); 关于这两个函数,作者给出的注释为:Normally, it is not nessecary to call any of these. MEMWATCH will automatically initialize itself on the first MEMWATCH function call, and set up a call to mwAbort() using atexit()。所以通常我们不需要显式调用这两个函数。 但是问题是,我们的程序可能不会显式的退出,从而atexit()函数不能正常被调用;在这种情况下,我们可以通过显式的调用来完成一定的内存检测工作; mwInit( void ) 和mwTerm( void )可以多次调用。但是两个和malloc和free一样,必须配对使用。你就可以通过这样方式控制MEMWATCH什么时候开始检测内存,什么时候结束检测内存;  MEMWATCH日志输出 MEMWATCH会输出日志到程序工作目录的memwatch.log文件中,如果这个文件不能创建的话,程序会尝试创建memwatNN.log,NN 从01 到 99。 如果你觉得没有必要输出日志到文件中,那么可以自己定义输出函数,方法为输出函数必须为void func(int c)类型,然后将输出函数地址作为参数显式调用mwSetOutFunc()指定输出;这样程序就会重定向输出到你定义的输出函数中;  野指针 当使用没有初始化的指针,或者指针指向的空间已被移动或者释放时,就会造成野指针问题。 避免这个问题的最好方法是:定义指针时总是初始化为NULL;当释放指针后,显式的将指针设置为NULL; 为了便于追踪这种指针错误,MEMWATCH程序将所有的内存区域赋予特定的数字;例如:刚分配但是未初始化的内存区域全部设置为0xFE;最近释放后没有被重新利用的内存全部设置为0xFD;所以如果你的程序在没有使用MEMWATCH时没有问题,但是加载MEMWATCH后会崩溃的话,可能程序中就存在指针分配后,没有初始化就使用;或者使用了已经释放的内存。  更深入的使用 更深入的使用请参见,<>, <>, <>. 我目前做的实验:在一个进程中(是否多个进程能够同时使用,还由待验证),初始化阶段,显式调用mwInit(), 然后在处理SIGINT,SIGTERM(因为程序运行过程中,我们会利用killall杀死我们不会停止的程序)的函数中显式调用mwTerm()函数; 另外我还修改了static void mwWrite( const char *format, ... )这个函数,在将有关信息输出到日志的过程中,同时利用printf输出到屏幕上(当然你也可以通过调用mwSetOutFunc()指定自己的输出函数)。如果是多线程的话,请定义MW_PTHREADS宏(可以通过在makefile中加 –DMW_PTHREADS)。  单线程: RT-VD4201M和RT-VS4201S的主线程都不存在问题;  多线程: 在RT-VD4201M的多线程程序中没有问题, 可以检测出那些没有被释放的内存; 在RT-VS4201S的主线程单独使用时,没有问题,可以检测出内存泄漏;而在RT-VS4201S多个线程中,常会出现在创建线程时程序崩溃现象,有时候程序可能会运行下去;具体原因不明,可能是作者提到的“并不能完全确保函数多线程安全”,或者“程序存在前面提到的野指针问题”,或者其它问题。 使用MEMWATCH初步对RT-VD4201M和RT-VS4201S主程序进行内存检测;除了一些程序初始化阶段分配的动态内存外(这些内存泄漏是程序已知的,例如RTSP,MP模块初始化分配的内存),近一个小时的运行,并没有发现其他内存泄漏问题。  多进程: 多个进程也可以支持。 4.2 YAMD (说明:资料从网上来,该软件没有试用过) YAMD 软件包由 Nate Eldredge 编写,可以查找 C 和 C++ 中动态的、与内存分配有关的问题。在撰写本文时,YAMD 的最新版本为 0.32。请下载 yamd-0.32.tar.gz(请参阅参考资料)。执行 make 命令来构建程序;然后执行 make install 命令安装程序并设置工具。 一旦您下载了 YAMD 之后,请在 test1.c 上使用它。请删除 #include memwatch.h 并对 makefile 进行如下小小的修改: 使用 YAMD 的 test1 gcc -g test1.c -o test1 清单 3 展示了来自 test1 上的 YAMD 的输出。 清单 3. 使用 YAMD 的 test1 输出 YAMD version 0.32 Executable: /usr/src/test/yamd-0.32/test1 ... INFO: Normal allocation of this block Address 0x40025e00, size 512 ... INFO: Normal allocation of this block Address 0x40028e00, size 512 ... INFO: Normal deallocation of this block Address 0x40025e00, size 512 ... ERROR: Multiple freeing At free of pointer already freed Address 0x40025e00, size 512 ... WARNING: Memory leak Address 0x40028e00, size 512 WARNING: Total memory leaks: 1 unfreed allocations totaling 512 bytes *** Finished at Tue ... 10:07:15 2002 Allocated a grand total of 1024 bytes 2 allocations Average of 512 bytes per allocation Max bytes allocated at one time: 1024 24 K alloced internally / 12 K mapped now / 8 K max Virtual program size is 1416 K End. YAMD 显示我们已经释放了内存,而且存在内存泄漏。让我们在清单 4 中另一个样本程序上试试 YAMD。 清单 4. 内存代码(test2.c) #include #include int main(void) { char *ptr1; char *ptr2; char *chptr; int i = 1; ptr1 = malloc(512); ptr2 = malloc(512); chptr = (char *)malloc(512); for (i; i <= 512; i++) { chptr[i] = 'S'; } ptr2 = ptr1; free(ptr2); free(ptr1); free(chptr); } 您可以使用下面的命令来启动 YAMD: ./run-yamd /usr/src/test/test2/test2 清单 5 显示了在样本程序 test2 上使用 YAMD 得到的输出。YAMD 告诉我们在 for 循环中有“越界(out-of-bounds)”的情况。 清单 5. 使用 YAMD 的 test2 输出 Running /usr/src/test/test2/test2 Temp output to /tmp/yamd-out.1243 ********* ./run-yamd: line 101: 1248 Segmentation fault (core dumped) YAMD version 0.32 Starting run: /usr/src/test/test2/test2 Executable: /usr/src/test/test2/test2 Virtual program size is 1380 K ... INFO: Normal allocation of this block Address 0x40025e00, size 512 ... INFO: Normal allocation of this block Address 0x40028e00, size 512 ... INFO: Normal allocation of this block Address 0x4002be00, size 512 ERROR: Crash ... Tried to write address 0x4002c000 Seems to be part of this block: Address 0x4002be00, size 512 ... Address in question is at offset 512 (out of bounds) Will dump core after checking heap. Done. MEMWATCH 和 YAMD 都是很有用的调试工具,它们的使用方法有所不同。对于 MEMWATCH,您需要添加包含文件 memwatch.h 并打开两个编译时间标记。对于链接(link)语句,YAMD 只需要 -g 选项。 4.3 Electric Fence (说明:资料从网上来,该软件没有试用过) 多数 Linux 分发版包含一个 Electric Fence 包,不过您也可以选择下载它。Electric Fence 是一个由 Bruce Perens 编写的 malloc() 调试库。它就在您分配内存后分配受保护的内存。如果存在 fencepost 错误(超过数组末尾运行),程序就会产生保护错误,并立即结束。通过结合 Electric Fence 和 gdb,您可以精确地跟踪到哪一行试图访问受保护内存。Electric Fence 的另一个功能就是能够检测内存泄漏。 五 C/C++代码覆盖、性能profiling工具 C/C++代码覆盖、性能profiling工具一般基于GNU的gprof和gcov。还有一类基于模拟器的profiling工具,如IBM Purify, Valgrind。KCahcegrind是Callgrind,OProfile等的GUI前端。性能测试工具有ggcof,kprof,lcov等等。lcov是Linux Testing Project工具之一,见http://ltp.sourceforge.net/tooltable.php上的工具列表。这儿还有压力测试、WEB Server测试等许多工具。在http://www.testingfaqs.org分类归纳了多种软件测试工具。 5.1 用gcov来测试代码覆盖率 gcov是gnu/gcc工具库中的一个组件, 用来测试代码的覆盖率;当构建一个程序时,gcov会监视一个程序的执行,并且会标识出执行了哪一行源码,哪一行没有执行。更进一步,gcov可以标识出某一行源执行的次数,这样就可以知道程序在哪里花费了大多数的时间。 为什么要测试代码覆盖率? 我是不喜欢在代码中有跑不到的地方,那只是在白白浪费空间,降低效率 当然了,有些时候,我们可以通过跑代码覆盖率来发现我们有什么异常情况没有进行测试,毕竟单元测试的用例,不可能一下就想的很全面的。 例如,你的程序在某个函数的入口前处检测了指针不为空,你进入调用函数以后又检测了一回这个指针,并且对为NULL的情况进行处理,那么两处之中必有一处是在浪费空间,当然你的硬盘大,放的下,但是代码写的精致一些,不是更好么?  获得gcov gcov是gnu/gcc工具库的组件,所以在建立交叉编译工具的时候需要指定创建这个工具。 在arm-linux-的交叉编译工具中,arm-linux-gcov好像默认是存在的;操作系统组给我的交叉编译环境中是有这一工具的;但是uClinux的交叉编译环境中默认好像是没有这个工具的;具体的搭建从下面gprof的讨论也许能够得到一些信息。因为现在我还没有移植操作系统的经验,所以无法对这个进行证实。 关于这个论坛上面的讨论是: > The gprof program, for historical reasons, is sometimes excluded > from a cross-targeted toolchain. If you have a source tree with > a Cygnus configure script at the top level, or a gcc source tree, > then look for the "native_only" variable from the top-level > configure.in, remove "gprof". Then reconfigure the build tree, > and run "make" in the gprof subdirectory. That did it! Just to be clear, this is what I did: I untar'd the binutils-2.12.1 tar ball and edited binutils-2.12.1/configure.in. There's a line containing "native_only" that several packages (like gprof, sed,...); I removed gprof from that list. I then followed the instructions here for building binutils: http://sources.redhat.com/ecos/tools/linux-arm-elf.html and arm-elf-gprof was created! Score!  使用gcov 使用gcov很简单, 首先在编译的时候加上-fprofile-arcs -ftest-coverage,同时链接的时候也加上这些选项;需要特别说明的时,gcov要求被测试程序在执行的时能够访问到它编译的那个目录,因为要使用到编译过程中生成的一个文件(我测试的时是这样的,执行时提示找不到/home/zpf/gdb_test/bubblesort.gcda这个文件,而我得bubblesort程序是在/home/zpf/gdb_tes/这个目录里编译的);所以如果是嵌入式的话,就需要nfs这样的工具支持。  示例程序源代码 例如我们需要测试以下bubblesort的代码: 1: #include 2: 3: void bubbleSort( int list[], int size ) 4: { 5: int i, j, temp, swap = 1; 6: 7: while (swap) { 8: 9: swap = 0; 10: 11: for ( i = (size-1) ; i >= 0 ; i— ) { 12: 13: for ( j = 1 ; j <= i ; j++ ) { 14: 15: if ( list[j-1] > list[j] ) { 16: 17: temp = list[j-1]; 18: list[j-1] = list[j]; 19: list[j] = temp; 20: swap = 1; 21: 22: } 23: 24: } 25: 26: } 27: 28: } 29: 30: } 31: 32: int main() 33: { 34: int theList[10]={10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; 35: int i; 36: 37: /* Invoke the bubble sort algorithm */ 38: bubbleSort( theList, 10 ); 39: 40: /* Print out the final list */ 41: for (i = 0 ; i < 10 ; i++) { 42: printf("%d ", theList[i]); 43: } 44: 45: }  编译程序 如果要使用gcov进行覆盖测试,在编译程序时,必须加-fprofile-arcs -ftest-coverage编译选项;下面是我们用来演示编译bubbleSort.c的命令: gcc bubblesort.c -o bubblesort -ftest-coverage -fprofile-arcs 当我们执行生成的bubblesort程序时会生成一些包含关于程序的相关数据的文件。gcov程序将会使用这些文件来报告数据并且向开发者提供相应的信息。当指定“-ftest-coverage”(注意这是一个选项而不是两个选项)选项时会为每一个源码生成两个文件,他们以“.bb”与“.bbg”为扩展名,并且用这些文件来重组每一个可执行程序的程序流图。当指定“-fprofile-arcs” (注意这是一个选项而不是两个选项),将会生成一个包含每一个指令分支的执行计数的以“.da”为扩展名的文件。这些文件会在执行以后与源码文件一起使用,来标识源码的执行行为。  运行程序 运行刚才编译生成的bubblesort程序就会生成我们在前面所讨论的那些附带文件。然后我们使用我们希望进行检测的源码运行gcov程序。如下面所示: $ ./bubblesort ... $ gcov bubblesort.c 100.00% of 17 source lines executed in file bubblesort.c Creating bubblesort.c.gcov. 以上信息告诉我们,在这个例子程序中所有的源码行至少都执行了一次。另外还可以通过查看生成的bubblesort.c.gcov文件来了解每一源码行所实际运行的次数。如下面所示: -: 0:Source:bubblesort.c -: 0:Graph:bubblesort.gcno -: 0:Data:bubblesort.gcda -: 0:Runs:1 -: 0:Programs:1 -: 1:#include -: 2:void bubbleSort(int list[],int size) 1: 3:{ 1: 4: int i,j,temp,swap=1; 4: 5: while(swap) -: 6: { 2: 7: swap=0; 22: 8: for(i=(size-1);i>=0;i--) -: 9: { 110: 10: for(j=1;j<=i;j++) -: 11: { 90: 12: if(list[j-1]>list[j]) -: 13: { 45: 14: temp=list[j-1]; 45: 15: list[j-1]=list[j]; 45: 16: list[j]=temp; 45: 17: swap=1; -: 18: } -: 19: } -: 20: } -: 21: } 1: 22:} -: 23:int main() 1: 24:{ 1: 25: int theList[10]={10,9,8,7,6,5,4,3,2,1}; -: 26: int i; -: 27: /*Invoke the buble sort algorithm*/ 1: 28: bubbleSort(theList,10); -: 29: -: 30: /*print out the final list*/ 11: 31: for(i=0;i<10;i++) -: 32: { 10: 33: printf("%d ",theList[i]); -: 34: } 1: 35: return 0; -: 36:} 第一列显示了源码中每一行源码所执行的次数。在一些情况下,执行次数并没有提供,因为他们是并不会影响代码的简单C源码元素。 这些计数可以提供一些关于程序执行的信息。例如,第12行执行了90次,而14-17行的代码只是执行了45次。这告诉我们当这个函数调用了90次,真正成功的仅是45次。换句话说,大部分的测试时间浪费在两个元素的交换上。这是由于测试数据的顺序所造成的。 从这里我们可以看到代码段中最常执行的部分就是排序算法的内循环部分。这是因为由于退出测试第10行要比第12行执行的次数多一些。 遗憾的是:我使用arm交叉编译环境按照上面的步骤编译程序,下载到目标板上运行时程序可以正常执行;但是没有日志文件产生;提示为“profiling:/home/zpf/gdb_test/bubblesort.gcda:Cannot open”;“/home/zpf/gdb_test/”是我在linux服务器上编译bubblesort的目录;从这里看,要想利用gcov进行覆盖测试的话,必须在你编译的那个目录执行,看来nfs是嵌入式调试的根本???  查看分支效率 使用-b选项可以查看程序的分支效率。这个选项会输出程序中每一个分支的频度与相应的摘要。例如,我们使用-b选项来执行gcov命令: $ gcov -b bubblesort.c 100.00% of 17 source lines executed in file bubblesort.c 100.00% of 12 branches executed in file bubblesort.c 100.00% of 12 branches taken at least once in file bubblesort.c 100.00% of 2 calls executed in file bubblesort.c Creating bubblesort.c.gcov. 所生成的bubblesort.c.gcov文件如下所示。 -: 0:Source:bubblesort.c -: 0:Graph:bubblesort.gcno -: 0:Data:bubblesort.gcda -: 0:Runs:1 -: 0:Programs:1 -: 1:#include -: 2:void bubbleSort(int list[],int size) function bubbleSort called 1 returned 100% blocks executed 100% 1: 3:{ 1: 4: int i,j,temp,swap=1; 4: 5: while(swap) branch 0 taken 67% branch 1 taken 33% (fallthrough) -: 6: { 2: 7: swap=0; 22: 8: for(i=(size-1);i>=0;i--) branch 0 taken 91% branch 1 taken 9% (fallthrough) -: 9: { 110: 10: for(j=1;j<=i;j++) branch 0 taken 82% branch 1 taken 18% (fallthrough) -: 11: { 90: 12: if(list[j-1]>list[j]) branch 0 taken 50% (fallthrough) branch 1 taken 50% -: 13: { 45: 14: temp=list[j-1]; 45: 15: list[j-1]=list[j]; 45: 16: list[j]=temp; 45: 17: swap=1; -: 18: } -: 19: } -: 20: } -: 21: } 1: 22:} -: 23:int main() function main called 1 returned 100% blocks executed 100% 1: 24:{ 1: 25: int theList[10]={10,9,8,7,6,5,4,3,2,1}; -: 26: int i; -: 27: /*Invoke the buble sort algorithm*/ 1: 28: bubbleSort(theList,10); call 0 returned 100% -: 29: -: 30: /*print out the final list*/ 11: 31: for(i=0;i<10;i++) branch 0 taken = 91% branch 1 taken = 100% branch 2 taken = 100% -: 32: { 10: 33: printf("%d ",theList[i]); call 0 returned 100% -: 34: } 1: 35: return 0; -: 36:} 与前一个的文件类似,但是这一次每一个分支点都用他们的频度进行了标示。 分支点依赖于目标结构指令集(看来要好好理解分支的含义,还需要对汇编有一定的了解)。第12行是一个简单的if语句,所以有一个分支点。在这里我们可以注意到这是50%,这通过我们前面观察程序的执行次数可以看出。其他的分支点有一些难于分析。例如, 第7行是一个while语句,有两个分支点。在X86汇编中,这一行编译成我们下面所看到的样子: 1: cmpl $0, -20(%ebp) 2: jne .L4 3: jmp .L1 从这里我们可看出,swap变量与0进行比较。如果他不等于0,就会跳转到第2行,.L4。否则要跳转到第3行,.L1。第2行所示的分支概率为67%,这是因为这一行执行3次,但是jne只执行了两次。当第2行的jne并没有执行时,我们直接跳转到第3行。这只执行一次,但是一旦执行,程序就结束了。所以分支1要花费100%的时间。 第33行的printf()调用;其下面有一行call 0 returned 100%; 关于这个在手册上是如此解释的:如果一个函数调用至少被执行一次的话,gcov给出的百分比表示“函数返回次数 除以 函数调用次数”, 通常这个数值是100%,但是对于象“exit”或者“longjmp”这类并不是每次调用都返回的函数来说,这个百分比就小于100%。(For a call, if it was executed at least once, then a percentage indicating the number of times the call returned divided by the number of times the call was executed will be printed. This will usually be 100%, but may be less for functions call "exit" or "longjmp", and thus may not return every time they are called.) 疑惑的地方:这里的分支是如何划分的,一个for怎么有三个分支,并且每个分支都执行到了??是不是“i=0;i<10;i++”认为是3个分支? 关于分支百分比,手册上是这样解释的:如果某个分支从没被执行过,则给出“never executed”;否则gcov给出的百分比说明这个分支“taken的次数”除以“executed的次数”(郁闷这里take如何理解那???)(For a branch, if it was executed at least once, then a percentage indicating the number of times the branch was taken divided by the number of times the branch was executed will be printed. Otherwise, the message ``never executed'' is printed.)。 所以分支概率在理解程序流时是相当有用的,但是要参考汇编代码,需要理解分支点在哪里。 看起来有点晕  不完整程序测试 当gcov计数一个测试并不是100%的程序时,并没有执行的行是标记为####,而不是执行次数。下面显示的是一个由gcov创建的文件来显示少于100%的测试。 1: #include 2: 3: int main() 4: 1 { 5: 1 int a=1, b=2; 6: 7: 1 if (a == 1) { 8: 1 printf("a = 1 "); 9: } else { 10: ###### printf("a != 1 "); 11: } 12: 13: 1 if (b == 1) { 14: ###### printf("b = 1 "); 15: } else { 16: 1 printf("b != 1 "); 17: } 18: 19: 1 return 0; 20: } 当这个程序运行时,gcov也会向标准输出输出相应的信息。他会显示可能执行的源码行的行数以及实际运行的百分比。 $ gcov incomptest.c 77.78% of 9 source lines executed in file incomptest.c Creating incomptest.c.gcov. $ 如果我们的例子程序有多个函数,我们可以通过使用-f选项来查看每一个函数的执行情况。如下面的我们以bubbleSort程序所进行的演示: $ gcov -f bubblesort.c 100.00% of 11 source lines executed in function bubbleSort 100.00% of 6 source lines executed in function main 100.00% of 17 source lines executed in file bubblesort.c Creating bubblesort.c.gcov. $  gcov可用的选项 gcov程序调用的格式为: gcov [options] sourcefile 其可用的选项如下: 选项 目的 -v,-version 打印版本信息 -h,-help 打印帮助信息 -b,-branch-probabilities 向输出文件输出分支频度 -c,-branch-counts 打印分支计数而不是分支频度 -n,-no-output 不创建gcov输出文件 -l,-long-file-names 创建长文件名 -f,-function-summaries 打印每一个函数的概要 -o,-object-directory .bb,.bbg,.da文件存放的目录 从上面这个表中,我们可以看到一个单个字符选项,以及一个长选项。当从命令行中使用gcov命令时短选项是比较有用的,但是当gcov是M