2017年5月14日 星期日

mtrace 與 libmemleak

本篇參考 https://en.wikibooks.org/wiki/Linux_Applications_Debugging_Techniques/Leaks
測試文中兩個偵測 memory leakage 的方法,分別是 mtrace 與 libmemleak,並且紀錄測試過程。

1. 使用 mtrace
a. 針對單一程式: 
a1. 在程式碼中 #include  ,並且在需要檢視 leakage 的區間分別加入 mtrace(),muntrace(),程式碼範例可參考此連結。 
a2. 編譯以及偵測方法
$ gcc -g dleaker.c -o dleaker
$ MALLOC_TRACE=test1/mtrace.plain.log  ./dleaker
$ mtrace  ./dleaker  test1/mtrace.plain.log  > test1/mtrace.plain.leaks.log 
a3. 原理簡介
mtrace() 執行之後,會將後續所有 malloc 相關函數全部置換。並且記錄 malloc/free 使用過程。每次配置記憶體使用 "+" 紀錄,釋放記憶體使用 "-" 紀錄,最後統計沒有被釋放的記憶體,就是 memory leakage
如下: 
$ cat  mtrace.plain.log
= Start
@ ./dleaker:[0x80484cb] + 0x9dde378 0xa
@ ./dleaker:[0x80484db] + 0x9dde388 0x14
@ ./dleaker:[0x80484ec] - 0x9dde378
= End 
$ cat mtrace.plain.leaks.log
Memory not freed:
-----------------
Address     Size     Caller
0x09dde388     0x14  at /home/albert/test/libtrace/dleaker.c:13 
a4. 注意:編譯時要加入 "-g" ,這樣 mtrace 才能夠顯示出正確的檔案名稱與行號。 
b. 有時候僅知道行號,還是無法確認問題,最好是有上 backtrace,提供更清楚的呼叫流程,實驗過程如下:
b1. 替換原本的 malloc,在記憶體配置之後,打印 backtrace。
extern void *__libc_malloc(size_t size);
extern void *malloc(size_t size)
{
    void *ptr = __libc_malloc(size);
    fprintf(stderr,"%s\n", __func__);
    _ShowBacktrace();
    return ptr;
b2. 一開始使用下列方法實作 _ShowBacktrace()
#include
static void _ShowBacktrace(void)
{
    void *bt[1024];
    int bt_size;
    char **bt_syms;
    int i;
    bt_size = backtrace(bt, 1024);
    bt_syms = backtrace_symbols(bt, bt_size);
    for (i = 1; i < bt_size; i++)
    {
printf("%s", bt_syms[i]);
    }
    free(bt_syms);
結果程式會掛掉,因為 backtrace() 會再一次呼叫 malloc,因此此項修改會不斷的呼叫 malloc,最後導致 stack overflow。所以需要加上 flag 避免此無限迴圈。相關討論可參考 https://patchwork.ozlabs.org/patch/442890/
若對 mtrace 內部實作有興趣,可以直接參考此檔案 http://code.metager.de/source/xref/gnu/glibc/malloc/mtrace.c


2. 使用 libmemleak.so
a. 首先自行編譯 libmemleak.so (實驗時使用 ubuntu 16.04)
$ sudo apt-get update
$ sudo apt-get install binutils-dev libiberty-dev libreadline-dev doxygen
$ wget https://github.com/CarloWood/libmemleak/releases/download/v0.1.1/libmemleak-0.1.1.tar.gz
$ tar zxvf libmemleak-0.1.1.tar.gz
$ cd libmemleak-0.1.1
$ ./configure
$ make
$ sudo make install 
b. 編譯測試範例
$ git clone https://github.com/alb423/memory_leakage.git
$ cd memory_leakage
$ make  
c. 執行測試程式,動態偵測記憶體資訊
$ export LIBMEMLEAK_SOCKNAME=./memleak_sock
$ cd ~/libmemleak-0.1.1
$ LD_PRELOAD='/usr/local/lib/libmemleak.so' ./hello
$ memleak_control 
d. 若要讓程式一開始就偵測記憶體,需加入 MEMLEAK_CONFIG 定義
$ export MEMLEAK_CONFIG=mtraceinit
$ cd ~/memory_leakage
$ LD_PRELOAD='/usr/local/lib/libmemleak.so' ./dleaker3
$ memleak_control
e.執行結果畫面截圖如下:







3. 編譯 ARM 平台上面的 libmemleak.so
先安裝 arm tool chain 
$ sudp apt-get update
$ sudp apt-get upgrade
$ sudo apt-get install gcc-arm-linux-gnueabihf 
編譯 libcurses.a
$ wget http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz
$ tar -pxzf ncurses-5.9.tar.gz
$ ./configure --host=arm-linux-gnueabihf --without-debug --without-ada --without-shared --enable-widec CPPFLAGS="-P"
$ sudo make install DESTDIR=$HOME/
註:此處編譯 library 成功,但是編譯測試程式仍然有誤,上述步驟應該有需要改善。
編譯 libreadline.so 
直接參考此篇即可,最後再安裝到相同目錄 (也許此處可以直接使用靜態編譯)  
$ sudo make install DESTDIR=$HOME/ 
編譯 libmemleak
$ CC=arm-linux-gnueabihf-gcc ./configure --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf LDFLAGS="-L$HOME/usr/local/lib -L$HOME/usr/lib -lreadline -lncursesw"
$ make
$ sudo make install DESTDIR=$HOME/


Reference:
  1. https://how-to-build-for-arm.wikispaces.com/readline
  2. http://www.plunk.org/~hatch/goodies/backtracefilt.C
  3. https://github.com/CarloWood/libmemleak
  4. http://stackoverflow.com/questions/34732/how-do-i-list-the-symbols-in-a-so-file
  5. https://en.wikibooks.org/wiki/Linux_Applications_Debugging_Techniques/Leaks