読者です 読者をやめる 読者になる 読者になる

jemalloc の解析機能

※この記事は3回シリーズのうちの一つ

jemalloc について調べたのでまとめた - zonomasaの日記

続 jemalloc について調べたのでまとめた(ビルドと組み込み方法) - zonomasaの日記

jemalloc の解析機能 - zonomasaの日記

photo by ed10vi

jemalloc のメモリ解析機能

jemalloc はそれ自身がいくつかのメモリ解析機能を持っている。コンパイルオプションと実行時オプション指定によりそれぞれON/OFFが可能な設計になっている。

外部ツールなどに比べ容易に、比較的高速に使用することができるこれらの機能についてまとめようと思う。

前回記事: jemalloc について調べたのでまとめた - zonomasaの日記 続 jemalloc について調べたのでまとめた(ビルドと組み込み方法) - zonomasaの日記

メモリリーク検出

jemalloc でのメモリリークとは、malloc() されて、free()されていないメモリブロックを指す。

void
leak_memory(void){
    char *ptr1, *ptr2;

    ptr1 = (char *)malloc(32);
    ptr2 = (char *)malloc(1024);

    return;
}

メモリリーク検出の出力

jemalloc では、プロセスの終了時にメモリリーク発生のサマリを表示してくれる。

<jemalloc>: Leak summary: 1024 bytes, 1 object, 3 contexts
<jemalloc>: Run pprof on "jeprof.9803.0.f.heap" for leak detail

これは1024byte のメモリブロックが1つ、free() されずにプロセスが終了したことを示す。

メモリリーク検出の使い方

メモリリークの検出を有効にするためは、jemalloc ライブラリのビルド時に以下のようにオプションを指定する。

$ ./configure --enable-prof
$ make

また、実行時にMALLOC_CONF環境変数に以下のオプションを指定して実行する。

MALLOC_CONF="prof_leak:true,prof:true,lg_prof_sample:1" LD_PRELOAD=./lib/libjemalloc.so myapp.exe

メモリ破壊検出(オーバーフロー検出)

いわゆるバッファーオーバーフローを検出してくれる。malloc() した領域をはみ出したアクセス(オーバーフロー)を行っていないかのチェックであり、メモリブロックの前後の領域が検査の対象となる。

    ptr = (char *)malloc(24);
    for (i = 0; i < 40; i++){
        ptr[i] = 'a';
    }
    free(ptr);

    ptr = (char *)malloc(24);
    for (i = 0; i > -8; i--){
        ptr[i] = 'b';
    }
    free(ptr);

メモリ破壊検出の出力

free() を呼び出した際にオーバーフローアクセスの検査が行われ、問題がある場合には以下のような出力が行われる。

<jemalloc>: Corrupt redzone 0 bytes after 0x2b0908c0f081 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 1 byte after 0x2b0908c0f081 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 2 bytes after 0x2b0908c0f081 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 3 bytes after 0x2b0908c0f081 (size 32), byte=0x61

これは、32byte のメモリブロックの後ろ4byte が意図せず書き換えられていた、つまりオーバーフローアクセスが行われたことを示している。

以下、コード例と合わせてどのような出力がされるのかを見ていく。

ケース1
    /* Can not detect */
    ptr = (char *)malloc(3);
    for (i = 0; i < 8; i++){
        ptr[i] = 'a';
    }
    free(ptr);

↑これは検出することが出来ない例である。3byte のmalloc() は内部的には8byte として扱われるため、4-8byte 目までの書き込みはオーバーフローとして扱わない。この点は、glibc mallocMALLOC_CHECK_ に比べて検出能力的に劣る点であると言わざるを得ない。

ケース2
    ptr = (char *)malloc(3);
    for (i = 0; i < 9; i++){
        ptr[i] = 'b';
    }
    free(ptr);

↑これは9byte 目にたいして書き込みが行われるので、以下のような出力がされる。

<jemalloc>: Corrupt redzone 0 bytes after 0x2b0f58f5a100 (size 8), byte=0x32
ケース3
    ptr = (char *)malloc(24);
    for (i = 0; i < 40; i++){
        ptr[i] = 'a';
    }
    free(ptr);

24byte 確保に対して、40byte の書き込みをした場合。

<jemalloc>: Corrupt redzone 0 bytes after 0x2b387c731180 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 1 byte after 0x2b387c731180 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 2 bytes after 0x2b387c731180 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 3 bytes after 0x2b387c731180 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 4 bytes after 0x2b387c731180 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 5 bytes after 0x2b387c731180 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 6 bytes after 0x2b387c731180 (size 32), byte=0x61
<jemalloc>: Corrupt redzone 7 bytes after 0x2b387c731180 (size 32), byte=0x61
ケース4
    ptr = (char *)malloc(24);
    for (i = 0; i > -8; i--){
        ptr[i] = 'd';
    }
    free(ptr);

メモリブロックの前方向(メモリアドレスの若い方向)に対して書き込みを行った場合も検出可能である。出力文がbefore になっている。

<jemalloc>: Corrupt redzone 1 byte before 0x2b0908c0f081 (size 32), byte=0x64
<jemalloc>: Corrupt redzone 2 bytes before 0x2b0908c0f081 (size 32), byte=0x64
<jemalloc>: Corrupt redzone 3 bytes before 0x2b0908c0f081 (size 32), byte=0x64
<jemalloc>: Corrupt redzone 4 bytes before 0x2b0908c0f081 (size 32), byte=0x64
<jemalloc>: Corrupt redzone 5 bytes before 0x2b0908c0f081 (size 32), byte=0x64
<jemalloc>: Corrupt redzone 6 bytes before 0x2b0908c0f081 (size 32), byte=0x64
<jemalloc>: Corrupt redzone 7 bytes before 0x2b0908c0f081 (size 32), byte=0x64

メモリ破壊検出の使い方

メモリリークの検出を有効にするためは、jemalloc ライブラリのビルド時に以下のようにオプションを指定する。

$ ./configure --enable-prof
$ make

上記に加え、--enable-debug を指定すると、opt.abort がON になり、warning 相当の事象が起きた際にプログラムがabort する。

また、実行時にMALLOC_CONF環境変数に以下のオプションを指定して実行する。

MALLOC_CONF="redzone:true,prof:true"

double-free 検出

double-free は以下のようにfree() を同じポインタに対して2度(以上)行う処理である。

    char *ptr;

    ptr = (char *)malloc(32);

    free(ptr);
    free(ptr);

jemalloc ではこのdouble-freeを検出する機能を備えている

double-free の他にも、おかしなポインタをfree() に渡した場合にも同様の検出ができる。

double-free 検出の出力例

double-free は検出した瞬間にAssertがかかり、プログラムは停止する。

<jemalloc>: src/arena.c:321: Failed assertion: "bitmap_get(bitmap, &bin_info->bitmap_info, regind)"
Aborted (core dumped)

double-free 検出の使い方

double-free 検出を有効にするためは、jemalloc ライブラリのビルド時に以下のようにオプションを指定する。

$ ./configure --enable-debug
$ make

また、実行時にMALLOC_CONF環境変数に以下のオプションを指定して実行することが推奨されている。(私の環境ではこのオプションを指定しない場合、適切に検出出来なかった)

MALLOC_CONF="tcache:false"

おわりに

jemalloc シリーズ第3弾として、メモリ解析機能を紹介した。

これらの解析機能は他の多くのツールでも実現されているものである。使いどころ、オーバーヘッドなど、他のツールとの比較を行いたい。