从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(&current_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文件的操作,下一篇将继续研究 :)