通过 QEMU/GDB 生成 .gcda 覆盖率文件

Generating .gcda coverage files via QEMU/GDB

执行摘要:我想使用 GDB 提取存储在我的嵌入式目标内存中的覆盖率执行计数,并使用它们创建 .gcda 文件(用于喂 gcov/lcov).

设置:

覆盖率: 现在,为了执行 "on-target" 覆盖率分析,我交叉编译了 -fprofile-arcs -ftest-coverage。 GCC 然后发出 64 位计数器来跟踪特定代码块的执行计数。

在正常(即基于主机,非交叉编译)执行下,当应用程序完成时 __gcov_exit 被调用 - 并将所有这些执行计数收集到 .gcda 文件中(gcov 然后使用报告覆盖细节)。

然而,在我的嵌入式目标中,没有文件系统可言 - libgcov 基本上包含所有 __gcov_... 函数的空存根。

解决方法 QEMU/GDB: 为了解决这个问题,并以与 GCC 版本无关的方式进行,我可以在我的二进制文件中列出与覆盖率相关的符号通过 MYPLATFORM-readelfgrep - 输出相关的(例如 __gcov0.Task1_EntryPoint__gcov0.worker 等):

$ MYPLATFORM-readelf -s binary | grep __gcov
...
46: 40021498  48 OBJECT  LOCAL  DEFAULT 4 __gcov0.Task1_EntryPoint
...

然后我可以使用 offsets/sizes 报告来自动创建 GDB 脚本 - 一个通过简单内存转储提取计数器数据的脚本(来自 offset,转储length 字节到本地文件)。

我不知道(也没有找到任何相关的 info/tool),是如何转换结果对 (内存偏移量,内存数据)进入 .gcda 个文件。如果这样的 tool/script 存在,我将有一种可移植的(与平台无关的)方式来覆盖任何 QEMU 支持的平台。

有这样的tool/script吗?

任何 suggestions/pointers 将不胜感激。

更新:我自己解决了这个问题,您可以在下面阅读 - 并写了一个 blog post about it.

原来有一个(好得多)更好的方法来做我想做的事。

Linux 内核 includes portable GCOV related functionality,通过提供此端点抽象出特定于 GCC 版本的详细信息:

size_t convert_to_gcda(char *buffer, struct gcov_info *info)

所以基本上,我能够通过以下步骤进行目标覆盖:

步骤 1

我在我的项目中添加了 linux gcov 文件的三个稍作修改的版本:base.c, gcc_4_7.c and gcov.h。我不得不替换其中的一些 linux-isms - 比如 vmalloc,kfree, 等等 - 使代码可移植(因此,可以在我的嵌入式平台上编译,这与任何事情无关Linux).

步骤 2

然后我提供了我自己的 __gcov_init...

typedef struct tagGcovInfo {
    struct gcov_info *info;
    struct tagGcovInfo *next;
} GcovInfo;
GcovInfo *headGcov = NULL;

void __gcov_init(struct gcov_info *info)
{
    printf(
        "__gcov_init called for %s!\n",
        gcov_info_filename(info));
    fflush(stdout);
    GcovInfo *newHead = malloc(sizeof(GcovInfo));
    if (!newHead) {
        puts("Out of memory!");
        exit(1);
    }
    newHead->info = info;
    newHead->next = headGcov;
    headGcov = newHead;
}

...和__gcov_exit:

void __gcov_exit()
{
    GcovInfo *tmp = headGcov;
    while(tmp) {
        char *buffer;
        int bytesNeeded = convert_to_gcda(NULL, tmp->info);
        buffer = malloc(bytesNeeded);
        if (!buffer) {
            puts("Out of memory!");
            exit(1);
        }
        convert_to_gcda(buffer, tmp->info);
        printf("Emitting %6d bytes for %s\n", bytesNeeded, gcov_info_filename(tmp->info));
        free(buffer);
        tmp = tmp->next;
    }
}

步骤 3

最后,我通过以下方式编写了我的 GDB(远程驱动 QEMU)脚本:

$ cat coverage.gdb
tar extended-remote :9976
file bin.debug/fputest
b base.c:88  <================= This breaks on the "Emitting" printf in __gcov_exit
commands 1
    silent
    set $filename = tmp->info->filename
    set $dataBegin = buffer
    set $dataEnd = buffer + bytesNeeded
    eval "dump binary memory %s 0x%lx 0x%lx", $filename, $dataBegin, $dataEnd
    c
end
c
quit

最后,同时执行 QEMU 和 GDB - 像这样:

$ # In terminal 1:
qemu-system-MYPLATFORM ... -kernel bin.debug/fputest  -gdb tcp::9976 -S

$ # In terminal 2:
MYPLATFORM-gdb -x coverage.gdb

...就是这样 - 我能够在本地文件系统中生成 .gcda 文件,然后查看 gcovlcov.

的覆盖率结果

更新:我写了一个blog post showing the process in detail