linux中代码覆盖率实现原理(二)
从linux中代码覆盖率实现原理(一)中可以知道,gcda文件生成的原理是__llvm_gcov_flush会依次调用llvm_gcda_start_file、llvm_gcda_emit_function、
llvm_gcda_emit_arcs函数将数据导出到gcda文件。但是查看kernel/gcov/clang.c中这几个函数的源码,发现其只是将数据保存在本地,那么怎么才能得到gcda文件呢?
void llvm_gcda_emit_arcs(u32 num_counters, u64 *counters)
{
struct gcov_fn_info *info = list_last_entry(¤t_info->functions,
struct gcov_fn_info, head);
info->num_counters = num_counters; //只是将数据保存在本地链表
info->counters = counters;
}
debugfs-gcov
查看kernel/gcov/fs.c中发现gcov_fs_init会向debugfs中注册gcov文件夹
/* Create debugfs entries. */
static __init int gcov_fs_init(void)
{
init_node(&root_node, NULL, NULL, NULL);
/*
* /sys/kernel/debug/gcov will be parent for the reset control file
* and all profiling files.
*/
root_node.dentry = debugfs_create_dir("gcov", NULL);
/*
* Create reset file which resets all profiling counts when written
* to.
*/
debugfs_create_file("reset", 0600, root_node.dentry, NULL,
&gcov_reset_fops);
/* Replay previous events to get our fs hierarchy up-to-date. */
gcov_enable_events();
return 0;
}
device_initcall(gcov_fs_init)
那么这个文件夹中存放哪些东西?如何存放的?与上面说的得到gcda文件有什么关系呢?
可知如果编译时添加了–coverage编译选项,那么clang会为每个模块插桩入__llvm_gcov_init函数,其主要功能就是调用llvm_gcov_init,
并将__llvm_gcov_writeout、__llvm_gcov_flush函数地址传入llvm_gcov_init。 __llvm_gcov_init函数被放在init_array这个section中,
其会在加载时在main函数之前调用,也就是说每个模块在加载时会调用到llvm_gcov_init函数。
llvm_gcov_init函数中新建了一个gcov_init的结构体,并且调用gcov_event(GCOV_ADD, info);
gcov_event中会判断是否存在对应的gcov_node,如果不存在则新建gcov_node
void gcov_event(enum gcov_action action, struct gcov_info *info)
{
struct gcov_node *node;
mutex_lock(&node_lock);
node = get_node_by_name(gcov_info_filename(info)); //查看是否已建立过此gcov_node
switch (action) {
case GCOV_ADD:
if (node)
add_info(node, info);
else
add_node(info); //新建gcov_node
break;
... ...
mutex_unlock(&node_lock);
}
新建node的代码最终会走到new_node函数中,其除了初始化gcov_node将gcov_info与gcov_node关联外,还调用debugfs_create_file
在gcov下创建了文件node->name,文件的private_data就是gcov_node,文件的file_operations是gcov_data_fops
static struct gcov_node *new_node(struct gcov_node *parent,
struct gcov_info *info, const char *name)
{
struct gcov_node *node;
node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL); //申请与name关联的gcov_node的内存
if (!node)
goto err_nomem;
if (info) {
node->loaded_info = kcalloc(1, sizeof(struct gcov_info *),
GFP_KERNEL);
if (!node->loaded_info)
goto err_nomem;
}
init_node(node, info, name, parent); //将info挂载到gcov_node上
/* Differentiate between gcov data file nodes and directory nodes. */
if (info) {
node->dentry = debugfs_create_file(deskew(node->name), 0600,
parent->dentry, node, &gcov_data_fops); //在debugfs中新建文件
} else
node->dentry = debugfs_create_dir(node->name, parent->dentry);
if (info)
add_links(node, parent->dentry);
list_add(&node->list, &parent->children);
list_add(&node->all, &all_head); //将gcov_node插入到全局gcov_node链表中
return node;
err_nomem:
kfree(node);
pr_warn("out of memory\n");
return NULL;
}
至此,我们可以知道每个模块在加载时会调用到llvm_gcov_init函数,llvm_gcov_init函数会通过gcov_event(GCOV_ADD, info)在debugfs中的gcov目录下
创建文件内容与gcov_info相关的文件,其file_operations为gcov_data_fops。
但目前仍未揭晓debugfs中的文件为什么是gcda文件的操作,下一篇将继续研究 :)