kmemleak检测内存泄露

本文介绍如何使用kmemleak工具检测Linux内核模块中的内存泄漏,包括安装预设、配置选项、编译内核模块以及执行内存泄漏检测的详细步骤。

detected kernel memory leak

This doc is from

http://psankar.blogspot.com/2010/11/detecting-memory-leaks-in-kernel.html

A memory leak is a behavior of a program when it consumes memory but never releases it. In user-space, these days, new applications are written mostly in sophisticated, evolved, modern programming languages like C#, Java etc. This releases the burden of memory management from the programmer. Programmers need to manage memory themselves, only when they code in pre-historic programming languages like C ;-) There are nice tools (like Valgrind) that can detect memory leaks, if they happen in user-space. Valgrind won't work in Kernel space. 


The Linux kernel is predominantly written in C, so that programmers can stay close to the hardware. Also there were no large-scale managed languages at the time the project was started. 

There are some interesting projects that help in writing FUSE filesystems via Mono/C# etc. However your grumpy blogger didn't find them to be active and for all practical purposes, C is the default Kernel programming language. 

Few days back, I was tasked with detecting memory leaks in a legacy kernel module that is not in Linus' tree. Code that goes into Linus' tree is usually of high quality and won't have memory leaks because of the rigorous reviews that are performed in LKML. So, any kernel code that is not merged upstream has a high chance of having leaks, among other bad things (so upstream your code, NOW). The simple tutorial below will explain how to detect memory leaks in kernel modules.

kmemleak

There is a nice tool named 'kmemleak' available in the Linux kernel since 2.6.31 to detect memory leaks. This tool is claimed to report a few false positives but that should not stop someone from using it. I was trying to find a kernel-space-leak-detector but did not find any links via Google. Some kernel hackers told me about this tool over IRC and this post is more as a pointer to the "kmemleak" docs, when somone googles for "kernel memory leak detection".

Pre-requisites: 
+ You need to know how to build and install your own kernel. 
+ You need to have 2.6.31 or newer version of the linux kernel
+ It is good if you know how to compile a kernel module. But don't worry if you don't know. You can refer to my previous tutorial for a simple hello-world kernel module. 

So, without further ado, the steps are: 

Step 1: Compile kernel with "CONFIG_DEBUG_KMEMLEAK" option enabled. You can get to this option via: make menuconfig, "Kernel Hacking", "Kernel Memory Leak Detector" , while compiling your kernel. 

Step 2: Increase the config option "Maximum kmemleak early log entires" value to a sufficiently large number like 1200. The default value of 400 may not work correctly in all configurations.

Step 3: Install this kernel and Reboot to this newly configured kernel. Do not be alarmed if your machine is slow.

Step 4: Upon reboot, Check if your debugfs is mounted. Otherwise mount it. If all is well, you should see a file kmemleak under your debugfs mounted location.

mount -t debugfs nodev /sys/kernel/debug/
cat /sys/kernel/debug/kmemleak

The above /sys/kernel/debug/kmemleak file will contain information about any memory leak that has been detected so far since the machine booted. Ideally there should be none, until this point in time. 

Step 5: Now we will see how we can detect a memory leak in a dummy kernel module as follows. Write a dummy kernel module with the following source (hello.c):


#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/vmalloc.h>

/* Never write a function like this ;) */
void myfunc(void)
{
        char *ptr;
        ptr = vmalloc(512);
        ptr = vmalloc(512);
        ptr = vmalloc(512);
}

int hello_init(void)
{
        printk(KERN_ALERT "Hello World");
        myfunc();
        return 0;
}

static void hello_exit(void)
{
        printk(KERN_ALERT "Goodbye World");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Your Name");


Now the most important line in the above code snippet is:
ptr = vmalloc(512);
We allocate memory, as above, in the kernel module but never free this memory. 

Step 6: vi Makefile
EXTRA_CFLAGS=-g
obj-m := hello-kernel.o
hello-kernel-objs := hello.o

Step 7: Generate the kernel-object file hello-kernel.ko in your current directory:
make -C /lib/modules/`uname -r`/build M=`pwd`

All commands from now on require root permission.

Step 7.5: [Optional] At any stage, if you want to clear the memory profiler output so far created, so that we can focus just on the leaks reported from then on, you can do:
echo clear > /sys/kernel/debug/kmemleak

Step 8: Now insert the kernel object
insmod hello-kernel.ko

Step 9: The memory leak detection thread runs periodically. If you want to perform a test at any instant you want, Do:
echo scan > /sys/kernel/debug/kmemleak

Step 10: Now we will check if the leak is detected. Do:
cat /sys/kernel/debug/kmemleak
You should see:

unreferenced object 0xf9061000 (size 512):
comm "insmod", pid 12750, jiffies 14401507 (age 110.217s)
hex dump (first 32 bytes):
1c 0f 00 00 01 12 00 00 2a 0f 00 00 01 12 00 00 ........*.......
38 0f 00 00 01 12 00 00 bc 0f 00 00 01 12 00 00 8...............
backtrace:
[< c10b0001>] create_object+0x114/0x1db
[< c148b4d0>] kmemleak_alloc+0x21/0x3f
[< c10a43e9>] __vmalloc_node+0x83/0x90
[< c10a44b9>] vmalloc+0x1c/0x1e
[< f9055021>] myfunc+0x21/0x23 [hello_kernel]
[< f9058012>] 0xf9058012
[< c1001226>] do_one_initcall+0x71/0x113
[< c1056c48>] sys_init_module+0x1241/0x1430
[< c100284c>] sysenter_do_call+0x12/0x22
[< ffffffff>] 0xffffffff


As you can see in the bold text above, the leak is detected in myfunc function.

Caution: The memory leak detector code may take some time to identify the leaks. So repeat steps 9 and 10, after few minutes, if you don't get the leaks reported first time. You can try to kiss your hand elbow to pass time meanwhile ;-)

Further Reading
+ LWN Article about kmemleak - http://lwn.net/Articles/187979/ 
+ Under Kernel sources directory: Documentation/kmemleak.txt

Thanks a lot to Catalin Marinas for kmemleak and the people at kernelnewbies for helping, not just for this problem but for nuuuumerous people.

<think>我们正在处理关于DMA-BUF内存泄漏检测的问题。根据用户的问题,我们需要提供在Linux内核中检测DMA-BUF内存泄漏的方法和工具。根据引用资料,DMA-BUF是Linux内核的一个子系统,用于在多个设备驱动程序和子系统之间共享缓冲区。内存泄漏可能发生在DMA-BUF的引用计数没有正确释放的情况下。在引用[2]中提到,操作DMA-BUF引用计数的函数有`dma_buf_get()`和`dma_buf_put()`,分别用于增加和减少引用计数。因此,内存泄漏可能是由于`dma_buf_put()`没有被正确调用,导致引用计数不为0,从而无法释放内存。检测内存泄漏的方法通常包括:1.使用内核内置的内存泄漏检测工具,如kmemleak。2.使用动态调试工具,如kprobes、tracepoints等跟踪引用计数的变化。3.使用内存分析工具,如Valgrind(但在内核中不直接适用,需要内核特定的工具)。具体到DMA-BUF,我们可以:-使用kmemleak扫描未释放的DMA-BUF对象。-使用ftrace或perf跟踪`dma_buf_get`和`dma_buf_put`的调用,检查是否成对出现。-在代码中添加自定义的跟踪点,记录DMA-BUF的分配和释放。此外,引用[3]提到DMA-BUF的同步机制(如栅栏)可能导致死锁,这也可能间接导致内存泄漏(例如,某个等待导致资源无法释放)。因此,我们可以建议以下步骤:1.**启用kmemleak**:-配置内核启用`CONFIG_DEBUG_KMEMLEAK`,然后挂载debugfs,通过`/sys/kernel/debug/kmemleak`触发扫描和报告。2.**使用ftrace跟踪函数调用**:-跟踪`dma_buf_get`和`dma_buf_put`函数,查看调用栈和调用次数是否匹配。3.**使用kprobes动态插桩**:-编写kprobe模块,在`dma_buf_get`和`dma_buf_put`处插入钩子,记录每次调用,并统计每个DMA-BUF对象的引用计数变化。4.**使用tracepoints**:-DMA-BUF子系统可能已经内置了一些tracepoints,我们可以通过`perf`或`trace-cmd`来使用它们。5.**检查内核日志**:-启用`CONFIG_DMA_BUF_DEBUG`(如果存在)以获取更多调试信息。6.**自定义调试代码**:-如果问题难以复现,可以在内核中增加调试代码,比如在每次`dma_buf_get`和`dma_buf_put`时打印对象地址和当前引用计数。7.**使用DRM(DirectRenderingManager)的调试工具**:-因为DMA-BUF常用于图形渲染,DRM提供了一些调试工具,如`drm_mm`(用于管理内存)的调试功能。8.**检查文件描述符泄漏**:-由于DMA-BUF通过文件描述符(fd)暴露给用户空间,因此用户空间的fd泄漏也可能导致内核中的DMA-BUF对象无法释放。可以使用`lsof`等工具检查用户空间进程的fd使用情况。9.**使用内存压力测试**:-反复分配和释放DMA-BUF,观察内存使用情况(如`/proc/meminfo`中的Slab信息)是否稳定。根据引用[1]和[4]中关于DMA-BUF的描述,我们还需要注意文件描述符的关闭行为(FD_CLOEXEC标志)以避免泄漏,但这主要是用户空间的泄漏,内核中的泄漏还是引用计数问题。综上所述,我们给出以下回答:</think>### DMA-BUF内存泄漏检测方法 在Linux内核中,DMA-BUF内存泄漏通常由**引用计数未正确归零**导致。以下是系统级的检测方法和工具: --- #### 1. **内核内存泄漏检测工具(kmemleak)** - **原理**:扫描未释放但无指针指向的内核对象[^4]。 - **步骤**: ```bash # 启用kmemleak echo scan > /sys/kernel/debug/kmemleak # 查看泄漏报告 cat /sys/kernel/debug/kmemleak ``` - **输出关键**:查找`dma_buf`相关对象(如`dma_buf_alloc`),显示未释放的调用栈。 --- #### 2. **动态跟踪引用计数** - **使用kprobes跟踪引用函数**: ```c // 示例kprobe模块(跟踪dma_buf_get/put) #include <linux/kprobes.h> static struct kprobe kp_get = { .symbol_name = "dma_buf_get", }; static struct kprobe kp_put = { .symbol_name = "dma_buf_put", }; // 在init函数中注册probe,记录调用次数和调用栈 ``` - **分析**:对比`dma_buf_get`和`dma_buf_put`的调用次数是否匹配。 --- #### 3. **ftrace跟踪DMA-BUF生命周期** - **操作**: ```bash echo 1 > /sys/kernel/debug/tracing/events/dma_buf/enable echo 1 > /sys/kernel/debug/tracing/tracing_on # 复现问题后,查看日志 cat /sys/kernel/debug/tracing/trace ``` - **关键事件**: - `dma_buf_export`(创建) - `dma_buf_release`(释放) - 检查未配对的`export`事件[^2]。 --- #### 4. **文件描述符泄漏检测** - **用户空间工具**: ```bash lsof -p <pid> | grep "dma-buf" # 检查进程持有的dma-buf FD cat /proc/<pid>/fdinfo/<fd> # 查看FD状态 ``` - **内核要求**:确保创建FD时设置`O_CLOEXEC`标志,避免exec继承泄漏[^1]。 --- #### 5. **调试选项启用** 编译内核时开启: - `CONFIG_DMA_BUF_DEBUG`:启用DMA-BUF调试日志 - `CONFIG_REFCOUNT_FULL`:全量引用计数检查 - `CONFIG_DEBUG_OBJECTS`:跟踪内核对象分配/释放 --- #### 6. **自定义调试代码** 在关键函数添加追踪: ```c // 在dma_buf_put()中添加 if (refcount_read(&dmabuf->refcount) == 1) { pr_debug("Releasing dma_buf:%p\n", dmabuf); } else { pr_warn("Unexpected refcount:%d for dma_buf:%p\n", refcount_read(&dmabuf->refcount), dmabuf); } ``` --- ### 常见泄漏场景 1. **跨驱动传递失败**:设备A导出buffer后,设备B导入失败但未释放引用[^4]。 2. **栅栏同步超时**:DMA操作等待栅栏信号超时,导致buffer滞留[^3]。 3. **用户空间FD泄漏**:未关闭dma-buf文件描述符[^1]。 > **提示**:结合`perf mem`分析DMA访问模式,或使用`drgn`工具实时检查内核对象状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值