kmemleak 是 Linux 内核用于检测内存泄露的一种工具,实现的基本原理是通过对 kmalloc()、vmalloc()、kmem_cache_alloc() 等内存分配接口,跟踪其分配的指针、分配内存大小、堆栈等跟踪信息,存储在 kmemleak 数据结构中。
配置kmemleak
kmemleak功能需要在内核开启相关配置项才可以使用,主要配置项如下:
CONFIG_DEBUG_KMEMLEAK
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE
CONFIG_DEBUG_KMEMLEAK_TEST
其他的一些依赖配置如下:
CONFIG_DEBUG_KERNEL
CONFIG_DEBUG_FS
CONFIG_STACKTRACE
CONFIG_KALLSYMS
CONFIG_CRC32
配置kmemleak后,将会有内核线程间隔10分钟扫描一次内存,并打印找到泄漏的数量。
检查kmemleak
通过/sys/kernel/debug/kmemleak 节点来检查 kmemleak 功能是否生效。
root@Linux:/# mount -t debugfs nodev /sys/kernel/debug/
root@Linux:/# cat /sys/kernel/debug/kmemleak
在启动阶段,由于默认 CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE 配置过小,系统启动阶段 early log 溢出,会导致 kmemleak 被自动禁用,log 如下:
[ 0.000000] kmemleak: Kernel memory leak detector disabled
[ 0.000000] clk r_dsp_cache0 not found in of_sunxi_periph_cpus_clk_setup
[ 0.000000] clk r_dsp_cache1 not found in of_sunxi_periph_cpus_clk_setup
[ 0.000000] clocksource: timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 79635851949 ns
[ 0.000000] arm_arch_timer: Architected cp15 timer(s) running at 24.00MHz (virt).
[ 0.000000] clocksource: arch_sys_counter: mask: 0xffffffffffffff max_cycles: 0x588fe9dc0, max_idle_ns: 440795202592 ns
[ 0.000005] sched_clock: 56 bits at 24MHz, resolution 41ns, wraps every 4398046511097ns
[ 0.008203] Console: colour dummy device 80x25
[ 0.012434] kmemleak: Early log buffer exceeded (1438), please increase DEBUG_KMEMLEAK_EARLY_LOG_SIZE
遇到该情况,需要从新配置内核 CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE 参数,把该参数按照 log 建议值设置或者设置最大值即可。
内存泄漏扫描
开机完成后,如果系统有内存泄漏,将会出现内存泄漏扫描的log(启动之后,等待一段时间,大概几十秒,如有泄漏将会有扫描结果),如下所示:
[ 65.088222] kmemleak: 3 new suspected memory leaks (see /sys/kernel/debug/kmemleak)
通过查看/sys/kernel/debug/kmemleak 节点,可以查看疑似内存泄露的地方。log如下:
root@Linux:/# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffffffc008efcc00 (size 512):
comm "swapper/0", pid 1, jiffies 4294892779 (age 162.728s)
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace:
[<ffffff8008156eac>] __save_stack_trace+0x24/0x30
[<ffffff80081574ac>] create_object+0x100/0x23c
[<ffffff80085e7d28>] kmemleak_alloc+0x30/0x5c
[<ffffff8008153cc4>] kmem_cache_alloc+0xd0/0x184
[<ffffff800833a008>] hub_probe+0x144/0x804
[<ffffff8008342190>] usb_probe_interface+0x1d4/0x1fc
[<ffffff80082d18cc>] driver_probe_device+0x1b4/0x26c
[<ffffff80082d1b04>] __device_attach_driver+0x9c/0xb0
[<ffffff80082cff08>] bus_for_each_drv+0x84/0x94
[<ffffff80082d1690>] __device_attach+0xa8/0x100
[<ffffff80082d1c58>] device_initial_probe+0x10/0x18
[<ffffff80082d0c84>] bus_probe_device+0x2c/0x8c
[<ffffff80082cf094>] device_add+0x45c/0x51c
[<ffffff800834063c>] usb_set_configuration+0x604/0x648
[<ffffff800834aee0>] generic_probe+0x58/0x80
[<ffffff8008341fa0>] usb_probe_device+0x28/0x44
unreferenced object 0xffffffc008efd000 (size 512):
comm "swapper/0", pid 1, jiffies 4294892825 (age 162.544s)
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace:
[<ffffff8008156eac>] __save_stack_trace+0x24/0x30
[<ffffff80081574ac>] create_object+0x100/0x23c
[<ffffff80085e7d28>] kmemleak_alloc+0x30/0x5c
[<ffffff8008153cc4>] kmem_cache_alloc+0xd0/0x184
[<ffffff8008339ff8>] hub_probe+0x134/0x804
[<ffffff8008342190>] usb_probe_interface+0x1d4/0x1fc
[<ffffff80082d18cc>] driver_probe_device+0x1b4/0x26c
[<ffffff80082d1b04>] __device_attach_driver+0x9c/0xb0
[<ffffff80082cff08>] bus_for_each_drv+0x84/0x94
[<ffffff80082d1690>] __device_attach+0xa8/0x100
[<ffffff80082d1c58>] device_initial_probe+0x10/0x18
[<ffffff80082d0c84>] bus_probe_device+0x2c/0x8c
[<ffffff80082cf094>] device_add+0x45c/0x51c
[<ffffff800834063c>] usb_set_configuration+0x604/0x648
[<ffffff800834aee0>] generic_probe+0x58/0x80
[<ffffff8008341fa0>] usb_probe_device+0x28/0x44
unreferenced object 0xffffffc008efd200 (size 512):
comm "swapper/0", pid 1, jiffies 4294892825 (age 162.544s)
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace:
[<ffffff8008156eac>] __save_stack_trace+0x24/0x30
[<ffffff80081574ac>] create_object+0x100/0x23c
[<ffffff80085e7d28>] kmemleak_alloc+0x30/0x5c
[<ffffff8008153cc4>] kmem_cache_alloc+0xd0/0x184
[<ffffff800833a008>] hub_probe+0x144/0x804
[<ffffff8008342190>] usb_probe_interface+0x1d4/0x1fc
[<ffffff80082d18cc>] driver_probe_device+0x1b4/0x26c
[<ffffff80082d1b04>] __device_attach_driver+0x9c/0xb0
[<ffffff80082cff08>] bus_for_each_drv+0x84/0x94
[<ffffff80082d1690>] __device_attach+0xa8/0x100
[<ffffff80082d1c58>] device_initial_probe+0x10/0x18
[<ffffff80082d0c84>] bus_probe_device+0x2c/0x8c
[<ffffff80082cf094>] device_add+0x45c/0x51c
[<ffffff800834063c>] usb_set_configuration+0x604/0x648
[<ffffff800834aee0>] generic_probe+0x58/0x80
[<ffffff8008341fa0>] usb_probe_device+0x28/0x44
实际代码的修改:
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 46c99ef6bb82..04dc4a7ada46 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1784,6 +1784,11 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
/* We found a hub */
dev_info(&intf->dev, "USB hub found\n");
+ {
+ struct usb_hub *test;
+ test = kcalloc(1, sizeof(*test), GFP_KERNEL);
+ test = kzalloc(sizeof(*test), GFP_KERNEL);
+ }
hub = kzalloc(sizeof(*hub), GFP_KERNEL);
if (!hub)
return -ENOMEM;
kmemleak 只能帮助解决一些非常简单的内存泄露问题,针对较为复杂的泄露路径,kmemleak 就无法处理了,并且 kmemleak 还存在着误检测的可能性。
kmemleak支持的操作
- echo scan > /sys/kernel/debug/kmemleak:触发一次扫描
- echo clear > /sys/kernel/debug/kmemleak:清除当前 kmemleak 记录的泄露信息
- echo off > /sys/kernel/debug/kmemleak:关闭kmemleak(不可逆转的)
- echo stack=off > /sys/kernel/debug/kmemleak:关闭task栈扫描
- echo stack=on > /sys/kernel/debug/kmemleak:使能task栈扫描
- echo scan=on > /sys/kernel/debug/kmemleak:启动自动内存扫描线程
- echo scan=off > /sys/kernel/debug/kmemleak:停止自动内存扫描线程
- echo scan=<secs> > /sys/kernel/debug/kmemleak:设置自动扫描线程扫描间隔,默认是600,设置0则是停止扫描
- echo dump=<addr> > /sys/kernel/debug/kmemleak:dump某个地址的内存块信息,比如上面的echo dump=0xffffffc008efd200 > /sys/kernel/debug/kmemleak即可查看详细信息
另外,通过kernel command line传递kmemleak=off
可关闭kmemleak,传递kmemleak=on
则可开启kmemleak,这个前提是编译内核时已经配置了kmemleak。
扫描算法步骤
- 将所有对象标记为白色(剩下的白色对象稍后将被视为孤儿);
- 从数据部分和栈开始扫描内存,根据存储在rbtree中的地址检查值。如果找到一个指向白色对象的指针,则该对象被添加到灰色列表中;
- 扫描灰色对象寻找匹配地址(有些白色对象会变成灰色,并添加到灰色列表的末尾),直到灰色集结束;
- 剩下的白色对象被认为是孤立的,通过/sys/kernel/debug/kmemleak报告;
至于kmemleak的更多内容,可查看Documentation/dev-tools/kmemleak.rst
内容。