核心洩露檢測(kmemleak)

cjok376240497發表於2014-02-24

核心洩露檢測(kmemleak)

原地址:http://blog.csdn.net/lishenglong666/article/details/8287783

介紹:

Kmemleak 提供了一種可選的核心洩漏檢測,其方法類似於跟蹤記憶體收集器。(http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29#Tracing_garbage_collectors)當獨立的物件沒有被釋放時,其報告記錄在 /sys/kernel/debug/kmemleak中。

用法:

CONFIG_DEBUG_KMEMLEAK 在Kernel hacking中被使能,一個核心執行緒每10分鐘(預設值)掃描記憶體,並列印發現新的未引用的物件的數量。

檢視核心列印資訊詳細過程如下:

1、掛載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

   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設定。

功能實現的基本方法原理

通過的kmallocvmallockmem_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是指向這樣的物件。為了減少假性報告的數目,kmemleakkmemleak_

ignorekmemleak_scan_areakmemleak_no_scankmemleak_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:檢測的機理是什麼,如何知道分配的記憶體被引用,或者沒有引用。

  • 關注點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

相關文章