Chinaunix首页 | 论坛 | 博客
  • 博客访问: 131177
  • 博文数量: 40
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 236
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-04 09:56
个人简介

小小博客,不足为外人道

文章分类

全部博文(40)

分类: C/C++

2017-03-03 17:01:40

原文链接 http://www.cnblogs.com/turtle-fly/archive/2013/01/09/2851474.html


写在前面

这个过程几乎从0开始,在此之前,我几乎没有在 linux 下编译链接过项目、没有接触过 makefile、没有读过 man-db、只 gcov 过一个仅有几个C文件的项目

现在,我用 gcov 完成了对 VIM 源码的覆盖,并通过 lcov 生成了非常易读的覆盖率报告





中间碰到了许多疑难杂症,但是更多的是若干教程中叮嘱的“不要放弃”,所以我大概按照下面的节点完成了这个工具的入门:

虚拟机安装Ubuntu,配置gcov和lcov环境

  --> 编译链接单个C文件

    --> 写一个多个C文件的项目,用 makefile 进行编译连接,完成覆盖

      --> 覆盖优秀的开源软件,例如 VIM

这里会按照上述节点逐渐展开,希望帮助和我一样从0开始的朋友们更容易的完成这个过程

如果您对一些问题已经有了研究,那么这里的内容可能太过浅显,敬请继续往下翻阅

如果您没有碰到这些问题,那么恭喜一切都很顺利

如果您要深究一些技术的原理,那么这里可能无法提供您所需要的信息:一是我期望在这里精炼出成功配置环境的方法,更倾向于去解决问题而非深入研究;二是掌握一项技术归根结底还要自己一步一步走下去,一点一点踏实学,绝不是一篇博文就能简单解决的

好了,下面开始 :-)

虚拟机配置安装 Ubuntu



--------此部分省略,如有需要请看原文---------


配置 Ubuntu,安装 gcov & lcov

ubuntu下:

  1. $ sudo apt-getinstall gcov
  2. $ sudo apt-getinstall lcov
--------部分省略,如有需要请看原文---------



GCOV 用于简单项目的覆盖

gcov 适用的场合:GNU C/C++,因此适用的编译器:cc, gcc, g++

这里举斐波那契数列的一个程序为例


  1. #include <stdio.h>

  2. int fibonacci(int n);

  3. int main ()
  4. {
  5.    int fib;
  6.    int n;

  7.    for (n = 0; n <= 41; n++) {
  8.       fib = fibonacci(n);
  9.       printf("fibonnaci(%d) = %d\n", n, fib);
  10.    }

  11.    return 0;
  12. }

  13. int fibonacci(int n)
  14. {
  15.    int fib;
  16.    if (n <= 0) {
  17.       fib = 0;
  18.    }
  19.    else if (n == 1) {
  20.       fib = 1;
  21.    }
  22.    else {
  23.       fib = fibonacci(n -1) + fibonacci(n - 2);
  24.    }

  25.    return fib;
  26. }



1. 编译


  1. $ gcc fib.o-o fib


除了 fib.o 之外,还生成了 fib.gcno 的话,成功了

.gcno是由-ftest-coverage产生的,它包含了重建基本块图和相应的块的源码的行号的信息。

2. 链接

  1. $ gcc fib.o-o fib


诶……怎么回事?


在看到了一封乘坐了时光机的来自2003年的之后,查阅了一下 gcc 的 man-db



我当时邮件给时光机的两个主角问了一下 gcov 的近况,并没有期望得到回复

但是就在昨天 Nathan 他老人家竟然回邮件了!带上以上所有已经提供的信息,他还感慨了一下 gcov has changed a lot since then...

回归正题,链接的时候下面三条任选一个执行即可


  1. $ gcc fib.o-o fib--coverage
  2. $ gcc fib.o-o fib-lgcov
  3. $ gcc fib.o-o fib-fprofile-arcs


应该会正常生成 fib

3. 运行程序 fib


  1. $./fib

会生成 .gcda 文件,.gcda是由加了-fprofile-arcs编译参数的编译后的文件运行所产生的,它包含了弧跳变的次数和其他的概要信息。

4. 生成 gcov 报告


  1. $ gcov fib.c

生成的 fib.c.gcov 文件中就包含了代码覆盖的统计数据,数字代表了每行代码被执行的次数及行号,相信这个不难分析


  1. -: 0:Source:fib.c
  2.          -: 0:Graph:fib.gcno
  3.          -: 0:Data:fib.gcda
  4.          -: 0:Runs:1
  5.          -: 0:Programs:1
  6.          -: 1:#include <stdio.h>
  7.          -: 2:
  8.          -: 3:int fibonacci(int n);
  9.          -: 4:
  10.          1: 5:int main ()
  11.          -: 6:{
  12.          -: 7: int fib;
  13.          -: 8: int n;
  14.          -: 9:
  15.         43: 10: for (n = 0; n <= 41; n++) {
  16.         42: 11: fib = fibonacci(n);
  17.         42: 12: printf("fibonnaci(%d) = %d\n", n, fib);
  18.          -: 13: }
  19.          -: 14:
  20.          1: 15: return 0;
  21.          -: 16:}
  22.          -: 17:
  23. 1402817422: 18:int fibonacci(int n)
  24.          -: 19:{
  25.          -: 20: int fib;
  26. 1402817422: 21: if (n <= 0) {
  27.  267914296: 22: fib = 0;
  28.          -: 23: }
  29. 1134903126: 24: else if (n == 1) {
  30.  433494436: 25: fib = 1;
  31.          -: 26: }
  32.          -: 27: else {
  33.  701408690: 28: fib = fibonacci(n -1) + fibonacci(n - 2);
  34.          -: 29: }
  35.          -: 30:
  36. 1402817422: 31: return fib;
  37.          -: 32:}
  38.          -: 33:



至于 gcov 的更多选项,例如 -b 分支覆盖 -f 函数覆盖, 就 man 吧。

5. 存在的问题

gcov 对每个源码的分析分散在对应的 .cov 文件中,不容易整理分析;文本,无图表……

这就是要使用 lcov 的原因

另外,如果您对gcc也十分不熟悉,正在寻求入门的话,可以参考这里:


LCOV 整理覆盖率数据

1. 汇总覆盖率数据,使用已经生成的 .gcno .gcda 文件生成覆盖率数据

  1. $ lcov-c-o fib.info-d.

简单解释一下三个选项

-c: lcov 的一个操作,表示要去捕获覆盖率数据

-o: 输出文件

-d: .gcno .gcda 所在的文件夹,注意这里有个“.”,是从当前文件夹中获取数据的

问题又来了,开始在 lcov 的过程中,碰到 Negative length 的问题,顺着提示找到 lcov 源码中的一处 $(length) ,之后并没有头绪为什么会是负值传入的,于是根据 sourceforge 上面的地址,发了一封邮件询问了一下,回信意思是我使用的 gcc 版本为 4.7.2,需要 lcov 1.10+ 版本支持,使用 1.09 或更低版本的 lcov 会出现这样的问题。于是到以下地址去下载了最新的 lcov

在 lcov 1.10 的 release notes 中写明了对 gcc 4.7+ 提供了支持。

2. 生成 html 格式的报告


  1. $ genhtml fib.info-o fib_result

genhtml 是安装 lcov 时附带的,使用上面产生的 .info 文件生成报告,存放于 fib_result 文件夹中

没错,这里的报告并不只是一个文件,有好多存放在你 -o 指定的目录下,生成之后进入 fib_result 就可以看见念想很久的 index.html 了

这里再分享一下怎么从 terminal 用浏览器打开网页:

  1. $ firefox index.html


3. gcov lcov 资料汇总

在学习过程中检索到的一些文章有对这两个工具的解读,我将有所收获、编排整齐的几篇列举如下,由浅入深,您也可以直接参考他们的文章:

i) gcov 和 lcov 的简明使用教程:http://magustest.com/blog/whiteboxtesting/using-gcov-lcov/

ii) gcov 和 lcov 的简单介绍,包括一些选项的含义,

  gcov: http://blog.csdn.net/livelylittlefish/article/details/6321861

  lcov: http://blog.csdn.net/livelylittlefish/article/details/6321887

iii) gcov 产生的覆盖率结果会存放在 .cov 文件中,这里有对 .cov 文件的解读:http://blog.csdn.net/ashhyc/article/details/1558598

iv) lcov 中间产物 .info 文件的解读:http://blog.csdn.net/vivasoft/article/details/8330186

v) gcov lcov 产生各类文件的简介:http://wx782870649.blog.163.com/blog/static/12989164120127224317532/




vi) gcov official online manual: 

vii) gcov FAQ: 

viii) 这里提到了怎么用 gcov 对 linux kernel 进行覆盖:http://blog.csdn.net/yukin_xue/article/details/7653482

ix) 这里分析了 gcov 的工作原理,并直接操纵其获取数据的出入口,实现了对后台进程的覆盖统计:

http://blog.linezing.com/2011/03/使用gcov完成代码覆盖率的测试


覆盖大项目-学习Makefile

为什么要有 makefile ?

  因为编译、链接项目如果需要一条一条手动敲命令的话,那对那种动辄几十几百个文件的项目实在太恐怖了,需要这样一个建设性的懒惰,于是有了 makefile 

makefile 是什么?

  原本归根结底,makefile 是原来的编译、链接命令的集合,把源文件逐个编译、最后链接,产生可执行文件

  至于为了灵活性而衍生出来的各类语法、变量、函数、隐晦规则……刚入门时可以先不必纠结

makefile 我还总结不出什么心得,这一阵儿学习是参考的陈皓老师的博客:http://blog.csdn.net/haoel/article/details/2886

或者这里有 pdf 文档,

我觉得,为了后面的工作,至少读通这份 pdf 的前8页,知道 makefile 怎么使用变量

1. 环境变量

相信您或多或少都听说过环境变量这个词,也知道他大概是什么意思,很多我们看不到的系统调用会用到这些变量,举个栗子:

打出命令 gcc 干嘛干嘛的时候,系统怎么执行你这个命令?系统不会听人说话,其实您已经调用了一个可执行文件 gcc

那这个 gcc 又是从哪调用的?其实系统会从一些目录下去找这个执行文件 gcc ,而这些目录就写在环境变量 $(PATH) 中,可以打印这个变量出来看看


  1. $ echo $(PATH)

而 gcc 可执行程序在 /usr/bin 这个文件夹中,他的路径已经写在 $(PATH) 里了,应该可以看到

Ubuntu 系统的环境变量存储在以下5个配置文件中:

/etc/environment

  系统登录时读取的第一个文件,用于为所有进程设置环境变量

/etc/profile

  系统登录时读取的第二个文件,会设定所有用户的环境变量

~/.profile

  对应当前登录用户的 profile 文件,用于定制当前用户的个人工作环境

/etc/bash.bashrc

  对应所有用户的 bash 初始化文件,这里设定的环境变量将应用于所有用户的 shell 中,此文件会在用户每次打开 shell 时执行一次

~/.bashrc

  对应当前登录用户 bash 的初始化文件,当用户每次打开shell时,系统都会执行此文件一次

这几个文件的读取书序依此是:

/etc/environment -> /etc/profile -> ~/.profile -> /etc/bash.bashrc -> ~/.bashrc

还可以进行一些实验验证,请参考:http://blog.sina.com.cn/s/blog_6405313801012pxw.html

2. makefile 中的变量

为什么要用变量?

  再举个栗子,gcc 有选项 -O0 -O2,前者表示编译时不优化,后者表示最大程度优化,现在有个 makefile 如下:


  1. executable: main.o kbd.o command.o display.o \
  2.         insert.o search.o files.o utils.o
  3.     gcc -o executable \
  4.         main.o kbd.o command.o display.o \
  5.                 insert.o search.o files.o utils.o

  6. main.o: main.c defs.h
  7.     gcc -O2 -c main.c
  8. kbd.o: kbd.c defs.h command.h
  9.     gcc -O2 -c kbd.c
  10. command.o: command.c defs.h command.h
  11.     gcc -O2 -c command.c
  12. display.o: display.c defs.h buffer.h
  13.     gcc -O2 -c display.c
  14. insert.o: insert.c defs.h buffer.h
  15.     gcc -O2 -c insert.c
  16. search.o: search.c defs.h buffer.h
  17.     gcc -O2 -c search.c
  18. files.o: files.c defs.h buffer.h command.h
  19.     gcc -O2 -c files.c
  20. utils.o: utils.c defs.h
  21.     gcc -O2 -c utils.c

当你要做覆盖率分析的时候,你期望编译过程不要优化,于是又要把所有的 -O2 改为 -O0 ……

当未来有一个比 gcc 更好的编译器 xcc ,又要把所有的 gcc 改为 xcc ......

当然,现在可以用 replace ,但是不管是期望更灵活的在以后来修改,还是强迫症……不如这样改写上述 makefile :


  1. CC="gcc"
  2. CFLAGS="-O2 -c"
  3. object=main.o kbd.o command.o display.o \
  4. insert.o search.o files.o utils.o

  5. executable: $(object)
  6. $(CC) -o executable $(object)

  7. main.o: main.c defs.h
  8. $(CC) $(CFLAGS) main.c
  9. kbd.o: kbd.c defs.h command.h
  10. $(CC) $(CFLAGS) kbd.c
  11. command.o: command.c defs.h command.h
  12. $(CC) $(CFLAGS) command.c
  13. display.o: display.c defs.h buffer.h
  14. $(CC) $(CFLAGS) display.c
  15. insert.o: insert.c defs.h buffer.h
  16. $(CC) $(CFLAGS) insert.c
  17. search.o: search.c defs.h buffer.h
  18. $(CC) $(CFLAGS) search.c
  19. files.o: files.c defs.h buffer.h command.h
  20. $(CC) $(CFLAGS) files.c
  21. utils.o: utils.c defs.h
  22. $(CC) $(CFLAGS) utils.c

在 VIM src 的 INSTALL 文档中有这么几行

至于 CFLAGS, CXXFLAGS, LIBS 这些变量的含义,请参考:http://www.cnblogs.com/taskiller/archive/2012/12/14/2817650.html

这里我用了另一种方法来确定我需要关注那些变量,在项目路径下,执行:


  1. $ ./configure -h

会显示 configure 的帮助文档,其中有这么几行:

把 gcvo lcov 中提到的知识应用到这儿,我们只需要设定好编译和链接相关的两个环境变量 CFLAGS 和 LIBS

如下设定:


  1. $ export CFLAGS="-c -ftest-coverage -fprofile-arcs"
  2. $ export LIBS="-fprofile-arcs"
//原创备注:本人实际编译项目中去掉-c,否则编译报错


随后在 VIM 项目目录中

  1. $ make
  2. $ make install

在 /src/objects 中应该生成了许多 .o 和 .gcno 文件吧,随后运行 VIM 生成 .gcda ,汇总覆盖率数据生成 .info ,将信息整理成 html 格式的命令都可以参考上面有关 gcov lcov 的使用

最后打开 index.html,就可以看到本文最开始出现的覆盖率数据了









阅读(4496) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~