内核测试指南¶
有很多不同的工具用于测试 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:用于模糊测试的代码覆盖率是一项可以构建到内核中的功能,允许在每个任务级别捕获覆盖率。因此,它对于模糊测试和其他情况下非常有用,在这些情况下,例如,有关单个系统调用期间执行的代码的信息很有用。
动态分析工具¶
内核还支持许多动态分析工具,这些工具尝试在运行的内核中发生问题时检测问题类别。这些工具通常各自查找不同类别的错误,例如无效的内存访问、诸如数据竞争之类的并发问题,或其他未定义的行为(如整数溢出)。
下面列出了一些此类工具
kmemleak 检测可能的内存泄漏。请参阅 内核内存泄漏检测器
KASAN 检测无效的内存访问,例如越界和释放后使用错误。请参阅 内核地址消毒器 (KASAN)
UBSAN 检测 C 标准未定义的行为,如整数溢出。请参阅 未定义行为消毒器 - UBSAN
KCSAN 检测数据竞争。请参阅 内核并发消毒器 (KCSAN)
KFENCE 是一种低开销的内存问题检测器,比 KASAN 快得多,可用于生产环境。请参阅 内核电子围栏 (KFENCE)
lockdep 是一个锁正确性验证器。请参阅 运行时锁正确性验证器
运行时验证 (RV) 支持检查给定子系统的特定行为。请参阅 运行时验证
内核中还有其他几部分调试工具,其中许多可以在 lib/Kconfig.debug 中找到
这些工具倾向于测试整个内核,并且不会像 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 更容易检查宏中的错误。Coccinelle 还会为您创建补丁,而其他工具不会这样做。
例如,使用 Coccinelle,您可以从 kmalloc(x * size, GFP_KERNEL)
大规模转换为 kmalloc_array(x, size, GFP_KERNEL)
,这非常有用。如果您只是创建了一个 Smatch 警告并尝试将转换工作推给维护人员,他们会感到恼火。您必须讨论每个警告是否真的会溢出。
Coccinelle 不分析变量值,这是 Smatch 的强项。另一方面,Coccinelle 允许您以简单的方式做简单的事情。