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。我建议您提交错误报告。即使这种行为是有意为之,编译器至少应该警告我们潜在的问题。
我的 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。我建议您提交错误报告。即使这种行为是有意为之,编译器至少应该警告我们潜在的问题。