上文最后说到gcov_event(GCOV_ADD, info)在debugfs中的gcov目录下创建文件内容与gcov_info相关的文件,其file_operations为gcov_data_fops
这个文件也就是gcda文件,而其生成逻辑就在gcov_data_fops中operation函数中

gcda生成

gcov_data_fops的定义如下:

static const struct file_operations gcov_data_fops = {
        .open           = gcov_seq_open,
        .release        = gcov_seq_release,
        .read           = seq_read,
        .llseek         = seq_lseek,
        .write          = gcov_seq_write,
};

我们可以看gcov_seq_open的主要逻辑,首先是从inode的gcov_node中通过get_accumulated_info得到其gcov_info的数据,这里之所以可以从inode中获取gcov_info的数据,是因为在new_node时会调用init_node时将gcov_info存放在gcov_node->loaded_info中

然后在函数gcov_inter_new中会调用covert_to_gcda将gcov_info转换成gcda格式的数据,存放在gcov_iterator的buffer里

最后将此seq文件的private置为gcov_iterator,即获取了gcda的数据

/*
 * Read from a profiling data copy to minimize reference tracking
 * complexity and concurrent access and to keep accumulating multiple
 * profiling data sets associated with one node simple.
 */
info = get_accumulated_info(node);
if (!info)
        goto out_unlock;
iter = gcov_iter_new(info);
if (!iter)
        goto err_free_info;
rc = seq_open(file, &gcov_seq_ops);
if (rc)
        goto err_free_iter_info;
seq = file->private_data;
seq->private = iter;

至此,gcov data的数据获取在linux中的完整流程分析完毕

gcov数据副本保存

不知道大家在看的时候有没有个疑惑,就是在llvm_gcda_start_file、llvm_gcda_emit_function等数据输出函数中,linux中的实现是将数据拷贝一份而不是直接输出到文件中

void llvm_gcda_start_file(const char *orig_filename, const char version[4],
                u32 checksum)
{
        current_info->filename = orig_filename;
        memcpy(&current_info->version, version, sizeof(current_info->version));   //复制数据
        current_info->checksum = checksum;
}

为什么是这样呢? 可能有人会觉得是因为要能过上面说的执行gcov_seq_open的逻辑时才导出文件,所以需要先复制保存一下
其实还有就是,llvm_gcda_start_file、llvm_gcda_emit_function等函数的执行是由编译器插桩的__llvm_gcov_flush函数调用而来
而在__llvm_gcov_flush函数中,将有效数据输出后会将计数器清零,因此每次会将数据复制下来

在get_accumulated_info中会将所有与此gcov_node相关的gcov_info中的计数器值相加,也印证了这个逻辑

如有不对请大家指证