內存泄漏是指由于疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內存。內存泄漏并非指內存在物理上的消失,而是應用程序分配某段內存后,由于設計錯誤,導致在釋放該段內存之前就失去了對該段內存的控制,從而造成了內存的浪費。
在Linux中調試內存泄漏,可以使用以下工具:
Valgrind:Valgrind是一個用于檢測C/C++程序中內存錯誤的工具,它可以檢測到內存泄漏。安裝后,使用valgrind --leak-check=full your_program來運行你的程序,它會在程序結束時報告內存泄漏的詳細信息。
GDB:GDB是Linux下的調試工具,可以用來檢查程序在運行時的內存使用情況。但是GDB本身不能直接用來檢測內存泄漏,但可以配合其他工具如gcore來生成core dump文件,然后用像Valgrind這樣的工具分析這個core dump。
MALLOC_TRACE:如果你的程序使用了mmap或其他方式分配了大量的內存,但是沒有及時釋放,你可以設置環(huán)境變量MALLOC_TRACE來跟蹤內存分配。例如:
export MALLOC_TRACE=memory.log./your_programmalloc_trace memory.log
這將會在memory.log文件中記錄所有的內存分配和釋放操作,然后你可以手動檢查這個文件來查找可能的內存泄漏。
Application Instrumentation:你可以在你的代碼中添加自定義的內存分配和釋放的跟蹤代碼,記錄每個內存塊的分配和釋放信息,這樣可以更精確地定位內存泄漏的位置。
DTrace/SystemTap:這些動態(tài)跟蹤工具可以用來跟蹤程序的內存分配和釋放行為,幫助定位內存泄漏。
LeakSanitizer:如果你在使用LLVM/Clang編譯器,可以使用LeakSanitizer來檢測內存泄漏。在編譯時加上-fsanitize=leak標志,運行時會報告內存泄漏的位置。
選擇合適的工具根據(jù)你的程序和需求進行使用。通常情況下,Valgrind是最簡單和最直接的選擇。
我們平時開發(fā)過程中不可避免的會遇到內存泄漏問題,你是如何排查的呢?估計你是使用下面這幾個工具吧?
valgrind
mtrace
dmalloc
ccmalloc
memwatch
debug_new
這里程序喵向大家推薦新的一個排查內存泄漏的工具:AddressSanitizer(ASan),該工具為gcc自帶,4.8以上版本都可以使用,支持Linux、OS、Android等多種平臺,不止可以檢測內存泄漏,它其實是一個內存錯誤檢測工具,可以檢測的問題有:
內存泄漏
堆棧和全局內存越界訪問
free后繼續(xù)使用
局部內存被外層使用
Initialization order bugs(中文不知道怎么翻譯才好,后面有代碼舉例,重要)
1、Kmemleak介紹
在Linux內核開發(fā)中,Kmemleak是一種用于檢測內核中內存泄漏的工具。
內存泄漏指的是程序中已經(jīng)不再使用的內存沒有被妥善地釋放,導致內存的浪費。內核中的內存泄漏同樣會導致系統(tǒng)性能下降、系統(tǒng)崩潰等問題。
Kmemleak能夠檢測內核中的內存泄漏,通過檢測內核中未被釋放但又無法找到其使用位置的內存,進一步定位、修復內存泄漏的問題。
在用戶空間,我們常用Valgrind來檢測;在內核空間,我們常用Kmemleak來檢測。
2、如何使用Kmemleak
2.1 內核配置
內核打開相應配置:
CONFIG_DEBUG_KMEMLEAK:Kmemleak被加入到內核
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE設置為16000:該參數(shù)為記錄內存泄露信息的內存池,越大記錄信息越多。
CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF :Kmemleak默認開關狀態(tài)
依賴的配置:
CONFIG_DEBUG_KERNEL:打開內核調試功能
CONFIG_DEBUG_FS:需要借助到debugfsCONFIG_STACKTRACE:記錄進程的堆棧信息
2.2 用戶空間配置
我們要想使用Kmemleak,需要掛在debugfs,來查看泄露的情況。
進入文件系統(tǒng)后,進行掛載:
代碼語言:javascript
復制
mount -t debugfs nodev /sys/kernel/debug/ # 掛在debugfs
設置掃描時間:
代碼語言:javascript
復制
echo scan=10 > /sys/kernel/debug/kmemleak # 10S掃描一次
默認內存泄露檢測時間為10min,上面設置為10s一次
查看泄露情況:
代碼語言:javascript
復制
cat /sys/kernel/debug/kmemleak # 查看內存泄露情況
其他指令:
代碼語言:javascript
復制
echo scan > /sys/kernel/debug/kmemleak #觸發(fā)一次掃描
echo clear > /sys/kernel/debug/kmemleak #清除當前 kmemleak 記錄的泄露信息
echo off > /sys/kernel/debug/kmemleak #關閉kmemleak(不可逆轉的)
echo stack=off > /sys/kernel/debug/kmemleak #關閉任務棧掃描
echo stack=on > /sys/kernel/debug/kmemleak #使能任務棧掃描
echo scan=on > /sys/kernel/debug/kmemleak #啟動自動內存掃描線程
echo scan=off > /sys/kernel/debug/kmemleak #停止自動內存掃描線程
echo scan= > /sys/kernel/debug/kmemleak#設置自動掃描線程掃描間隔,默認是600,設置0則是停止掃描
echo dump= > /sys/kernel/debug/kmemleak #dump某個地址的內存塊信息,比如上面的echo dump=0xffffffc008efd200 > /sys/kernel/debug/kmemleak即可查看詳細信息
2.3 通過Linux啟動參數(shù)控制開關
Kmemleak的默認開關狀態(tài)可以通過CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF 配置來控制,當然也可以通過向Linux內核啟動參數(shù)中加入kmemleak=off來控制。
3、Kmemleak原理
Kmemleak提供了一種跟蹤垃圾回收器tracing garbage collector的原理,來檢測內核中存在的內存泄露,其不同之處在于:孤立的對象并沒有被釋放掉,而是通過/sys/kernel/debug/kmemleak僅僅被報告。
這種方法同樣應用于Valgrind中,不過該工具主要用于檢測用戶空間不同應用的內存泄露情況。在用戶空間,我們常用Valgrind來檢測應用進程;在內核空間,我們常用Kmemleak來檢測內核代碼。
通過kmalloc()、vmalloc()、kmem_cache_alloc()等函數(shù)分配內存時,會跟蹤指針,堆棧等信息,將其存儲在一個紅黑樹中。
同時跟蹤相應的釋放函數(shù)調用,并從kmemleak數(shù)據(jù)結構中刪除指針。
簡單理解:相當于追蹤內存分配相關接口,記錄分配內存的首地址,堆棧大小等信息,在內存釋放階段將其刪除。
我們通過查看相關內核文檔可知,內存泄露檢測的掃描算法步驟如下:
將所有對象標記為白色(最后剩余的白色對象將被視為孤立對象)
從數(shù)據(jù)段和堆棧開始掃描內存,根據(jù)紅黑樹中存儲的地址信息來檢查值,如果找到指向白色對象的指針,則添加到灰色列表
掃描灰色列表以查找地址匹配的對象,直到灰色列表完成
剩下的白色對象被視為孤立對象,并通過/sys/kernel/debug/kmemleak進行報告
4、Kmemleak API接口
代碼語言:javascript
復制
kmemleak_init - 初始化 kmemleak
kmemleak_alloc - 內存塊分配通知
kmemleak_alloc_percpu - 通知 percpu 內存塊分配
kmemleak_vmalloc - 通知 vmalloc() 內存分配
kmemleak_free - 通知內存塊釋放
kmemleak_free_part - 通知釋放部分內存塊
kmemleak_free_percpu - 通知 percpu 內存塊釋放
kmemleak_update_trace - 更新對象分配堆棧跟蹤
kmemleak_not_leak - 將對象標記為非泄漏
kmemleak_ignore - 不掃描或報告對象泄漏
kmemleak_scan_area - 在內存塊內添加掃描區(qū)域
kmemleak_no_scan - 不掃描內存塊
kmemleak_erase - 擦除指針變量中的舊值
kmemleak_alloc_recursive - 作為kmemleak_alloc,但檢查遞歸性
kmemleak_free_recursive - 作為kmemleak_free,但檢查遞歸性
5、Kmemleak特殊情況
漏報:真正內存泄露了,但是未報告,因為在內存掃描期間找到的值指向此類對象。為了減少誤報的數(shù)量,kmemleak提供了kmemleak_ignore,kmemleak_scan_area,kmemleak_no_scan和kmemleak_erase功能
誤報:實際沒有泄露,但是卻錯誤的報告了內存泄露。kmemleak提供了kmemleak_not_leak功能。
6、Kmemleak驗證
內核也提供了一個示例:kmemleak-test模塊,該模塊用以判斷是否打開了Kmemleak功能。通過配置CONFIG_DEBUG_KMEMLEAK_TEST選項可以選擇。
代碼語言:javascript
復制
# modprobe kmemleak-test
# echo scan > /sys/kernel/debug/kmemleak
代碼語言:javascript
復制
# 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
...