kmemleak的使用【轉】

轉自:https://blog.csdn.net/lishenglong666/article/details/8287783#t6

內核泄露檢測(kmemleak)

介紹:

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

 

功能實現的基本方法原理

通過的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:檢測的機理是什麼,如何知道分配的內存被引用,或者沒有引用。
 
  • 關注點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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章