在 Linux 内核中使用 gcov¶
gcov 分析内核支持允许将 GCC 的覆盖率测试工具 gcov 与 Linux 内核一起使用。运行中内核的覆盖率数据通过“gcov”debugfs 目录以 gcov 兼容格式导出。要获取特定文件的覆盖率数据,请切换到内核构建目录并使用带有 -o
选项的 gcov,如下所示(需要 root 权限)
# cd /tmp/linux-out
# gcov -o /sys/kernel/debug/gcov/tmp/linux-out/kernel spinlock.c
这将在当前目录中创建带有执行计数注释的源代码文件。此外,诸如 lcov 之类的图形 gcov 前端可用于自动化收集整个内核的数据并以 HTML 格式提供覆盖率概览的过程。
可能的用途
调试(是否已到达此行?)
改进测试(如何更改我的测试以覆盖这些行?)
最小化内核配置(如果关联的代码从未运行,我是否需要此选项?)
准备工作¶
使用以下配置内核
CONFIG_DEBUG_FS=y
CONFIG_GCOV_KERNEL=y
要获取整个内核的覆盖率数据
CONFIG_GCOV_PROFILE_ALL=y
请注意,使用分析标志编译的内核将明显更大且运行速度更慢。此外,并非所有架构都支持 CONFIG_GCOV_PROFILE_ALL。
只有在挂载 debugfs 后,分析数据才能访问
mount -t debugfs none /sys/kernel/debug
自定义¶
要为特定文件或目录启用分析,请将类似于以下行的行添加到相应的内核 Makefile 中
对于单个文件(例如 main.o)
GCOV_PROFILE_main.o := y
对于一个目录中的所有文件
GCOV_PROFILE := y
要排除在指定 CONFIG_GCOV_PROFILE_ALL 时不进行分析的文件,请使用
GCOV_PROFILE_main.o := n
和
GCOV_PROFILE := n
此机制仅支持链接到主内核映像或编译为内核模块的文件。
模块特定配置¶
下面描述了特定模块的 Gcov 内核配置
- CONFIG_GCOV_PROFILE_RDS
在 RDS 上启用 GCOV 分析,以检查执行了哪些函数或行。此配置由 rds 自测试用于生成覆盖率报告。如果未设置,则会省略报告。
文件¶
gcov 内核支持在 debugfs 中创建以下文件
/sys/kernel/debug/gcov
所有 gcov 相关文件的父目录。
/sys/kernel/debug/gcov/reset
全局重置文件:写入时将所有覆盖率数据重置为零。
/sys/kernel/debug/gcov/path/to/compile/dir/file.gcda
gcov 工具理解的实际 gcov 数据文件。写入时将文件覆盖率数据重置为零。
/sys/kernel/debug/gcov/path/to/compile/dir/file.gcno
指向 gcov 工具所需的静态数据文件的符号链接。此文件由 gcc 在使用选项
-ftest-coverage
编译时生成。
模块¶
内核模块可能包含仅在模块卸载期间运行的清理代码。gcov 机制提供了一种收集此类代码的覆盖率数据的方法,方法是保留与卸载模块关联的数据副本。此数据通过 debugfs 仍然可用。一旦再次加载模块,关联的覆盖率计数器将使用其先前实例的数据进行初始化。
可以通过指定 gcov_persist 内核参数来禁用此行为
gcov_persist=0
在运行时,用户还可以选择通过写入其数据文件或全局重置文件来丢弃已卸载模块的数据。
分离的构建和测试机器¶
gcov 内核分析基础结构旨在开箱即用地用于在同一台机器上构建和运行内核的设置。如果内核在单独的机器上运行,则必须进行特殊准备,具体取决于 gcov 工具的使用位置
gcov 在测试机器上运行
测试机器上的 gcov 工具版本必须与用于内核构建的 gcc 版本兼容。此外,还需要将以下文件从构建机器复制到测试机器
- 从源代码树中
所有 C 源文件 + 标头
- 从构建树中
所有 C 源文件 + 标头
所有 .gcda 和 .gcno 文件
所有指向目录的链接
请务必注意,这些文件需要放置在测试机器上与构建机器上完全相同的文件系统位置中。如果任何路径组件是符号链接,则需要使用实际目录(由于 make 的 CURDIR 处理)。
gcov 在构建机器上运行
每个测试用例之后,都需要将以下文件从测试机器复制到构建机器
- 来自 sysfs 中的 gcov 目录
所有 .gcda 文件
所有指向 .gcno 文件的链接
这些文件可以复制到构建机器上的任何位置。然后必须使用指向该目录的 -o 选项调用 gcov。
构建机器上的示例目录设置
/tmp/linux: kernel source tree /tmp/out: kernel build directory as specified by make O= /tmp/coverage: location of the files copied from the test machine [user@build] cd /tmp/out [user@build] gcov -o /tmp/coverage/tmp/out/init main.c
关于编译器的说明¶
GCC 和 LLVM gcov 工具不一定兼容。使用 gcov 来处理 GCC 生成的 .gcno 和 .gcda 文件,并使用 llvm-cov 来处理 Clang。
GCC 和 Clang gcov 之间的构建差异由 Kconfig 处理。它会根据检测到的工具链自动选择适当的 gcov 格式。
问题排查¶
- 问题
编译在链接步骤期间中止。
- 原因
分析标志是为未链接到主内核或由自定义链接器过程链接的源文件指定的。
- 解决方案
通过在相应的 Makefile 中指定
GCOV_PROFILE := n
或GCOV_PROFILE_basename.o := n
来排除受影响的源文件的分析。- 问题
从 sysfs 复制的文件显示为空或不完整。
- 原因
由于 seq_file 的工作方式,某些工具(如 cp 或 tar)可能无法正确地从 sysfs 复制文件。
- 解决方案
使用
cat
读取.gcda
文件,并使用cp -d
复制链接。或者使用附录 B 中所示的机制。
附录 A:gather_on_build.sh¶
在构建机器上收集覆盖率元文件的示例脚本(请参阅 分离的构建和测试机器 a.)
#!/bin/bash
KSRC=$1
KOBJ=$2
DEST=$3
if [ -z "$KSRC" ] || [ -z "$KOBJ" ] || [ -z "$DEST" ]; then
echo "Usage: $0 <ksrc directory> <kobj directory> <output.tar.gz>" >&2
exit 1
fi
KSRC=$(cd $KSRC; printf "all:\n\t@echo \${CURDIR}\n" | make -f -)
KOBJ=$(cd $KOBJ; printf "all:\n\t@echo \${CURDIR}\n" | make -f -)
find $KSRC $KOBJ \( -name '*.gcno' -o -name '*.[ch]' -o -type l \) -a \
-perm /u+r,g+r | tar cfz $DEST -P -T -
if [ $? -eq 0 ] ; then
echo "$DEST successfully created, copy to test system and unpack with:"
echo " tar xfz $DEST -P"
else
echo "Could not create file $DEST"
fi
附录 B:gather_on_test.sh¶
在测试机器上收集覆盖率数据文件的示例脚本(请参阅 分离的构建和测试机器 b.)
#!/bin/bash -e
DEST=$1
GCDA=/sys/kernel/debug/gcov
if [ -z "$DEST" ] ; then
echo "Usage: $0 <output.tar.gz>" >&2
exit 1
fi
TEMPDIR=$(mktemp -d)
echo Collecting data..
find $GCDA -type d -exec mkdir -p $TEMPDIR/\{\} \;
find $GCDA -name '*.gcda' -exec sh -c 'cat < $0 > '$TEMPDIR'/$0' {} \;
find $GCDA -name '*.gcno' -exec sh -c 'cp -d $0 '$TEMPDIR'/$0' {} \;
tar czf $DEST -C $TEMPDIR sys
rm -rf $TEMPDIR
echo "$DEST successfully created, copy to build system and unpack with:"
echo " tar xfz $DEST"