1、什么是kmemleak
kmemleak 提供了一种以类似于跟踪垃圾收集器的方式检测内核内存泄漏的方法。
kmemleak检测原理:
kmemleak通过追踪kmalloc(), vmalloc(), kmem_cache_alloc()与friends,把分配内存的指针和大小、时间、stack trace等信息记录在一个rbtree中,等到调用free释放内存时就把相应的记录从rbtree中删除。rbtree中的记录就是已经分配出去但尚未释放的内存。
其中有些内存尚未释放是因为还在被使用,这属于正常情况;而不正常的情况,即真正“泄漏”的内存都是不会再被使用的,那么如何找出泄漏的内存呢?kmemleak缺省每10分钟对内存做一次扫描,寻找内存中有没有rbtree中记录的地址(内存中有记录则在使用),如果某个地址在内存中找不到,就认为这个地址是无人引用的,是“泄漏”了,然后,把这些泄漏的内存地址以及rbtree中记录的相关信息通过 /sys/kernel/debug/kmemleak 这个文件节点打印出来。
kmemleak误检测:
- kmemleak的扫描算法存在误报的可能,比如内存中碰巧有一个数据与rbtree中的某个地址相同,但它只是数据而非指针,kmemleak是无法分辨的,会把它当作访问内存的指针;
- 再比如rbtree中的某个地址在内存中找不到,但程序可能还在用它,只是因为程序并没有直接保存访问地址,而是通过某种方式临时计算访问地址,这种情况kmemleak也无法分辨,会认为是泄漏。
但是请注意,kmemleak 这个工具的目的是为了给进一步分析提供线索,并不需要绝对精确,小概率的误报并不影响这个工具的实用性。
2、搭建qemu环境,调试linux内核
不熟悉如何搭建qemu环境的话,可参考:virtualbox + ubuntu + qemu + busybox + gdb 调试linux内核
3、使能内核kmemleak配置
必须启用“Kernel hacking”中的 CONFIG_DEBUG_KMEMLEAK模块。
#打开内核配置页面
cd ~/study/linux-4.19.157/
make menuconfig
按如下路径选择,并选中:
Kernel hacking -> Memory debugging -> Kernel memory leak detector
可查看.config文件中对应的配置是否均打开:
CONFIG_HAVE_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=16000
CONFIG_DEBUG_KMEMLEAK_TEST=m
# CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF is not set
4、编译内核
# 编译成功后,源码根目录下会生成带调试信息的 vmlinux 文件,
# 内核文件在 arch/x86/boot 目录下,文件名为 bzImage,即 vmlinuz。
make -j4
5、制作内核启动的文件系统initrd-busybox.img
#用编译出来的kmemleak-test.ko制作内核启动的文件系统initrd-busybox.img
cd ~/study/rootfs
cp ~/study/linux-4.19.157/mm/kmemleak-test.ko ./
find . -print0 | cpio --null -ov --format=newc | pigz -9 > ../initrd-busybox.img
上述命令执行成功后,会在 ~/study/ 目录下生成新的 initrd-busybox.img,即 initrd。不熟悉的话可阅读:4.编译busybox
6、启动qemu
这次使用qemu-system-x86_64启动,可不带-S -s选项,即不使用gdb调试,直接启动。
#输入如下命令启动qemu
#-m:指定RAM大小(默认128)
#-kernel:指定的内核镜像
#-initrd:设置刚刚利用 busybox 创建的initrd-busybox.img,作为内核启动的文件系统
#-append:附加选项,指定no kaslr可以关闭随机偏移
#--nographic和console=ttyS0一起使用,启动的界面就变成当前终端
#-s:相当于-gdb tcp::1234的简写,可以直接通过主机的gdb远程连接
#-S 启动gdb调试,在gdb中使用'c'开始执行
qemu-system-x86_64 -m 1700 -kernel ~/study/linux-4.19.157/arch/x86/boot/bzImage -initrd ~/study/initrd-busybox.img -append "console=ttyS0 nokaslr" -nographic
7、运行kmemleak-test.ko
qemu启动成功后,在根目录下会出现我们拷贝过来的kmemleak-test.ko文件。
#运行ko文件
insmod kmemleak-test.ko
#挂载debugfs
mount -t debugfs nodev /sys/kernel/debug/
#显示所有可能扫描的内存泄漏的详细信息
cat /sys/kernel/debug/kmemleak
#手动打开kmemlean扫描功能,有13处可以内存泄漏
echo scan > /sys/kernel/debug/kmemleak
[ 548.712979] kmemleak: 13 new suspected memory leaks (see /sys/kernel/debug/kmemleak)
#显示所有可能扫描的内存泄漏的详细信息
cat /sys/kernel/debug/kmemleak
#第一处可疑泄露信息如下:
#泄露32字节
unreferenced object 0xffff888002b37cf8 (size 32):
#相关进程信息
comm "insmod", pid 131, jiffies 4294914022 (age 474.204s)
#二进制打印
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:
[<(ptrval)>] kmem_cache_alloc_trace+0x144/0x210
[<(ptrval)>] 0xffffffffc0007038
[<(ptrval)>] do_one_initcall+0x52/0x1d7
[<(ptrval)>] do_init_module+0x5f/0x221
[<(ptrval)>] load_module+0x2614/0x2b90
[<(ptrval)>] __do_sys_init_module+0x186/0x1a0
[<(ptrval)>] __x64_sys_init_module+0x1a/0x20
[<(ptrval)>] do_syscall_64+0x5a/0x110
[<(ptrval)>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[<(ptrval)>] 0xffffffffffffffff
#清除kmemleak的扫描结果
echo clear > /sys/kernel/debug/kmemleak