Content1. 序2. gcov-dump原理分析2.1 gcov-dump程序结构2.2 dump_file函数分析2.3 处理各种tag的callback定义2.4 基本读取函数gcov_read_words2.5 分配空间函数gcov_allocate2.6 重要数据结构gcov_var3. 处理tag的callback分析3.1 FUNCTION tag: tag_function()函数3.2 BLOCKS tag: tag_blocks()函数3.3 ARCS tag: tag_arcs()函数3.4 LINES tag: tag_lines()函数3.5 COUNTER tag: tag_counters()函数3.6 OBJECT/PROGRAM SUMMARY tag: tag_summary()函数4. 小结 1. 序 gcov的相关文件.gcda(data文件)/.gcno(note文件)文件是以二进制方式写入的(fwrite),普通编辑文件打开看到的只是乱码,用ultraedit打开也只是看到十六进制的数据。如果你了解.gcda/.gcno的文件格式(可以参考"Linux平台代码覆盖率测试工具GCOV相关文件分析"),看起来会好些;否则,看起来便不知所云,除非有一种工具或程序能将其内容按照有意义的(文件)格式dump出来,如果再加上一些提示,就更好了。 ——这就是gcov-dump程序。 gcov-dump是一个dump程序,输入是一个gcov的文件,或者.gcda,即gcov的data文件;或者.gcno,即gcov的note文件。 有了"Linux平台代码覆盖率测试工具GCOV相关文件分析"和"Linux平台代码覆盖率测试-GCC如何编译生成gcov/gcov-dump程序及其bug分析"这两篇文章做基础,gcov-dump的原理就很好理解了。本文不予详细叙述,只做一些代码注释和简单记录,便于用到的时候查询。好头脑赶不上烂笔头嘛。 本文例子所用的gcov-dump程序来自"Linux平台代码覆盖率测试-从GCC源码中抽取gcov/gcov-dump程序"一文。 2. gcov-dump原理分析 2.1 gcov-dump程序结构
图中实线表示调用,实线旁边的数字表示tag值。tag的值请参考gcov_io.h文件,或者"Linux平台代码覆盖率测试工具GCOV相关文件分析"。 2.2 dump_file函数分析 gcov-dump程序的主函数main,是靠调用dump_file()函数来完成文件内容的输出。该函数定义如下。其中的注释为笔者所加。
static voiddump_file (const char *filename){ unsigned tags[4]; unsigned depth = 0; if (! gcov_open (filename, 1)) /* it will open .gcda/.gcno file, and save information into gcov_var */ { fprintf (stderr, "%s:cannot open
", filename); return; } /* magic */ { unsigned magic = gcov_read_unsigned (); unsigned version; const char *type = NULL; int endianness = 0; char m[4], v[4]; /***** compare magic read just now with "gcda" or "gcno" to confirm file type */ if ((endianness = gcov_magic (magic, GCOV_DATA_MAGIC))) type = "data"; else if ((endianness = gcov_magic (magic, GCOV_NOTE_MAGIC))) type = "note"; else { printf ("%s:not a gcov file
", filename); gcov_close (); return; } /***** read version, an unsigned word */ version = gcov_read_unsigned (); /***** Convert a magic or version number to a 4 character string with ASCII */ GCOV_UNSIGNED2STRING (v, version); GCOV_UNSIGNED2STRING (m, magic); printf ("%s:%s:magic `%.4s":version `%.4s"%s
", filename, type, m, v, endianness < 0 ? " (swapped endianness)" : ""); if (version != GCOV_VERSION) { char e[4]; GCOV_UNSIGNED2STRING (e, GCOV_VERSION); printf ("%s:warning:current version is `%.4s"
", filename, e); } } /* stamp */ { unsigned stamp = gcov_read_unsigned (); printf ("%s:stamp %lu
", filename, (unsigned long)stamp); } while (1) { gcov_position_t base, position = gcov_position (); unsigned tag, length; tag_format_t const *format; unsigned tag_depth; int error; unsigned mask; /***** read a tag, for example, 0x01000000, 0x01a10000, 0xa1000000, etc */ tag = gcov_read_unsigned (); if (! tag) /***** tag=0x00000000, then, to the end of file, break *****/ break; /***** read its length tag */ length = gcov_read_unsigned (); base = gcov_position (); /***** for example, tag=0x01000000, then, tag- 1=0xFFFFFF, * then, GCOV_TAG_MASK (tag)=0x1FFFFFF, then, mask = 0x1FFFFFF/ 2 = 0xFFFFFF */ mask = GCOV_TAG_MASK (tag) >> 1; /****** validate the tag */ for (tag_depth = 4; mask; mask >>= 8) { if ((mask & 0xff) != 0xff) { printf ("%s:tag `%08x" is invalid
", filename, tag); break; } tag_depth-- ; } /***** find the tag in tag_table, if found, then call its procedure */ for (format = tag_table; format- >name; format++) if (format- >tag == tag) goto found; format = &tag_table[GCOV_TAG_IS_COUNTER (tag) ? 2: 1];found: ; if (tag) { if (depth && depth < tag_depth) { if (! GCOV_TAG_IS_SUBTAG (tags[depth - 1], tag)) printf ("%s:tag `%08x" is incorrectly nested
", filename, tag); } depth = tag_depth; tags[depth - 1] = tag; } /***** print some spaces to represent the depth level */ print_prefix (filename, tag_depth, position); printf ("%08x:%4u:%s", tag, length, format- >name); /***** call the procedure of this tag stored in tag_table */ if (format- >proc) (*format- >proc) (filename, tag, length); //此处调用相应的tag处理函数 printf ("
"); if (flag_dump_contents && format- >proc) { unsigned long actual_length = gcov_position () - base; if (actual_length > length) printf ("%s:record size mismatch %lu bytes overread
", filename, actual_length - length); else if (length > actual_length) printf ("%s:record size mismatch %lu bytes unread
", filename, length - actual_length); } /***** base stands for the base position of a tag, then, synchronize the pointer */ gcov_sync (base, length); if ((error = gcov_is_error ())) { printf (error < 0 ? "%s:counter overflow at %lu
" : "%s:read error at %lu
", filename, (long unsigned) gcov_position ()); break; } } gcov_close ();}
dump_file函数首先通过gcov_open打开.gcda/.gcno文件,将文件信息保存到全局变量gcov_var(稍后介绍该变量),接着读取文件头信息,包括magic,version,stamp,然后循环读取每个tag,length,并通过函数指针处理该tag,直到文件结束(0x00000000)。下面介绍各种tag的callback。 2.3 处理各种tag的callback定义 处理tag的callback函数定义如下。
static const tag_format_t tag_table[] ={ {0,"NOP", NULL}, {0,"UNKNOWN", NULL}, {0,"COUNTERS", tag_counters}, {GCOV_TAG_FUNCTION, "FUNCTION", tag_function}, {GCOV_TAG_BLOCKS, "BLOCKS", tag_blocks}, {GCOV_TAG_ARCS, "ARCS", tag_arcs}, {GCOV_TAG_LINES, "LINES", tag_lines}, {GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary}, {GCOV_TAG_PROGRAM_SUMMARY, "PROGRAM_SUMMARY", tag_summary}, {0, NULL, NULL}};
其类型tag_format_t为一个结构,分别由tag本身,tag name和处理该tag的函数指针组成,定义如下。
typedef struct tag_format{ unsigned tag; char const *name; void (*proc) (const char *, unsigned, unsigned);} tag_format_t;