LoginSignup
3
4

More than 1 year has passed since last update.

malloc デバッグ用のライブラリ 'malloc_hook' を作りました

Last updated at Posted at 2022-09-27

最近妙にメモリリークに悩まされる事が多いので、malloc デバッグ用のライブラリを一個作りました。

malloc_hook という名前のライブラリです。
Github ⇒ https://github.com/tmurakam/malloc_hook

普通は Valgrind あたり使っておけば十分なことが多いのですが、動作が重くなりますし、細かい制御ができません。
自分でいろいろ制御したい場合にはこちらのライブラリが便利かと思います。すごく車輪の再発明くさいのですが、なかなかニーズに合うものがなく。。。

ライセンスは BSD ですが、条項が1個だけなので非常にゆるいライセンスです (ソース配布のときに copyright notice 残しておくだけ)

特徴

機能的にはこんな感じのものです。

  • malloc, realloc, free 実行時にフックを仕掛けることができます (これが基本機能)
    • glibc には __malloc_hook() などがあったのですが deprecated になってしまったので、これの代替用です
  • mtrace と同様に、メモリ割り当て・解放のトレースログを出力できます。
    • mtrace と異なり、呼び出し元のコールスタックを5段ほど出力できます。
  • ヒープダンプ機能を備えています
    • 割当中のメモリ情報(アドレス、サイズ、割当時のコールスタックを含む)をフルダンプできます。
    • 特定の範囲で割り当てたメモリだけをダンプするという機能もあります。
  • 完全スレッドセーブなライブラリなので、マルチスレッドアプリケーションで安全に使用できます。
    • mtrace や glibc __malloc_hook はスレッドセーフではなかったので、マルチスレッドアプリケーションで使えませんでした。

動作環境は Linux のみ、サポートしているコンパイラは gcc/g++ です。

使い方

ライブラリとして提供しているので、お使いのアプリケーションに組み込んで使ってください。
ビルド手順については README.md に記載しています。

フック

malloc, realloc, free 実行時にフックを仕掛けることができます (calloc は malloc 扱いです)。
以下に例を示します。

#include "mallok_hook.h"

void malloc_hook(void *ptr, size_t size, void **caller) {
    fprintf(stderr, "malloc: ptr=%p, size=%ld, caller=%p\n", ptr, size, caller[0]);
    dump_backtrace(15);
}

void realloc_hook(void *oldPtr, size_t oldSize, void *newPtr, size_t newSize, void **caller) {
    fprintf(stderr, "realloc: oldPtr=%p, oldSize=%ld, newPtr=%p, newSize=%ld, caller=%p\n", 
            oldPtr, oldSize, newPtr, newSize, caller[0]);
}

void free_hook(void *ptr, size_t size, void **caller) {
    fprintf(stderr, "free: ptr=%p, size=%ld, caller=%p\n", ptr, size, caller[0]);
}

int main() {
    set_malloc_hook(malloc_hook);
    set_realloc_hook(realloc_hook);
    set_free_hook(free_hook);
    
    // Your code here...
}

フックを仕掛けるには set_xxx_hook() を使います。
フック関数が呼ばれる際に、引数にメモリアドレス、サイズ、コールスタック(backtrace)などの情報が渡ってきます。

malloc, realloc については、メモリ割り当てが完了してからフックが呼ばれます。
free については、メモリ解放の前にフックが呼ばれます。
なお、フック内で malloc などを呼び出してもフックが再帰的に呼ばれることはありません。ガードをかけていますので。

glibc の __malloc_hook() と異なり、malloc 機能自体をフックが提供する必要はありません。単なるイベントハンドラのようなイメージです。

なお、コールスタックのアドレスをシンボルに変換したい場合は、get_caller_symbol() を使ってください。

    char buffer[1024];
    get_caller_symbol(caller[0], buffer, sizeof(buffer));
    fprintf(stderr, "caller=%s\n", buffer);

ヒープダンプ

割当中の全メモリの情報をダンプすることができます。以下の例では stderr にダンプします。

    malloc_heap_dump(stderr, true);

以下のような感じで、アドレス、サイズ、コールスタック(最大5段)がダンプされます。

== Start heap dump
0: [0x5555555fcc68] size=10000
  - ./malloc_hook_test(+0xbad9) [0x55555555fad9]
  - ./malloc_hook_test(+0x4e4f8) [0x5555555a24f8]
  - ./malloc_hook_test(+0x4663d) [0x55555559a63d]
  - ./malloc_hook_test(+0x21e38) [0x555555575e38]
  - ./malloc_hook_test(+0x228bf) [0x5555555768bf]
1: [0x5555555fc758] size=248
  - /lib/x86_64-linux-gnu/libstdc++.so.6(_Znwm+0x19) [0x7ffff7e4ab39]
  - ./malloc_hook_test(+0x2144b) [0x55555557544b]
  - ./malloc_hook_test(+0xe03e) [0x55555556203e]
  - ./malloc_hook_test(+0xe078) [0x555555562078]
  - ./malloc_hook_test(+0x4e624) [0x5555555a2624]

ちょっと便利な機能として、特定のマークしたポイントより時系列的に後に割り当てられたメモリのみをダンプするという機能があります。

    void *p1 = malloc(100);
    malloc_heap_dump_mark();
    void *p2 = malloc(200);
    malloc_heap_dump(stderr, true);

上記の例だと、malloc_heap_dump_mark() よりも後に割り当てられた p2 の情報だけがダンプされます。

大量にメモリを確保していてダンプが大量になってしまう場合や、ある程度絞り込んでメモリリークを見つけたいときなどに便利です。

マークをリセットするときは malloc_heap_dump_unmark() を呼び出してください。

mtrace 互換機能

mtrace/muntrace 互換機能があり、メモリ割り当て・解放をすべてログ出力することできます。
なお本機能は内部でフックを使うので、利用者側のフックと合わせて使用することはできません。

    malloc_hook_mtrace("./malloc_hook_test", "mtrace.log", true, 5);

       // your code here

    malloc_hook_muntrace();

malloc_hook_mtrace() でトレースを開始します。引数は順に、プログラム名称、ログファイル名、シンボルを解決するか、コールスタック表示最大段数、です。

以下のような形でログがファイルに出力されます。

= Start
@ ./malloc_hook_test:[0x5555555621e5] + 0x5555555fc8a8 0x64 (./malloc_hook_test(+0xe1e5) [0x5555555621e5], ./malloc_hook_test(+0x4e528) [0x5555555a2528])
@ ./malloc_hook_test:[0x5555555621fa] < 0x5555555fc8a8 (./malloc_hook_test(+0xe1fa) [0x5555555621fa], ./malloc_hook_test(+0x4e528) [0x5555555a2528])
@ ./malloc_hook_test:[0x5555555621fa] > 0x5555555ff5c8 0x2710 (./malloc_hook_test(+0xe1fa) [0x5555555621fa], ./malloc_hook_test(+0x4e528) [0x5555555a2528])
@ ./malloc_hook_test:[0x5555555621fa] - 0x5555555ff5c8 (./malloc_hook_test(+0xe1fa) [0x5555555621fa], ./malloc_hook_test(+0x4e528) [0x5555555a2528])
= End

左から順にプログラム名、呼び出し元のアドレス、種別、メモリアドレス、サイズ、コールスタックです。
種別は "+" がメモリ割り当て、"-" が解放、"<" と ">" がそれぞれ realloc の前と後を表しています。

このファイルは mtrace の上位互換になっているので、mtrace コマンドを使ってリークチェックが可能です。

$ mtrace ./mtrace.log

各行の末尾にはメモリ割り当て時のコールスタックが出力されるようになっています。これは malloc_hook ライブラリの独自拡張です。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4