通过 QEMU/GDB 生成 .gcda 覆盖率文件
Generating .gcda coverage files via QEMU/GDB
执行摘要:我想使用 GDB 提取存储在我的嵌入式目标内存中的覆盖率执行计数,并使用它们创建 .gcda
文件(用于喂 gcov/lcov).
设置:
- 我可以针对我的特定嵌入式目标成功交叉编译我的二进制文件 - 然后在 QEMU 下执行它。
- 我还可以使用 QEMU 的 GDB 支持来调试二进制文件(即使用
tar extended-remote localhost:...
附加到 运行 QEMU GDB 服务器,并完全控制我的二进制文件的执行)。
覆盖率:
现在,为了执行 "on-target" 覆盖率分析,我交叉编译了
-fprofile-arcs -ftest-coverage
。 GCC 然后发出 64 位计数器来跟踪特定代码块的执行计数。
在正常(即基于主机,非交叉编译)执行下,当应用程序完成时 __gcov_exit
被调用 - 并将所有这些执行计数收集到 .gcda
文件中(gcov 然后使用报告覆盖细节)。
然而,在我的嵌入式目标中,没有文件系统可言 - libgcov 基本上包含所有 __gcov_...
函数的空存根。
解决方法 QEMU/GDB: 为了解决这个问题,并以与 GCC 版本无关的方式进行,我可以在我的二进制文件中列出与覆盖率相关的符号通过 MYPLATFORM-readelf
和 grep
- 输出相关的(例如 __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 文件,然后查看 gcov
和 lcov
.
的覆盖率结果
执行摘要:我想使用 GDB 提取存储在我的嵌入式目标内存中的覆盖率执行计数,并使用它们创建 .gcda
文件(用于喂 gcov/lcov).
设置:
- 我可以针对我的特定嵌入式目标成功交叉编译我的二进制文件 - 然后在 QEMU 下执行它。
- 我还可以使用 QEMU 的 GDB 支持来调试二进制文件(即使用
tar extended-remote localhost:...
附加到 运行 QEMU GDB 服务器,并完全控制我的二进制文件的执行)。
覆盖率:
现在,为了执行 "on-target" 覆盖率分析,我交叉编译了
-fprofile-arcs -ftest-coverage
。 GCC 然后发出 64 位计数器来跟踪特定代码块的执行计数。
在正常(即基于主机,非交叉编译)执行下,当应用程序完成时 __gcov_exit
被调用 - 并将所有这些执行计数收集到 .gcda
文件中(gcov 然后使用报告覆盖细节)。
然而,在我的嵌入式目标中,没有文件系统可言 - libgcov 基本上包含所有 __gcov_...
函数的空存根。
解决方法 QEMU/GDB: 为了解决这个问题,并以与 GCC 版本无关的方式进行,我可以在我的二进制文件中列出与覆盖率相关的符号通过 MYPLATFORM-readelf
和 grep
- 输出相关的(例如 __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 文件,然后查看 gcov
和 lcov
.