Linux内核态内存泄漏检测工具--kmemleak工具原理及应用
kmemleak通过追踪kmalloc(), vmalloc(), kmem_cache_alloc()等函数,把分配内存的指针和大小、时间、stack trace等信息记录在一个红黑树中,等到调用free释放内存时就把相应的记录从红黑树中删除。也就是说红黑树中的记录就是已经分配出去但尚未释放的内存,其中有些内存尚未释放是因为还在被使用,这属于正常情况,而不正常的情况,即真正“泄漏”的内存都是不会
kmemleak工具原理及应用
kmemleak原理
kmemleak通过追踪kmalloc(), vmalloc(), kmem_cache_alloc()等函数,把分配内存的指针和大小、时间、stack trace等信息记录在一个红黑树中,等到调用free释放内存时就把相应的记录从红黑树中删除。也就是说红黑树中的记录就是已经分配出去但尚未释放的内存,其中有些内存尚未释放是因为还在被使用,这属于正常情况,而不正常的情况,即真正“泄漏”的内存都是不会再被使用的,那么如何找出泄漏的内存呢?kmemleak缺省每10分钟对内存做一次扫描,寻找内存中有没有红黑树中记录的地址,如果某个地址在内存中找不到,就认为这个地址是无人引用的,已经被泄露的内存没有任何方法可以把其地址传递到内存释放函数中。最后把红黑树中所有被视为泄露的内存的相关信息(地址,大小,申请者,申请时间等)输出到/sys/kernel/debug/kmemleak接口。
注:
kmemleak的扫描算法存在误报的可能,比如内存中碰巧有一个数据与红黑树中的某个地址相同,但它只是数据而非指针,kmemleak是无法分辨的,会认为它是正常使用中的内存块;再比如红黑树中的某个地址在内存中找不到,但程序可能还在用它,只是因为程序并没有直接保存访问地址,而是通过某种方式临时计算访问地址,kmemleak也无法分辨,会认为这是个泄漏。kmemleak有误报的可能性,但是它提供了观察内存的路径和视角。
扫描算法
1.将树中所有的节点标识为白色,如果经过内存扫描后,内存对象管理树中仍标志为白色的节点则认为该节点代表的内存块已经泄露
2.扫描data段和每个线程(除了扫描线程本身)的栈空间内的所有地址,对于每一个地址去对内存对象管理树去进行查找,如果查找到了,就将该地址在树中对应的节点标记为灰色,加入灰色节点链表。(实际上统计出了所有可以通过data段和栈空间直接访问的堆内存)
3.遍历灰色节点链表,对于每一个灰色节点所管理的内存地址(即kmalloc,vmalloc等开辟的可访问的堆空间)去对存对象管理树去进行查找,如果查找到了,就将该地址在树中对应的节点标记为灰色,加入灰色节点链表尾部。(统计可以从一块堆内存访问到的另一块堆内存)
4.此时树中所有还是白色的节点所代表的内存块都被认为已泄露,信息输出至/sys/kernel/debug/kmemleak
启用kmemleak
1、内核启动参数增加kmemleak=on,或者直接修改内核源码,将该判断设定恒为on
//mm/kmemleak.c
kmemleak_skip_disable = 1;
if (!kmemleak_skip_disable) {
kmemleak_disable();
return;
}
2、打开内核编译选项:
CONFIG_HAVE_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_MEM_POOL_SIZE=16000
使用方法
kmemleak的用户接口是:
/sys/kernel/debug/kmemleak
发送指令和输出信息都是通过以上文件进行的。要访问这个文件,必须先挂载以下文件系统:
# mount -t debugfs nodev /sys/kernel/debug/
写入命令 | 效果 |
---|---|
stack=on | 使扫描内存的时候对栈空间进行扫描(该功能默认为开) |
stack=off | 使扫描内存的时候不对栈空间经醒扫描 |
scan=on | 开启定期扫描内存(默认每十分钟进行一次内存扫描) |
scan=off | 关闭定期扫描内存 |
scan= | 开启定期扫描内存,且将两次扫描的间隔时间设置为sec秒 |
scan | 立即触发一次内存扫描 |
off | 关闭kmemleak功能(同时关闭内存申请/释放函数内的埋点和定期的扫描,该操作不可逆) |
dump= | 如果addr已被申请,则输出该addr的申请者pid,comm,申请时间,backtrace等信息 |
示例:
echo scan=off > /sys/kernel/debug/kmemleak // 停止自动扫描进程
echo scan > /sys/kernel/debug/kmemleak // 手动出发扫描
echo clear > sys/kernel/debug/kmemleak // 清除log
cat /sys/kernel/debug/kmemleak // 查看内存泄漏报告
实例
通过修改DEVICE_ATTR创建设备节点程序一文中的show函数如下:
static ssize_t hello_test_show(struct device *dev, struct device_attribute *attr, char *buf)
{
size_t kmalloc_size = 26;
char *hello_test = kmalloc(kmalloc_size,GFP_KERNEL);
hello_test[kmalloc_size - 1] = 'x';
return 0;
}
以上函数可以看出每次cat对应的节点都会kmalloc申请26字节的大小的内存,但并没有去释放这一块内存,所以可以通过如下方式,不停的去cat此节点,就可以看出对应的kmemleak中频繁出现此函数。
1、一次scan: echo scan > sys/kernel/debug/kmemleak //开始扫描
2、然后 cat sys/kernel/debug/kmemleak //会得到很多backtrace,但是这其中有些是误抓的(kmemleak存在误报情况)
3、然后echo clear > sys/kernel/debug/kmemleak //清除log
4、此时查看log是没有东西:cat sys/kernel/debug/kmemleak
5、不断cat /sys/devices/platform/rtk_keypad/hello_test节点
6、第二次scan:echo scan > sys/kernel/debug/kmemleak //开始扫描
7、然后 cat sys/kernel/debug/kmemleak可以看到频繁出现hello_test_show函数。
console:/ # cat /sys/kernel/debug/kmemleak
unreferenced object 0xffffff806f3ad9c0 (size 64):
comm "cat", pid 8697, jiffies 4295106397 (age 210.044s)
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 78 00 00 00 00 00 00 .........x......
backtrace:
[<00000000f24697e6>] kmem_cache_alloc_trace+0x208/0x34c
[<000000007f1910d2>] hello_test_show+0x24/0x3c
[<00000000a5eac913>] dev_attr_show+0x3c/0x78
[<00000000ab101433>] sysfs_kf_seq_show+0xa4/0x114
[<000000003d997699>] kernfs_seq_show+0x44/0x54
[<000000002c0637bd>] seq_read+0x174/0x470
[<00000000c60c08dd>] kernfs_fop_read+0x74/0x1c8
[<00000000f79147bd>] __vfs_read+0x58/0x1a0
[<00000000c22dcc99>] vfs_read+0xc0/0x160
[<000000001fb9613e>] ksys_read+0x84/0xf0
[<00000000fae481fa>] __arm64_sys_read+0x24/0x34
[<00000000e4206da5>] el0_svc_common+0xa0/0x16c
[<00000000236963c1>] el0_svc_compat_handler+0x2c/0x3c
[<0000000002636f05>] el0_svc_compat+0x8/0x24
unreferenced object 0xffffff80567509c0 (size 64):
comm "cat", pid 10143, jiffies 4295107009 (age 207.596s)
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 78 00 00 00 00 00 00 .........x......
backtrace:
[<00000000f24697e6>] kmem_cache_alloc_trace+0x208/0x34c
[<000000007f1910d2>] hello_test_show+0x24/0x3c
[<00000000a5eac913>] dev_attr_show+0x3c/0x78
[<00000000ab101433>] sysfs_kf_seq_show+0xa4/0x114
[<000000003d997699>] kernfs_seq_show+0x44/0x54
[<000000002c0637bd>] seq_read+0x174/0x470
[<00000000c60c08dd>] kernfs_fop_read+0x74/0x1c8
[<00000000f79147bd>] __vfs_read+0x58/0x1a0
[<00000000c22dcc99>] vfs_read+0xc0/0x160
[<000000001fb9613e>] ksys_read+0x84/0xf0
[<00000000fae481fa>] __arm64_sys_read+0x24/0x34
[<00000000e4206da5>] el0_svc_common+0xa0/0x16c
[<00000000236963c1>] el0_svc_compat_handler+0x2c/0x3c
[<0000000002636f05>] el0_svc_compat+0x8/0x24
unreferenced object 0xffffff806567e340 (size 64):
comm "cat", pid 12284, jiffies 4295107886 (age 204.088s)
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 78 00 00 00 00 00 00 .........x......
backtrace:
[<00000000f24697e6>] kmem_cache_alloc_trace+0x208/0x34c
[<000000007f1910d2>] hello_test_show+0x24/0x3c
[<00000000a5eac913>] dev_attr_show+0x3c/0x78
[<00000000ab101433>] sysfs_kf_seq_show+0xa4/0x114
[<000000003d997699>] kernfs_seq_show+0x44/0x54
[<000000002c0637bd>] seq_read+0x174/0x470
[<00000000c60c08dd>] kernfs_fop_read+0x74/0x1c8
[<00000000f79147bd>] __vfs_read+0x58/0x1a0
[<00000000c22dcc99>] vfs_read+0xc0/0x160
[<000000001fb9613e>] ksys_read+0x84/0xf0
[<00000000fae481fa>] __arm64_sys_read+0x24/0x34
[<00000000e4206da5>] el0_svc_common+0xa0/0x16c
[<00000000236963c1>] el0_svc_compat_handler+0x2c/0x3c
[<0000000002636f05>] el0_svc_compat+0x8/0x24
unreferenced object 0xffffff807d3e6a80 (size 64):
comm "cat", pid 16105, jiffies 4295109474 (age 197.736s)
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 78 00 00 00 00 00 00 .........x......
backtrace:
[<00000000f24697e6>] kmem_cache_alloc_trace+0x208/0x34c
[<000000007f1910d2>] hello_test_show+0x24/0x3c
[<00000000a5eac913>] dev_attr_show+0x3c/0x78
[<00000000ab101433>] sysfs_kf_seq_show+0xa4/0x114
[<000000003d997699>] kernfs_seq_show+0x44/0x54
[<000000002c0637bd>] seq_read+0x174/0x470
[<00000000c60c08dd>] kernfs_fop_read+0x74/0x1c8
[<00000000f79147bd>] __vfs_read+0x58/0x1a0
[<00000000c22dcc99>] vfs_read+0xc0/0x160
[<000000001fb9613e>] ksys_read+0x84/0xf0
[<00000000fae481fa>] __arm64_sys_read+0x24/0x34
[<00000000e4206da5>] el0_svc_common+0xa0/0x16c
[<00000000236963c1>] el0_svc_compat_handler+0x2c/0x3c
[<0000000002636f05>] el0_svc_compat+0x8/0x24
unreferenced object 0xffffff80797d66c0 (size 64):
comm "cat", pid 17036, jiffies 4295109864 (age 196.268s)
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 78 00 00 00 00 00 00 .........x......
backtrace:
[<00000000f24697e6>] kmem_cache_alloc_trace+0x208/0x34c
[<000000007f1910d2>] hello_test_show+0x24/0x3c
[<00000000a5eac913>] dev_attr_show+0x3c/0x78
[<00000000ab101433>] sysfs_kf_seq_show+0xa4/0x114
[<000000003d997699>] kernfs_seq_show+0x44/0x54
[<000000002c0637bd>] seq_read+0x174/0x470
[<00000000c60c08dd>] kernfs_fop_read+0x74/0x1c8
[<00000000f79147bd>] __vfs_read+0x58/0x1a0
[<00000000c22dcc99>] vfs_read+0xc0/0x160
[<000000001fb9613e>] ksys_read+0x84/0xf0
[<00000000fae481fa>] __arm64_sys_read+0x24/0x34
[<00000000e4206da5>] el0_svc_common+0xa0/0x16c
[<00000000236963c1>] el0_svc_compat_handler+0x2c/0x3c
[<0000000002636f05>] el0_svc_compat+0x8/0x24
unreferenced object 0xffffff803dcb2300 (size 64):
comm "cat", pid 18251, jiffies 4295110350 (age 194.324s)
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 78 00 00 00 00 00 00 .........x......
backtrace:
[<00000000f24697e6>] kmem_cache_alloc_trace+0x208/0x34c
[<000000007f1910d2>] hello_test_show+0x24/0x3c
[<00000000a5eac913>] dev_attr_show+0x3c/0x78
[<00000000ab101433>] sysfs_kf_seq_show+0xa4/0x114
[<000000003d997699>] kernfs_seq_show+0x44/0x54
[<000000002c0637bd>] seq_read+0x174/0x470
[<00000000c60c08dd>] kernfs_fop_read+0x74/0x1c8
[<00000000f79147bd>] __vfs_read+0x58/0x1a0
[<00000000c22dcc99>] vfs_read+0xc0/0x160
[<000000001fb9613e>] ksys_read+0x84/0xf0
[<00000000fae481fa>] __arm64_sys_read+0x24/0x34
[<00000000e4206da5>] el0_svc_common+0xa0/0x16c
[<00000000236963c1>] el0_svc_compat_handler+0x2c/0x3c
[<0000000002636f05>] el0_svc_compat+0x8/0x24
kmemleak的检测结果信息中,清晰地记录了申请该内存的调用栈信息、内存头32byte的信息以及泄漏的内存地址及大小。
更多推荐
所有评论(0)