内核内存泄漏检测器¶
Kmemleak 提供了一种检测可能的内核内存泄漏的方法,其方式类似于追踪垃圾回收器,不同之处在于,孤立对象不会被释放,而仅通过 /sys/kernel/debug/kmemleak 报告。Valgrind 工具(memcheck --leak-check
)使用类似的方法来检测用户空间应用程序中的内存泄漏。
用法¶
必须启用“内核黑客”中的 CONFIG_DEBUG_KMEMLEAK。内核线程每 10 分钟(默认情况下)扫描一次内存,并打印找到的新未引用对象的数量。如果 debugfs
尚未挂载,请使用以下命令挂载:
# mount -t debugfs nodev /sys/kernel/debug/
要显示所有可能的扫描到的内存泄漏的详细信息:
# cat /sys/kernel/debug/kmemleak
要触发中间内存扫描:
# echo scan > /sys/kernel/debug/kmemleak
要清除所有当前可能的内存泄漏的列表:
# echo clear > /sys/kernel/debug/kmemleak
然后,在再次读取 /sys/kernel/debug/kmemleak
时,会出现新的泄漏。
请注意,孤立对象按照它们分配的顺序被列出,并且列表开头的对象可能导致其他后续对象被报告为孤立对象。
可以通过写入 /sys/kernel/debug/kmemleak
文件在运行时修改内存扫描参数。支持以下参数:
- off
禁用 kmemleak(不可逆)
- stack=on
启用任务堆栈扫描(默认)
- stack=off
禁用任务堆栈扫描
- scan=on
启动自动内存扫描线程(默认)
- scan=off
停止自动内存扫描线程
- scan=<secs>
设置自动内存扫描周期,以秒为单位(默认 600,0 停止自动扫描)
- scan
触发内存扫描
- clear
清除当前内存泄漏嫌疑对象的列表,通过将所有当前报告的未引用对象标记为灰色来完成,如果 kmemleak 已被禁用,则释放所有 kmemleak 对象。
- dump=<addr>
转储在 <addr> 处找到的对象的信息
也可以在启动时通过在内核命令行上传递 kmemleak=off
来禁用 Kmemleak。
在 kmemleak 初始化之前,可能会分配或释放内存,并且这些操作存储在早期日志缓冲区中。此缓冲区的大小通过 CONFIG_DEBUG_KMEMLEAK_MEM_POOL_SIZE 选项配置。
如果启用了 CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF,则默认情况下禁用 kmemleak。在内核命令行上传递 kmemleak=on
可启用该功能。
如果您收到类似“写入 stdout 时出错”或“write_loop: 无效参数”的错误,请确保 kmemleak 已正确启用。
基本算法¶
通过 kmalloc()
、vmalloc()
、kmem_cache_alloc()
等函数进行的内存分配会被跟踪,并且指针以及大小和堆栈跟踪等附加信息会存储在 rbtree 中。相应的释放函数调用会被跟踪,并且指针会从 kmemleak 数据结构中删除。
如果通过扫描内存(包括保存的寄存器)无法找到指向其起始地址或块内任何位置的指针,则分配的内存块被认为是孤立的。这意味着内核可能无法将分配的块的地址传递给释放函数,因此该块被认为是内存泄漏。
扫描算法步骤:
将所有对象标记为白色(剩余的白色对象稍后将被认为是孤立的)
从数据段和堆栈开始扫描内存,检查这些值与存储在 rbtree 中的地址是否匹配。如果找到指向白色对象的指针,则将该对象添加到灰色列表
扫描灰色对象以查找匹配的地址(一些白色对象可以变为灰色并添加到灰色列表的末尾),直到灰色集完成
剩余的白色对象被认为是孤立的,并通过 /sys/kernel/debug/kmemleak 报告
一些分配的内存块的指针存储在内核的内部数据结构中,并且无法检测为孤立对象。为了避免这种情况,kmemleak 还可以存储指向块地址范围内地址的值的数量,需要找到这些值,以便该块不被认为是泄漏。一个例子是 __vmalloc()。
使用 kmemleak 测试特定部分¶
在初始启动时,您的 /sys/kernel/debug/kmemleak 输出页面可能非常广泛。如果您在开发时有非常多的错误代码,也可能出现这种情况。为了解决这些情况,您可以使用“clear”命令从 /sys/kernel/debug/kmemleak 输出中清除所有报告的未引用对象。通过在“clear”之后发出“scan”,您可以找到新的未引用对象;这应该有助于测试代码的特定部分。
要在按需测试关键部分并清理 kmemleak,请执行以下操作:
# echo clear > /sys/kernel/debug/kmemleak
... test your kernel or modules ...
# echo scan > /sys/kernel/debug/kmemleak
然后像往常一样使用以下命令获取您的报告:
# cat /sys/kernel/debug/kmemleak
释放 kmemleak 内部对象¶
为了允许在用户禁用 kmemleak 或由于致命错误导致 kmemleak 被禁用后访问先前发现的内存泄漏,当禁用 kmemleak 时,不会释放内部 kmemleak 对象,并且这些对象可能会占用物理内存的很大一部分。
在这种情况下,您可以使用以下命令回收内存:
# echo clear > /sys/kernel/debug/kmemleak
Kmemleak API¶
有关函数原型,请参见 include/linux/kmemleak.h 标头。
kmemleak_init
- 初始化 kmemleakkmemleak_alloc
- 通知内存块分配kmemleak_alloc_percpu
- 通知每个 CPU 内存块分配kmemleak_vmalloc
- 通知 vmalloc() 内存分配kmemleak_free
- 通知内存块释放kmemleak_free_part
- 通知部分内存块释放kmemleak_free_percpu
- 通知每个 CPU 内存块释放kmemleak_update_trace
- 更新对象分配堆栈跟踪kmemleak_not_leak
- 将对象标记为非泄漏kmemleak_transient_leak
- 将对象标记为瞬时泄漏kmemleak_ignore
- 不要扫描或报告对象为泄漏kmemleak_scan_area
- 在内存块中添加扫描区域kmemleak_no_scan
- 不要扫描内存块kmemleak_erase
- 擦除指针变量中的旧值kmemleak_alloc_recursive
- 作为 kmemleak_alloc,但检查递归性kmemleak_free_recursive
- 作为 kmemleak_free,但检查递归性
以下函数将物理地址作为对象指针,并且仅当该地址具有低内存映射时才执行相应的操作
kmemleak_alloc_phys
kmemleak_free_part_phys
kmemleak_ignore_phys
处理误报/漏报¶
误报是真正的内存泄漏(孤立对象),但 kmemleak 没有报告,因为在内存扫描期间发现的值指向这些对象。为了减少误报的数量,kmemleak 提供了 kmemleak_ignore、kmemleak_scan_area、kmemleak_no_scan 和 kmemleak_erase 函数(参见上文)。任务堆栈也会增加误报的数量,并且默认情况下不启用它们的扫描。
误报是被错误报告为内存泄漏(孤立对象)的对象。对于已知不是泄漏的对象,kmemleak 提供了 kmemleak_not_leak 函数。如果已知内存块不包含其他指针,也可以使用 kmemleak_ignore,并且将不再扫描该内存块。
一些报告的泄漏只是暂时的,尤其是在 SMP 系统上,因为指针暂时存储在 CPU 寄存器或堆栈中。Kmemleak 定义了 MSECS_MIN_AGE(默认为 1000),表示对象被报告为内存泄漏的最小年龄。
局限性和缺点¶
主要的缺点是内存分配和释放的性能降低。为了避免其他损失,仅在读取 /sys/kernel/debug/kmemleak 文件时才执行内存扫描。无论如何,此工具旨在用于调试目的,在这种情况下,性能可能不是最重要的要求。
为了保持算法的简单性,kmemleak 扫描指向块地址范围内任何地址的值。这可能会导致误报数量增加。但是,真正的内存泄漏最终可能会变得可见。
另一个误报的来源是存储在非指针值中的数据。在未来的版本中,kmemleak 可能只会扫描分配结构中的指针成员。此功能将解决上述许多误报情况。
该工具可以报告误报。这些情况是指分配的块不需要释放(init_call 函数中的某些情况),指针通过container_of宏以外的其他方法计算,或者指针存储在 kmemleak 未扫描的位置。
页面分配和 ioremap 不会被跟踪。
使用 kmemleak-test 进行测试¶
要检查您是否已设置好所有内容以使用 kmemleak,您可以使用 kmemleak-test 模块,该模块会故意泄漏内存。将 CONFIG_SAMPLE_KMEMLEAK 设置为模块(不能用作内置),并在启用 kmemleak 的情况下启动内核。加载模块并使用以下命令执行扫描:
# modprobe kmemleak-test
# echo scan > /sys/kernel/debug/kmemleak
请注意,您可能不会立即或在第一次扫描时获得结果。当 kmemleak 获得结果时,它将记录 kmemleak: <count of leaks> new suspected memory leaks
。然后读取文件以查看它们
# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff89862ca702e8 (size 32):
comm "modprobe", pid 2088, jiffies 4294680594 (age 375.486s)
hex dump (first 32 bytes):
6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk
6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5 kkkkkkkkkkkkkkk.
backtrace:
[<00000000e0a73ec7>] 0xffffffffc01d2036
[<000000000c5d2a46>] do_one_initcall+0x41/0x1df
[<0000000046db7e0a>] do_init_module+0x55/0x200
[<00000000542b9814>] load_module+0x203c/0x2480
[<00000000c2850256>] __do_sys_finit_module+0xba/0xe0
[<000000006564e7ef>] do_syscall_64+0x43/0x110
[<000000007c873fa6>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
...
使用 rmmod kmemleak_test
删除模块也应该触发一些 kmemleak 结果。