libgcov fork 和 exec 钩子

libgcov fork and exec hooks

我的 gcc 手册页声明了 --coverage 选项:

Also "fork" calls are detected and correctly handled (double counting will not happen).

我注意到我的 /usr/lib/gcc/x86_64-linux-gnu/5.4.0/libgcov.a 包含符号 __gcov_fork__gcov_execl 和其他 __gcov_exec* 变体。网上查了下这些函数的定义,貌似会dump和clear coverage输出,避免数据重复或丢失。

但这似乎对我不起作用:

gcov_test$ rm *.gcno *.gcda
gcov_test$ cat gcov_test.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void) {
    puts("Before loop");
    for (int i=0; i<5; ++i)
        printf("i=%d\n", i);
    puts("After loop");
    pid_t child1 = fork();
    if (child1<0) {
        perror("fork 1");
        exit(1);
    } else if (child1==0) {
        printf("In child 1: %d\n", (int)getpid());
        execl("/bin/true", "/bin/true", (char*)NULL);
        perror("execl");
        exit(1);
    }
    printf("Parent spawned child 1: %d\n", (int)child1);
    pid_t child2 = fork();
    if (child2<0)
    {
        perror("fork 2");
        exit(1);
    } else if (child2==0) {
        printf("In child 2: %d\n", (int)getpid());
    } else {
        printf("Parent spawned child 2: %d\n", (int)child2);
        if (waitpid(child1, NULL, 0)<0)
            perror("waitpid 1");
        if (waitpid(child2, NULL, 0)<0)
            perror("waitpid 2");
        puts("Parent done");
    }
    return 0;
}

gcov_test$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

gcov_test$ gcc -c -std=c11 -Wall --coverage gcov_test.c 
gcov_test$ gcc --coverage gcov_test.o -o gcov_test
gcov_test$ ./gcov_test
Before loop
i=0
i=1
i=2
i=3
i=4
After loop
Parent spawned child 1: 31569
Parent spawned child 2: 31570
In child 2: 31570
In child 1: 31569
Parent done
gcov_test$ gcov gcov_test.c
File 'gcov_test.c'
Lines executed:64.29% of 28
Creating 'gcov_test.c.gcov'

gcov_test$ cat gcov_test.c.gcov
        -:    0:Source:gcov_test.c
        -:    0:Graph:gcov_test.gcno
        -:    0:Data:gcov_test.gcda
        -:    0:Runs:2
        -:    0:Programs:1
        -:    1:#include <stdlib.h>
        -:    2:#include <stdio.h>
        -:    3:#include <unistd.h>
        -:    4:#include <sys/types.h>
        -:    5:#include <sys/wait.h>
        -:    6:
        2:    7:int main(void) {
        2:    8:    puts("Before loop");
       12:    9:    for (int i=0; i<5; ++i)
       10:   10:        printf("i=%d\n", i);
        2:   11:    puts("After loop");
        2:   12:    pid_t child1 = fork();
        2:   13:    if (child1<0) {
    #####:   14:        perror("fork 1");
    #####:   15:        exit(1);
        2:   16:    } else if (child1==0) {
    #####:   17:        printf("In child 1: %d\n", (int)getpid());
    #####:   18:        execl("/bin/true", "/bin/true", (char*)NULL);
    #####:   19:        perror("execl");
    #####:   20:        exit(1);
        -:   21:    }
        2:   22:    printf("Parent spawned child 1: %d\n", (int)child1);
        2:   23:    pid_t child2 = fork();
        2:   24:    if (child2<0)
        -:   25:    {
    #####:   26:        perror("fork 2");
    #####:   27:        exit(1);
        2:   28:    } else if (child2==0) {
        1:   29:        printf("In child 2: %d\n", (int)getpid());
        -:   30:    } else {
        1:   31:        printf("Parent spawned child 2: %d\n", (int)child2);
        1:   32:        if (waitpid(child1, NULL, 0)<0)
    #####:   33:            perror("waitpid 1");
        1:   34:        if (waitpid(child2, NULL, 0)<0)
    #####:   35:            perror("waitpid 2");
        1:   36:        puts("Parent done");
        -:   37:    }
        2:   38:    return 0;
        -:   39:}
        -:   40:
gcov_test$ 

在我看来,"child 1" 进程从未将其覆盖结果写入文件,因为特别是 "In child 1" 行已执行但未显示为已覆盖。第二个 fork 之前的所有行似乎都报告了双倍的覆盖率,因此覆盖率结果似乎没有像手册页声称的那样在调用 fork 时重置。

我还需要做些什么来启用那些 libgcov 挂钩吗?我不应该只是在覆盖模式下编译时用实际的钩子名称替换系统调用,对吗?

解决方案:将c11替换为gnu11

-std=c11 表示纯 C11(没有 GNU 扩展)。 -std=gnu11 还启用 GNU 扩展。我无法解释 -std=--coverage 之间的联系(可能 -std= 通常会影响内置函数的使用,而 __gcov_fork 就是其中之一),但只需更改gnu11 的标准似乎解决了这个问题,即第 17 行和第 29 行现在的执行计数都等于 1。(我在 GCC 5.4.0 和最近的主干修订版上都试过了)。

P.S。我建议您提交错误报告。即使这种行为是有意为之,编译器至少应该警告我们潜在的问题。