LWN:用 Smatch 来发现 bug!

关注了就能看到更多这么棒的文章哦~

Finding locking bugs with Smatch

By Daroc Alden
June 11, 2025
Linaro Connect
Gemini flash translation
https://lwn.net/Articles/1023646/

Smatch 是一个遵守 GPL 许可(GPL-licensed)的 C 语言静态分析工具(static-analysis tool),它为内核(kernel)提供了大量专用检查。Smatch 在内核中 已使用了 超过 20 年;其主要作者 Dan Carpenter 去年决定,其插件系统(plugin system)的一些细节需要重写。他在 Linaro Connect 2025 上谈论了关于 Smatch 的工作、其实现的改变,以及这些改变如何让他能够轻松地为内核中与锁相关的 bug 添加额外的检查。

此次演讲的 视频已发布,Carpenter 的幻灯片可在 Linaro 网站 上找到。Carpenter 一开始就为这次演讲的相对复杂性表示歉意,因为这与他往年关于 Smatch 的一些演示相比有所不同。他解释说:“我们快没有容易编写的检查项了。” Smatch 旨在允许编写项目专用检查(project-specific checks);多年来,代码中已经添加了大量针对内核的专用检查(kernel-specific checks),因此最新的工作已经转向更复杂的主题,例如锁(locking)。

Carpenter 说,Smatch 与其他静态分析工具的不同之处在于它支持控制流分析(control-flow analysis)和跨函数分析(cross-function analysis)。他经常使用这两个功能来理解新的子系统(subsystems);Smatch 可以“告诉你变量在哪里设置、函数的调用者(callers)在哪里以及函数可以返回什么”等等。例如,Smatch 可能会显示某个特定函数有三个调用者,所有这些调用者在调用它时都持有一个特定的锁。由此,程序员可以推断出该函数的隐式锁要求(implicit locking requirements)。

这类报告需要跨函数分析(cross-function analysis),以跟踪持有锁的嵌套调用(nested calls),但它也需要一些关于哪些函数获取或释放锁(acquire or release a lock)的起始知识。在内核中,Smatch 从一个手写表格(hand-written table)中获取所有锁相关函数的信息,然后将这些信息通过不同的调用树(call trees)传播。重建完整的调用数据库(database)需要多次遍历(multiple passes)和五到六个小时,他说他每晚都会这样做。

然而,一旦数据库建成,它就可以轻松检查文件中的常见锁问题(locking problems)。他说,最常见的错误是在函数的错误路径(error path)中未能释放锁。Smatch 通过查找获取锁的函数,然后寻找存在某个可能的控制流路径(control-flow path)而该锁未被释放的地方来发现这种情况。这听起来比实际操作要复杂一些。

他解释说:“要知道初始状态(start state)是什么,比你想象的要难。” 如果一个函数调用了 spin_lock() ,人们可能会合理地认为在此之前锁没有被持有。但有些函数的行为会根据锁是否已被持有所不同,因此这也具有控制流敏感性(control-flow-sensitive)。此外,有时一个锁会被多个名字引用,通过一个名字加锁,再通过另一个名字解锁。这种复杂性导致 Smatch 的锁跟踪代码(lock-tracking code)慢慢变得一团难以阅读的混乱。“而我就是编写它的人。”

因此,在过去一年中,Carpenter 重写了所有代码。他说,锁检查的重新实现(reimplementation)为如何编写模块化 Smatch 检查(modular Smatch checks)提供了一个蓝图(blueprint)。现在,检查可以调用 add_lock_hook() 和 add_unlock_hook() ,以便在 Smatch 发现函数调用在其调用树中的某个地方获取或释放锁时得到通知。现在锁也按类型(tracked by type)而不是按名称进行跟踪,以减少一个锁被多个名称引用的问题。

对于使用 C 语言的 cleanup 属性 来自动解锁(automatically unlock)的代码,有一个小小的棘手之处(wrinkle)。一方面,它基本消除了与遗忘解锁(forgotten unlocks)相关的 bug;另一方面,它对于静态分析来说是“一个令人头疼的问题”,因为它使得锁对象(lock object)更难访问和跟踪。最终,由于它们避免了许多与锁相关的 bug,Smatch 可以“基本上忽略它们”。

Carpenter 还利用新结构编写了重复加锁和解锁 bug(double lock and unlock bugs)的检查。与其他静态分析项目不同,Smatch 更少关注“通用静态特性”(universal static properties),而更多关注“人们实际编写的 bug”。Smatch 不会捕捉所有可能的重复加锁、重复解锁或遗忘解锁。他说,这会增加工具的假阴性(false negatives)数量,但能大幅减少假阳性(false positives)数量。

我问他如何找到人们实际编写的 bug 类型(classes of bugs),以便用 Smatch 检查来针对它们。他解释说,他会审查发送到 linux-stable 邮件列表(linux-stable mailing list)的补丁(patches),以发现那些本可以更早通过静态分析发现的 bug。他鼓励其他人也尝试这样做,因为他发现这很有教育意义(educational)。

未来,Carpenter 希望将 Smatch 的重复加锁检查扩展到跨函数边界(function boundaries)操作,以利用 Clang 即将支持的锁与其数据之间关系跟踪功能,并处理锁顺序 bug(lock-ordering bugs)。

随着时间接近尾声,一位听众想知道 Smatch 与 Cppcheck、Coccinelle 以及其他静态分析工具相比如何。Carpenter 说,其他开源工具(open-source tools)没有良好的控制流分析,而且几乎没有跨函数分析。Smatch 在这些方面做得更好,但它也有自己的弱点(weaknesses)。他解释说,主要问题是它还没有在内核之外进行过真正的测试,所以不清楚 Smatch 将如何处理其他风格的代码。Smatch 的速度也相对较慢。

Coccinelle 速度很快,可以为许多问题生成修复。Sparse 擅长发现大小端错误(endianness bugs)和在内核空间解引用用户空间指针(user-space pointers dereferenced in kernel space)的问题。他说:“但在流分析(flow analysis)方面,Smatch 是我们开源领域唯一的工具。”

Carpenter 演讲结束后,我用这个工具对我的内核副本进行了测试;在此过程中,我了解到 Smatch 最好从源码运行(run from source)。上一个发布版早于(predates)Carpenter 的重写,并且源码分发版(source distribution)中包含了一些在分发包(distribution packages)中没有的有用脚本。Smatch 简洁的文档 介绍了如何构建其分析数据库(analysis database)和运行现有检查,但没有说明如何以更自由的形式(free-form manner)查询数据库。源码分发版中包含的 smatch_data/db/smdb.py 脚本可以用于此目的。

[感谢 Linaro 赞助我参加 Linaro Connect 的差旅。]

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值