kmemleak 分析


structobjectthreadeach数据结构leak
1:检测内核内存泄漏的功能
2:Documentation/kmemleak.txt
3:内核demo:mm/kmemleak-test.c


对于kmemleak,需要理解下面三点就可以了
1:我们需要知道它能检测哪几种内存泄漏(即用什么方法分配的内存可以检测)
2:内核存在特殊情况,即分配内存但没有引用。使用什么方法可以防止kmemleak report
3:检测的机理是什么,如何知道分配的内存被引用,或者没有引用。


关注点1
kmalloc/kzalloc
vmalloc
kmem_cache_alloc
per_cpu
[Page allocations and ioremap are not tracked]
关注点2
kmemleak_not_leak、kmemleak_ignore、kmemleak_no_scan
这几个函数在内核中被使用,是为了不被kmemleak 打印出来。但是深层次的区别是什么?


kmemleak_not_leak
/**
* kmemleak_not_leak - mark an allocated object as false positive
* @ptr:        pointer to beginning of the object
*
* Calling this function on an object will cause the memory block to no longer
* be reported as leak and always be scanned.
*/
不打印;但是要扫描这个指针所分配的内存的内容。分配数据结构那么该结构本身不打印,但是会扫描结构内部的成员变量,是否引用其他指针。
这个函数往往用在:分配内存的内存永远不会被释放(与内核是一体,vmlinux或者不可移除的模块一类)。
kmemleak_ignore
/**
* kmemleak_ignore - ignore an allocated object
* @ptr:        pointer to beginning of the object
*
* Calling this function on an object will cause the memory block to be
* ignored (not scanned and not reported as a leak). This is usually done when
* it is known that the corresponding block is not a leak and does not contain
* any references to other allocated memory blocks.
*/
既不打印,也不扫描指针所指的数据结构的成员变量。如果知道分配的数据结构内部不包含其他引用(不含指针)。
kmemleak_no_scan
/**
* kmemleak_no_scan - do not scan an allocated object
* @ptr:        pointer to beginning of the object
*
* This function notifies kmemleak not to scan the given memory block. Useful
* in situations where it is known that the given object does not contain any
* references to other objects. Kmemleak will not scan such objects reducing
* the number of false negatives.
*/
该指针本身被扫描,但是内容不会扫描。
关注点3
所谓reference即所分配的内存有指针引用。如果没有任何指针引用那么肯定就是memleak。
所以要查找所有的指针的内容,来寻找其内容是否包含我们已经记录的分配内存的地址(包括在其实地址+size之间)




那么这些指针变量的
1:函数的局部变量
     这些变量本身在栈中,所以需要检测进程的内核栈
2:全局变量(整个系统/模块内)静态变量
     这些变量是存在:ELF的bss/data     
     这些变量可以通过查看vmlinux或者*.ko查看这类指针变量的区段。
     可以通过objdump -x file   
---指针是静态分配
3:指针本身是动态分配的,即动态分配内存块(struct).成员变量是指针
     所以必须要搜索这类动态分配的内存块的内容





通过objdump -x vmlinux
.data
      where global tables, variables, etc. stand. objdump -s -j .data .process.o will hexdump it. 
.bss
      don't look for bits of .bss in your file: there's none. That's where your uninitialized arrays and variable are, and the loader 'knows' they should be filled with zeroes ... there's no point storing more zeroes on your disk than there already are, is it ? 
 .rodata
      that's where your strings go, usually the things you forgot when linking and that cause your kernel not to work. objdump -s -j .rodata .process.o will hexdump it. Note that depending on the compiler, you may have more sections like this.
 
.data..percpu


kmemleak_scan()
data/bss 段扫描
    /* data/bss scanning */
     scan_block(_sdata, _edata, NULL, 1);
     scan_block(__bss_start, __bss_stop, NULL, 1);


data..percpu
#ifdef CONFIG_SMP
     /* per-cpu sections scanning */
     for_each_possible_cpu(i)
          scan_block(__per_cpu_start + per_cpu_offset(i),
                  __per_cpu_end + per_cpu_offset(i), NULL, 1);
#endif


-->>>>以上都是全局指针变量、per_cpu变量


struct pagep[]数组
        /*  
         * Struct page scanning for each node.
         */
        lock_memory_hotplug();
        for_each_online_node(i) {
                pg_data_t *pgdat = NODE_DATA(i);
                unsigned long start_pfn = pgdat->node_start_pfn;
                unsigned long end_pfn = start_pfn + pgdat->node_spanned_pages;
                unsigned long pfn;


                for (pfn = start_pfn; pfn < end_pfn; pfn++) {
                        struct page *page;


                        if (!pfn_valid(pfn))
                                continue;
                        page = pfn_to_page(pfn);
                        /* only scan if page is in use */
                        if (page_count(page) == 0)
                                continue;
                        scan_block(page, page + 1, NULL, 1);
                }   
        }   
        unlock_memory_hotplug();


内核struct page数组是动态分配的,所以也要单独的进行检测。


内核进程栈
if (kmemleak_stack_scan) {
          struct task_struct *p, *g;


          read_lock(&tasklist_lock);
          do_each_thread(g, p) {
               scan_block(task_stack_page(p), task_stack_page(p) +
                       THREAD_SIZE, NULL, 0);
          } while_each_thread(g, p);
          read_unlock(&tasklist_lock);
     
一般遍历内核所有的进程用的是:for_each_process();
但是这里却使用:do_each_thread(){};while_each_thread()


>>>for_each_process:只打印进程;而不打印进程内的线程
>>>do_each_thread(){};while_each_thread():打印进程以及进程内的线程信息。这是因为线程有自己单独的内核栈信息。


分配的内存块的内部


分配一块内存(一般是分配数据结构),内部的成员变量是指针,所以这部分也需要检测。


>>>     scan_gray_list();---->scan_object():
扫描分配内存的全部内容或者部分内容,是否引用其他指针。
    pointer+size
 




问题
1:读代码理解下面的扫描
struct A*a ---> struct A {
                              struct B * b------>struct B {
                                                                       struct C *c ------->struct C


如果struct A *a = NULL.
                                 
<think>嗯,用户问的是kmemleak是在什么时候被加载的,加载顺序是怎样的。我需要先回忆一下kmemleak在内核中的工作机制。kmemleak是内核的一个调试工具,用于检测内存泄漏。用户之前的问题是关于驱动加载前内存泄漏是否能被检测到,现在他们可能想更深入了解kmemleak本身的加载时机,这可能和内核初始化的顺序有关。 首先,kmemleak的加载时间取决于它是作为模块编译还是直接编译进内核。如果是作为模块(动态加载),通常会在用户空间初始化之后,通过insmod或modprobe命令手动加载,或者在启动脚本中自动加载。这种情况下,kmemleak的初始化会比较晚,可能无法捕捉到启动早期的内存分配。 如果kmemleak被编译进内核(built-in),那么它会在内核启动的早期阶段初始化,可能在子系统初始化期间。这样,它能够跟踪到更多早期的内存分配,包括那些在内核启动过程中发生的分配。用户之前的问题提到驱动加载前的泄漏,如果kmemleak是built-in的,可能就能检测到,而如果是模块则不行。 接下来是加载顺序的问题。内核启动过程有多个阶段,包括start_kernel、各种子系统的初始化等。kmemleak如果是built-in,可能在subsys_initcall或device_initcall阶段初始化。需要查看内核源码中的初始化函数修饰符,比如module_init或其他级别的initcall。例如,kmemleak的初始化函数可能被标记为early_initcall,这样它会在较早的阶段被调用,从而更早地开始跟踪内存分配。 用户可能想知道这些细节,以便配置他们的内核,确保kmemleak能够尽早加载,从而检测到更多的内存泄漏。特别是如果他们遇到启动阶段的内存泄漏问题,了解加载顺序可以帮助他们调整配置,比如将kmemleak编译进内核而不是作为模块,或者调整初始化级别。 另外,需要提到kmemleak的初始化流程,比如在start_kernel函数中的初始化步骤,或者作为模块时的加载流程。可能还需要对比不同配置下的加载时机,以及这对内存泄漏检测的影响。例如,当作为模块时,kmemleak只能在加载后开始记录,而之前的分配不会被跟踪,这可能导致漏检。 还要注意用户可能存在的误区,比如认为kmemleak无论何时加载都能检测所有泄漏,但实际上它的检测范围受限于加载时间。因此,明确加载顺序和时机对于正确使用kmemleak至关重要。最后,可能需要提供一些建议,如将kmemleak设为built-in,或结合其他工具来覆盖早期阶段的泄漏检测。</think>**kmemleak的加载时机和顺序**取决于其在内核中的**编译方式**(模块或内置)和**初始化优先级**。以下是详细分析: --- ### **1. 编译方式决定加载时机** #### **(1) 作为模块(`CONFIG_DEBUG_KMEMLEAK=m`)** - **加载时机**: 在用户空间初始化完成后(例如通过`insmod`或`modprobe`命令手动加载,或由系统启动脚本自动加载)。 - **特点**: 仅能跟踪加载后的内存分配,**无法检测内核启动早期的泄漏**。 #### **(2) 内置到内核(`CONFIG_DEBUG_KMEMLEAK=y`)** - **加载时机**: 在内核启动的**早期阶段**(`start_kernel()`函数中调用`kmemleak_init()`)。 - **关键初始化点**: 在`start_kernel()` -> `setup_arch()` -> `mm_init()`中完成初始化,早于大多数子系统和驱动的加载。 - **特点**: 可跟踪内核启动阶段(如`start_kernel()`、`initcall`)的内存分配,覆盖范围更广。 --- ### **2. 加载顺序与初始化优先级** 内核初始化通过**`initcall`机制**划分优先级,kmemleak的初始化优先级直接影响其加载顺序: - **kmemleak初始化函数**: 通过`early_initcall(kmemleak_late_init)`或`device_initcall()`注册(具体版本可能不同)。 - **典型加载顺序**: 1. 内核启动入口`start_kernel()` 2. 架构相关初始化(`setup_arch()`) 3. 内存管理初始化(`mm_init()`,包含`kmemleak_init()`) 4. 其他子系统初始化(如调度器、文件系统) 5. 驱动加载(通过`module_init()`或`subsys_initcall()`) --- ### **3. 不同场景下的跟踪能力** | **场景** | **内置到内核** | **作为模块加载** | |------------------------|----------------|------------------| | 启动阶段内存分配 | ✅ 可检测 | ❌ 不可检测 | | 驱动加载前的内存泄漏 | ✅ 可检测 | ❌ 不可检测 | | 驱动加载后的内存泄漏 | ✅ 可检测 | ✅ 可检测 | --- ### **4. 验证方法** #### **(1) 查看初始化日志** 通过`dmesg`搜索关键字`kmemleak`,观察其初始化时间戳: ```bash dmesg | grep "kmemleak" ``` 输出示例: ``` [ 0.000000] kmemleak: Kernel memory leak detector initialized ``` #### **(2) 检查系统启动顺序** 若kmemleak为模块,使用`lsmod`查看是否已加载: ```bash lsmod | grep kmemleak ``` --- ### **5. 总结** - **内置到内核**:在内核启动早期加载,可检测启动阶段和驱动加载前的泄漏。 - **作为模块**:加载顺序较晚,仅能检测模块加载后的泄漏。 - **建议**: - 需检测全生命周期内存泄漏时,**将kmemleak编译进内核**。 - 结合`KASAN`或启动参数`memtest`增强早期内存问题检测。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值