libgcov的fork和exec钩子

4

我的gcc的人手页面声称有关--coverage选项的:

还可以检测并正确处理“fork”调用(不会发生重复计数)。

我注意到我的/usr/lib/gcc/x86_64-linux-gnu/5.4.0/libgcov.a包含符号__gcov_fork__gcov_execl和其他__gcov_exec*变体。在网上查找这些函数的定义,看起来它们会转储和清除覆盖输出以避免重复或丢失数据。

但是这对我似乎没有用:

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”进程似乎从未将其覆盖结果写入文件,因为特别是执行了“在 child 1 中”但未显示为已覆盖。并且第二个“fork”之前的所有行似乎报告了加倍的覆盖率,因此似乎在调用“fork”时未重置覆盖率结果,正如手册所述。
是否还需要做其他事情才能启用那些libgcov hooks?我不应该在编译覆盖模式时实际替换syscalls为实际hook名称,对吗?
1个回答

2
解决方案:将c11替换为gnu11-std=c11表示纯C11(不包括GNU扩展)。-std=gnu11还启用了GNU扩展。我无法解释-std=--coverage之间的联系(可能-std=通常影响内置函数的使用,而__gcov_fork是其中之一),但简单地将标准更改为gnu11似乎可以解决问题,即,现在第17行和第29行的执行次数都等于1。(我在GCC 5.4.0和最近的主干版本上都尝试过)。
附言:建议您提交错误报告。即使这种行为是有意的,编译器也应该至少警告我们可能存在的问题。

修复:__gcov_fork不是内置函数,exec*fork是。 - Mikhail Maltsev
这方面有一份错误报告:https://gcc.gnu.org/bugzilla//show_bug.cgi?id=82457。行为显然是有意的,因为在 -std=c11 模式下,GCC 不会假设它知道 POSIX 函数的工作方式。 - Tavian Barnes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接