7    Third Degree によるプログラムのデバッグ

Third Degree ツールは,C および C++ プログラムにおけるヒープ・メモリのリーク,無効アドレスの参照,および初期化されていないメモリの読み込みを検査します。 始めに,-g または -gn オプションを使用してプログラムをコンパイルしておく必要があります (n は 0 より大きな値)。 また,Third Degree は,ヒープ・オブジェクトのリストを作成し,浪費されているメモリを検出するので,プログラムの割り当てパターンの決定にも役立ちます。 これは,メモリ管理サービスを自動的に監視し,実行時に命令をロード/格納するための追加のコードで,実行可能オブジェクトを計測することによって行われます。 要求されたレポートは,オプションで表示できる 1 つまたは複数のログ・ファイルに書き込まれるか, xemacs(1) エディタを使用してソース・コードに関連付けられます。

Third Degree は,省略時の設定ではメモリ・リークのみを検査するため,計測および実行時分析を短時間で行うことができます。 費用がかかり,処理のじゃまになるその他の検査は,コマンド行のオプションで選択します。 詳細については, third(1) を参照してください。

Third Degree は,次のタイプのアプリケーションに使用できます。

7.1    アプリケーションにおける Third Degree の実行

Third Degree を起動するには,次のように third(1) コマンドを使用します。

third [option...] app [argument...]

このコマンド形式では,option には,省略時のスレッドを使用しないリーク検査以外に 1 つまたは複数のオプションを選択します。 app は,アプリケーションの名前です。 argument は,計測機構付きプログラムをすぐに実行したい場合にアプリケーションに渡される 1 つまたは複数のオプションの引数を表します (app が引数を必要としない場合は,-run オプションを使用します)。

app.third ( third(1) を参照) と名付けられた計測機構付きプログラムは,次の点で元のアプリケーションと異なる動作をします。

Third Degree のエラー・メッセージの記述形式は,C コンパイラが使用する記述形式と類似しています。 省略時の設定では,Third Degree は,app.3log という名前のログ・ファイルにエラー・メッセージを書き込みます。 emacs を使用すると,各エラーを自動的に順番に指定することができます。 emacs では,[Esc/X] compile を使用して,省略時の make コマンドを cat app.3log などのコマンドに置き換え,[Ctrl/X] ` を使用して,コンパイル・エラーの場合のように,順番にエラーを表示していきます。

ログ・ファイルに使用される名前は,次のオプションのうちいずれかを指定することによって変更できます。

-pids

プロセス識別番号 (PID) をログ・ファイル名に含めます。

-dirname directory-name

Third Degree がそのログ・ファイルを作成するディレクトリ・パスを指定します。

-fork

フォークした各プロセスのログ・ファイル名に PID を含めます。

使用するオプションによって,ログ・ファイル名は次のようになります。

オプション ファイル名 使用
なし,または -fork parent app.3log 省略時の設定
-pids または -fork child app.12345.3log PID を取り込む
-dirname /tmp /tmp/app.3log ディレクトリを設定
-dirname /tmp -pids /tmp/app.12345.3log ディレクトリと PID を設定

シグナル・ハンドラでのエラーは,追加の .sig.3log オプションにより通知されます。

7.1.1    シェアード・ライブラリでの Third Degree の使用

strcpy 関数に渡すバッファが小さすぎるなど,アプリケーション内のエラーは,ライブラリ・ルーチンで見つかることがよくあります。 Third Degree はシェアード・ライブラリの計測をサポートしており,-non_shared または -call_shared オプションを指定してリンクされたプログラムを計測します。

次のオプションを指定すると,Third Degree が計測するシェアード・ライブラリを決定できます。

-all

呼び出し共用実行可能プログラムにリンクされたすべてのシェアード・ライブラリを計測します。

-excobj objname

指定したシェアード・ライブラリを計測から除外します。 -excobj オプションを複数回使用すれば,複数のシェアード・ライブラリを指定できます。

-incobj objname

指定したシェアード・ライブラリを計測します。 -incobj オプションを複数回使用すれば,dlopen() を使用してロードしたシェアード・ライブラリを含め,複数のシェアード・ライブラリを指定できます。

-Ldirectory

リンカやローダが認識する通常の位置にプログラムのシェアード・ライブラリがない場合,Third Degree にその検索位置を通知します。

Third Degree がアプリケーションの計測を終了すると,現在のディレクトリには,指定した各シェアード・ライブラリの計測機構付きバージョンが含まれています。 また,必要に応じて libc.solibcxx.so,および libpthread.so の最低限の計測機構付きバージョンが作成されます。 計測機構付きアプリケーションは,ライブラリのこれらのバージョンを使用する必要があります。 LD_LIBRARY_PATH 環境変数を定義して,計測機構付きシェアード・ライブラリがどこに常駐するかを,計測機構付きアプリケーションに通知してください。 third(1) コマンドは,-run オプションを指定する場合,またはアプリケーションに引数を指定している場合にこれを自動的に行います。 また,計測機構付きプログラムを自動的に起動します。

省略時の設定では,Third Degree は,アプリケーションが使用するシェアード・ライブラリを完全には計測しません。 ただし,使用するときには,最低限 libc.solibcxx.so,および libpthread.so を計測する必要があります。 これにより,計測の動作がはるかに高速になるとともに,計測機構付きアプリケーションも実行速度がより高速になります。 Third Degree は,通常,計測機構付き部分のエラーを検出して報告しますが,計測機構のないライブラリのエラーは検出しません。 部分的に計測したアプリケーションがクラッシュまたは誤動作し,Third Degree によって報告されたエラーをすべて修正した場合は,そのすべてのシェアード・ライブラリとともにアプリケーションを再計測し,新しい計測機構付きバージョンを実行するか,または Third Degree の -g オプションを使用してデバッガで問題を調査してください。

Third Degree は,プロシージャのスタック・トレースを含むエラー・レポートを生成するため,シェアード・ライブラリを計測する必要があります (ただし,省略時の設定では最小限のみ)。 また,デバッグ可能なプロシージャ (たとえば,-g オプションを使用してコンパイルしたもの) は,エラーに最も近い最初のいくつかのスタック・フレーム内に表示される必要があります。 これにより,高度に最適化され,システム・ライブラリのアセンブリ・コードによって生成される可能性がある見せかけのエラーのプリントが回避されます。 この機能を無効にするには,-hide オプションを使用してください。

pthread プログラムでは,Third Degree は,ある種のシステム・シェアード・ライブラリ (libc を含む) のエラーを検査しません。 これは,そうした場合にスレッド・セーフでなくなるためです。

7.2    デバッグ例

次のソース・コード ex.c で表される小さなアプリケーションをデバッグしなければならないとします。

     1  #include <assert.h>
     2
     3  int GetValue() {
     4      int q;
     5      int *r=&q;
     6      return q;           /* q is uninitialized */
     7  }
     8
     9  long* GetArray(int n) {
    10      long* t = (long*) malloc(n * sizeof(long));
    11      t[0] = GetValue();
    12      t[0] = t[1]+1;      /* t[1] is uninitialized */
    13      t[1] = -1;
    14      t[n] = n;           /* array bounds error*/
    15      if (n<10) free(t);  /* may be a leak */
    16      return t;
    17  }
    18
    19  main() {
    20      long* t = GetArray(20);
    21      t = GetArray(4);
    22      free(t);            /* already freed */
    23      exit(0);
    24  }

以降の各項で,Third Degree を使用して,このサンプル・アプリケーションをデバッグする方法について説明します。

7.2.1    Third Degree のカスタマイズ

コマンド行オプションを使って,Third Degree のさまざまな機能のオン/オフを切り替えることができます。

オプションを何も指定しない場合,Third Degree はプログラムを次のように計測しますが,計測機構付きプログラムの実行や,生成される .3log ファイルの表示は行いません。

計測機構付きアプリケーションは,LD_LIBRARY_PATH 環境変数の設定後に ./app.third arg1 arg2 などのコマンドを使って実行できます。 また,アプリケーションの引数を third(1) コマンド行に追加したり,-run または -display オプションを指定したりすることもできます。 生成された .3log ファイルは,手動または -display オプションを指定することにより表示できます。

メモリ・エラー検査を追加するには,-invalid オプションと -uninit オプションのいずれかまたは両方を指定します。

-invalid オプションは,すべての third(1) オプションと同様に 3 文字 (-inv) に短縮できます。 このオプションは,Third Degree に対し,重要なロードおよび格納命令のすべてが,アプリケーション・コードが使用すべき有効なメモリ・アドレスにアクセスしているかどうかを検査します。 このオプションを使用すると,目立った性能のオーバヘッドが生じますが,実行時環境にはほとんど影響を与えません。

-uninit オプションには,キーワード引数を "+" で区切ってリストします。 これは,通常 heap+stack (または h+s) となり,すべての重要なロード命令で,ヒープ・メモリとスタック・メモリの両方を検査するよう指示します。 検査には,malloc など (calloc ではなく) を使って割り当てられたすべてのスタック・フレームおよびヒープ・オブジェクトに,通常ではあり得ないパターン 0xfff8a5a5 を充填し,この値をメモリから読み取るすべてのロード命令を報告することが含まれます。 この場合,選択されたメモリは,初期化されていないデータ領域を読み取るコードを強調表示する点で,cc -trapuv と同様に重大な問題があります。 問題を引き起こすコードが計測として選択されている場合,Third Degree はそれぞれの場合を .3log ファイル内で一度だけ報告します。 ただし,コードが計測されたかどうかに関係なく,コードは,元のプログラムがロードするはずの値の代わりに問題のあるパターンをロードおよび処理します。 この場合,パターンが有効なポインタ,文字,浮動小数点数ではなく,負の整数であるために,プログラムの誤動作またはクラッシュが引き起こされる場合があります。 このような動作は,プログラムにバグが含まれている印となります。

計測機構付きプログラム上で回帰テストを実行することにより,誤動作を識別できます。 third(1) 内で実行する場合には,-quiet を指定して,-display を省略します。 .3log ファイル内のエラー・メッセージを参照し,計測機構付きプログラムを, dbx(1) や,C++ 用の ladebug(1),および pthread アプリケーションなどのデバッガ内で実行することにより,誤動作またはクラッシュのデバッグを行うことができます。 デバッガを使用するには,-g オプションを付けてコンパイルし, third(1) のコマンド行でも同様に -g を指定します。

-uninit オプションを指定すると,偽のエラー,特に変数,配列要素,32 ビット未満の構造体のメンバ (shortchar ビットフィールドなど) がレポートされます。 詳細は,7.6 節を参照してください。 ただし,-uninit heap+stack オプションを使用すると,リーク・レポートの正確性が向上します。

ヒープの使用分析を追加する場合は,-history オプションを指定します。 これにより,-uninit heap オプションが有効になります。

7.2.2    Makefile の変更

アプリケーションの makefile に,次のエントリを追加します。

ex.third: ex
        third ex

次のように ex.third を作成します。

> make ex.third
third ex

ここで,計測機構付きアプリケーション ex.third を実行して,ログ ex.3log をチェックします。 あるいは,プログラム名の前に -display を追加すると,そのアプリケーションを実行して,.3log ファイルが直ちに表示されます。

7.2.3    Third Degree のログ・ファイルの検査

ex.3log ファイルには,以降の項で説明する部分がいくつか含まれています。 このコマンド行は,次のとおりであると想定します。

> third -invalid -uninit h+s -history -display ex

7.2.3.1    実行時メモリ・アクセス・エラーのリスト

実行時に Third Degree が検出できるエラーのタイプには,初期化されていないメモリの読み取り,割り当てられていないメモリの読み取りまたは書き込み,誤ったメモリの解放,および例外を起こす可能性の高い一定の重大エラーなどの状態が含まれます。 各エラーに対して,次の項目を持つエラー・エントリが生成されます。

次の例に,ログ・ファイルからのエントリを示します。

詳細については,7.3 節を参照してください。

7.2.3.2    メモリ・リーク

次のプログラムの抜粋は,プログラム終了時のリーク検出を選択した場合 (省略時の設定) に生成されたレポートを示しています。 このレポートは,重要度と呼び出しスタックでソートされたメモリ・リークのリストを示しています。

---------------------------------------------------------------
---------------------------------------------------------------
New blocks in heap after program exit
 
Leaks - blocks not yet deallocated but apparently not in use:
 * A leak is not referenced by static memory, active stack frames,
   or unleaked blocks, though it may be referenced by other leaks.
 * A leak "not referenced by other leaks" may be the root of a leaked tree.
 * A block referenced only by registers, unseen thread stacks, mapped memory,
   or uninstrumented library data is falsely reported as a leak. Instrumenting
   shared libraries, if any, may reduce the number of such cases.
 * Any new leak lost its last reference since the previous heap report, if any.
 
A total of 160 bytes in 1 leak were found:
 
160 bytes in 1 leak (including 1 not referenced by other leaks) created at:
    malloc                         ex
    GetArray                       ex, ex.c, line 10
    main                           ex, ex.c, line 20
    __start                        ex
 
Objects - blocks not yet deallocated and apparently still in use:
 * An object is referenced by static memory, active stack, or other objects.
 * A leaked block may be falsely reported as an object if a pointer to it
   remains when a new stack frame or heap block reuses the pointer's memory.
   Using the option to report uninitialized stack and heap may avoid such cases.
 * Any new object was allocated since the previous heap report, if any.
 
A total of 0 bytes in 0 objects were found:

ソースをチェックすると,GetArray の最初の呼び出しによってメモリ・オブジェクトが解放されなかったことと,それがプログラムの他の箇所でも解放されていないことがわかります。 さらに,このオブジェクトへのポインタがプログラムのどこにもないため,"not referenced by other leaks" と認定されます。 この区別は,大きなメモリ・リークの本当の原因を発見するのに役立つことがよくあります。

たとえば,大きなツリー構造で,ルートへのポインタが消去されたと仮定します。 構造内のすべてのブロックがリークしていますが,ルートを指すポインタが失われていることがリークの本当の原因です。 ルート以外のすべてのブロックは,その構造へのポインタを持っているため,他のリークからだけですが,ルートだけが特別に識別されます。 したがって,これがメモリ損失の原因である可能性が高いと考えられます。

詳細については,7.4 節を参照してください。

7.2.3.3    ヒープ・ヒストリ

ヒープ・ヒストリが使用可能な場合,Third Degree は動的に割り当てられたメモリに関する情報を収集します。 この情報は,アプリケーションが解放するすべてのブロック,およびプログラムの実行が終了した時点でまだ存在しているすべてのブロック (メモリ・リークを含む) について収集されます。 次のプログラムの抜粋は,ヒープ割り当てヒストリ・レポートを示しています。

----------------------------------------------------------------
----------------------------------------------------------------
                Heap Allocation History for parent process
 
 
Legend for object contents:
        There is one character for each 32-bit word of contents.
        There are 64 characters, representing 256 bytes of memory per line.
        '.' : word never written in any object.
        'z' : zero in every object.
        'i' : a non-zero non-pointer value in at least one object.
        'pp': a valid pointer or zero in every object.
        'ss': a valid pointer or zero in some but not all objects.
 
192 bytes in 2 objects were allocated during program execution:
 
------------------------------------------------------------------
160 bytes allocated (8% written) in 1 objects created at:
    malloc                         ex
    GetArray                       ex, ex.c, line 10
    main                           ex, ex.c, line 20
    __start                        ex
 
Contents:
     0: i.ii....................................
 
------------------------------------------------------------------
32 bytes allocated (38% written) in 1 objects created at:
    malloc                         ex
    GetArray                       ex, ex.c, line 10
    main                           ex, ex.c, line 21
    __start                        ex
 
Contents:
     0: i.ii....
 

このサンプル・プログラムでは,合計 192 バイト (8*(20+4)) の 2 個のオブジェクトが割り当てられています。 各オブジェクトは,異なる呼び出しスタックから割り当てられているため,ヒストリには 2 つのエントリがあります。 各配列の最初の数バイトだけが有効な値に設定され,その結果,ここに示されているような書き込み率になります。

このサンプル・プログラムが実際のアプリケーションだとすると,初期化されている動的メモリが極端に少ないことから,アプリケーションによるメモリの使用が非効率的であることがわかります。

詳細については,7.4.4 項を参照してください。

7.2.3.4    メモリ・レイアウト

レポートのメモリ・レイアウト・セクションでは,プログラムが使用するメモリの概要を,サイズおよびアドレス範囲により示します。 次のプログラムの抜粋は,メモリ・レイアウト・セクションを示しています。

-----------------------------------------------------------------
-----------------------------------------------------------------
                memory layout at program exit
                  heap      40960 bytes [0x14000c000-0x140016000]
                 stack       2720 bytes [0x11ffff560-0x120000000]
               ex data      48528 bytes [0x140000000-0x14000bd90]
               ex text    1179648 bytes [0x120000000-0x120110000]

示されたヒープ・サイズおよびアドレス範囲は,プログラムの終了時に sbrk(0) により返された値 (ヒープ・ブレーク) を反映します。 このため,サイズは,プロセスに割り当てられたヒープ・スペース全体を表します。 Third Degree は,この sbrk(0) の解釈を変更するような malloc 変数の使用をサポートしません。

スタック・サイズとアドレス範囲は,プログラムの実行中にメイン・スレッドのスタック・ポインタが達する最下位アドレスを反映します。 つまり,Third Degree は,計測機構付きプロシージャが呼び出されるたびに,最下位アドレスを追跡します。 この値がスタック・サイズの最大値を反映するには,すべてのシェアード・ライブラリが計測機構付きである必要があります (たとえば,スレッド化されていないプログラムでは, third(1) コマンドの -all オプションを使用し,dlopen(3) を使ってロードしたライブラリでは -incobj オプションを使用します)。 スレッドのスタック (pthread_create を使用して作成された) は含まれません。

データやテキストのサイズおよびアドレス範囲は,実行可能プログラムの静的な部分や各シェアード・ライブラリがロードされた場所を示します。

7.3    Third Degree エラー・メッセージの解釈

Third Degree は,回復不可能なエラーとメモリ・アクセス・エラーの両方を報告します。 回復不可能なエラーには,次のものが含まれます。

回復不可能なエラーが起こると,計測機構付きアプリケーションが,ログ・ファイルをフラッシュした後にクラッシュします。 アプリケーションがクラッシュした場合には,まずログ・ファイルをチェックしたのち, third(1) コマンド行に -g を指定して,デバッガのもとでそれを再実行します。

メモリ・エラーには,次のものが含まれます (英字 3 文字の短縮形で表します)。

名前 エラー
ror 範囲外の読み込み。 ヒープ,スタック,静的領域のいずれでもない (たとえば NULL)。
ris スタック内の無効なデータの読み取り。 多くの場合,配列境界エラー。
rus スタック内の,有効だか初期化されていない記憶位置の読み取り。
rih ヒープ内の無効なデータの読み取り。 多くの場合,配列境界エラー。
ruh ヒープ内の,有効だが初期化されていない記憶位置の読み取り。
wor 範囲外の書き込み。 ヒープ,スタック,静的領域のいずれでもない。
wis スタックへの無効なデータの書き込み。 多くの場合,配列境界エラー。
wih ヒープへの無効なデータを書き込み。 多くの場合,配列境界エラー。
for 範囲外の解放。 ヒープ,スタックのいずれでもない。
fis スタック内のアドレスの解放。
fih ヒープ内の無効なアドレスの解放。 有効なオブジェクトがない。
fof すでに解放されたオブジェクトの解放。
fon null ポインタの解放 (単なる警告)。
mrn malloc による null の戻し。

特定のメモリ・エラーの報告は,-ignore オプションを 1 つまたは複数指定することによって抑制できます。 これは,ソースのないライブラリ関数内でエラーが発生した場合に役立つことがあります。 Third Degree を使用すると,個々のプロシージャおよびファイル内の特定のメモリ・エラーを特定の行番号で抑制できます。 詳細は, third(1) を参照してください。

代わりに,-excobj を指定するか,または -all-incobj オプションを省略することによって,チェックのためにライブラリを選択しないという方法もあります。

7.3.1    エラーの修正とアプリケーションの再試行

Third Degree が,計測機構付きプログラムから多数の書き込みエラーを報告する場合は,最初のいくつかのエラーを修正してから,プログラムを再計測します。 書き込みエラーは悪化する可能性があるだけではなく,Third Degree 内部のデータ構造体を破壊することもあります。

7.3.2    初期化されていない値の検出

初期化されていない値の使用を検出するという Third Degree の手法により,動作していたプログラムが,計測時に異常終了することがあります。 たとえば,プログラムが,malloc 関数への最初の呼び出しでゼロに初期化されたブロックが返されるということを前提にしている場合は,Third Degree がすべてのブロックを非ゼロ値 (省略時の設定では 0xfff8a5a5) にしてしまうため,そのプログラムの計測機構付きバージョンは異常終了します。

逆参照またはその他の方法でこの初期化されていない値を使用したために発生したシグナルを検出した場合には,Third Degree は次の形式のメッセージを表示します。

*** Fatal signal SIGSEGV detected.
*** This can be caused by the use of uninitialized data.
*** Please check all errors reported in app.3log.
 

初期化されていないデータの使用は,計測機構付きプログラムがクラッシュする最大の原因です。 問題の原因を判別するには,まず,初期化されていないスタックの読み取りと初期化されていないヒープの読み取りによるエラーがないかどうかを,ログ・ファイルでチェックします。 ほとんどの場合,ログ・ファイルの最後のエラーのうちの 1 つが問題の原因です。

エラーの発生源を示している問題がある場合は,それが本当に初期化されていないデータの読み取りに起因しているかどうかを,-uninit オプション (またはオプション全体) から heap および stack オプションを削除することで確認できます。 stack を削除すると,通常,Third Degree が各プロシージャのエントリで実行する,新規に割り当てられたスタック・メモリの非ゼロ値による埋め込みが不能に設定されます。 同様に,heap の削除は,各動的メモリ割り当てで実行される,ヒープ・メモリの初期化を不能に設定します。 これらのオプションの一方または両方を使用することにより,計測機構付きプログラムの動作を変更して,そのプログラムを正常に完了させることができる場合があります。 これは,計測機構付きプログラムがどのタイプのエラーでクラッシュしたかを判別するのに役立ちます。 その結果,ログ・ファイル内の特定のメッセージに集中できるようになります。

代わりに,計測機構付きプログラムをデバッガで実行して ( third(1) コマンドの -g オプションを使用),失敗の原因を削除する方法もあります。 メモリ・リークをチェックしたいだけの場合は,-uninit オプションを使用する必要はありませんが,-uninit オプションを使用すると,より正確なリークのレポートを得ることができます。

プログラムによってシグナル・ハンドラが設定される場合は,Third Degree が省略時のシグナル・ハンドラを変更しても,それを妨げる可能性はほとんどありません。 Third Degree は,通常プログラムのクラッシュを起こすシグナル (SIGILL, SIGTRAP, SIGABRT, SIGEMT, SIGFPE, SIGBUS, SIGSEGV, SIGSYS, SIGXCPU,および SIGXFSZ を含む) に対してだけ,シグナル・ハンドラを定義します。 Third Degree によるシグナルの処理は,-signals オプションを指定することにより不能にすることができます。

7.3.3    ソース・ファイルの探索

Third Degree は,各エラー・メッセージの前に,コンパイラが使用するスタイルでファイルおよび行番号を付加します。 たとえば,次のようになります。

----------------------------------------------------- fof -- 3 --
ex.c: 21: freeing already freed heap at byte 0 of 32-byte block
    free                               malloc.c
    main                               ex.c, line 21
    __start                            crt0.s
 

Third Degree は,エラーの発生源にできる限り近づいて指摘しようとします。 通常は,この例のように,エラー発生時の呼び出しスタックの最上位に近いプロシージャのファイルと行番号を表示します。 ただし,それがライブラリの中にあったり,現在のディレクトリになかったりすると,Third Degree はこのソース・ファイルを見つけることができません。 この場合,Third Degree は,指摘可能なソース・ファイルを見つけるまで,呼び出しスタックを下へ移動します。 通常は,これはライブラリ・ルーチン呼び出しのポイントです。

これらのエラー・メッセージにタグをつけるには,Third Degree がプログラムのソース・ファイルの位置を判断しなければなりません。 ソース・ファイルを含むディレクトリで Third Degree を実行している場合,Third Degree はそのディレクトリにあるソース・ファイルを探索します。 そうでない場合,Third Degree の探索パスにディレクトリを追加するには,-use オプションを 1 つまたは複数指定します。 これにより,Third Degree は,他のディレクトリに含まれるソース・ファイルを見つけることができるようになります。 各ソース・ファイルの位置は,そのファイルが探索された探索パスの最初のディレクトリです。

7.4    アプリケーションによるヒープ使用の検査

適切に割り当てられたメモリだけがアクセスおよび解放されていることを確認する実行時の検査に加え,Third Degreeは,アプリケーションによるヒープの使用法を理解するために,次の 2 つの方法を提供しています。

省略時の設定では,Third Degree はプログラムの終了時にリークがないかどうかを検査します。

この節では,Third Degree により供給された情報を使用して,アプリケーションによるヒープの使用法を分析する方法を説明します。

7.4.1    メモリ・リークの検出

メモリ・リークは,使用中のポインタが存在しないヒープ内のオブジェクトです。 このオブジェクトは,アクセスすることができず,使用も解放もできません。 これは役に立たず,消えることもなく,メモリの無駄使いです。

Third Degree は,単純なトレース・アンド・スィープ (trace-and-sweep) のアルゴリズムを使用して,メモリ・リークを見つけます。 Third Degree はルートのセット (現在アクティブなスタックおよび静的領域) から開始して,ヒープ内のオブジェクトへのポインタを見つけ,これらのオブジェクトに訪問済みのマークをつけます。 次に,これらのオブジェクトの中にある潜在的なすべてのポインタを再帰的に見つけ,最後にヒープをスィープして,マークが付いていないオブジェクトをすべて報告します。 マークの付いていないこれらのオブジェクトがリークです。

トレース・アンド・スィープのアルゴリズムによって,循環構造も含め,すべてのリークが見つかります。 このアルゴリズムは保守的であり,型情報がない場合,適切に境界合わせされ,ヒープ内の有効なオブジェクトの内部を指し示す 64 ビット・パターンは,すべてポインタとみなされます。 このように想定することにより,次のような問題が滅多に起こらないようにします。

リーク・レポートの精度を最大限にするには,-uninit h+s および -all オプションを使用します。 ただし,-uninit オプションを指定すると,プログラムが異常終了する恐れがあり,-all オプションでは,計測と実行時間が増加します。 このため,Leaks と Objects のリストだけをチェックして,プログラム・エラーの可能性を調べてください。

7.4.2    ヒープの読み取りとリーク・レポート

コマンド・オプションを指定すると,すべてのヒープ・オブジェクトまたはリークについての前回のレポートまたはリスト以降の,新しいヒープ・オブジェクトまたはリークだけをリストした,ヒープおよびリークの増分レポートを Third Degree に生成させることができます。 これらのレポートは,プログラムの終了時,あるいはユーザ指定の関数への n 回目の呼び出しの前または後に要求できます。 -blocks-every-before,および -after オプションの詳細については, third(1) を参照してください。 -blocks オプション (省略時の設定) では,ヒープ内のリークとオブジェクトの両方がレポートされるため,間違ったタイプとして分類されたイベント内のリークやオブジェクトを見落とすことがありません。 .3log ファイルには,間違った分類が生じる可能性のある状況が,レポートの正確さを向上させる方法と一緒に記述されています。

Third Degree は,レポートされたブロックが本当にリークしていることを示す証拠を見つけているが,一方,その証拠は,オブジェクトとしてレポートされたブロックがそうでないことを示しているので,リークのレポートは注意深く見る必要があります。 ただし,プログラムのデバッグやチェックにより,そうでないことがわかった場合は,ツールが証拠を誤って解釈したと推断することができます。

Third Degree は,関係するバイト数に基づいて重要度を減少させることにより,メモリ・オブジェクトおよびリークをレポートにリストします。 そこでは,同じ呼び出しスタックで割り当てられたオブジェクトは,まとめてグループ化されます。 たとえば,同一の呼び出しシーケンスによって,1 バイトのオブジェクトが 100 万割り当てられた場合には,Third Degree はそれらを 100 万の割り当てを含む 1 MB のグループとして報告します。

オブジェクトまたはリークが同じであり,レポートの中でグループ化する必要がある場合 (またはオブジェクトやリークが異なり,そのためにグループ化するべきでない場合) に Third Degree に指示するには,-depth オプションを指定します。 このオプションは,Third Degree がリークまたはオブジェクトを区別するために使用する呼び出しスタックの深さを設定します。 たとえば,オブジェクトに対して 1 という深さを指定した場合,Third Degree は,呼び出し元の関数に関係なく,ヒープ内の有効なオブジェクトを割り当てた関数と行番号により,それらをグループ化します。 逆に,リークに対して非常に大きな深さを指定した場合,Third Degree は,main から上方の,同一の呼び出しスタックのあるポイントに割り当てられたリークだけをグループ化します。

ほとんどのヒープのレポートでは,最初の 2,3 のエントリが記憶域のほとんどの原因となっていますが,小さなエントリの非常に長いリストがあることもあります。 レポートの長さを制限するには,-min オプションが使用できます。 このオプションは,リークしたメモリ総量,またはオブジェクトが使用中のメモリ総量のパーセンテージをしきい値として定義します。 残りの小さいリークまたはオブジェクトの合計が,このしきい値よりも小さい場合には,Third Degree は,これらを最後の 1 つのエントリにまとめてグループ化します。

注意

realloc 関数は (malloccopy,および free への呼び出しを含めることにより),常に新規のオブジェクトを割り当てるため,これを使用すると,Third Degree レポートの解釈が直観に反したものになります。 オブジェクトは,異なる識別で 2 回リストされることがあります。

リークとオブジェクトは相互に排他的です。 オブジェクトは,ルートから到達可能でなければなりません。

7.4.3    リークの探索

メモリ・リークを探索する時点は,常にはっきりしているわけではありません。 省略時の設定では,Third Degree はプログラムの終了後にリークがないかどうかをチェックしますが,これが常にユーザの要求するものとは限りません。

リークの検出は,使用されている構造体がすべて有効範囲内にあるうちに,プログラムの終了にできるだけ近いところで行うのが最もよい方法です。 ただし,リーク検出用のルートは,スタックおよび静的領域の内容であることを憶えておいてください。 プログラムが main から戻って終了し,その構造体の 1 つへの唯一のポインタがスタックに保持されている場合には,リーク探索の間,このポインタはルートとして参照されず,リークしたメモリとして誤って報告されることになります。 たとえば,次のような場合です。

     1  main (int argc, char* argv[]) {
     2      char* bytes = (char*) malloc(100);
     3      exit(0);
     4  }
 

このプログラムを計測する場合に,-blocks all -before exit を指定すると,Third Degree はリークを検出しません。 プログラムが exit 関数を呼び出すとき,main の変数はすべてまだ有効範囲内にあります。

ここで,次の例を検討してください。

     1  main (int argc, char* argv[]) {
     2      char* bytes = (char*) malloc(100);
     3  }
 

同じオプションを指定して (または指定しないで) このプログラムを計測すると,チェックが行われる前に main が戻ったために,Third Degree のリーク検査によって記憶域リークが報告されることがあります。 これら 2 つの動作内容は,bytes が本当のリークか,単に main が戻った時点でまだ使用中のデータ構造体であるかにより,いずれも正しいものです。

リーク検出を実行する時点を理解するために,プログラムを慎重に読むのではなく,指定したプロシージャへの指定した回数の呼び出しの後に,新しいリークがないかを検査できます。 次のオプションを指定して,省略時のリーク・チェックを無効にし,プロシージャ proc_name への各 10,000 回目の呼び出しの前にリークを要求します。

-blocks cancel
-blocks new -every 10000 -before proc_name

7.4.4    ヒープ・ヒストリの解釈

-history オプションを使用して,このプログラムを計測すると,Third Degree がプログラムのヒープ・ヒストリを生成できます。 ヒープ・ヒストリを使用することにより,プログラムが実行中に動的メモリをどのように使用したかがわかります。 たとえば,この機能を使用すると,データ構造体の未使用のフィールドを削除したり,または処理中のフィールドをパックしてメモリを効率的に使用することができます。 ヒープ・ヒストリは,割り当てられたけれど,アプリケーションが使用しなかったメモリ・ブロックも示します。

ヒープ・ヒストリが使用可能に設定されている場合,Third Degree は,動的に割り当てられた各オブジェクトについての情報を,それがアプリケーションによって解放される時点で収集します。 プログラムの実行が完了すると,Third Degree は,まだ有効なすべてのオブジェクトについてこの情報 (メモリ・リークも含む) をアセンブルします。 各オブジェクトごとに,Third Degree はオブジェクトの内容を見て,各ワードを,アプリケーションが書き込まなかったもの,0,有効なポインタ,またはその他の値に分類します。

Third Degree は次に,各オブジェクトに関する情報を,プログラム内の同じ呼び出しスタックで割り当てられたその他のオブジェクトすべてについて収集した情報とマージします。 その結果,任意の型のすべてのオブジェクトの使用に関する累積的な様子が提供されます。

Third Degree では,プログラムの存在期間中に割り当てられたすべてのオブジェクトと,それらの内容が使用された目的の要約が提供されます。 このレポートは,割り当てポイント (たとえば malloc または new などの割り当て機能を持つ関数が呼び出された箇所の呼び出しスタック) ごとに 1 つのエントリを示します。 エントリは,割り当て量で降順にソートされます。

各エントリは,次の内容を提供します。

各エントリの内容部分は,オブジェクトがどのように使用されたかを記述します。 割り当てられたすべてのオブジェクトが同じサイズではない場合,Third Degree は,すべてのオブジェクトに共通な最小のサイズだけを考慮します。 非常に大きな割り当てに関しては,オブジェクトの最初の部分の内容だけを要約します。 省略時の値では,これは最初の 1 KB です。 -size オプションを指定することによって,最大サイズの値が調整できます。

エントリの内容部分では,Third Degree は次の文字の 1 つを使用して,チェックする 32 ビットの各ロングワードを表します。

文字 説明
ドット(.) オブジェクトに書き込まれなかったロングワードを示し,メモリが浪費されている明らかなしるしです。 通常,より詳しい分析を行って,これが単にテストが不十分でこのフィールドを使用しなかっただけのか,フィールドのスワップまたはよりよい型を選択すれば解決する埋め込みの問題であるのか,あるいはこのフィールドがもはや使われていないものなのかを確認する必要があります。
z すべてのオブジェクトで,値が常に 0 (ゼロ) であるフィールドを示します。
pp ポインタ,つまりスタック,静的データ領域,ヒープへの有効なポインタだった (または 0 だった) 64 ビットの量を示します。
ss サムタイム・ポインタを示します。 このロングワードは,最低 1 つのオブジェクトではポインタと似ていましたが,すべてのオブジェクトでそうであったわけではありません。 これは,一部のインスタンスの初期化されていないポインタまたは共用体である可能性があります。 ただし,これは重大なプログラミング・エラーのしるしかもしれません。
i 最低 1 つのオブジェクトでなんらかの非ゼロ値で書き込まれたが,どのオブジェクトでもポインタ値を含んでいなかったロングワードを示します。

エントリが 100 MB を割り当てているとリストされていても,割り当てられたオブジェクトが任意の時点で 100 MB のヒープ・ストレージを使用したことを意味するわけではありません。 これは累積の数字であり,この時点までのプログラムの存在期間全体で 100 MB が割り当てられていることを示しています。 この 100 MB は,解放されたかもしれないし,リークしたか,あるいはまだヒープ内にあるかもしれません。 この数字は,単にこの割り当て関数が非常に多くの処理をしていることを示しています。

理想的には,実際に書き込まれたバイトの小数部は,常に 100 パーセントに近くならなければなりません。 ずっと低い場合には,割り当てられたバイトの一部が使用されていません。 パーセンテージが低い一般的な理由には,次のものがあります。

7.5    シンボル情報が不十分なプログラムにおける Third Degree の使用

計測した実行可能プログラムにシンボル情報がほとんどなく,Third Degree がいくつかのプログラム記憶位置を正確に指摘することが困難な場合,Third Degree は,プロシージャ名,ファイル名,または行番号が認識できないというメッセージを出力します。 たとえば,次のようになります。

------------------------------------------------------ rus -- 0 --
reading uninitialized stack at byte 40 of 176 in frame of main
    proc_at_0x1200286f0                libc.so
    pc = 0x12004a268                   libc.so
    main                               app, app.c, line 16
    __start                            app
 

Third Degree はスタック・トレースのプロシージャ名をプリントしようとしますが,(これが静的なプロシージャであるために) プロシージャ名が失われている場合は,Third Degree は計測機構付きプログラムのプログラム・カウンタをプリントします。 この情報により,デバッガを使って記憶位置を見つけることができます。 プログラム・カウンタが使用できない場合には,Third Degree は名前のないプロシージャのアドレスをプリントします。

もっと頻繁に起こるのは,ファイルが省略時の -g0 オプションでコンパイルされたためにファイル名または行番号が使用できない場合です。 この場合,Third Degree は,プロシージャが見つかったオブジェクトの名前をプリントします。 このオブジェクトは,メイン・アプリケーションまたはシェアード・ライブラリのいずれかです。

省略時の設定では,エラー・レポートがプリントされるのは,ソース・ファイル名と行番号のあるスタック・フレームが,スタックの上の 2 つのフレーム内に表示される場合に限ります。 これにより,システム・ライブラリ内の高度に最適化されたアセンブラ言語コードによって生成される可能性のある偽のレポートが隠されます。 デバッグ不能なプロシージャに関連するレポートが隠されるのを少なく (多く) するには,-hide オプションを使用します。

シンボル情報がないためにデバッグが困難な場合は,シンボル情報を多くしてプログラムを再コンパイルすることを検討します。 -g または -g1 オプションをつけて再コンパイルし,-x オプションなしでリンクします。 -g オプションを使用すると,レポートには,前述の例にあるようなバイト・オフセットではなく,変数名が表示されます。

7.6    Third Degree エラー・レポートの有効性検査

まれに,次のような見かけ上のエラーが発生することがあります。

誤った正の数を見つけたと思う場合は,エラーが報告されたプロシージャにデバッガを使用することにより確認できます。 Third Degree によって報告されるすべてのエラーは,アプリケーションのロードおよび格納時に検出され,エラー・レポートに示される行番号は,逆アセンブルによる出力に示されるものと一致します。 -g オプションを指定してプログラムのコンパイルおよび計測を行ってから,デバッグをします。

7.7    検出されないエラー

Third Degree は,次のような,実在のエラーの検出に失敗することがあります。