内核测试指南

有许多不同的工具用于测试 Linux 内核,因此了解何时使用它们可能是一个挑战。本文档概述了它们的区别以及它们如何协同工作。

编写和运行测试

大部分内核测试是使用 kselftest 或 KUnit 框架编写的。它们都提供了基础设施来帮助简化测试和测试组的运行,并提供了帮助编写新测试的辅助工具。

如果您希望验证内核的行为——特别是内核的特定部分——那么您将需要使用 KUnit 或 kselftest。

KUnit 与 kselftest 的区别

KUnit (KUnit - Linux 内核单元测试) 是一个完全在内核内的系统,用于“白盒”测试:由于测试代码是内核的一部分,它可以访问不向用户空间公开的内部结构和函数。

因此,KUnit 测试最适合针对内核中小的、自包含的部分编写,这些部分可以独立测试。这与“单元”测试的概念非常吻合。

例如,KUnit 测试可能会测试单个内核函数(甚至是一个函数中的单个代码路径,如错误处理情况),而不是整个功能。

这也使得 KUnit 测试构建和运行速度非常快,允许它们作为开发过程的一部分频繁运行。

有一份 KUnit 测试风格指南,可能在 测试风格和命名法 中提供进一步的提示。

另一方面,kselftest (Linux 内核自测) 大部分在用户空间中实现,测试是普通的用户空间脚本或程序。

这使得编写更复杂的测试,或者需要更多地操作整个系统状态(例如,生成进程等)的测试变得更容易。然而,无法直接从 kselftest 调用内核函数。这意味着只有通过某种方式(例如通过系统调用、设备、文件系统等)暴露给用户空间的内核功能才能通过 kselftest 进行测试。为了解决这个问题,一些测试包含一个伴随的内核模块,该模块暴露更多信息或功能。但是,如果测试主要或完全在内核内部运行,KUnit 可能是更合适的工具。

因此,kselftest 非常适合对整个功能的测试,因为这些功能会向用户空间公开一个接口,可以对其进行测试,但不会暴露实现细节。这与“系统”或“端到端”测试非常吻合。

例如,所有新的系统调用都应该附带 kselftest 测试。

代码覆盖率工具

Linux 内核支持两种不同的代码覆盖率测量工具。这些工具可以用来验证测试是否正在执行特定的函数或代码行。这对于确定内核有多少部分正在被测试,以及查找未被相应测试覆盖的边缘情况非常有用。

在 Linux 内核中使用 gcov 是 GCC 的覆盖率测试工具,可以与内核一起使用以获取全局或每个模块的覆盖率。与 KCOV 不同,它不记录每个任务的覆盖率。覆盖率数据可以从 debugfs 读取,并使用常用的 gcov 工具进行解释。

KCOV:模糊测试的代码覆盖率 是一种可以内置到内核中的功能,允许在每个任务级别捕获覆盖率。因此,它对于模糊测试和其他在例如单个系统调用期间执行的代码信息有用的情况非常有用。

动态分析工具

内核还支持多种动态分析工具,这些工具试图在运行中的内核中发生问题时检测出特定类别的问题。这些工具通常各自查找不同类别的 bug,例如无效的内存访问、数据竞争等并发问题,或其他未定义行为如整数溢出。

下面列出了一些此类工具:

这些工具倾向于整体测试内核,并且不像 kselftest 或 KUnit 测试那样“通过”。它们可以与 KUnit 或 kselftest 结合使用,方法是在启用这些工具的内核上运行测试:这样您就可以确保在测试期间不会发生这些错误。

其中一些工具与 KUnit 或 kselftest 集成,如果检测到问题,将自动使测试失败。

静态分析工具

除了测试运行中的内核,还可以使用**静态分析**工具直接分析内核源代码(**在编译时**)。内核中常用的工具允许检查整个源代码树或其中特定文件。它们使得在开发过程中更容易检测和修复问题。

Sparse 可以通过执行类型检查、锁检查、值范围检查,以及在检查代码时报告各种错误和警告来帮助测试内核。有关如何使用它的详细信息,请参阅 Sparse 文档页面。

Smatch 扩展了 Sparse,并提供了额外的检查,用于检测编程逻辑错误,例如 switch 语句中缺少 break、错误检查中未使用的返回值、在错误路径的返回中忘记设置错误代码等。Smatch 还针对更严重的问题进行测试,例如整数溢出、空指针解引用和内存泄漏。请参阅项目页面:http://smatch.sourceforge.net/

Coccinelle 是我们可用的另一个静态分析器。Coccinelle 通常用于辅助源代码的重构和附带演进,但它也可以帮助避免常见代码模式中发生的某些错误。可用的测试类型包括 API 测试、内核迭代器正确使用测试、释放操作健全性检查、锁定行为分析以及其他已知有助于保持内核使用一致性的测试。有关详细信息,请参阅 Coccinelle 文档页面。

但请注意,静态分析工具会产生**误报**。在尝试修复它们之前,需要仔细评估错误和警告。

何时使用 Sparse 和 Smatch

Sparse 进行类型检查,例如验证带注释的变量不会导致字节序错误,检测不正确使用 __user 指针的地方,以及分析符号初始化器的兼容性。

Smatch 进行流分析,如果允许构建函数数据库,它还会进行跨函数分析。Smatch 试图回答诸如这个缓冲区在哪里分配?它有多大?这个索引是否可以由用户控制?这个变量是否比那个变量大?等问题。

在 Smatch 中编写检查通常比在 Sparse 中编写检查更容易。然而,Sparse 和 Smatch 的检查之间存在一些重叠。

Smatch 和 Coccinelle 的强项

Coccinelle 可能是编写检查最简单的工具。它在预处理器之前工作,因此使用 Coccinelle 检查宏中的 bug 更容易。Coccinelle 还会为您创建补丁,这是其他工具所没有的功能。

例如,使用 Coccinelle,您可以将 kmalloc(x * size, GFP_KERNEL) 大量转换为 kmalloc_array(x, size, GFP_KERNEL),这非常有用。如果您只是创建了一个 Smatch 警告并试图将转换工作推给维护者,他们会感到恼火。您将不得不就每个警告是否真的会溢出进行争论。

Coccinelle 不对变量值进行分析,这是 Smatch 的强项。另一方面,Coccinelle 允许您以简单的方式做简单的事情。