ttsiodras
ttsiodras

Reputation: 11258

Generating .gcda coverage files via QEMU/GDB

Executive summary: I want to use GDB to extract the coverage execution counts stored in memory in my embedded target, and use them to create .gcda files (for feeding to gcov/lcov).

The setup:

Coverage: Now, to perform "on-target" coverage analysis, I cross-compile with -fprofile-arcs -ftest-coverage. GCC then emits 64-bit counters to keep track of execution counts of specific code blocks.

Under normal (i.e. host-based, not cross-compiled) execution, when the app finishes __gcov_exit is called - and gathers all these execution counts into .gcdafiles (that gcov then uses to report coverage details).

In my embedded target however, there's no filesystem to speak of - and libgcov basically contains empty stubs for all __gcov_... functions.

Workaround via QEMU/GDB: To address this, and do it in a GCC-version-agnostic way, I could list the coverage-related symbols in my binary via MYPLATFORM-readelf, and grep-out the relevant ones (e.g. __gcov0.Task1_EntryPoint, __gcov0.worker, etc):

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

I could then use the offsets/sizes reported to automatically create a GDB script - a script that extracts the counters' data via simple memory dumps (from offset, dump length bytes to a local file).

What I don't know (and failed to find any relevant info/tool), is how to convert the resulting pairs of (memory offset,memory data) into .gcda files. If such a tool/script exists, I'd have a portable (platform-agnostic) way to do coverage on any QEMU-supported platform.

Is there such a tool/script?

Any suggestions/pointers would be most appreciated.

UPDATE: I solved this myself, as you can read below - and wrote a blog post about it.

Upvotes: 4

Views: 2625

Answers (1)

ttsiodras
ttsiodras

Reputation: 11258

Turned out there was a (much) better way to do what I wanted.

The Linux kernel includes portable GCOV related functionality, that abstracts away the GCC version-specific details by providing this endpoint:

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

So basically, I was able to do on-target coverage via the following steps:

Step 1

I added three slightly modified versions of the linux gcov files to my project: base.c, gcc_4_7.c and gcov.h. I had to replace some linux-isms inside them - like vmalloc,kfree, etc - to make the code portable (and thus, compileable on my embedded platform, which has nothing to do with Linux).

Step 2

I then provided my own __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;
}

...and __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;
    }
}

Step 3

Finally, I scripted my GDB (driving QEMU remotely) via this:

$ 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

And finally, executed both QEMU and GDB - like this:

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

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

...and that's it - I was able to generate the .gcda files in my local filesystem, and then see coverage results over gcov and lcov.

UPDATE: I wrote a blog post showing the process in detail.

Upvotes: 4

Related Questions