核心記憶體洩漏檢測器¶
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 資料結構中刪除。
如果透過掃描記憶體(包括儲存的暫存器)無法找到指向其起始地址或塊內任何位置的指標,則分配的記憶體塊被認為是孤立的。這意味著核心可能無法將分配的塊的地址傳遞給釋放函式,因此該塊被認為是記憶體洩漏。
掃描演算法步驟:
將所有物件標記為白色(剩餘的白色物件稍後將被認為是孤立的)
從資料段和堆疊開始掃描記憶體,檢查這些值與儲存在 rbtree 中的地址是否匹配。如果找到指向白色物件的指標,則將該物件新增到灰色列表
掃描灰色物件以查詢匹配的地址(一些白色物件可以變為灰色並新增到灰色列表的末尾),直到灰色集完成
剩餘的白色物件被認為是孤立的,並透過 /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- 初始化 kmemleakkmemleak_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_physkmemleak_free_part_physkmemleak_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 結果。