1:检测内核内存泄漏的功能
scan_block(__bss_start, __bss_stop, NULL, 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.
*/
* 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_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.
*/
* 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 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 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);
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.