英語

核心記憶體洩漏檢測器

Kmemleak 提供了一種檢測可能的核心記憶體洩漏的方法,其方式類似於追蹤垃圾回收器,不同之處在於,孤立物件不會被釋放,而僅透過 /sys/kernel/debug/kmemleak 報告。Valgrind 工具(memcheck --leak-check)使用類似的方法來檢測使用者空間應用程式中的記憶體洩漏。

用法

必須啟用“核心駭客”中的 CONFIG_DEBUG_KMEMLEAK。核心執行緒每 10 分鐘(預設情況下)掃描一次記憶體,並列印找到的新未引用物件的數量。如果 debugfs 尚未掛載,請使用以下命令掛載:

# mount -t debugfs nodev /sys/kernel/debug/

要顯示所有可能的掃描到的記憶體洩漏的詳細資訊:

# cat /sys/kernel/debug/kmemleak

要觸發中間記憶體掃描:

# echo scan > /sys/kernel/debug/kmemleak

要清除所有當前可能的記憶體洩漏的列表:

# echo clear > /sys/kernel/debug/kmemleak

然後,在再次讀取 /sys/kernel/debug/kmemleak 時,會出現新的洩漏。

請注意,孤立物件按照它們分配的順序被列出,並且列表開頭的物件可能導致其他後續物件被報告為孤立物件。

可以透過寫入 /sys/kernel/debug/kmemleak 檔案在執行時修改記憶體掃描引數。支援以下引數:

  • off

    停用 kmemleak(不可逆)

  • stack=on

    啟用任務堆疊掃描(預設)

  • stack=off

    停用任務堆疊掃描

  • scan=on

    啟動自動記憶體掃描執行緒(預設)

  • scan=off

    停止自動記憶體掃描執行緒

  • scan=<secs>

    設定自動記憶體掃描週期,以秒為單位(預設 600,0 停止自動掃描)

  • scan

    觸發記憶體掃描

  • clear

    清除當前記憶體洩漏嫌疑物件的列表,透過將所有當前報告的未引用物件標記為灰色來完成,如果 kmemleak 已被停用,則釋放所有 kmemleak 物件。

  • dump=<addr>

    轉儲在 <addr> 處找到的物件的資訊

也可以在啟動時透過在核心命令列上傳遞 kmemleak=off 來停用 Kmemleak。

在 kmemleak 初始化之前,可能會分配或釋放記憶體,並且這些操作儲存在早期日誌緩衝區中。此緩衝區的大小透過 CONFIG_DEBUG_KMEMLEAK_MEM_POOL_SIZE 選項配置。

如果啟用了 CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF,則預設情況下停用 kmemleak。在核心命令列上傳遞 kmemleak=on 可啟用該功能。

如果您收到類似“寫入 stdout 時出錯”或“write_loop: 無效引數”的錯誤,請確保 kmemleak 已正確啟用。

基本演算法

透過 kmalloc()vmalloc()kmem_cache_alloc() 等函式進行的記憶體分配會被跟蹤,並且指標以及大小和堆疊跟蹤等附加資訊會儲存在 rbtree 中。相應的釋放函式呼叫會被跟蹤,並且指標會從 kmemleak 資料結構中刪除。

如果透過掃描記憶體(包括儲存的暫存器)無法找到指向其起始地址或塊內任何位置的指標,則分配的記憶體塊被認為是孤立的。這意味著核心可能無法將分配的塊的地址傳遞給釋放函式,因此該塊被認為是記憶體洩漏。

掃描演算法步驟:

  1. 將所有物件標記為白色(剩餘的白色物件稍後將被認為是孤立的)

  2. 從資料段和堆疊開始掃描記憶體,檢查這些值與儲存在 rbtree 中的地址是否匹配。如果找到指向白色物件的指標,則將該物件新增到灰色列表

  3. 掃描灰色物件以查詢匹配的地址(一些白色物件可以變為灰色並新增到灰色列表的末尾),直到灰色集完成

  4. 剩餘的白色物件被認為是孤立的,並透過 /sys/kernel/debug/kmemleak 報告

一些分配的記憶體塊的指標儲存在核心的內部資料結構中,並且無法檢測為孤立物件。為了避免這種情況,kmemleak 還可以儲存指向塊地址範圍內地址的值的數量,需要找到這些值,以便該塊不被認為是洩漏。一個例子是 __vmalloc()。

使用 kmemleak 測試特定部分

在初始啟動時,您的 /sys/kernel/debug/kmemleak 輸出頁面可能非常廣泛。如果您在開發時有非常多的錯誤程式碼,也可能出現這種情況。為了解決這些情況,您可以使用“clear”命令從 /sys/kernel/debug/kmemleak 輸出中清除所有報告的未引用物件。透過在“clear”之後發出“scan”,您可以找到新的未引用物件;這應該有助於測試程式碼的特定部分。

要在按需測試關鍵部分並清理 kmemleak,請執行以下操作:

# echo clear > /sys/kernel/debug/kmemleak
... test your kernel or modules ...
# echo scan > /sys/kernel/debug/kmemleak

然後像往常一樣使用以下命令獲取您的報告:

# cat /sys/kernel/debug/kmemleak

釋放 kmemleak 內部物件

為了允許在使用者停用 kmemleak 或由於致命錯誤導致 kmemleak 被停用後訪問先前發現的記憶體洩漏,當停用 kmemleak 時,不會釋放內部 kmemleak 物件,並且這些物件可能會佔用物理記憶體的很大一部分。

在這種情況下,您可以使用以下命令回收記憶體:

# echo clear > /sys/kernel/debug/kmemleak

Kmemleak API

有關函式原型,請參見 include/linux/kmemleak.h 標頭。

  • kmemleak_init - 初始化 kmemleak

  • kmemleak_alloc - 通知記憶體塊分配

  • kmemleak_alloc_percpu - 通知每個 CPU 記憶體塊分配

  • kmemleak_vmalloc - 通知 vmalloc() 記憶體分配

  • kmemleak_free - 通知記憶體塊釋放

  • kmemleak_free_part - 通知部分記憶體塊釋放

  • kmemleak_free_percpu - 通知每個 CPU 記憶體塊釋放

  • kmemleak_update_trace - 更新物件分配堆疊跟蹤

  • kmemleak_not_leak - 將物件標記為非洩漏

  • kmemleak_transient_leak - 將物件標記為瞬時洩漏

  • kmemleak_ignore - 不要掃描或報告物件為洩漏

  • kmemleak_scan_area - 在記憶體塊中新增掃描區域

  • kmemleak_no_scan - 不要掃描記憶體塊

  • kmemleak_erase - 擦除指標變數中的舊值

  • kmemleak_alloc_recursive - 作為 kmemleak_alloc,但檢查遞迴性

  • kmemleak_free_recursive - 作為 kmemleak_free,但檢查遞迴性

以下函式將物理地址作為物件指標,並且僅當該地址具有低記憶體對映時才執行相應的操作

  • kmemleak_alloc_phys

  • kmemleak_free_part_phys

  • kmemleak_ignore_phys

處理誤報/漏報

誤報是真正的記憶體洩漏(孤立物件),但 kmemleak 沒有報告,因為在記憶體掃描期間發現的值指向這些物件。為了減少誤報的數量,kmemleak 提供了 kmemleak_ignore、kmemleak_scan_area、kmemleak_no_scan 和 kmemleak_erase 函式(參見上文)。任務堆疊也會增加誤報的數量,並且預設情況下不啟用它們的掃描。

誤報是被錯誤報告為記憶體洩漏(孤立物件)的物件。對於已知不是洩漏的物件,kmemleak 提供了 kmemleak_not_leak 函式。如果已知記憶體塊不包含其他指標,也可以使用 kmemleak_ignore,並且將不再掃描該記憶體塊。

一些報告的洩漏只是暫時的,尤其是在 SMP 系統上,因為指標暫時儲存在 CPU 暫存器或堆疊中。Kmemleak 定義了 MSECS_MIN_AGE(預設為 1000),表示物件被報告為記憶體洩漏的最小年齡。

侷限性和缺點

主要的缺點是記憶體分配和釋放的效能降低。為了避免其他損失,僅在讀取 /sys/kernel/debug/kmemleak 檔案時才執行記憶體掃描。無論如何,此工具旨在用於除錯目的,在這種情況下,效能可能不是最重要的要求。

為了保持演算法的簡單性,kmemleak 掃描指向塊地址範圍內任何地址的值。這可能會導致誤報數量增加。但是,真正的記憶體洩漏最終可能會變得可見。

另一個誤報的來源是儲存在非指標值中的資料。在未來的版本中,kmemleak 可能只會掃描分配結構中的指標成員。此功能將解決上述許多誤報情況。

該工具可以報告誤報。這些情況是指分配的塊不需要釋放(init_call 函式中的某些情況),指標透過container_of宏以外的其他方法計算,或者指標儲存在 kmemleak 未掃描的位置。

頁面分配和 ioremap 不會被跟蹤。

使用 kmemleak-test 進行測試

要檢查您是否已設定好所有內容以使用 kmemleak,您可以使用 kmemleak-test 模組,該模組會故意洩漏記憶體。將 CONFIG_SAMPLE_KMEMLEAK 設定為模組(不能用作內建),並在啟用 kmemleak 的情況下啟動核心。載入模組並使用以下命令執行掃描:

# modprobe kmemleak-test
# echo scan > /sys/kernel/debug/kmemleak

請注意,您可能不會立即或在第一次掃描時獲得結果。當 kmemleak 獲得結果時,它將記錄 kmemleak: <count of leaks> new suspected memory leaks。然後讀取檔案以檢視它們

# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff89862ca702e8 (size 32):
  comm "modprobe", pid 2088, jiffies 4294680594 (age 375.486s)
  hex dump (first 32 bytes):
    6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
    6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5  kkkkkkkkkkkkkkk.
  backtrace:
    [<00000000e0a73ec7>] 0xffffffffc01d2036
    [<000000000c5d2a46>] do_one_initcall+0x41/0x1df
    [<0000000046db7e0a>] do_init_module+0x55/0x200
    [<00000000542b9814>] load_module+0x203c/0x2480
    [<00000000c2850256>] __do_sys_finit_module+0xba/0xe0
    [<000000006564e7ef>] do_syscall_64+0x43/0x110
    [<000000007c873fa6>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
...

使用 rmmod kmemleak_test 刪除模組也應該觸發一些 kmemleak 結果。