HeapSnap工具原理及其应用
HeapSnap工具原理及其應用
- HeapSnap工具原理及其應用
- 簡介
- HeapSnap工具演示
- HeapSnap工具的實現原理
簡介
HeapSnap工具,其名稱源于Heap Snapshot,意即堆內存快照。其實現方式是:在不同的時間點上保存堆內存的快照,然后對比這些不同時間點的快照,找出導致內存增長的泄露點。
HeapSnap工具專門用于處理Android系統中native進程的heap內存泄露問題。它的實現是基于Android已有的libc debug機制,對目標進程的影響小,易于使用,且解決問題的概率高。
源碼下載: https://github.com/albuer/heapsnap
HeapSnap工具演示
演示環境: Android5.1- 使能malloc leak調試開關
setprop libc.debug.malloc 1 - 啟動測試程序,該程序每3秒鐘泄露4KB大小的內存,測試代碼如下:
- 向目標進程注入代碼,從而讓目標進程擁有保存heap快照的功能
heapsnap -p <進程ID> -l libheapsnap.so 保存heap快照
kill -21 <進程ID>從logcat信息中可以看到heap快照被保存在/data/local/tmp/heap_snap/proc_7104_0000.heap,其內容如下:
- Total memory: 103424,指的是進程申請的堆內存大小,此處是103424 Bytes
- Allocation records: 2,總共有2條內存分配路徑
- size 4096, dup 25, 0xb6ec576e, … ,size指的是每塊內存的大小,此處為4096Bytes,dup指重復的次數,此處為25,表示這個內存分配路徑被調用了25次,后面的地址就是backtrace,以及對backtrace解析后的信息。
這個demo程序中只有兩條heap分配記錄,第一條的size為4096,dup為25,表明這個heap分配路徑被重復執行了25次,且分配的內存都沒有釋放掉,因些我們能夠很明顯看出這個內存分配路徑就是泄露點了。
從backtrace我們可以看到調用malloc函數的位置是
#02 pc 000004ba /system/bin/leak_test
我們調用addr2line查詢地址”0x000004ba”所指向的代碼行:
arm-linux-gnueabi-addr2line -ife out/target/product/rk3288/symbols/system/bin/leak_test 0x4ba
test
/home/cmy/WORK/android-src/android5.1-vr/external/heapsnap/leak_test.c:7
通過addr2line命令反查到”0x4ba”這個地址指向leak_test.c文件的第7行,foo()函數。
在實際的程序中,heap快照中的分配記錄通常都是非常多的,不容易找出哪個內存分配路徑是泄露點,此時可以通過對比不同時間點上,相同內存分配路徑下的dup次數來做出判斷,如果某個分配路徑的dup次數處于持續增長的狀態,那么它就很可能是一個泄露點了。
HeapSnap工具的實現原理
獲取進程的heap信息
我們首先來看下在Android中,進程的heap分配的backtrace信息是如何被保存以及如何讀取的。方法很簡單,只需要先打開malloc調試開關之后,再執行目標進程,那么在這個新運行的進程內發生的所有heap分配的backtrace信息就會被記錄下來,之后在該進程內調用get_malloc_leak_info函數即可獲取到所有前面保存的heap’s backtrace記錄。
malloc調試開關
調用’setprop libc.debug.malloc 1’設置好屬性后,malloc開關就使能了且被設置為leak模式(注:在Android7以及之后的版本,設置屬性”setprop libc.debug.malloc.options backtrace”)。
接著,我們開始執行目標進程,進行啟動過程上中會初始化一個加載器,在Android中是/system/bin/linker
通過加載器linker,把所有需要用到的so文件都加載進來。
在linker加載so文件過程中,會自動執行so文件內的.init/.init_array section代碼
在libc.so中,定義了一個函數”attribute((constructor)) static void libc_preinit()”,修飾符“__attribute((constructor))”告知編譯器把__libc_preinit函數指針放到.init_array section,于是函數__libc_preinit在so被linker加載時被自動執行了
在__libc_preinit()函數中,會去讀取libc.debug.malloc屬性值,并根據所獲得的值設置malloc/free…等函數指針指向不同的函數實現,此處libc.debug.malloc為1,則函數指針指向leak_malloc/leak_free….等leak_xxxx形式的函數實現。
于是,屬性libc.debug.malloc決定了所使用到的mallc/free的實現函數。
heap’s backtrace信息的保存與讀取
- 當malloc調試開關設置為leak模式后,在進程內執行malloc/calloc等函數時候,實際調用的是leak_malloc/leak_calloc等函數,這些函數會在分配的內存前面附加一個頭部信息,在該頭部信息里面保存了此次內存分配的backtrace信息。
- libc提供了一個函數get_malloc_leak_info,通過該函數我們就能獲得當前進程所有未釋放的heap的backtrace信息了。最終我們獲取到的backtrace信息如前面所示。
進程注入
進程注入流程圖如下所示:
前面說到調用get_malloc_leak_info可以獲得當前進程的heap信息,但要怎么才能讓目標進程去調用get_malloc_leak_info函數,一種是修改代碼目標代碼,加入獲取進程heap快照的相關代碼后重新編譯;還有另外一種方式就是使用進程注入,它能把一段代碼注入到目標進程中并執行。大致流程描述如下:
先調用ptrace(PTRACE_ATTACH, …)把當前程序附著在目標進程之上,即成為目標進程的父進程。
在目標進程中開辟一塊內存空間用于傳遞參數信息到目標進程
把要注入的程序庫文件名信息保存在前面開辟出來的內存空間,并調用dlopen把該程序庫加載到目標進程中。
在程序庫中定義有一個“extern “C” void attribute((constructor)) prepare()”函數,這個函數會在so被加載到目標進程后被自動執行,它會為信號SIGTTIN注冊一個處理函數,當該信號被觸發后,其注冊的處理函數會調用get_malloc_leak_info獲得當前進程的heap快照,并保存成文件。
至此,注入程序已經把指定代碼注入到目標進程中,并且在目標進程中注冊了一個函數用于處理信號SIGTTIN,于是我們可以用kill命令向目標進程發送SIGTTIN信號,然后目標進程的當前heap快照就會被保存下來了。
最后,我們需要把先前在目標進程中開辟的那塊內存空間釋放掉,還原寄存器,調用ptrace(PTRACE_DETACH, pid, …)結束對該進程的追蹤,目標進程就能沿著原來的流程繼續執行下去了,只是在它內部多了我們注入的代碼。
總結
以上是生活随笔為你收集整理的HeapSnap工具原理及其应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linus中帮助命令man
- 下一篇: docker查看命令帮助手册