linux中代码覆盖率实现原理(三)
上文最后说到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(¤t_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中的计数器值相加,也印证了这个逻辑
如有不对请大家指证