GCC Coverage代码分析-GCC如何编译生成gcov/gcov-dump程序及其bug分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/livelylittlefish/article/details/6382489

本博客(http://blog.csdn.net/livelylittlefish)贴出作者(阿波)相关研究、学习内容所做的笔记,欢迎广大朋友指正!

Content

0.

1.编译gcov/gcov-dump

2. 额外的话

3. gcov-dump程序的一个bug

3.1 bug描述

3.2 bug分析与修复

3.3 正确的输出

3.4 gcov-dump的打印开关

3.5一个问题:上面红色的0是什么?谁打印出来的?

4. 总结

 

 

0.

 

某些版本的Gcc在默认情况下编译,可能不会产生gcov-dump程序,或者不会安装到/usr/bin。但gcov-dump程序在做覆盖率测试时dump相关文件(.gcda/.gcno)内容时非常必要和好用。

 

Gcc的编译耗时又繁琐,如果某些配置不正确,会导致编译过程中各种莫名其妙的错误。因此,本文主要讲述在不重新编译整个Gcc项目的情况下,如何获得gcov-dump程序。

 

本文在Linux平台上实验,以gcc-4.1.2为例,且gcc源代码在/usr/src/gcc-4.1.2目录。以下若不做特别说明,.表示gcc源代码目录,即/usr/src/gcc-4.1.2

 

1.编译gcov/gcov-dump

 

Gcov-dump.c位于./gcc目录下,因此,可以通过./gccmakefile文件编译生成gcov-dumpgcc目录下configure程序即可生成该makefile

 

makefile文件中的gcov-dump如下,由./gcc/build下的Makefile文件中抽取出来。

exeext =

 

CPPLIB = ../libcpp/libcpp.a

LIBIBERTY = ../libiberty/libiberty.a

 

# Internationalization library.

LIBINTL =

LIBINTL_DEP =

 

# Character encoding conversion library.

LIBICONV =

LIBICONV_DEP =

 

LIBS = $(CPPLIB) $(LIBINTL) $(LIBICONV) $(LIBIBERTY)

 

gcov.o: gcov.c gcov-io.c $(GCOV_IO_H) intl.h $(SYSTEM_H) coretypes.h $(TM_H) /

   $(CONFIG_H) version.h

gcov-dump.o: gcov-dump.c gcov-io.c $(GCOV_IO_H) $(SYSTEM_H) coretypes.h /

   $(TM_H) $(CONFIG_H)

 

GCOV_OBJS = gcov.o intl.o version.o errors.o

gcov$(exeext): $(GCOV_OBJS) $(LIBDEPS)

$(CC) $(ALL_CFLAGS) $(LDFLAGS) $(GCOV_OBJS) $(LIBS) -o $@

 

GCOV_DUMP_OBJS = gcov-dump.o version.o errors.o

gcov-dump$(exeext): $(GCOV_DUMP_OBJS) $(LIBDEPS)

$(CC) $(ALL_CFLAGS) $(LDFLAGS) $(GCOV_DUMP_OBJS) $(LIBS) -o $@

至于其他的定义,非本文重点,不予解释。

# cd /usr/src/gcc-4.1.2/gcc/build

# make clean

# make gcov-dump

...

make: *** No rule to make target `../build-i686-pc-linux-gnu/libiberty/libiberty.a', needed by `build/genmodes'. Stop.

 

# make gcov

...

make: *** No rule to make target `../build-i686-pc-linux-gnu/libiberty/libiberty.a', needed by `build/genmodes'. Stop.

程序提示在../build-i686-pc-linux-gnu/libiberty目录下没有找到genmodes所需的静态库libiberty.a

 

makefile文件中也了解到,gcov/gcov-dump实际所需的静态库是libcpp.alibiberty.a。实际上,只需在gcc目录下的makefile文件中指定好这两个静态库的路径(绝对路径和相对路径均可)即可解决问题。例如:/usr/bin/libiberty.a

 

2.额外的话

 

不得不指出的一点:事实上,对libcpp.a的依赖几乎为0,且对libiberty.a的依赖也仅限于以下函数。

FILE *fopen_unlocked (const char *, const char *);

void unlock_std_streams (void);

这两个函数的声明在./include/libiberty.h中。

 

因此,了解这些之后,就可以将gcov/gcov-dump相关的文件抽取出来,单独成为一个独立的项目,来编译出gcov/gcov-dump,以方便对gcov/gcov-dump源代码和原理的学习、调试。

——将另文讨论。

 

3. gcov-dump程序的一个bug

 

3.1 bug描述

 

用上述生成的gcov-dump程序dump出某个.gcda文件的内容,如下。

# cd /home/zubo/gcc/test

#/usr/src/gcc-4.1.2/gcc/build/gcov-dump test.gcda

test.gcda:data:magic `gcda':version `401p'

test.gcda:stamp 1427241144

test.gcda: 01000000:  2:FUNCTION ident=3, checksum=0xeb65a768

test.gcda: 01a10000:  10:COUNTERS arcs 5 counts

test.gcda:             0 10 0 1 0 1

test.gcda: a1000000:  9:OBJECT_SUMMARY checksum=0x00000000

test.gcda:             counts=5, runs=1, sum_all=12, run_max=10, sum_max=10

test.gcda:             counts=1, runs=1, sum_all=577750259318514008, run_max=-6845471430389142944, sum_max=577764773093965833

test.gcda:             counts=3214008580, runs=134522083, sum_all=577765013746656483, run_max=-1208884056, sum_max=29051165790196

test.gcda:             counts=134527488, runs=1882271796, sum_all=-6845471431969184921, run_max=577765507560701952, sum_max=4429488512

test.gcda:             counts=1734567009, runs=875573616, sum_all=4429489327, run_max=91621554360, sum_max=-6845471433603153901

test.gcda: a3000000:  9:PROGRAM_SUMMARY checksum=0x51924f98

test.gcda:             counts=5, runs=1, sum_all=12, run_max=10, sum_max=10

test.gcda:             counts=1, runs=1, sum_all=577750259318514008, run_max=-6701356242313287072, sum_max=577764837518475273

test.gcda:             counts=3214008580, runs=134522083, sum_all=577765013746656483, run_max=-1208884056, sum_max=29051165790196

test.gcda:             counts=134527488, runs=1882271796, sum_all=-6701356243893329049, run_max=577765507560701952, sum_max=4429488512

test.gcda:             counts=1734567009, runs=875573616, sum_all=4429489327, run_max=138866194616, sum_max=-6701356245527298018

dump出的数据怎么会这么大?会不会有什么问题?使用od命令dump该文件的内容,看看该文件里到底有没有这些庞大的数据。如下。数据分析可以参考"Linux平台代码覆盖率测试工具GCOV相关文件分析"一文。

# od -t x4 -w16 test.gcda

0000000 67636461 343031705511f8b801000000 //'gcda', '401p', timestamp, tag=0x01000000

000002000000002 00000003 eb65a76801a10000 //FUNCTION, 0x01a10000

00000400000000a0000000a 0000000000000000 //length=0xa=10, counter content: 0xa, 0, 1, 0, 1

00000600000000000000001 0000000000000000 //8 Bytes for each counter

0000100 00000000 00000001 00000000 a1000000

0000120 00000009 000000000000000500000001 //length=9, checksum=0, counts=5, runs=1

00001400000000c 000000000000000a 00000000 //sum_all=0xc=12(8 Bytes), run_max=0xa=10(8 Bytes)

00001600000000a 00000000a3000000 00000009 //sum_max=0xa=10(8 Bytes), tag=a3000000, length=9

0000200 51924f9800000005000000010000000c //same as above

0000220 00000000 0000000a 000000000000000a

0000240 00000000 00000000

0000250

很显然,该文件里并没有这些庞大的数据,也就证实了我们的猜测。基本可以确定,gcov-dumpbug

 

3.2 bug分析与修复

 

如何分析,自然想到了gdb。有问题的数据貌似在dump Object SummaryProgram Summary时出现的。那么在tag_summary()函数中设置断点是很自然的事。经一番调试后,发现问题就在tag_summary()函数里。如下。

static void

tag_summary (const char *filename ATTRIBUTE_UNUSED,

     unsigned tag ATTRIBUTE_UNUSED, unsigned length ATTRIBUTE_UNUSED)

{

  struct gcov_summary summary;

  unsigned ix;

 

 unsigned count = gcov_read_summary (&summary); /* 还应该修改该函数的声明和定义 */

  printf (" checksum=0x%08x", summary.checksum);

 

 /* for (ix = 0; ix !=GCOV_COUNTERS; ix++) */ /* 原来的代码 */

 for (ix = 0; ix <count; ix++)                /* 应该如此修改 */

    {

      printf ("/n");

      print_prefix (filename, 0, 0);

      printf ("/t/tcounts=%u, runs=%u",

      summary.ctrs[ix].num, summary.ctrs[ix].runs);

 

      printf (", sum_all=" HOST_WIDEST_INT_PRINT_DEC,

      (HOST_WIDEST_INT)summary.ctrs[ix].sum_all);

      printf (", run_max=" HOST_WIDEST_INT_PRINT_DEC,

      (HOST_WIDEST_INT)summary.ctrs[ix].run_max);

      printf (", sum_max=" HOST_WIDEST_INT_PRINT_DEC,

      (HOST_WIDEST_INT)summary.ctrs[ix].sum_max);

    }

}

其中,GCOV_COUNTERS的定义如下,其值为5,故每次打印均打印出5summary的内容。但实际应该是按照summary(Object或者program)的个数来打印信息。

 

/* Countersthat arecollected. */

#defineGCOV_COUNTER_ARCS0/ * Arc transitions.*/

#defineGCOV_COUNTERS_SUMMABLE   1/ *Counters whichcan be

summaried.*/

#defineGCOV_FIRST_VALUE_COUNTER1 / *The firstof countersused forvalue

profiling.They mustform aconsecutive

intervaland theirorder mustmatch the orderof HIST_TYPEsin

value-prof.h.*/

#defineGCOV_COUNTER_V_INTERVAL   1/ *Histogramof valueinsidean interval.*/

#defineGCOV_COUNTER_V_POW2  2/ *Histogramof exactpower2 logarithm

ofa value.*/

#defineGCOV_COUNTER_V_SINGLE3/ * The mostcommon valueof expression.*/

#defineGCOV_COUNTER_V_DELTA 4/ * The mostcommon differencebetween

consecutivevalues ofexpression. */

#defineGCOV_LAST_VALUE_COUNTER4/ * The lastof countersused forvalue

profiling.*/

#defineGCOV_COUNTERS         5

输出的信息,如,

counts=5, runs=1, sum_all=12, run_max=10, sum_max=10

即为gcov_ctr_summary结构,其定义如下。在./gcc/gcov_io.h文件中。

 

 

/*Cumulative counterdata. */

struct gcov_ctr_summary

{

   gcov_unsigned_t num;    /*number of  ounters.*/

   gcov_unsigned_t runs;    /*number ofprogram runs*/

   gcov_type sum_all;       /* sumof allcounters accumulated.*/

   gcov_type run_max;      /* maximumvalue ona singlerun. */

   gcov_type sum_max;     /*sum ofindividualrun maxvalues.*/

};


/*Object &program summaryrecord. */

struct gcov_summary

{

   gcov_unsigned_t checksum;   /*checksum ofprogram */

    structgcov_ctr_summaryctrs[GCOV_COUNTERS_SUMMABLE];

};

GCOV_COUNTERS_SUMMABLE=1,因此,gcov_summary结构中的ctrs数组,实际上是一个指针而已,当summary有多个时(不超过5),应该为其分配空间。

 

3.3正确的输出

 

修复该bug后,在看结果,就正常了,如下。对照上面的分析,一目了然。

# /usr/src/gcc-4.1.2/gcc/build/gcov-dump test.gcda

test.gcda:data:magic `gcda':version `401p'

test.gcda:stamp1427241144

test.gcda: 01000000:   2:FUNCTION ident=3, checksum=0xeb65a768

test.gcda: 01a10000: 10:COUNTERS arcs 5 counts

test.gcda: a1000000:  9:OBJECT_SUMMARY checksum=0x00000000

test.gcda:             counts=5, runs=1, sum_all=12, run_max=10, sum_max=10

test.gcda: a3000000:  9:PROGRAM_SUMMARY checksum=0x51924f98

test.gcda:             counts=5, runs=1, sum_all=12, run_max=10, sum_max=10

可以看到,输出的Object SummaryProgram Summary均只有一个。

 

dump相应的.gcno文件,如下。

# /usr/src/gcc-4.1.2/gcc/build/gcov-dumptest.gcno

test.gcno:note:magic `gcno':version `401p'

test.gcno:stamp 1487149731

test.gcno: 01000000:  9:FUNCTION ident=3, checksum=0xeb65a768, `main' test.c:4

test.gcno: 01410000:   9:BLOCKS 9 blocks

test.gcno: 01430000:   3:ARCS 1 arcs

test.gcno: 01430000:   3:ARCS 1 arcs

test.gcno: 01430000:   3:ARCS 1 arcs

test.gcno: 01430000:   5:ARCS 2 arcs

test.gcno: 01430000:   5:ARCS 2 arcs

test.gcno: 01430000:   5:ARCS 2 arcs

test.gcno: 01430000:   5:ARCS 2 arcs

test.gcno: 01430000:   3:ARCS 1 arcs

test.gcno: 01450000:  10:LINES

test.gcno: 01450000:   9:LINES

test.gcno: 01450000:   8:LINES

test.gcno: 01450000:   8:LINES

test.gcno: 01450000:   8:LINES

test.gcno: 01450000:   8:LINES

3.4 gcov-dump的打印开关

 

Gcov-dump程序中有两个打印开关,如下。

static int flag_dump_contents =1; //打开该开关,将其置为1即可,将打印coutner的内容

static int flag_dump_positions = 0;

flag_dump_contents开关置为1,重新编译gcov-dump,便可打印counter的内容。如下。

# /usr/src/gcc-4.1.2/gcc/build/gcov-dump test.gcda

test.gcda:data:magic `gcda':version `401p'

test.gcda:stamp 1427241144

test.gcda: 01000000:  2:FUNCTION ident=3, checksum=0xeb65a768

test.gcda: 01a10000:  10:COUNTERS arcs 5 counts

test.gcda:             010 0 1 0 1 //此处便是5counter,共40字节

test.gcda: a1000000:  9:OBJECT_SUMMARY checksum=0x00000000

test.gcda:             counts=5, runs=1, sum_all=12, run_max=10, sum_max=10

test.gcda: a3000000:  9:PROGRAM_SUMMARY checksum=0x51924f98

test.gcda:             counts=5, runs=1, sum_all=12, run_max=10, sum_max=10

flag_dump_positions开关置为1,重新编译gcov-dump,便可打印读取的每一个tagpositioncounterposition。如下。

# /usr/src/gcc-4.1.2/gcc/build/gcov-dump test.gcda

test.gcda:data:magic `gcda':version `401p'

test.gcda:stamp 1427241144

test.gcda:3: 01000000:  2:FUNCTION ident=3, checksum=0xeb65a768

test.gcda:7: 01a10000:  10:COUNTERS arcs 5 counts

test.gcda:9:           0 10 0 1 0 1

test.gcda:19: a1000000:  9:OBJECT_SUMMARY checksum=0x00000000

test.gcda:0:           counts=5, runs=1, sum_all=12, run_max=10, sum_max=10

test.gcda:30: a3000000:  9:PROGRAM_SUMMARY checksum=0x51924f98

test.gcda:0:           counts=5, runs=1, sum_all=12, run_max=10, sum_max=10

3.5一个问题

 

flag_dump_contents开关置1时,上面红色的0是什么?谁打印出来的?

 

只有第一个counter需要显示(打印)文件名,因此tag_counters()函数通过位与运算判断是否是第一个counter,如果是第一个counter,则打印文件名并显示该序号ix。如下。

static void

tag_counters (const char *filename ATTRIBUTE_UNUSED,

      unsigned tag ATTRIBUTE_UNUSED,unsigned length ATTRIBUTE_UNUSED)

{

  static const char *const counter_names[] = GCOV_COUNTER_NAMES;

  unsigned n_counts = GCOV_TAG_COUNTER_NUM (length);

 

  printf (" %s %u counts",

  counter_names[GCOV_COUNTER_FOR_TAG (tag)], n_counts);

  if (flag_dump_contents)

    {

      unsigned ix;

 

   for (ix = 0; ix != n_counts; ix++)

{

  gcov_type count;

 

 if (!(ix &7)) //只有ix0时该条件为真

    {

      printf ("/n");

      print_prefix (filename, 0, gcov_position ());

     printf ("/t/t%u", ix); //此处打印这个ix=0

    }

 

  count = gcov_read_counter ();

  printf (" ");

  printf (HOST_WIDEST_INT_PRINT_DEC, count);

}

    }

}

实际上应该是gcc的设计者的一个技巧,此处的0,永远是0,即文件名之后打印一个0,表示后续counter内容的开始,亦可做测试时使用,如果此处不为0,一定是代码有bug

 

4.总结

 

本文主要描述了Linux平台代码覆盖率测试工具GCOV相关程序,主要是gcov-dump程序的编译生成及其bug分析、修复。后续的文章开始讨论gcov/gcov-dump设计及其原理。

 

Reference

./gcc/gcov-io.h

./gcc/gcov-io.c

./gcc/gcov-dump.c


Technorati 标签: GCC;单元测试;GCOV

阅读更多

没有更多推荐了,返回首页