|
8. mallocライブラリとデバッグ |
|
|
|
これまでにmallocライブラリの処理内容の詳細について述べてきました。ここでは、mallocライブラリを使用して問題が発生した場合の簡単な調査方法について説明していきます。
|
|
問題はどのような状況で起こるでしょう?下記の様なケースが考えられます。
|
|
(1) |
確保した領域サイズ以上のサイズで使用 |
|
(2) |
二重解放 |
|
(3) |
解放したメモリの使用 |
|
(4) |
メモリの解放漏れ |
|
(5) |
確保したメモリの二重使用 |
|
|
これらの問題を検出する為の色々なツールも存在しますが、mallocライブラリ側でも簡単なチェック機能が備えられています。これは予めライブラリ内に組み込まれている為、簡単に使用することができ、アプリケーション側の修正も必要ありません。ただ簡単な仕組みの為、余り詳細なチェックは行えません。上記の(1)と(2)には対応が可能です。以下、この機能の仕組みについて説明します。
これは環境変数MALLOC_CHECK_に特定の値を設定する事により、ライブラリ側にデバッグ機能使用の通知を行いチェック機能を働かせるものです。この機能はmallocのmanにも書かれています。ここではライブラリでの内部的な処理についても述べて行きます。
MALLOC_CHECK_の値はpublic_mALLOc()コール時の最初の処理でcheck_actionという静的変数に設定されます。これは、publi c_mALLOc()コール時の最初の処理で呼ばれる関数ポインタhook()に設定されている関数malloc_hook_ini()で処理されます。ライブラリ内ではこのcheck_actionのビット0、1をチェックする事で処理を行います。ビットの値が1の時の意味は右記のとおりとなります。
|
|
この為MALLOC_CHECK_の設定値は0、1、2、3が有効で、それぞれ次の意味を持つ事になります。
|
|
0 |
:処理なし |
|
1 |
:メッセージ表示 |
|
2 |
:abort() call |
|
3 |
:メッセージ表示後、abort() call |
|
|
メモリに関する問題は、発生時には発覚せず比較的処理が進んでから検出される事が往々にして見られます。abort()の呼び出しは早い段階での問題検出に役立ちます。
check_actionは初期値としてはDEFAULT_CHECK_ACTIONが設定されています。DEFAULT_CHECK_ACTIONの値は1に定義されています。この為、環境変数MALLOC_CHECK_が定義されていない場合でも最低限のチェックは行われています。MALLOC_CHECK_が定義されていない場合には_int_free()関数でだけcheck_actionのチェックが行われます。チェックの内容は下記で述べますが、不正があった場合check_actionのビット0だけがオンの為エラーメッセージが表示されるだけです。
MALLOC_CHECK_が定義されている場合、ライブラリの初期処理段階で、不正な操作をチェックする為の準備関数が呼ばれます。
public_mALLOc() -> malloc_hook_ini()
-> ptmalloc_init()という流れで呼ばれる関数ptmalloc_init()でMALLOC_CHECK_の値がcheck_actionに設定されます。またその延長で呼ばれる__malloc_check_init()関数中でメモリチェックの為のフック関数を設定します。
|
|
104 __malloc_hook =
malloc_check;
105 __free_hook = free_check;
106 __realloc_hook =
realloc_check;
107 __memalign_hook =
memalign_check;
|
|
|
|
|
これらの__xxx_hook()という関数は各フック関数に応じたmallocライブラリの入り口で呼ばれます。__malloc_hook()の場合はpublic_mALLOc()で呼ばれます。
MALLOC_CHECK_が定義されていない場合はこれらの関数ポインタにはNULLが設定されている為、なにも行われません。ここではmalloc()、free()でのチェック処理について述べます。
malloc()をコールする場合public_mALLOc()が呼ばれることは以前述べました。public_mALLOc()では処理の先頭で__malloc_hook()をコールします。通常の流れと違い、メモリ確保の処理を行う_int_malloc()は__malloc_hook()の延長で呼ばれ__malloc_hook()から戻るとpublic_mALLOc()からリターンします。__malloc_hook()関数ポインタにはmalloc_check()が設定されている為、これがコールされます。
malloc_check()では、まずtop_check()によりarenaのtopの値の妥当性をチェックします。これは次の様になります。
|
|
234 if((char*)t +
chunksize(t) ==
mp_.sbrk_base +
main_arena.system_mem ||
235 t == initial_top(&main_arena))
return 0;
|
|
|
|
|
top_check()が異常な場合、check_actionの値をチェックし、次の様に処理されます。
|
|
bit 0 ON |
:fprintf(stderr, "malloc:
top chunk is
corrupt\n");を実行。 |
|
bit 1 ON |
:abort();をコール。 |
|
|
正常な場合_int_malloc()が_int_malloc(&main_arena,
sz+1)の様に呼ばれます。_int_malloc()での処理内容は通常と変わりませんがサイズが要求された値+1となります。これはチェックを行う為の領域として確保されています。_int_malloc()から戻った後、mem2mem_check()が呼ばれます。これはメモリチェックをする為の準備を行う関数です。ここでの設定が、free()をコールした際にチェックされます。mem2mem_check()では該当のchunkに対してマジックバイトを設定します。chunkの要求サイズの次のバイトと、次のchunkのprev_sizeの最後のバイトです。
マジックバイトはMAGICBYTE()で算出します。定義は次のものになります。
|
|
128 #define
MAGICBYTE(p) ( ( ((size_t)p
>> 3) ^ ((size_t)p >> 11)) &
0xFF )
|
|
|
|
|
pはchunkのアドレスになります。これでmalloc()での準備は終わりです。
次にfree()の説明をします。こちらも以前述べました様にpublic_fREe()が呼ばれます。public_fREe()では処理の先頭で__free_hook()をコールします。__free_hook()は即ちfree_check()です。malloc()と同様にここでも__free_hook()から戻るとpublic_fREe()からリターンします。
free_check()ではmem2chunk_check()をコールし対象のメモリをチェックします。mem2chunk_check()の結果がエラーの場合check_actionの値をチェッ
クします。次の処理が行われabort()がコールされなければそのままリターンします。
|
|
bit 0 ON |
:fprintf(stderr,
"free(): invalid pointer
%p!\n", mem);を実行。 |
|
bit 1 ON |
:abort();をコール。 |
|
|
正常の場合、_int_free()関数がコールされます。_int_free()では関数の最初で、解放で指定されたアドレスと該当のchunkのヘッダ部にあるsizeメンバがチェックされます。不正な場合、check_actionの値がチェックされ次の処理が行われます。やはりabort()がコールされなければそのままリターンします。
|
|
bit 0 ON |
:fprintf (stderr,
"free(): invalid pointer
%p!\n", mem);を実行。 |
|
bit 1 ON |
:abort();をコール。 |
|
|
mem2chunk_check()では次の様なチェックが行われます。
|
|
 |
メモリのアラインメント(8バイト境界になっているか)。 |
|
 |
chunkの管理情報のsizeの値が正しいか。 |
|
 |
アドレスチェック(アドレスがヒープ範囲内にあるか)。 |
|
 |
マジックバイトのチェック(マジックバイト値がメモリから計算した値と合っているか)。チェック後マジックバイト値と0xffで排他的論理輪を取って値を反転します。 |
|
|
これにより二重フリーや、メモリ破壊がある程度チェックされます。
それでは、MALLOC_CHECK_を使った簡単なテストをしてみましょう。合わせてchunkのヘッダ部の変化も少し見てみましょう。
下記ではmalloc()は必ず成功し、プロセス起動の初期段階ではchunkが連続して取られると言う推定の下に行っています。
|
|
|
|
以上でmallocライブラリのデバッグの説明を終わります。
mallocライブラリのデバッグの為には、MALLOC_CHECK_以外にも色々なツールがありますので、試してみるのもよいでしょう。
mallocライブラリの説明については今回が最後です。glibcはLinuxでプログラミングするには欠かせないものでありながら、man以外にその中身について説明した文書はほとんど見られません(最もこれはオープンソースの常ではありますが)。man自身もソースに追随して常に最新に保たれている訳ではありませんので、使用しているライブラリの動きについて調べたい場合はやはり直接ソースを読む必要も出て来ます。その様な場面で本資料がソースを読み解く手助けや問題解決の手助けになればと思います。皆さんご自身でも一度ライブラリのソースを読んで理解を深められてはどうでしょう。プログラムを組む上で何か得る所が有るのではないかと思います。
|
|
戻る |