Linux平台代码覆盖率测试-gcov-dump原理分析
Linux平台代码覆盖率测试-gcov-dump原理分析
Content
1. 序
2. gcov-dump原理分析
2.1 gcov-dump程序结构
2.2 dump_file函数分析
2.3 处理各种tag的callback定义
2.4 基本读取函数gcov_read_words
2.5 分配空间函数gcov_allocate
2.6 重要数据结构gcov_var
3. 处理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 void
dump_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\n", 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\n", 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\n", 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'\n", filename, e);
}
}
/* stamp */
{
unsigned stamp = gcov_read_unsigned ();
printf ("%s:stamp %lu\n", 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\n", 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\n",
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 ("\n");
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\n",
filename, actual_length - length);
else if (length > actual_length)
printf ("%s:record size mismatch %lu bytes unread\n",
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\n" :
"%s:read error at %lu\n", 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;
|
评论暂时关闭