本博客(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目录下,因此,可以通过./gcc的makefile文件编译生成gcov-dump。gcc目录下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.a和libiberty.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-dump有bug。
3.2 bug分析与修复
如何分析,自然想到了gdb。有问题的数据貌似在dump Object Summary和Program 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,故每次打印均打印出5个summary的内容。但实际应该是按照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 */
struct gcov_ctr_summary ctrs [ 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 Summary和Program 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 //此处便是5个counter,共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,便可打印读取的每一个tag的position及counter的position。如下。
# /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)) //只有ix为0时该条件为真
{
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