获取更多相关的嵌入式开发工具,可收藏系列博文,持续更新中:
【开发工具】嵌入式常用开发工具汇总帖
kmemleak原理
kmemleak(kernel memory leak detector)是检测内核空间的内存泄漏的调试工具。检测对象是memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函数分配的内存块,该内存块由struct kmemleak_object来描述(简称为object)。kmemleak的实现原理非常简单,通过暴力扫描内存(假定内存中存放的都是指针,以ARM64为例,每次扫描8个字节),如果找不到指向起始地址或者内存块任何位置的指针,则分配的内存块被认为是孤立的。这意味着内核可能无法将分配内存块的地址传递给释放函数,因此该内存块被视为内存泄漏。
kmemleak配置
kmemleak(kernel memory leak detector)是检测内核空间的内存泄漏的调试工具。可以在make menuconfig编译配置内核时,添加kmemleak功能配置。
宏 | 内容 |
CONFIG_DEBUG_KMEMLEAK=y | 在kernel hacking中打开CONFIG_DEBUG_KMEMLEAK宏,表示内核支持kmemleak功能。 |
CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN=y | 如果开启CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN宏,则会触发自动扫描,调用start_scan_thread函数进行扫描。 |
CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=n | 如果定义了CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF宏或者通过cmdline设置为kmemleak=off,则默认关闭kmemleak。如果cmdline中设置kmemleak=on,则表示默认开启kmemleak功能。如果没有定义CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF宏,则默认是开启kmemleak功能。 |
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=20000 | 在启动阶段,由于默认 CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE 配置过小,系统启动阶段 early log 溢出,会导致 kmemleak 被自动禁用. 因此需要将值设置的大一些 |
kmemleak使用
使用kmemleak可以很方便地检测出内核态的内存泄露。可以用于设备驱动程序或内核模块的评估。虽然kmemleak的扫描算法存在漏报和误报的可能,但是并不影响我们的使用。因为这个工具的目的是为了给我们进一步分析提供线索,并不需要绝对精确,小概率的误报和漏报并不影响这个工具的实用性
挂载debugfs
mount -t debugfs nodev /sys/kernel/debug/
开启kmemleak扫描
echo 【参数】 > /sys/kernel/debug/kmemleak
举例:
echo stack=on > /sys/kernel/debug/kmemleak
echo scan=on > /sys/kernel/debug/kmemleak
echo scan=10 > /sys/kernel/debug/kmemleak
echo scan > /sys/kernel/debug/kmemleak
具体参数说明:
参数 | 内容 |
off | 禁用kmemleak。不再跟踪内存分配和释放。一旦禁用,就不能再次开启。 |
stack=on | 启用线程栈区域的扫描。默认是on |
stack=off | 禁用线程栈区域的扫描 |
scan=on | 开启kmemleak内核线程的自动扫描。默认为on |
scan=off | 停止kmemleak内核线程的自动扫描 |
scan=<s> | 设置kmemleak线程执行扫描时间间隔。单位为秒,默认为600s(10分钟)。0s表示停止自动扫描 |
scan | 手动触发扫描,立即扫描 |
clear | 清除检测出的数据,即清除之前判断为内存泄露的object信息,会把这些object标记为KMEMLEAK_GREY,并不会把object从红黑树和双向链表中删除,不在/sys/kernel/debug/kmemleak中显示,只是不显示,可以使用dump参数进行确定。在使用kmemleak前清除没有关系的信息时使用 |
dump=<addr> | 显示addr对应object信息 |
详细的使用方法参考kenrel文档Documentation/dev-tools/kmemleak.rst
获取kmemleak信息
详细的输出信息通过/sys/kernel/debug/kmemleak获取
cat /sys/kernel/debug/kmemleak
kmemleak实例
编译kmemleak_test
构造一个内存泄漏的实例,具体代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/page.h>
void kmemleak_memalloc(void)
{
char *pmem;
pmem = kmalloc(300, GFP_KERNEL);
if (!pmem)
{
printk("[Kmemleak]: kmalloc fail!\n");
}
else
{
printk("[Kmemleak]: kmalloc return %p \n", pmem);
}
}
int __init kmemleak_test_init(void)
{
printk("[Kmemleak]: kmemleak_test_init! \n");
kmemleak_memalloc();
return 0;
}
void __exit kmemleak_test_exit(void)
{
printk("[Kmemleak]: kmemleak_test_exit now \n");
}
module_init(kmemleak_test_init)
module_exit(kmemleak_test_exit)
MODULE_LICENSE("GPL");
Makefile如下:
Makefile需要注意平台(ARCH)、交叉编译工具链(CROSS_COMPILE)、内核路径(KERDIR)等,根据实际项目配置。
obj-m = kmemleak_test.o
export ARCH=arm
export CROSS_COMPILE=...
KERDIR := ...
CURDIR := $(shell pwd)
all:
make -C $(KERDIR) M=$(CURDIR) modules
clean:
make -C $(KERDIR) M=$(CURDIR) clean
执行make名,编译生成kmemleak_test.ko。
加载kmemleak_test
tftp上传kmemleak_test.ko,通过insmod kmemleak_test.ko加载
# tftp -gr kmemleak_test.ko 10.15.140.133
kmemleak_test.ko 100% |*******************************| 3608 0:00:00 ETA
#
# insmod kmemleak_test.ko
[ 128.494465] [Kmemleak]: kmemleak_test_init!
[ 128.494513] [Kmemleak]: kmalloc return c1f93a00
开启kmemleak扫描后,过一段时间,检测到内存泄漏事件
# echo stack=on > /sys/kernel/debug/kmemleak
#
# echo scan=on > /sys/kernel/debug/kmemleak
#
# echo scan=10 > /sys/kernel/debug/kmemleak
[ 180.771875] kmemleak: Automatic memory scanning thread ended
[ 180.774379] kmemleak: Automatic memory scanning thread started
[ 192.744462] kmemleak: 1 new suspected memory leaks (see /sys/kernel/debug/kmemleak)
#
# cat /sys/kernel/debug/kmemleak
unreferenced object 0xc1f93a00 (size 512):
comm "insmod", pid 174, jiffies 4294950135 (age 76.610s)
hex dump (first 32 bytes):
00 3c f9 c1 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:
[<c008fb48>] kmem_cache_alloc+0x88/0xf4
[<bf02b014>] kmemleak_memalloc+0x14/0x44 [kmemleak_test]
[<bf02d010>] 0xbf02d010
[<c00096f4>] do_one_initcall+0x100/0x120
[<c00541cc>] do_init_module+0x54/0x198
[<c0055880>] load_module+0x150c/0x1abc
[<c0055fe4>] SyS_finit_module+0x88/0x90
[<c000de00>] ret_fast_syscall+0x0/0x48
[<ffffffff>] 0xffffffff
#
解析kmemleak日志
通过kmemleak report的输出信息,我们可以获取如下信息:
- 产生泄漏内存块对应的object的起始地址为0xc1f93a00
- 泄漏的字节大小为512字节
- 进程名为insmod,pid为174,创建object时的jiffies为4294950135
- 泄漏内存块的钱32字节数据被打印
- 泄漏点的backtrace信息被打印