內核泄露檢測(kmemleak)
介紹:
Kmemleak 提供了一種可選的內核泄漏檢測,其方法相似於跟蹤內存收集器。(http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29#Tracing_garbage_collectors)當獨立的對象沒有被釋放時,其報告記錄在 /sys/kernel/debug/kmemleak中。 node
用法:
CONFIG_DEBUG_KMEMLEAK 在Kernel hacking中被使能,一個內核線程每10分鐘(默認值)掃描內存,並打印發現新的未引用的對象的數量。 linux
查看內核打印信息詳細過程以下: 算法
一、掛載debugfs文件系統 數組
mount -t debugfs nodev /sys/kernel/debug/ 數據結構
2、開啓內核自動檢測線程 函數
echo scan > /sys/kernel/debug/kmemleak 工具
3、查看打印信息 性能
cat /sys/kernel/debug/kmemleak 測試
4、清除內核檢測報告,新的內存泄露報告將從新寫入/sys/kernel/debug/kmemleak this
echo clear > /sys/kernel/debug/kmemleak
內存掃描參數能夠進行修改經過向/sys/kernel/debug/kmemleak 文件寫入。 參數使用以下:
off 禁用kmemleak(不可逆)
stack=on 啓用任務堆棧掃描(default)
stack=off 禁用任務堆棧掃描
scan=on 啓動自動記憶掃描線程(default)
scan=off 中止自動記憶掃描線程
scan=<secs> 設置n秒內自動記憶掃描,默認600s
scan 開啓內核掃描
clear 清除內存泄露報告
dump=<addr> 轉存信息對象在<addr>
經過「kmemleak = OFF」,也能夠在啓動時禁用Kmemleak在內核命令行。在初始化kmemleak以前,內存的分配或釋放這些動做被存儲在一個前期日誌緩衝區。這個緩衝區的大小經過配CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE設置。
功能實現的基本方法原理
經過的kmalloc、vmalloc、kmem_cache_alloc等內存分配會跟蹤其指針,連同其餘
的分配大小和堆棧跟蹤信息,存儲在PRIO搜索樹。
相應的釋放函數調用跟蹤和指針就會從kmemleak數據結構中移除。
分配的內存塊,被認爲是獨立的,若是沒有指針指向它起始地址或塊的內部的任何位置,能夠發現掃描內存(包括已保存的寄存器)。這意味着,有可能沒有辦法爲內核經過所分配的地址傳遞塊到一個釋放函數,所以,該塊被認爲是一個內存泄漏。
掃描算法步驟:
1。標記的全部分配對象爲白色(稍後將剩餘的白色物體
考慮獨立的)
2。掃描存儲器與所述數據片斷和棧開始,檢查對地址的值存儲在PRIO搜索樹。若是
一個白色的對象的指針被發現,該對象將被添加到黑名單
3。掃描的灰色對象匹配的地址(一些白色物體能夠變成黑色,並添加結束時的黑名單),直到黑色集結束
4。剩下的白色物體被認爲是獨立兒,並報告寫入/sys/kernel/debug/kmemleak。
一些分配的內存塊的指針在內核的內部數據結構和它們不能被檢測爲孤兒。對
避免這種狀況,kmemleak也能夠存儲的數量的值,指向一個
內的塊的地址範圍內的地址,須要找到使
塊不被認爲是泄漏。其中一個例子是使用vmalloc()函數。
Kmemleak API
------------
見include / linux / kmemleak.h中的函數原型的頭。
kmemleak_init - 初始化kmemleak
kmemleak_alloc - 一個內存塊分配的通知
kmemleak_alloc_percpu - 通知的一個percpu的內存塊分配
kmemleak_free - 通知的內存塊釋放
kmemleak_free_part - 通知釋放部份內存塊
kmemleak_free_percpu - 一個percpu內存塊釋放的通知
kmemleak_not_leak - 當不是泄露時,標記對象
kmemleak_ignore - 當泄漏時不掃描或報告對象
kmemleak_scan_area - 添加掃描區域內的內存塊
kmemleak_no_scan - 不掃描的內存塊
kmemleak_erase - 刪除一個指針變量的舊值
kmemleak_alloc_recursive - 爲kmemleak_alloc,只檢查遞歸
kmemleak_free_recursive - 爲kmemleak_free,只檢查遞歸
處理假陽性/陰性
--------------------------------------
對於假性的內存泄漏,但不須要報告的,因爲值的內存掃描過程當中發現kmemleak是指向這樣的對象。爲了減小假性報告的數目,kmemleak提供kmemleak_
ignore,kmemleak_scan_area,kmemleak_no_scan,kmemleak_erase的功能,能夠指定指針掃描方式,他們的掃描默認狀況下不啓用。
對於不能肯定是不是內存泄露的,kmemleak提供kmemleak_not_leak。kmemleak_ignore的功能能夠指定固定類型的數據是否須要掃描或打印,以上具體函數分析詳見3.3詳細處理處理過程及功能函數分析。
有的泄露只是瞬間的,尤爲是在SMP系統,由於指針暫時存儲在CPU的寄存器或棧。當內存泄漏時Kmemleak定義MSECS_MIN_AGE(默認爲1000)一個對象的最低時間。
限制和缺點
-------------------------
主要缺點是減小了內存分配和性能釋放。爲了不其餘開銷,只進行內存掃描,當在/ sys /kernel/debug/ kmemleak文件被讀取。無論怎樣,這個工具是用於調試目的,其表現的性能不是重要的。爲了保持算法簡單,kmemleak的值指向任何掃描一個塊的地址範圍內的地址。這可能會致使增長假陰性的報告。然而,它包括真正的內存泄漏,最終內存泄露將變得可見。
假陰性的另外一個來源是數據存儲在非指針值。
在將來的版本中,kmemleak只能掃描指針成員中分配的結構。此功能解決了許多上述假陰性的狀況下。
該工具可能存在誤報。這些個案的分配塊可能不須要被釋放(如一些在init_call功能的狀況下),這樣的指針經過其餘方法計算,與一般的container_of宏或指針被存儲在一個位置相比不會被kmemleak掃描。頁分配和ioremap不被跟蹤
測試的特定部分kmemleak
---------------------------------------
在初始啓動時,/sys/kernel/debug/kmemleak輸出頁面比較多。這樣的狀況下,當檢測指定已經開發的代碼錯誤時,能夠經過清除/sys/kerner/debug/kmemleak的輸出。經過啓動kmemleak的掃描後,你能夠找到新的未引用的對象,這應該與測試特定的代碼段。
詳細步驟以下:
要測試的關鍵部分以前須要清除kmemleak報告:
echo clear > /sys/kernel/debug/kmemleak
測試你的內核或模塊...
echo scan =5> /sys/kernel/debug/kmemleak
而後像往常同樣查看報告:
cat /sys/kernel/debug/kmemleak
已經測試的實例詳見內核文檔kmenleak_test.txt文檔
1:檢測內核內存泄漏的功能
2:Documentation/kmemleak.txt
3:內核demo:mm/kmemleak-test.c
對於kmemleak,須要理解下面三點就能夠了
1:咱們須要知道它能檢測哪幾種內存泄漏(即用什麼方法分配的內存能夠檢測)
2:內核存在特殊狀況,即分配內存但沒有引用。使用什麼方法能夠防止kmemleak report
3:檢測的機理是什麼,如何知道分配的內存被引用,或者沒有引用。
kmalloc/kzalloc
vmalloc
kmem_cache_alloc
per_cpu
[Page allocations and ioremap are not tracked]
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.
*/
該指針自己被掃描,可是內容不會掃描。
所謂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
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