Sun Studio 12: dbx コマンドによるデバッグ

第 9 章 実行時検査

実行時検査 (RTC) を行うと、開発段階においてネイティブコードアプリケーションの実行時エラー (メモリーアクセスエラー、メモリーリークなど) を自動的に検出できます。メモリーの使用状況も監視できます。Java コードでは、実行時検査を行うことはできません。

この章は次の各節から構成されています。

概要

RTC は、統合的なデバッグ機能であり、コレクタによるパフォーマンスデータの収集時を除けば、実行時にあらゆるデバッグ機能を利用できます。

次に、RTC の機能を簡単に説明します。

-g フラグを付けてコンパイルすると、RTC エラーメッセージでのソース行番号の関連性が与えられます。RTC は、最適化 -O フラグによってコンパイルされたプログラムを検査することもできます。-g オプションによってコンパイルされていないプログラムについては、特殊な考慮事項があります。

RTC を実行するには、check コマンドを使用します。

RTC を使用する場合

大量のエラーが一度に検出されないようにするには、RTC を開発サイクルの初期の段階で使用します。この段階では、プログラムの構成要素となる個々のモジュールを開発します。この各モジュールを実行する単位テストを作成し、RTC を各モジュールごとに 1 回ずつ使用して検査を行います。これにより、一度に処理するエラーの数が減ります。すべてのモジュールを統合して完全なプログラムにした場合、新しいエラーはほとんど検出されません。エラー数をゼロにしたあとでモジュールに変更を加えた場合にのみ、RTC を再度実行してください。

RTC の必要条件

RTC を使用するには、次の要件を満たす必要があります。

実行時検査の制限については、「実行時検査の制限」を参照してください。

実行時検査

実行時検査を使用するには、使用したい検査の種類を指定します。

メモリー使用状況とメモリーリーク検査を有効化

メモリー使用状況とメモリーリークの検査をオンにするには、次を入力します。


(dbx) check -memuse

MUC か MLC がオンになっている場合、showblock コマンドを実行する、所定のアドレスにおけるヒープブロックに関する詳細情報を表示できます。この詳細情報では、ブロックの割り当て場所とサイズを知ることができます。詳細については、showblock コマンド」を参照してください。

メモリーアクセス検査を有効化

メモリーアクセス検査をオンにするには、次を入力します。


(dbx) check -access

すべての RTC を有効化

メモリーリーク、メモリー使用状況、およびメモリーアクセスの各検査をオンにするには、次のように入力します。


(dbx) check -all

詳細については、check コマンド」を参照してください。

RTC を無効化

RTC をすべて無効にするには、次のように入力します。


(dbx) uncheck -all

詳細については、uncheck コマンド」を参照してください。

プログラムを実行

目的のタイプの RTC を有効にしてテストするプログラムを実行します。この場合、ブレークポイントを設定してもしなくてもかまいません。

プログラムは正常に動作しますが、それぞれのメモリーアクセスが発生する直前にその妥当性チェックが行われるため、動作速度は遅くなります。無効なアクセスを検出すると、dbx はそのエラーの種類と場所を表示します。 制御はユーザーに戻ります (dbx 環境変数 rct_auto_continueon になっている場合を除く (dbx 環境変数の設定」を参照))。

次に、dbx コマンドを実行します。where コマンドでは現在のスタックトレースを呼び出すことができます。また print を実行すれば変数を確認できます。エラーが致命的でなければ、 cont コマンドでプログラムの処理を続行します。プログラムは次のエラーまたはブレークポイントまで、どちらか先に検出されるところまで実行されます。詳細については、cont コマンド」を参照してください。

rtc_auto_continue 環境変数が on に設定されている場合、RTC はそのままエラーを求めて自動的に続行されます。 検出したエラーは、dbx 環境変数 rtc_error_log_name で指定したファイルにリダイレクトされます (dbx 環境変数の設定」を参照)。デフォルトログファイル名は、/tmp/dbx.errlog.uniqueid です。

RTC エラーの報告が不要な場合は、suppress コマンドを使用します。詳細については、suppress コマンド」を参照してください。

次の例は、hello.c と呼ばれるプログラムのメモリーアクセス検査とメモリー使用状況検査をオンにする方法を示しています。


% cat -n hello.c
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4
     5 char *hello1, *hello2;
     6
     7 void
     8 memory_use()
     9 {
    10      hello1 = (char *)malloc(32);
    11      strcpy(hello1, "hello world");
    12      hello2 = (char *)malloc(strlen(hello1)+1);
    13      strcpy(hello2, hello1);
    14 }
    15
    16 void
    17 memory_leak()
    18 {
    19      char *local;
    20      local = (char *)malloc(32);
    21      strcpy(local, "hello world");
    22 }
    23
    24 void
    25 access_error()
    26 {
    27      int i,j;
    28
    29      i = j;
    30 }
    31
    32 int
    33 main()
    34 {
    35      memory_use();
    36      access_error();
    37      memory_leak();
    38      printf("%s\n", hello2);
    39      return 0;
    40 }
% cc -g -o hello hello.c

% dbx -C hello
ld.so.1 の読み込み中
librtc.so のシンボル情報を読んでいます
libw.so.1 の読み込み中
libdl.so.1 の読み込み中

(dbx) check -access
アクセス検査 - ON
(dbx) check -memuse
メモリー使用状況検査 - ON
(dbx) run Running: hello
(プロセス id 18306)
実行時検査を有効にしています...done
非初期化領域からの読み取り (rui):
4 バイト読み取りをアドレス 0xeffff068 でしようとしました
               それは 96 バイト現スタックポインタより上です
変数は 'j' です。
現関数: access_error
    29       i = j;
(dbx) cont
hello world
メモリーリーク検査中...
実際のリークの報告    (実際のリーク:        1  合計サイズ:      32 バイト)
 合計      ブロック   リーク     割り当て呼び出しスタック
 サイズ     数      ブロック
                アドレス
==========  ======     ========== =======================================
     32    1    0x21aa8 memory_leak < main

起こり得るリークの報告  (起こり得るリーク:      0  合計サイズ:      0 バイト)
メモリー使用状況検査中...
ブロック使用量の報告   (ブロック使用量:       2  合計サイズ:      44 バイト

 合計     割合   ブロック  平均   割り当て呼び出しスタック
 サイズ    %    数     サイズ
========== ====   ======    ======  =======================================
     32  72%    1    32  memory_use < main
     12  27%    1    12  memory_use < main

実行完了。終了コードは 0 です。

関数 access_error() は、初期化される前の変数 j を読み取ります。RTC は、このアクセスエラーを非初期化領域からの読み取り (rui) として報告します。

関数 memory_leak() は、終了する前に local を解放 (free()) しません。memory_leak() が終了してしまうと、local がスコープ外になり、行 20 で確保したブロックがリークになります。

プログラムは、常にスコープ内にある大域変数 hello1hello2 を使用します。これらの変数はいずれも、使用中ブロック (biu) として報告される割り当て済みメモリーを動的に指します。

アクセス検査の使用

アクセス検査では、読み取り、書き込み、割り当て、解放の各操作を監視することによって、プログラムがメモリーに正しくアクセスするかどうかを検査します。

プログラムは、さまざまな方法で間違ってメモリーを読み取ったり、メモリーに書き込んだりすることがあります。 このようなエラーをメモリーアクセスエラーといいます。たとえば、ヒープブロックの free() 呼び出しを使用したり、または関数が局所変数にポインタを返したために、プログラムが参照するメモリーブロックの割り当てが解放されている可能性があります。アクセスエラーはプログラムでワイルドポインタの原因になり、間違った出力やセグメント不正など、プログラムの異常な動作を引き起こす可能性があります。メモリーアクセスエラーには、検出が非常に困難なものもあります。

RTC は、プログラムによって使用されているメモリーの各ブロックの情報を追跡するテーブルを管理します。プログラムがメモリー操作を行うと、RTC は関係するメモリーブロックの状態に対してその操作が有効かどうかを判断します。メモリーの状態として次のものがあります。

RTC を使用してメモリーアクセスエラーを見つける方法は、コンパイラがプログラム中の構文エラーを見つける方法と似ています。いずれの場合でも、プログラム中のエラーが発生した位置と、その原因についてのメッセージとともにエラーのリストが生成され、リストの先頭から順に修正していかなければなりません。これは、あるエラーがほかのエラーと関連して連結されたような作用があるためです。連結の最初のエラーが先頭の原因となり、そのエラーを修正することにより、そのエラーから派生したほかの問題も解決されることがあります。

たとえば、初期化されていないメモリーの読み取りにより、不正なポインタが作成されるとします。 すると、これが原因となって不正な読み取りと書き込みのエラーが発生し、それがまた原因となってさらに別の問題が発生するというようなことになる場合があります。

メモリーアクセスエラーの報告

メモリーアクセスエラーを検出すると RTC は次の情報を出力します。

エラー 

情報 

type

エラーの種類。 

access

試みられたアクセスの種類 (読み取りまたは書き込み)。 

size

試みられたアクセスのサイズ。 

address

試みられたアクセスのアドレス 

size

リークしたブロックのサイズ 

detail

アドレスについてのさらに詳しい情報。たとえば、アドレスがスタックの近くに存在する場合、現在のスタックポインタからの相対位置が与えられ ます。アドレスが複数存在する場合、一番近いブロックのアドレス、サイズ、相対位置が与えられます。 

stack

エラー時の呼び出しスタック (バッチモード)。 

allocation

addr がヒープにある場合、もっとも近いヒープブロックの割り当てトレースが与えられます。 

location

エラーが発生した位置。行が特定できる場合には、ファイル名、行番号、関数が示されます。行番号がわからないときは関数とアドレスが示されます。 

代表的なアクセスエラーは次のとおりです。


非初期化領域からの読み取り (rui):
4 バイト読み取りをアドレス 0xefffee50 でしようとしました
    それは 96 バイト現スタックポインタより上です
変数は 'j' です。
現関数: rui
   12           i = j;

メモリーアクセスエラー

RTC は、次のメモリーアクセスエラーを検出します。

メモリーリークの検査

メモリーリークとは、プログラムで使用するために割り当てられているが、プログラムのデータ領域中のいずれも指していないポインタを持つ、動的に割り当てられたメモリーブロックを言います。そのようなブロックは、メモリーのどこに存在しているかプログラムにわからないため、プログラムに割り当てられていても使用することも解放することもできません。RTC はこのようなブロックを検知し、報告します。

メモリーリークは仮想メモリーの使用を増やし、一般的にメモリーの断片化を招きます。その結果、プログラムやシステム全体のパフォーマンスが低下する可能性があります。

メモリーリークは、通常、割り当てメモリーを解放しないで、割り当てブロックへのポインタを失うと発生します。メモリーリークの例を次に示します。


void
foo()
{
    char *s;
    s = (char *) malloc(32);

    strcpy(s, "hello world");

    return; /* s が解放されていない。foo が戻るとき、     */
            /* malloc されたブロックを指しているポイントが存在しないため、         */
            /* ブロックはリークする                         */
}

リークは、API の不正な使用が原因で起こる可能性があります。


void
printcwd()
{

    printf("cwd = %s\n", getcwd(NULL, MAXPATHLEN));

    return; /* libc の関数 getcwd() は、最初の引数が     */
            /* NULL の場合 malloc された領域へのポインタを返す */
            /* プログラムは、これを解放する必要がある。この場合、   */
            /* ブロックが解放されていないため、結果的にリークになる。*/
}

メモリーリークを防ぐには、必要のないメモリーは必ず解放します。また、メモリーを確保するライブラリ関数を使用する場合は、メモリーを解放することを忘れないでください。

解放されていないブロックを「メモリーリーク」と呼ぶこともあります。ただし、この定義はあまり使用されません。 プログラムが短時間で終了する場合でも、通常のプログラミングではメモリーを解放しないからです。プログラムにそのブロックに対するポインタがある場合、RTC はそのようなブロックはメモリーリークとして報告しません。

メモリーリーク検査の使用

RTC では、次のメモリーリークエラーを検出します。

リークの可能性

RTC が「リークの可能性」として報告するエラーには 2 種類あります。1 つは、ブロックの先頭を指すポインタが検知されず、ブロックの内部を指しているポインタが見つかった場合です。これは、ブロック中のアドレス (aib) エラーとして報告されます。このようなブロック内部を指すポインタが見つかった場合は、プログラムに実際にメモリーリークが発生しています。ただし、プログラムによってはポインタに対して故意にそのような動作をさせている場合があり、これは当然メモリーリークではありません。RTC はこの違いを判別できないため、本当にリークが発生しているかどうかはユーザー自身の判断で行う必要があります。

もう 1 つのリークの種類は、レジスタ中のアドレス (air) エラーとして報告されるリークです。これは、ある領域を指すポインタがデータ空間中には存在せず、レジスタ内に存在する場合です。レジスタがブロックを不正に指していたり、古いメモリーポインタが残っている場合には、実際にメモリーリークが発生しています。ただし、コンパイラが最適化のために、ポインタをメモリーに書き込むことなく、レジスタのブロックに対して参照させることがありますが、この場合はメモリーリークではありません。プログラムが最適化され、showleaks コマンドでエラーが報告された場合のみ、 リークでない可能性があります。詳細については、showleaks コマンド」を参照してください。


注 –

RTC リーク検査では、標準の libcmalloc/free/realloc 関数またはアロケータをこれらの関数に基づいて使用する必要があります。ほかのアロケータについては、「実行時検査アプリケーションプログラミングインタフェース」を参照してください。


リークの検査

メモリーリーク検査がオンの場合、メモリーリークの走査は、テスト中のプログラムが終了する直前に自動的に実行されます。検出されたリークはすべて報告されます。プログラムを、kill コマンドによって強制的に終了しないでください。 次に、典型的なメモリーリークエラーによるメッセージを示します。


メモリーリーク (mel):
大きさ 6 バイトのリークのあるブロックをアドレス 0x21718 に発見
割り当て時のスタックの状態:
        [1] foo() 行番号 63 test.c
        [2] main() 行番号 47 test.c

プログラムには通常 main (FORTRAN 77 では MAIN) 手続きが存在します。プログラムは exit(3) が呼び出されるか、main から返った時点で終了します。いずれの場合でも、main のすべての局所変数はプログラムが停止するまでスコープから出ず、それらを指す特定のヒープブロックはすべてメモリーリークとして報告されます。

main() に割り当てられているヒープブロックはプログラムでは解放しないのが一般的です。これらのヒープブロックはプログラムが停止するまでスコープ内に残り、プログラムの停止後オペレーティングシステムによって自動的に解放されるためです。 main() に割り当てられたブロックがメモリーリークとして報告されないようにするには、main() が終了する直前にブレークポイントを設定しておきます。プログラムがそこで停止したとき、RTC の showleaks コマンドを実行すれば、main() とそこで呼び出されるすべての手続きで参照されなくなったヒープブロックのすべてが表示されます。

詳細については、showleaks コマンド」を参照してください。

メモリーリークの報告を理解する

リーク検査を有効にすると、プログラムの終了時にリークレポートが自動的に生成されます。kill コマンドでプログラムを終了した場合を除き、リークの可能性がすべて報告されます。レポートの詳細レベルは、dbx 環境変数 rtc_mel_at_exit (dbx 環境変数の設定」を参照) で制御します。デフォルトで、非冗長リークレポートが生成されます。

レポートは、リークのサイズによってソートされます。実際のメモリーリークが最初に報告され、次に可能性のあるリークが報告されます。詳細レポートには、スタックトレース情報の詳細が示されます。 行番号とソースファイルが使用可能であれば、これらも必ず含まれます。

次のメモリーリークエラー情報が、2 種類の報告のどちらにも含まれます。

情報 

内容の説明 

サイズ 

リークしたブロックのサイズ 

場所 

リークしたブロックが割り当てられた場所 

アドレス 

リークしたブロックのアドレス 

スタック 

割り当て時の呼び出しスタック。 check -frames によって制約される

次に、対応する簡易メモリーリークレポートを示します。


実際のリークの報告      (実際のリーク:        3  合計サイズ:      2427 バイト)

 合計       ブロック   リーク      割り当て呼び出しスタック
 サイズ      数      ブロック
                          アドレス
==========   ======     ==========    =======================================
    1852     2    -       true_leak < true_leak
     575     1   0x22150  true_leak < main

起こり得るリークの報告    (起こり得るリーク:      1  合計サイズ:      8 バイト)
 合計       ブロック   リーク         割り当て呼び出しスタック
 サイズ      数      ブロック
                             アドレス
==========   ======     ==========      =======================================
      8      1    0x219b0    in_block < main

次に、典型的な詳細リークレポートを示します。


実際のリークの報告      (実際のリーク:        3  合計サイズ:      2427 バイト)

メモリーリーク (mel):
大きさ 1 バイトのリークのあるブロックをアドレス 0x20f18 に発見
割り当て時のスタックの状態:
        [1] true_leak()   行番号  220 "leaks.c"
        [2] true_leak()   行番号  224 "leaks.c"

メモリーリーク (mel):
大きさ 575 バイトのリークのあるブロックをアドレス 0x22150 に発見
割り当て時のスタックの状態:
        [1] true_leak()   行番号  220 "leaks.c"
        [2] main()        行番号  87 "leaks.c"

起こり得るリークの報告    (起こり得るリーク:      1  合計サイズ:      8 バイト)

メモリーリークの可能性 -- ブロック中のアドレス (aib):
大きさ 4 バイトのリークのあるブロックをアドレス 0x219b0 に発見
割り当て時のスタックの状態:
        [1] in_block()    行番号  177 "leaks.c"
        [2] main()        行番号  100 "leaks.c"

リークレポートの生成

showleaks コマンドを使用すると、いつでもリークレポートを要求することができます。このコマンドは、前回の showleaks コマンド以降の新しいメモリーリークを報告するものです。詳細については、showleaks コマンド」を参照してください。

リークレポート

リークレポートの数が多くなるのを避けるため、RTC は同じ場所で割り当てられたリークを自動的に 1 つにまとめて報告します。1 つにまとめるか、それぞれ各リークごとに報告するかは、一致フレーム数引数によって決まります。この引数は、check -leaks コマンドを実行する際は -match m オプション、showleaks コマンドを実行する際は -m オプションで指定します。呼び出しスタックが 2 つ以上のリークを割り当てる際に m 個のフレームと一致した場合は、リークは 1 つにまとめて報告されます。

次の 3 つの呼び出しシーケンスを考えてみます。

ブロック 1 

ブロック 2 

ブロック 3 

[1] malloc

[1] malloc

[1] malloc

[2] d() at 0x20000

[2] d() at 0x20000

[2] d() at 0x20000

[3] c() at 0x30000

[3] c() at 0x30000

[3] c() at 0x31000

[4] b() at 0x40000

[4] b() at 0x41000

[4] b() at 0x40000

[5] a() at 0x50000

[5] a() at 0x50000

[5] a() at 0x50000

これらのブロックがすべてメモリーリークを起こす場合、m の値によって、これらのリークを別々に報告するか、1 つのリークが繰り返されたものとして報告するかが決まります。m が 2 のとき、ブロック 1 とブロック 2 のリークは 1 つのリークが繰り返されたものとして報告されます。 これは、malloc() の上にある 2 つのフレームが共通しているためです。ブロック 3 のリークは、c() のトレースがほかのブロックと一致しないので別々に報告されます。m が 2 よりも大きい場合、RTC はすべてのリークを別々に報告します (malloc はリークレポートでは表示されません)。

一般に、m の値が小さければリークのレポートもまとめられ、m の値が大きければまとめられたリークレポートが減り、別々のリークレポートが生成されます。

メモリーリークの修正

RTC からメモリーリーク報告を受けた場合にメモリーリークを修正する方法についてのガイドラインを次に示します。

詳細については、showleaks コマンド」を参照してください。

メモリー使用状況検査の使用

メモリー使用状況検査は、使用中のヒープメモリーすべてを確認することができます。この情報によって、プログラムのどこでメモリーが割り当てられたか、またはどのプログラムセクションが大半の動的メモリーを使用しているかを知ることができます。この情報は、プログラムの動的メモリー消費を削減するためにも有効であり、パフォーマンスの向上に役立ちます。

メモリー使用状況検査は、パフォーマンス向上または仮想メモリーの使用制御に役立ちます。プログラムが終了したら、メモリー使用状況レポートを生成できます。 メモリー使用情報は、メモリーの使用状況を表示させるコマンド (showmemuse) を使用して、プログラムの実行中に随時取得することもできます。詳細については、showmemuse コマンド」を参照してください。

メモリー使用状況検査をオンにすると、リーク検査もオンになります。プログラム終了時のリークレポートに加えて、使用中ブロック (biu) レポートも得ることができます。デフォルトでは、使用中ブロックの簡易レポートがプログラムの終了時に生成されます。 メモリー使用状況レポートの詳細を制御するには、dbx 環境変数 rtc_biu_at_exit (dbx 環境変数の設定」を参照) を使用します。

次に、典型的な簡易メモリー使用状況レポートを示します。


ブロック使用量の報告 (ブロック使用量: 5   合計サイズ: 40 バイト)

 合計     割合    ブロック  平均     割り当て呼び出しスタック
 サイズ          %     数      サイズ
========== ====    ======    ======   =====================================
     16  40%     2       8   nonleak < nonleak
      8  20%     1       8   nonleak < main
      8  20%     1       8   cyclic_leaks < main
      8  20%     1       8   cyclic_leaks < main

次に、対応する詳細メモリー使用状況レポートを示します。

ブロック使用量の報告 (ブロック使用量: 5   合計サイズ: 40 バイト)

ブロック使用状況(biu):
2 個のブロックを見つけました。合計 16 bytes (合計 40.00%; 平均ブロックサイズ 8)
割り当て時のスタックの状態:
     [1] nonleaks()    行番号 182 "memuse.c"
     [2] nonleaks()    行番号 185 "memuse.c"

ブロック使用状況(biu):
サイズ 8 bytes のブロックをアドレス 0x21898 で見つけました (合計 20.00%)
割り当て時のスタックの状態:
     [1] nonleaks()    行番号 182 "memuse.c"
     [2] main()        行番号  74 "main.c"

ブロック使用状況(biu):
サイズ 8 bytes のブロックをアドレス 0x21958 で見つけました (合計 20.00%)
割り当て時のスタックの状態:
     [1] cycle_leaks() 行番号 154 "memuse.c"
     [2] main()        行番号 118 "main.c"

ブロック使用状況(biu):
サイズ 8 bytes のブロックをアドレス 0x21978 で見つけました (合計 20.00%)
割り当て時のスタックの状態:
     [1] cycle_leaks() 行番号 155 "memuse.c"
     [2] main()        行番号 118 "main.c"

showmemuse コマンドを使用すると、メモリー使用状況レポートをいつでも要求できます。

エラーの抑止

RTC はエラーレポートの数や種類を限定するよう、エラーの抑制機能を備えています。エラーが発生してもそれが抑制されている場合は、エラーは無視され、報告されずにプログラムは継続します。

エラーは suppress コマンド (suppress コマンド」を参照) で抑止できます。

エラー抑止を取り消すには、unsuppress コマンド (unsuppress コマンド」を参照) を使用します。

抑止機能は同じデバッグ節内の run コマンドの実行期間中は有効ですが、debug コマンドを実行すると無効になります。

抑止のタイプ

次の抑制機能があります。

スコープと種類による抑制

どのエラーを抑止するかを指定する必要があります。次のように、プログラムのどの部分に抑制を適用するかを指定できます。

オプション 

内容の説明 

大域 

スコープが指定されていないと全体のスコープが対象になり、すべてのプログラムに適用されます。 

ロードオブジェクト 

共有ライブラリなど、すべてのロードオブジェクトが対象になります。 

ファイル 

特定のファイルのすべての関数が対象になります。 

関数 

特定の関数が対象になります。 

行 

特定のソース行が対象になります。 

アドレス 

特定のアドレスが対象になります。 

最新エラーの抑止

デフォルトで RTC を実行すると、最新のエラーで同じエラーが繰り返し報告されることがなくなります。この機能は、dbx 環境変数 rtc_auto_suppress で制御します。 rtc_auto_suppresson のとき (デフォルト)、特定箇所の特定エラーは最初の発生時にだけ報告され、そのあと同じエラーが同じ場所で発生しても報告が繰り返されることはありません。最新エラーを抑止すると、繰り返し実行するループに 1 つのエラーがあっても、それが何度も報告されることがなく、便利です。

エラー報告回数の制限

dbx 環境変数 rtc_error_limit では、報告されるエラーの回数を制限します。エラー制限は、アクセスエラーとリークエラーに別々に設定します。たとえば、エラー制限を 5 に設定すると、プログラムの終了時のリークレポートと、showleaks コマンドの実行ごとに、アクセスエラーとリークエラーがそれぞれ最高で 5 回報告されます。 デフォルトは 1000 です。

エラー抑止の例

次の例では、main.cc はファイル名、foobar は関数を示し、a.out は実行可能ファイルの名前を示します。

割り当てが関数 foo で起こったメモリーリークは報告しません。


suppress mel in foo

libc.so.1 から割り当てられた使用中のブロック報告を抑止します。


suppress biu in libc.so.1

a.out の非初期化機能からの読み取りを抑止します。


suppress rui in a.out

ファイル main.cc の非割り当てメモリーからの読み取りを報告しません。


suppress rua in main.cc

main.cc の行番号 10 での重複解放を抑止します。


suppress duf at main.cc:10

関数 bar のすべてのエラー報告を抑止します。


suppress all in bar

詳細については、suppress コマンド」を参照してください。

デフォルトの抑止

RTC では、-g オプション (記号) を指定してコンパイルを行わなくてもすべてのエラーを検出できます。しかし、非初期化メモリーからの読み取りなど、正確さを保証するのに 記号 (-g) 情報が必要な特定のエラーもあります。このため、a.outrui や 共有ライブラリの ruiaibair など特定のエラーは、記号情報が取得できない場合は、デフォルトで抑制されます。この動作は、suppressunsuppress コマンドの -d オプションを使用することで変更できます。

たとえば、次を実行すると、RTC は記号情報が存在しない (-g オプションを指定しないでコンパイルした) コードについて「非初期化メモリーからの読み取り (rui)」を抑制しません。


unsuppress -d rui

詳細については、unsuppress コマンド」を参照してください。

抑止によるエラーの制御

プログラムが大きい場合、エラーの数もそれに従って多くなることが予想されます。このような場合は、このような場合は、suppress コマンドを使用することにより、エラーレポートの数を管理しやすい大きさまで抑制し、一度で修正するエラーを制限します。

たとえば、一度で検出するエラーをタイプによって制限できます。一般的によくあるエラーのタイプは ruiruawua に関連したもので、この順序で検出されます。rui エラーはそれほど致命的なエラーではなく、このエラーが検出されてもたいていの場合プログラムは問題なく実行終了します。それに比べて ruawua エラーは不正なメモリーアドレスにアクセスし、ある種のコーディングエラーを引き起こすため、問題は深刻です。

まず ruirua エラーを抑制し、wua エラーをすべて修正したあと、もう一度プログラムを実行します。次に rui エラーだけを抑制し、rua エラーをすべて修正したあと、もう一度プログラムを実行します。さらにエラーの抑制をせずに、すべての rui エラーを修正します。最後にプログラムを実行し、エラーがすべて修正されたことを確認してください。

最新のエラー報告を抑止するには、「suppress -last」を実行します。

子プロセスにおける RTC の実行

子プロセスで RTC を実行するには、dbx 環境変数 rtc_inheriton に設定します。デフォルトでは off になります (dbx 環境変数の設定」を参照)。

親で RTC が有効になっていて、dbx 環境変数 follow_fork_mode が child に設定されているときに dbx を実行するとプロセスの RTC を実行できます (dbx 環境変数の設定」を参照)。

分岐が発生すると、dbx は子に RTC を自動的に実行します。プログラムが exec() を呼び出すと、exec() を呼び出すプログラムの RTC 設定がそのプログラムに渡ります。

特定の時間に RTC の制御下におくことができるプロセスは 1 つだけです。次に例を示します。


% cat -n program1.c
     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4
     5 int
     6 main()
     7 {
     8      pid_t child_pid;
     9      int parent_i, parent_j;
    10
    11      parent_i = parent_j;
    12
    13      child_pid = fork();
    14
    15      if (child_pid == -1) {
    16          printf("parent: Fork failed\n");
    17          return 1;
    18      } else if (child_pid == 0) {
    19          int child_i, child_j;
    20
    21          printf("child: In child\n");
    22          child_i = child_j;
    23          if (execl("./program2", NULL) == -1) {
    24              printf("child: exec of program2 failed\n");
    25              exit(1);
    26          }
    27      } else {
    28          printf("parent: child's pid = %d\n", child_pid);
    29      }
    30      return 0;
    31 }

 % cat -n program2.c
     1
     2 #include <stdio.h>
     3
     4 main()
     5 {
     6      int program2_i, program2_j;
     7
     8      printf ("program2: pid = %d\n", getpid());
     9      program2_i = program2_j;
    10
    11      malloc(8);
    12
    13      return 0;
    14 }
%

 % cc -g -o program1 program1.c
 % cc -g -o program2 program2.c
 % dbx -C program1
program1 のシンボル情報を読んでいます
rtld /usr/lib/ld.so.1 のシンボル情報を読んでいます
librtc.so のシンボル情報を読んでいます
libc.so.1 のシンボル情報を読んでいます
libdl.so.1 のシンボル情報を読んでいます
libc_psr.so.1 のシンボル情報を読んでいます
 (dbx) check -all
アクセス検査 - ON
メモリー使用状況検査 - ON
 (dbx) dbxenv follow_fork_mode child
 (dbx) run
実行中: program1
(プロセス id 3885)
実行時検査を有効にしています...done
RTC は親プロセス、program1 の最初のエラーを報告します。
非初期化領域からの読み取り (rui):
4 バイト読み取りをアドレス 0xeffff110 でしようとしました
それは 104 バイト現スタックポインタより上です
変数は 'parent_j' です。
現関数: main
   11       parent_i = parent_j;
(dbx) cont
   dbx 警告: フォークしました。親プロセス内での実行時検査機能は休止します
  プロセス 3885 から切り離し中
  プロセス 3886 に接続しました。
follow_fork_mode が child に設定されているため、フォークが起こると、エ
ラー検査が親プロセスから子プロセスに切り替えられます。
  _libc_fork で停止しました アドレス  0xef6b6040
  0xef6b6040: _fork+0x0008:bgeu        _fork+0x30
  現関数: main
    13       child_pid = fork();
   親: child's pid = 3886
 (dbx) cont
   子: In child
   非初期化領域からの読み取り (rui):
   4 バイト読み取りをアドレス 0xeffff108 でしようとしました
        それは 96 バイト現スタックポインタより上です
RTC は子プロセスのエラーを報告します。
  変数は 'child_j' です。
  現関数: main
    22       child_i = child_j;
 (dbx) cont
dbx: プロセス 3886 は exec("./program2") をするところです
dbx: プログラム "./program2" が今 exec されました
dbx: オリジナルプログラムに戻るには "debug $oprog" を使用します
program2 のシンボル情報を読んでいます
すでに読んでいるので、ld.so.1 を飛ばします
すでに読んでいるので、librtc.so を飛ばします
すでに読んでいるので、libc.so.1 を飛ばします
すでに読んでいるので、libdl.so.1 を飛ばします
すでに読んでいるので、libc_psr.so.1 を飛ばします
program2 の実行が起こると、RTC 設定値は program2 から継承されるため、ア
クセスおよびメモリー使用状況の検査がそのプロセスに対して有効になります。

実行時検査を有効にしています...done
main で停止しました 行番号 8 ファイル "program2.c"
     8       printf ("program2: pid = %d\n", getpid());
(dbx) cont
 program2: pid = 3886
非初期化領域からの読み取り (rui):
4 バイト読み取りをアドレス 0xeffff13c でしようとしました
それは 100 バイト現スタックポインタより上です
RTC は、実行されたプログラム、program2 のアクセスエラーを報告します。
変数は 'program2_j' です。
現関数: main
     9       program2_i = program2_j;
 (dbx) cont
メモリーリーク検査中...
RTC は、RTC 制御下にある間に終了したプロセス、program2 に関するメモリー使
用状況レポートとメモリーリークレポートを出力します。
実際のリークの報告   (実際のリーク:        1    合計サイズ:      8 バイト) 

 合計       ブロック    リーク       割り当て呼び出しスタック
 サイズ      数       ブロック
                  アドレス
==========   ======      ==========    ====================================
      8      1     0x20c50  main
起こり得るリークの報告 (起こり得るリーク:      0    合計サイズ:      0 バイト)

実行完了。終了コードは 0 です。

接続されたプロセスへの RTC の使用

実行時検査は、影響を受けるメモリーがすでに割り当てられている場合に RUI が検出できなかった例外を伴う接続済みプロセスで機能します。ただし、実行時検査を開始する際、rtcaudit.so を事前に読み込んでおく必要があります。接続先のプロセスが 64 ビットプロセスである場合、64 ビットの rtcaudit.so を使用します。製品が /opt ディレクトリにインストールされている場合、rtcaudit.so は次の場所にあります。

/opt/SUNWspro/lib/v9/rtcaudit.so (64 ビット SPARC プラットフォーム)

/opt/SUNWspro/lib/amd64/rtcaudit.so (AMD64 プラットフォーム)

32 ビットプラットフォームの場合は /opt/SUNWspro/lib

rtcaudit.so を事前に読み込むには、次のように入力します。


% setenv LD_AUDIT path-to-rtcaudit/rtcaudit.so

rtcaudit.so を常時読み込んだ状態にせず、必要なときにだけ読み込まれるように環境変数 LD_AUDIT を設定してください。たとえば、次のようにします。


% setenv LD_AUDIT...
% アプリケーションの実行
% unsetenv LD_AUDIT

プロセスに接続したら、RTC を有効にすることができます。

接続したいプログラムがフォークされるか、または別のプログラムによって実行された場合は、LD_AUDIT をフォークを行うメインプログラムに設定する必要があります。 LD_AUDIT の設定値は、フォーク先および実行主体を問わず継承されます。

環境変数 LD_AUDIT は 32 ビットプログラムと 64 ビットプログラムの両方に適用されるため、64 ビットプログラムを実行する 32 ビットプログラム用、または 32 ビットプログラムを実行する 64 ビットプログラム用に正しいライブラリを選択することが困難です。Solaris OS のバージョンによっては、環境変数 LD_AUDIT_32 をサポートしているものと環境変数 LD_AUDIT_64 をサポートしているものがあり、それぞれ 32 ビットプログラムと 64 ビットプログラムのみを対象としています。実行している Solaris OS のバージョンで、これらの変数がサポートされているかどうか確認するには、『リンカーとライブラリ』を参照してください。

RTC での修正継続機能の使用

RTC を修正継続機能とともに使用すると、プログラミングエラーを簡単に分離して修正することができます。修正継続機能を組み合わせて使用すると、デバッグに要する時間を大幅に削減することができます。次に例を示します。


% cat -n bug.c
     1 #include stdio.h
     2 char *s = NULL;
     3
     4 void
     5 problem()
     6 {
     7      *s = 'c';
     8 }
     9
    10 main()
    11 {
    12      problem();
    13      return 0;
    14 }
% cat -n bug-fixed.c
     1 #include stdio.h
     2 char *s = NULL;
     3
     4 void
     5 problem()
     6 {
     7
     8      s = (char *)malloc(1);
     9      *s = 'c';
    10 }
    11
    12 main()
    13 {
    14      problem();
    15      return 0;
    16 }
yourmachine46: cc -g bug.c
yourmachine47: dbx -C a.out
a.out のシンボル情報を読んでいます
rtld /usr/lib/ld.so.1 のシンボル情報を読んでいます
librtc.so のシンボル情報を読んでいます
libc.so.1 のシンボル情報を読んでいます
libintl.so.1 のシンボル情報を読んでいます
libdl.so.1 のシンボル情報を読んでいます
libw.so.1 のシンボル情報を読んでいます
(dbx) check -access
アクセス検査 - ON
(dbx) run
実行中: a.out
(プロセス id 15052)
実行時検査を有効にしています...done
非割り当て領域への書き込み (wua):
1 バイト書き込みを NULL ポインタを通してしようとしました。
現関数: problem
    7       *s = 'c';
(dbx) pop
main で停止しました 行番号 12 ファイル "bug.c"
   12       problem();
(dbx) #ここでファイルを編集します。この例では正しいバージョンをコピーしま
す。 
(dbx) cp bug-fixed.c bug.c
(dbx) fix
修正中 "bug.c" ......
pc は "bug.c":14 に移動しました
main で停止しました 行番号 14 ファイル "bug.c"
   14       problem();
(dbx) cont

実行完了。終了コードは 0 です。
(dbx) quit
'a.out' の次のモジュールは変更されました (修正済み):
bug.c
プログラムの再構築が必要です。

修正と継続についての詳細は、「メモリーリーク (mel) エラー」を参照してください。

実行時検査アプリケーションプログラミングインタフェース

リーク検出およびアクセスの両方の検査では、共有ライブラリ libc.so 内の標準ヒープ管理ルーチンを使用する必要があります。 これは、RTC がプログラム内のすべての割り当てと解放を追跡できるためです。アプリケーションの多くは、独自のメモリー管理ルーチンを malloc/free にかぶせて作成したり、または最初から作成します。独自のアロケータ (専用アロケータと呼ばれる) を使用すると、RTC はそれらを自動的に追跡できません。したがって、それらの不正な使用によるリークエラーとメモリーアクセスエラーを知ることができません。

ただし、RTC には専用アロケータを使用するための API があります。この API を使用すると、専用アロケータを、標準ヒープアロケータと同様に扱うことができます。API 自体はヘッダーファイル rtc_api.h に入っており、Sun Studio ソフトウェアの一部として配布されます。マニュアルページの rtc_api(3x) には、RTC API 入口の詳細が記載されています。

専用アロケータがプログラムヒープを使用しない場合の RTC アクセスエラーレポートには小さな違いがいくつかあります。エラーレポートに、割り当て項目は含まれません。

バッチモードでの RTC の使用

bcheck(1) は、dbx の RTC 機能の便利なバッチインタフェースです。これは、dbx のもとでプログラムを実行し、デフォルトにより RTC エラー出力をデフォルトファイルの program.errs に入れます。

bcheck は、メモリーリーク検査、メモリーアクセス検査、メモリー使用状況検査のいずれか、またはこのすべてを実行できます。デフォルトでは、リーク検査だけが実行されます。この使用方法の詳細については、bcheck(1) のマニュアルページを参照してください。


注 –

64 ビット Linux OS を実行しているシステムで bcheck ユーティリティーを実行するには、その前に環境変数 _DBX_EXEC_32 を設定する必要があります。


bcheck 構文

bcheck の構文は次のとおりです。


bcheck [-V] [-access | -all | -leaks | -memuse] [-o logfile] [-q]
[-s script] program [args]

-o logfile オプションを使用すると、ログファイルに別の名前を指定することができます。-s script オプションはプログラムの実行前に script を実行します。ファイル script に含まれる dbxコマンドを読み取ることができます。script ファイルには通常、suppressdbxenv などのコマンドが含まれていて、bcheck によるエラー出力を調整します。

-q オプションは、bcheck を完全な静止状態にして、プログラムと同じ状況になります。これは、スクリプトまたはメイクファイルで bcheck を使用したい場合に便利です。

bcheck の例

hello に対してリーク検査だけを実行します。


bcheck hello

mach に引数 5 を付けてアクセス検査だけを実行します。


bcheck -access mach 5

cc に対してメモリー使用状況検査だけを静止状態で実行し、通常の終了状況で終了します。


bcheck -memuse -q cc -c prog.c

プログラムは、実行時エラーがバッチモードで検出されても停止しません。すべてのエラー出力がエラーログファイル logfile にリダイレクトされます。しかしプログラムは、ブレークポイントを検出するか、またはプログラムが割り込みを受けると停止します。

バッチモードでは、完全なスタックバックトレースが生成されて、エラーログファイルにリダイレクトされます。スタックフレームの数は、dbxenv 変数 stack_max_size によって指定できます。

ファイル logfile がすでに存在する場合、bcheck はそのファイルの内容を消去してから、そこに出力をリダイレクトします。

dbx からバッチモードを直接有効化

バッチモードに似たモードを、直接 dbx から有効にすることもできます。具体的には、dbx 環境変数 rtc_auto_continue および rtc_error_log_file_name を設定します (dbx 環境変数の設定」を参照)。

rtc_auto_continueon に設定されていると、RTC はそのままエラーを求めて自動的に実行されます。検出したエラーは、dbx 環境変数 rtc_error_log_name で指定したファイルにリダイレクトされます (dbx 環境変数の設定」を参照)。デフォルトログファイル名は、/tmp/dbx.errlog.uniqueid です。すべてのエラーを端末にリダイレクトするには、rtc_error_log_file_name 環境変数を /dev/tty に設定します。

rtc_auto_continue はデフォルト値は、off です。

トラブルシューティングのヒント

プログラム中でエラー検査がオンになっていて、プログラムが実行中の場合、次のエラーが検出されることがあります。

librtc.so と dbx とのバージョンが合いません。エラー検査を休止状態にしました

これは、RTC を接続されたプロセスに使用していて、LD_AUDIT を、各自の Sun Studio dbx に添付されたもの以外の rtcaudit.so バージョンに設定した場合に起こる可能性があります。これを修正するには、LD_AUDIT の設定値を変更してください。

パッチエリアが遠すぎます (8M バイトの制限); アクセス検査を休止状態にしました

RTC は、アクセス検査を有効にするためにロードオブジェクトに十分に近いパッチスペースを検出できませんでした。次の「実行時検査の制限」を参照してください。

実行時検査の制限

実行時検査には次の制限があります。

より高い効果を得るにはより多くのシンボルおよびデバッグ情報が必要になる

アクセス検査では、ロードオブジェクトにいくつかのシンボル情報が必要です。ロードオブジェクトが完全に削除されている場合、実行時検査ですべてのエラーをキャッチできないことがあります。初期化されていないメモリーからの読み取りエラーは正しくない可能性があるため、抑止されます。この抑止は unsuppress rui コマンドを使用して上書きできます。シンボルオブジェクトのシンボルテーブルを取得するには、ロードオブジェクトを削除する際に -x オプションを使用します。

x86 プラットフォームでは SIGSEGV シグナルと SIGALTSTACK シグナルが制限される

実行時検査では、メモリーアクセス命令を計測してアクセス検査をします。これらの命令は、実行時に SIGSEGV ハンドラによって処理されます。実行時検査には、独自の SIGSEGV ハンドラとシグナル代替スタックが必要なため、SIGSEGV ハンドラまたは SIGALTSTACK ハンドラをインストールしようとしても無視されるか、EINVAL エラーが生成されます。

SIGSEGV ハンドラの呼び出しは入れ子にできません。入れ子にすると、エラー terminating signal 11 SEGSEGV が生成されます。このエラーが表示された場合は、rtc skippatch コマンドを使用して、影響のある関数の計測機構を飛ばします。

より高い効果を得るには、十分なパッチ領域を設け、すべての既存コードを含めて 8M バイト以内にする (SPARC プラットフォームのみ)

既存のすべてのコードを含め、8M バイト以内に十分なパッチ領域がない場合、2 つの問題が発生する可能性があります。

使用しているプログラムに前述のどちらかの状況があてはまり、アクセス検査を有効にするとプログラムの動作が異なってくるようであれば、そのプログラムはトラップハンドラの制限の影響を受けている可能性があります。この制限を回避するには、次の操作を実行します。

RTC エラー

RTC で報告されるエラーは、通常はアクセスエラーとリークの 2 種類があります。

アクセスエラー

アクセス検査がオンのとき、RTC による検出と報告の対象になるのは次のタイプのエラーです。

不正解放 (baf) エラー

意味: 割り当てられたことのないメモリーを解放しようとした。

考えられる原因: free() または realloc() にヒープデータ以外のポインタを渡した。

例:

char a[4];
char *b = &a[0];

free(b);                    /* 不正開放 (baf) */

重複解放 (duf) エラー

意味: すでに解放されているヒープブロックを解放しようとした。

考えられる原因: 同じポインタを使用して free() を 2 回以上呼び出した。C++ では、同じポインタに対して "delete" 演算子を 2 回以上使用した。

例:

char *a = (char *)malloc(1);
free(a);
free(a);                    /* 重複開放 (duf) */

境界整列を誤った解放 (maf) エラー

意味: 境界合わせされていないヒープブロックを解放しようとした。

考えられる原因: free() または realloc() に正しく境界合わせされていないポインタを渡した。 malloc によって返されたポインタを変更した。

例:

char *ptr = (char *)malloc(4);
ptr++;
free(ptr);                    /* 境界整列を誤った開放 (maf) */

境界整列を誤った読み取り (mar) エラー

意味: 適切に境界合わせされていないアドレスからデータを読み取ろうとした。

考えられる原因: ハーフワード、ワード、ダブルワードの境界に合わせられていないアドレスから、それぞれ 2 バイト、4 バイト、8 バイトを読み取った。

例:

char *s = "hello world";
int *i = (int *)&s[1];
int j;

j = *i;                    /* 境界整列を誤った読み取り (mar) */

境界整列を誤った書き込み (maw) エラー

意味: 適切に境界合わせされていないアドレスにデータを書き込もうとした。

考えられる原因: ハーフワード、ワード、ダブルワードの境界に合わせられていないアドレスに、それぞれ 2 バイト、4 バイト、8 バイトを書き込んだ。

例:

char *s = "hello world";
int *i = (int *)&s[1];

*i = 0;                    /* 境界整列を誤った書き込み (maw) */

メモリー不足 (oom) エラー

意味: 利用可能な物理メモリーより多くのメモリーを割り当てようとした。

考えられる原因: プログラムがこれ以上システムからメモリーを入手できない。oom エラーは、malloc() からの戻り値が NULL かどうか検査していない (プログラミングでよく起きる誤り) ために発生する問題の追跡に役立ちます。

例:

char *ptr = (char *)malloc(0x7fffffff);
/* メモリー不足 (oom), ptr == NULL */

非割り当てメモリーからの読み取り (rua) エラー

意味: 存在しないメモリー、割り当てられていないメモリー、マップされていないメモリーからデータを読み取ろうとした。

考えられる原因: ストレイポインタ (不正な値を持つポインタ)、ヒープブロック境界のオーバーフロー、すでに解放されたヒープブロックへのアクセス。

例:

char c, *a = (char *)malloc(1);
c = a[1];    /* 非割り当てメモリーからの読み取り (rua) */

非初期化メモリーからの読み取り (rui) エラー

意味: 初期化されていないメモリーからデータを読み取ろうとした。

考えられる原因: 初期化されていない局所データまたはヒープデータの読み取り。

例:

foo()
{   int i, j;
    j = i;    /* 非初期化メモリーからの読み取り (rui) */
}

読み取り専用メモリーへの書き込み (wro) エラー

意味: 読み取り専用メモリーにデータを書き込もうとした。

考えられる原因: テキストアドレスへの書き込み、読み取り専用データセクション (.rodata) への書き込み、読み取り専用として mmap されているページへの書き込み。

例:

foo()
{   int *foop = (int *) foo;
    *foop = 0;                /* 読み取り専用メモリーへの書き込み (wro) */
}

非割り当てメモリーへの書き込み (wua) エラー

意味: 存在しないメモリー、割り当てられていないメモリー、マップされていないメモリーにデータを書き込もうとした。

考えられる原因: ストレイポインタ (不正な値を持つポインタ)、ヒープブロック境界のオーバーフロー、すでに解放されたヒープブロックへのアクセス。

例:

char *a = (char *)malloc(1);
a[1] = '\0';            /* 非割り当てメモリーへの書き込み (wua) */

メモリーリークエラー

リーク検査をオンにしておくと、RTC では次のエラーが報告されます。

ブロック中のアドレス (aib)

意味: メモリーリークの可能性がある。割り当てたブロックの先頭に対する参照はないが、そのブロック内のアドレスに対する参照が少なくとも 1 つある。

考えられる原因: そのブロックの先頭を示す唯一のポインタが増分された。

例 :

char *ptr;
main()
{
   ptr = (char *)malloc(4);
   ptr++;    /* ブロック中のアドレス */
}

レジスタ中のアドレス (air)

意味: メモリーリークの可能性がある。割り当てられたブロックが解放されておらず、そのブロックに対する参照がプログラムのどこにもないが、レジスタには参照がある。

考えられる原因: コンパイラがプログラム変数をメモリーではなくレジスタにだけ保存している場合にこのエラーになる。最適化をオンにしてコンパイラを実行すると、局所変数や関数パラメータにこのような状況がよく発生する。最適化をオンにしていないのにこのエラーが発生する場合は、メモリーリークが疑われる。ブロックを解放する前に、割り当てられたブロックに対する唯一のポインタが範囲外を指定するとメモリーリークになる。

例:

if (i == 0) {
       char *ptr = (char *)malloc(4);
       /* ptr は範囲外になる */
}
  /* レジスタ中のメモリーリーク */

メモリーリーク (mel) エラー

意味: 割り当てられたブロックが解放されておらず、そのブロックへの参照がプログラム内のどこにも存在しない。

考えられる原因: プログラムが使用されなくなったブロックを解放しなかった。

例:

char *ptr;

    ptr = (char *)malloc(1);
    ptr = 0;
/* メモリーリーク (mel) */