Sun メモリエラー探索ツール (Discover) ソフトウェアは、メモリーアクセスエラーを検出するための高度な開発ツールです。
この章には、次の情報が含まれます。
Discover は、Solaris 10 5/08 オペレーティングシステムまたはそれ以降の Solaris 10 update を実行している SPARCベースまたは x86 ベースシステム上で Sun Studio 12、Sun Studio 12 Update 1、Oracle Solaris Studio 12.2 コンパイラ、または GCC for Sun Systems コンパイラバージョン 4.2.0 以降を使用してコンパイルされたバイナリ上で機能します。
Sun Studio または Oracle Solaris Studio コンパイラを使用する際には、-O オプションまたは -xO[n] オプションを使用することによって最適化してコンパイルを行う必要があります。GCC コンパイラを使用する際には、特別な最適化レベルは必要ありません。
これらの要件が満たされない場合、Discover でエラーが発生するか、またはバイナリが計測されません。ただし、-l オプション (「計測オプション」を参照) を使用することによって、これらの要件を満たさないバイナリを計測し、それを実行して限定された数のエラーを検出できます。
前述のようにコンパイルされたバイナリには、注釈と呼ばれる情報が含まれ、Discover がバイナリを正しく計測するのに役立っています。このわずかな情報が追加されることで、バイナリのパフォーマンスまたは実行時のメモリー使用量に影響を及ぼすことはありません。
バイナリのコンパイル時に -g オプションを使用してデバッグ情報を生成することにより、Discover はエラーおよび警告を報告しながらソースコードおよび行番号情報を表示し、より正確な結果を生成することができます。バイナリが -g オプションを使用してコンパイルされない場合、Discover には、対応する機械レベルの命令のプログラムカウンタのみが表示されます。また、-g オプションを使用してコンパイルすることにより、Discover はより正確なレポートを生成できます (「Discover エラーメッセージの解釈」を参照)。
Discover は実行時リンカーの一部の特定の機能を使用するため、プリロードまたは監査を使用するバイナリと併用することはできません。
プログラムが LD_PRELOAD 環境変数の設定を必要とする場合は、Discover を使用して適切に機能しない可能性があります。それは Discoverは特定のシステム関数に割り込む必要があり、関数がプリロードされている場合は割り込めないためです。
同様に、プログラムが実行時監査を使用している (バイナリが -p オプションまたは -P オプショとリンクされているか、LD_AUDIT 環境変数を設定する必要がある) 場合、この監査は Discover の監査の使用と衝突します。バイナリが監査とリンクされている場合、Discover は計測時に失敗します。実行時に LD_AUDIT 環境変数を設定している場合、結果は定義されません。
次の内容は、プログラムを準備し、Discover を使用して計測を行い、それを実行して、検出したメモリーアクセスエラーに関するレポートを生成する例です。この例は初期化されていないデータにアクセスする単純なプログラムを使用します。
% cat test_UMR.c #include <stdio.h> #include <stdlib.h> int main() { // UMR: accessing uninitialized data int *p = (int*) malloc(sizeof(int)); printf("*p = %d\n", *p); free(p); } % cc -g -02 test_UMR.c % a.out *p = 131464 % discover -w - a.out % a.out ERROR (UMR): accessing uninitialized data from address 0x50010 (4 bytes) at: main() + 0x54 <test_UMR.c:7> 4: { 5: // UMR: accessing uninitialized data 6: int *p = (int*) malloc(sizeof(int)); 7:=> printf(**p = %d\n", *p); 8: free(p); 9: } _start() + 0x108 block at 0x50010 (4 bytes long) was allocated at: malloc() + 0x220 main() + 0x1c <test_UMR.c:6> 3: int main() 4: { 5: // UMR: accessing uninitialized data 6:=> int *p = (int*) malloc(sizeof(int)); 7: printf(**p = %d\n", *p); 8: free(p); 9: } _start() + 0x108 *p = 327704 **************** Discover Memory Report ***************** No allocated memory left on program exit. DISCOVER SUMMARY: unique errors : 1 (1 total, 0 filtered) unique warnings : 0 (0 total, 0 filtered) FILTERS: type=ML:func=_rt_boot:depth=*; type=*:func__f90_esfw:depth=*; type=ML:func=__f90_ssfw:depth=*; |
Discover 出力は、初期化されていないメモリーが使用された場所、およびそのメモリーが割り当てられた場所を、結果の概要とともに示します。
ターゲットバイナリを準備したら、次の手順はその計測です。計測は戦略的な場所にコードを追加して、Discover がバイナリの実行中にメモリー操作を追跡できるようにします。
discover コマンドを使用して、バイナリを計測します。たとえば、次のコマンドは、バイナリ a.out を計測し、入力 a.out を計測済みの a.out で上書きします。
discover a.out |
計測済みのバイナリを実行する場合、Discover はプログラムのメモリーの使用を監視します。実行時に、Discover は Web ブラウザで表示可能な HTML ファイル (この場合、デフォルトで a.out.html) にメモリーアクセスエラーを詳述するレポートを書き込みます。バイナリを計測してレポートを ASCII ファイルまたは stderr に書き込むように要求する場合は、-w オプションを使用できます。
Discover がバイナリを計測する際に、注釈が付けられていないために計測できないコードを検出する場合、次のような警告が表示されます。
discover: (warning): a.out: 80% of code instrumented (16 out of 20 functions) |
注釈付きではないコードは、バイナリにリンクされているアセンブリ言語コード、またはコンパイラでコンパイルされたモジュール、または「バイナリは正しく準備される必要がある」にリストされているシステムより古いオペレーティングシステム上から来ている可能性があります。
Discover がバイナリを計測する際には、コードを追加し、実行時リンカーを使用して、実行時にロードされる場合には依存共有ライブラリを計測できるようにします。計測済みライブラリは、元のライブラリが最後に計測されてから変更されていない場合には再使用可能なキャッシュに格納されます。デフォルトでは、キャッシュディレクトリは $HOME/SUNW_Bit_Cache です。このディレクトリは -D オプションを使用して変更できます。
すべての共有ライブラリを含む、プログラム全体が計測される場合、Discover は最も正確な結果を生成します。デフォルトでは、主要な実行可能ファイルを計測する際に、Discover はプログラムの実行時のようなコードを挿入し、Discover は共有ファイルが開かれるとすべて自動的に計測します。それらのファイルがプログラムに静的にリンクされているか、dlopen() によって動的に開かれているかは関係ありません。
プログラムで使用されるすべての共有ライブラリは、「バイナリは正しく準備される必要がある」で説明されているように準備される必要があります。デフォルトで、実行時リンカーが準備されていないライブラリを検出する場合、致命的なエラーが発生します。ただし、Discover に 1 つ以上のライブラリを無視するように指示できます。
一部のライブラリは、準備できないか、または何らかの理由で計測できない場合があります。このような場合に、多少、正確さを低下させるために、-s、-T、または -N オプション (「「計測オプション」」を参照)、bit.rc ファイル (「「bit.rc 初期化ファイル」」を参照) の仕様を使用して、Discover にこれらのライブラリを無視するように指示できます。
ライブラリが計測できず、「無視可能」と指定されていない場合、Discover は計測時に失敗するか、またはプログラムが実行時にエラーメッセージを伴って失敗します。
デフォルトで、Discover はシステムの bit.rc ファイルの仕様を使用して、特定のシステムおよびコンパイラが提供するライブラリを、準備されていないため「無視可能」として設定します。Discover は最も一般的に使用されるライブラリのメモリー特性を知っているため、正確さに対する影響は最小限です。
discover コマンドとともに次のオプションを使用して、バイナリを計測できます。
計測済みのバイナリを file に書き込みます。デフォルトで、計測済みのバイナリは入力バイナリを上書きします。
バイナリ上の Discover のレポートを text_file に書き込みます。計測済みのバイナリを実行するときに、ファイルが作成されます。text_file が相対パス名である場合、ファイルは計測済みバイナリを実行する作業ディレクトリを基準として相対的に配置されます。バイナリを実行するたびにファイル名を一意にするには、文字列 %p をファイル名に追加して、Discover ランタイムにプロセス ID を含めるように求めます。たとえば、オプション -w report.%p.txt は report.process_id.txt を持つレポートファイルを生成します。2 回以上、ファイル名に %p を含む場合、最初のインスタンスだけがプロセス ID と置き換えられます。
このオプションまたは -H オプションを指定しない場合、レポートは HTML 形式で output_file.html に書き込まれます。output_file は、計測済みバイナリのベース名です。ファイルは、計測済みバイナリを実行する作業ディレクトリに配置されます。
このオプションおよび -H オプションを両方指定して、テキストおよび HTML 形式の両方でレポートを書き込みます。
Discover のバイナリのレポートを HTML 形式で html_file に書き込みます。このファイルは計測済みバイナリの実行時に作成されます。html_file が相対パス名である場合、計測済みバイナリを実行する作業ディレクトリを基準として相対的に配置されます。バイナリを実行するたびにファイル名を一意にするには、文字列 %p をファイル名に追加して、Discover ランタイムにプロセス ID を含めるように求めます。たとえば、オプション -H report.%p.html は、ファイル名 report.process_id.html を持つレポートファイルを生成します。2 回以上、ファイル名に %p を含む場合、最初のインスタンスだけがプロセス ID と置き換えられます。
このオプションまたは -w オプションを指定しない場合、レポートは HTML 形式で output_file.html に書き込まれます。output_file は計測済みバイナリのベース名です。ファイルは、計測済みバイナリを実行する作業ディレクトリに配置されます。
このオプションおよび -w オプションを指定して、テキストおよび HTML ファイル形式の両方でレポートを書き込むことができます。
レポートに n メモリーエラーのみを表示します (デフォルトでは、すべてのエラーを表示します)。
レポートに n メモリーリークのみを表示します (デフォルトは 100 です)。
レポートのオフセットを表示します (デフォルトは非表示です)。
レポートの符号化された名前を表示します (デフォルトは符号化されていない名前の表示です)。
レポートに n スタックフレームのみを表示します (デフォルトは 8 です)。
Discover を簡易モードで実行します。このオプションは、プログラムのより高速な実行を提供し、「バイナリは正しく準備される必要がある」で説明されるように、プログラムが特別に準備される必要はないが検出されるエラー数は制限されます。
スレッドアナライザを使用してデータ競合を検出するために計測します。このオプションを使用する場合は、データ競合検出のみが実行時に行われ、他のメモリー検査は行われません。計測済みのバイナリは、collect コマンドを使用して実行し、パフォーマンスアナライザで表示可能な実験を生成する必要があります (『Oracle Solaris Studio 12.2: スレッドアナライザユーザーズガイド』を参照)。
計測不可能なバイナリの計測を試みる場合は、警告を発するが、エラーのフラグは立てないでください。
指定されたバイナリのみを計測します。依存共有ライブラリを実行時に計測しないでください。
接頭辞 library に一致する依存共有ライブラリを計測しないでください。ライブラリ名の最初の文字が library に一致する場合、ライブラリは無視されます。library が / で始まる場合、ライブラリの完全な絶対パス名でマッチングが行われます。それ以外の場合、ライブラリのベース名でマッチングが行われます。
bit.rc 初期化ファイルを読み取らないでください (「「bit.rc 初期化ファイル」」を参照)。
キャッシュされた計測済みバイナリを格納するためのルートディレクトリとして cache_directory を使用します。デフォルトでは、キャッシュディレクトリは $HOME/SUNW_Bit_Cache です。
キャッシュで検出されたライブラリの再計測を強制します。
ヘルプ。短いヘルプメッセージを出力して、終了します。
冗長。Discover が実行している内容のログを出力します。詳細については、オプションを繰り返してください。
Discover バージョン情報を出力して終了します。
Discoverは、起動時に一連の bit.rc ファイルを読み取ることによってその状態を初期化します。システムファイル、Oracle_Solaris_Studio_installation_directory /prod/lib/postopt/bit.rc は、特定の変数のデフォルトの値を提供します。Discover は最初にこのファイルを読み取り、次に、存在する場合は $HOME/.bit.rc 、および存在する場合は current_directory/.bit.rc を読み取ります。
bit.rc ファイルには特定の変数を設定、追加、または削除するコマンドが含まれています。Discover が set コマンドを読み取る場合、変数の前の値がある場合には、それを無効にします。append コマンドを読み取る場合、変数の既存の値に (コロンセパレータの後に) 引数を追加します。remove コマンドを読み取る場合、変数の既存の値から引数とそのコロンセパレータを削除します。
bit.rc ファイルの変数セットには、計測時に無視するライブラリのリスト、およびバイナリ内の注釈の付いていない (準備されていない) コードの割合を計算する場合に無視する関数または関数接頭語のリストが含まれます。
詳細については、システム bit.rc ファイルのヘッダーのコメントを参照してください。
SUNW_DISCOVER_OPTIONS 環境変数をコマンド行オプション -b、-e、-E、-f、-H、-l、-L、-m, -S および -w のリストに設定することによって、計測済みバイナリの実行時動作を変更できます。たとえば、レポートされるエラー数を 50 に変更し、レポート内のスタックの深さを 3 に制限する場合、環境変数を -e 50-s 3 に設定します。
Discover を使用してバイナリを計測した後で、それを通常の場合と同じ方法で実行します。通常、特定の組み合わせの入力により、プログラムが別の動作を行う場合、そのプログラムを Discover を使用して計測し、同じ入力を使用して実行して、潜在的なメモリーの問題を調べます。計測済みのプログラムの実行中に、Discover は、選択した形式 (テキスト、HTML、またはその両方) の指定された出力ファイルに、検出されるメモリーエラーに関する情報を書き込みます。レポートの解釈に関する詳細については、「「Discover レポートの分析」」を参照してください。
計測のオーバーヘッドのため、プログラムは計測後に大幅に低速に実行されます。メモリーアクセスの頻度に応じて、50 倍も低速に実行される場合があります。
Discover レポートは、ソースコードで効果的に自動補完して問題を修正する情報を提供します。
デフォルトでは、レポートは output_file.html に HTML 形式で書き込まれます。output_file は、計測済みバイナリのベース名です。ファイルは、計測済みバイナリを実行する作業ディレクトリに配置されます。
バイナリを計測する際には、-H オプションを使用して、HTML 出力を指定されたファイルに書き込むように要求するか、または -w オプションを使用して、テキストファイルに書き込むように要求できます (「コマンド行オプション」を参照)。
バイナリの計測後に、たとえば、プログラムを後続の実行用に異なるファイルにレポートを書き込みたい場合に、「SUNW_DISCOVER_OPTIONS 環境変数」でレポートの - H および -w オプションの設定を変更できます。
HTML レポート形式では、プログラムの対話型分析が可能です。HTML 形式のデータは、電子メールを使用するか、Web ページ上に配置して、開発者間で容易に共有できます。JavaScript インタラクティブ機能と組み合わせると、Discover のメッセージを検索する便利な方法が提供されます。
「エラー (Errors)」タブ (「「エラー (Errors)」タブの使用法」を参照)、「警告 (Warnings)」タブ (「「警告 (Warnings)」タブの使用法」を参照)、および「メモリーリーク (Memory Leaks) タブ」 (「「メモリーリーク (Memory Leaks)」タブの使用法」を参照) では、エラーメッセージ、警告メッセージ、およびメモリーリークレポートをそれぞれ検索できます。
左側のコントロールパネル (「コントロールパネルの使用法」を参照) では、右側に現在表示されているタブの内容を変更できます。
ブラウザで最初に HTML レポートを開く場合、「エラー (Errors)」タブが選択され、計測済みのバイナリの実行中に検出されたメモリーアクセスエラーのリストが表示されます。
エラーをクリックすると、エラー時のスタックトレースが表示されます。
-g オプションを使用してコードをコンパイルした場合、関数をクリックすることによってスタックトレースの関数ごとのソースコードを表示できます。
「警告 (Warnings)」タブには、起こり得るアクセスエラーの警告メッセージのすべてが表示されます。警告をクリックすると、警告時のスタックトレースが表示されます。コードを -g オプションを使用してコンパイルした場合、関数をクリックすることによって、スタックトレースの関数ごとのソースコードを表示できます。
「メモリーリーク (Memory Leaks)」タブには、下記にリストされているブロック数とともに、最上部にプログラムの実行終了時に割り当てられている残存ブロック総数が表示されます。
ブロックをクリックすると、ブロックのスタックトレースが表示されます。-g オプションを使用してコードをコンパイルした場合、関数をクリックすることによって、スタックトレースの関数ごとのソースコードを表示できます。
エラー、警告、およびメモリーリークのすべてのスタックトレースを表示するには、コントロールパネルの「スタックトレース (Stack Traces)」セクションの「すべて展開 (Expand All)」をクリックします。関数のすべてのソースコードを表示するには、コントロールパネルの「ソースコード (Source Code)」セクションの「すべて展開 (Expand All)」をクリックします。
エラー、警告、およびメモリーリークのすべてのスタックトレースまたはソースコードを非表示にするには、対応する「すべて折りたたむ (Collapse All)」をクリックします。
コントロールパネルの「エラーの表示 (Show Errors)」セクションは、「エラー (Errors)」タブが選択され、表示されるエラーのタイプを制御できる場合に表示されます。デフォルトでは、検出されたエラーのすべてのチェックボックスがオンになっています。エラーのタイプを非表示にするには、チェックボックスをクリックして、チェックマークを外します。
コントロールパネルの「警告の表示 (Show Warnings)」セクションは、「警告 (Warnings )」タブが選択され、表示される警告のタイプを制御できる場合に表示されます。デフォルトで、検出された警告のすべてのチェックボックスがオンになっています。警告のタイプを非表示にするには、チェックボックスをクリックして、チェックマークを外します。
エラーおよび警告の総数を一覧表示するレポートの概要、およびリークしたメモリー量がコントロールパネルの下方に表示されます。
Discover レポートの ASCII (テキスト) 形式は、スクリプトで処理したり、Web ブラウザにアクセスできない場合に適しています。次に示すのは ASCII レポートの例です。
$ a.out ERROR 1 (UAW): writing to unallocated memory at address 0x50088 (4 bytes) at: main() + 0x2a0 <ui.c:20> 17: t = malloc(32); 18: printf("hello\n"); 19: for (int i=0; i<100;i++) 20:=> t[32] = 234; // UAW 21: printf("%d\n", t[2]); //UMR 22: foo(); 23: bar(); _start() + 0x108 ERROR 2 (UMR): accessing uninitialized data from address 0x50010 (4 bytes) at: main() + 0x16c <ui.c:21>$ 18: printf("hello\n"); 19: for (int i=0; i<100;i++) 20: t[32] = 234; // UAW 21:=> printf("%d\n", t[2]); //UMR 22: foo(); 23: bar(); 24: } _start() + 0x108 was allocated at (32 bytes): main() + 0x24 <ui.c:17> 14: x = (int*)malloc(size); // AZS warning 15: } 16: int main() { 17:=> t = malloc(32); 18: printf("hello\n"); 19: for (int i=0; i<100;i++) 20: t[32] = 234; // UAW _start() + 0x108 0 WARNING 1 (AZS): allocating zero size memory block at: foo() + 0xf4 <ui.c:14> 11: void foo() { 12: x = malloc(128); 13: free(x); 14:=> x = (int*)malloc(size); // AZS warning 15: } 16: int main() { 17: t = malloc(32); main() + 0x18c <ui.c:22> 19: for (int i=0; i<100;i++) 20: t[32] = 234; // UAW 21: printf("%d\n", t[2]); //UMR 22:=> foo(); 23: bar(); 24: } _start() + 0x108 ***************** Discover Memory Report ***************** 1 block at 1 location left allocated on heap with a total size of 128 bytes 1 block with total size of 128 bytes bar() + 0x24 <ui.c:9> 6: 7: void bar() { 8: int *y; 9:=> y = malloc(128); // Memory leak 10: } 11: void foo() { 12: x = malloc(128); main() + 0x194 <ui.c:23> 20: t[32] = 234; // UAW 21: printf("%d\n", t[2]); //UMR 22: foo(); 23:=> bar(); 24: } _start() + 0x108 ERROR 1: repeats 100 times DISCOVER SUMMARY: unique errors : 2 (101 total, 0 filtered) unique warnings : 1 (1 total, 0 filtered) |
このレポートはエラーと警告メッセージ、およびその概要で構成されます。
エラーメッセージは、ERROR という単語で開始され、3 文字のコード、ID 番号、およびエラーの説明 (例では、writing to unallocated memory) が含まれます。その他の詳細には、アクセスされたメモリーアドレス、および読み取られた、または書き込まれた数またはバイト数が含まれます。説明の後には、プロセスライフサイクルでエラーの場所を自動補完するエラー時のスタックトレースが表示されます。。
プログラムが -g オプションを使用してコンパイルされた場合、スタックトレースには、ソースファイルおよび行番号が含まれます。ソースファイルにアクセス可能な場合、エラー付近のソースコードが出力されます。各フレームのターゲットソース行は => シンボルによって示されます。
同じバイト数を持つ同じメモリーの場所の同じエラーの種類が繰り返される場合、スタックトレースを含む完全なメッセージが 1 度だけ出力されます。後続のエラーの出現が数えられ、次の例に表示されるように繰り返し数が、複数回発生する同一エラーごとにレポートの末尾に一覧表示されます。
ERROR 1: repeats 100 times |
不正なメモリーアクセスのアドレスがヒープ上にある場合、対応するヒープブロックに関する情報がスタックトレース後に出力されます。その情報には、ブロック開始アドレスとサイズ、およびブロックが割り当てられた時点のスタックトレースが含まれます。ブロックが解放された場合、解放ポイントのスタックトレースも含まれます。
警告メッセージは、WARNINGという単語で開始されている場合を除いて、エラーメッセージと同じ形式で出力されます。概して、これらのメッセージは、アプリケーションの正確さに影響を及ぼさない状態に対して警告しますが、問題を改善するために使用可能な役立つ情報を提供します。たとえば、0 サイズのメモリーを割り当てることは有害ではありませんが、頻繁すぎると、パフォーマンスを低下させる可能性があります。
メモリーリークレポートには、ヒープ上に割り当てられているがプログラムの終了時にリリースされないメモリーブロックに関する情報が含まれます。次に示すのは、メモリーリークレポートの例です。
$ DISCOVER_MEMORY_LEAKS=1 ./a.out ... ***************** Discover Memory Report ***************** 2 blocks left allocated on heap with total size of 44 bytes block at 0x50008 (40 bytes long) was allocated at: malloc() + 0x168 [libdiscover.so:0xea54] f() + 0x1c [a.out:0x3001c] <discover_example.c:9>: 8: { 9:=> int *a = (int *)malloc( n * sizeof(int) ); 10: int i, j, k; main() + 0x1c [a.out:0x304a8] <discover_example.c:33>: 32: /* Print first N=10 Fibonacci numbers */ 33:=> a = f(N); 34: printf("First %d Fibonacci numbers:\n", N); _start() + 0x5c [a.out:0x105a8] ... |
ヘッダーに続く最初の行は、ヒープ上に割り当てられて残されているヒープブロック数とその合計サイズを要約しています。レポートされるサイズは、開発者の見解であり、すなわちメモリーアロケータのブックキーピングのオーバーヘッドは含まれません。
メモリーリークの概要の後に、割り当てポイントのスタックトレースを持つ未解放ヒープブロックごとの詳細情報が出力されます。スタックトレースレポートは、エラーおよび警告メッセージに対して説明されるレポートと同様です。
Discover レポートには概要全体が記載されています。かっこ付きの一意の警告およびエラー数、繰り返しを含むエラーおよび警告の総数を報告します。次に例を示します。
DISCOVER SUMMARY: unique errors : 3 (3 total) unique warnings : 1 (5 total) |
Discover は多数のメモリーアクセスエラー、およびエラーである可能性のあるアクセスに関する警告を検出および報告します。
Discover は次のメモリーアクセスエラーを検出します。
ABR: 配列範囲外からの読み取り (beyond array bounds read)
ABW: 配列範囲外への書き込み (beyond array bounds write)
BFM:不正なメモリーブロックの解放 (freeing the wrong memory block)
BRP:不正な realloc アドレスパラメータ (bad realloc address parameter)
CGB:破壊された配列ガードブロック (corrupted array guard block)
DFM: メモリーの二重解放 (double freeing memory)
FMR: 解放されたメモリーからの読み取り (freed memory read)
FMW: 解放されたメモリーへの書き込み (freed memory write)
FRP: 解放された realloc パラメータ (freed realloc parameter)
IMR: 無効なメモリーからの読み取り (invalid memory read)
IMW: 無効なメモリーへの書き込み (invalid memory write)
PIR: 部分的に初期化された領域からの読み取り (partially initialized read)
SBR: スタックフレームの範囲外からの読み取り (beyond stack frame bounds read)
SBW: スタックフレームの範囲外への書き込み (beyond stack frame bounds write)
UAR: 非割り当てメモリーからの読み取り (unallocated memory read)
UAW: 非割り当てメモリーへの書き込み (unallocated memory write)
UMR: 非初期化メモリーからの読み取り (unitialized memory read)
次のセクションに、これらのエラーの一部を生成する簡単なサンプルプログラムを一覧表示します。
#include <stdio.h> #include <stdlib.h> int main() { // ABR: reading memory beyond array bounds at address 0x%1x (%d byte%s)" int *a = (int*) malloc(sizeof(int[5])); printf("a[5] = %d\n",a[5]); ) |
#include <stdlib.h> int main() { // ABW: writing to memory beyond array bounds int *a = (int*) malloc(sizeof(int[5])); a[5] = 5; } |
#include <stdlib.h> int main() { // BFM: freeing wrong memory block int *p = (int*) malloc(sizeof(int)); free(p+1); } |
#include <stdlib.h> int main() { // BRP is "bad address parameter for realloc 0x%1x" int *p = (int*) realloc(0,sizeof(int)); int *q = (int*) realloc(p+20,sizeof(int[2])); } |
#include <stdlib.h> int main() { // DFM is "double freeing memory" int *p = (int*) malloc(sizeof(int)); free(p); free(p);' } |
#includ <stdio.h> #include <stdlib.h> int main() { // FMR is "reading from freed memory at address 0x%1x (%d byte%s)" int *p = (int*) malloc(sizeof(int)); free(p); printf("p = 0x%h\n",p); } |
#include <stdlib.h> int main() { // FMW is "writing to freed memory at address 0x%1x (%d byte%s)" int *p = (int*) malloc(sizeof(int)); free(p); *p = 1; } |
#include <stdlib.h> int main() { // FRP: freed pointer passed to realloc int *p = (int*) malloc(sizeof(int)); free(0); int *q = (int*) realloc(p,sizeof(int[2])); } |
#include <stdlib.h> int main() { // IMR: read from invalid memory address int *p = 0; int i = *p; // generates Signal 11... } |
#include <stdlib.h> int main() { // IMW: write to invalide memory address int *p = 0; *p = 1; // generates Signal 11... } |
#include <stdio.h> #include <stdlib.h> int main() { // PIR: accessing partially initialized data int *p = (int*) malloc(sizeof(int)); *((char*)p) = 'c'; printf("*(p = %d\n",*(p+1)); } |
#include <stdio.h> #include <stdlib> int main() { // UAR is "reading from unallocated memory" int *p = (int*) malloc(sizeof(int)); printf("*(p+1) = %d\n",*(p+1)); } |
#include <stdio.h> #include <stdlib.h> int main() { // UAW is "writing to unallocated memory" int *p = (int*) malloc(sizeof(int)); *(p+1) = 1; } |
#include <stdio.h> #include <stdlib.h> int main() { // UMR is "accessing uninitialized data from address 0x%1x (A%d byte%s)" int *p = (int*) malloc(sizeof(int)); printf("*p = %d\n",*p); } |
Discover は次のメモリーアクセスの警告を報告します。
AZS: 0 サイズの割り当て (allocating zero size)
NAR: 注釈の付かない領域からの読み取り (non-annotated read)
NAW: 注釈の付かない領域への書き込み (non-annotated write)
SMR: 投機的メモリーからの読み取り (speculative memory read)
SMW: 投機的メモリーへの書き込み (speculative memory write)
UFR: 不明なスタックフレームからの読み取り (unknown stack frame read)
UFW: 不明なスタックフレームへの書き込み (unknown stack frame write)
USR: 読み取り中の不明ステータス (unknown status while reading)
USW: 書き込み中の不明なステータス (unknown status while writing)
次のセクションは、AZS 警告を生成する簡単なプログラム例を一覧表示します。
#include <stdlib.h> int main() { // AZS: allocating zero size memory block int *p = malloc(); } |
場合によっては、Discover は実際にはエラーでないエラーを報告することがあります。そのようなケースは、擬陽性と呼ばれます。Discover は計測時にコードを分析し、同様なツールと比較して、擬陽性の発生を削減しますが、依然として発生する場合があります。次のセクションでは、Discover レポートでの擬陽性を特定し、可能であれば回避できるようにするヒントを提供します。
C および C++ のビットフィールドでは、コンパクトなデータ型を作成できます。次に例を示します。
struct my_struct { unsigned int valid : 1; char c; }; |
この例では、構造メンバー my_struct.valid はメモリー内の 1 ビットのみ取得します。ただし、SPARC プラットフォーム上では、CPU はバイト単位でのみメモリーを変更できるため、struct.valid を含むバイト全体が構造メンバーにアクセスまたは変更するためにロードされる必要があります。また、コンパイラは 1 度に数バイト (たとえば、4 バイトの機械語) をロードする方がより効率的であることがわかる場合があります。Discover がこのようなロードを検出する場合、追加情報なしに、すべて 4 バイトが使用されると仮定します。また、たとえば、フィールド my_struct.valid は初期化されたが、フィールド my_struct.c は初期化されず、両方のフィールドを含む機械語がロードされた場合、Discover は部分的に初期化されたメモリーからの読み取り (PIR) にフラグを立てます。
擬陽性の別のソースはビットフィールドの初期化です。1 バイト部分を書き込むには、コンパイラは最初にバイトをロードするコードを生成する必要があります。バイトが読み取るより前に書き込まれていない場合、結果は非初期化メモリーからの読み取りエラー (UMR) となります。
ビットフィールドの擬陽性を回避するには、コンパイル時に -g オプションまたは -g0 オプションを使用します。これらのオプションは、Discover に追加のデバッグ情報を提供し、ビットフィールドのロードと初期化を特定するのに役立ち、多くの擬陽性を削除します。何らかの理由で -g オプションを使用してコンパイルできない場合は、memset() などの関数を使用して構造を初期化します。次に例を示します。
... struct my_struct s; /* Initialize structure prio to use */ memset(&sm 0, sizeof(struct my_struct)); ... |
コンパイラは、ロードの結果がすべてのプログラムパスで有効ではない条件下の不明なメモリーアドレスからロードを生成する場合があります。このようなロード命令は分岐命令の遅延スロットに配置できるため、この状況は SPARK プラットフォーム上で発生する場合がよくあります。たとえば、ここに C コードフラグメントがあります。
int i' if (foo(&i) != 0) { /* foo returns nonzero if it has initialized i */ printf("5d\n", i); } |
このコードから、コンパイラは次のコードに等しいコードを生成できます。
int i; int t1, t2' t1 = foo(&i); t2 = i; /* value in i is loaded */ if (t1 != 0) { printf("%d\n", t2); } |
この例では、関数 foo() が 0 を返し、i を初期化しないと仮定します。i からのロードは、依然として生成されますが、使用されません。しかし、ロードは Discover によって確認され、非初期化変数のロード (UMR) を報告します。
Discover はデータフロー分析を使用して、可能な場合には常にそのようなケースを特定しますが、検出できない場合もあります。
最適化レベルを低くしてコンパイルすることによって、これらのタイプの擬陽性の発生を削減できます。
Discover はプログラムの 100% を計測できない場合があります。おそらく、コードの一部がアセンブリ言語のソースファイルまたは再コンパイルできないサードパーティのライブラリから来ているため、計測できない場合があります。Discover には、未計測コードがアクセスまたは変更しているメモリーブロックの知識がありません。たとえば、サードパーティの共有ライブラリから得られる関数がのちにメイン (計測済み) プログラムによって読み取られるメモリーブロックを初期化すると仮定します。Discover はメモリーがライブラリで初期化されていることを認識していないため、後続の読み取りにより、非初期化メモリーエラー (UMR) が生成されます。
このような場合の解決策を提供するため、Discover API には次の関数が含まれています:
void __ped_memory_write(unsigned long addr, long size, unsigned long pc); void __ped_memory_read(unsigned long addr, long size, unsigned long pc); void __ped_memory_copy(unsigned long src, unsigned lond dst, long size, unsigned long pc); |
プログラムから API 関数を呼び出し、Discover にメモリー領域への書き込み (__ped_memory_write ()) やメモリー領域からの読み取り (__ped_memory read()) などの特定のイベントを知らせることができます。どちらの場合も、メモリー領域の開始アドレスは、addr パラメータで渡され、そのサイズは size パラメータで渡されます。pc パラメータを 0 に設定します。
__ped_memory_copy 関数を使用して、Discover にメモリーがある場所から別の場所にコピーされていることを知らせます。ソースメモリーの開始アドレスは src パラメータで渡され、宛先領域の開始アドレスは dst パラメータで渡され、サイズは size パラメータで渡されます。pc パラメータを 0 に設定します。
API を使用するには、プログラムでこれらの関数を weak と宣言します。たとえば、ソースコードに次のコードフラグメントを含めます。
#ifdef __cplusplus extern "C" { #endif extern void __ped_memory_write(unsigned long addr, long size, unsigned long pc); extern void __ped_memory_read(unsigned long addr, long size, unsigned long pc); extern void __ped_memory_copy(unsigned long src, unsigned long dst, long size, unsigned long pc); #prgama weak __ped_memory_write #pragma weak __ped_memory_read #pragma weak __ped_memory_copy #ifdef __cplusplus } #endif |
API 関数は内部 Discover ライブラリで定義され、計測時にプログラムとリンクされます。ただし、プログラムが計測されない場合、このライブラリはリンクされず、API 関数へのすべての呼び出しによってアプリケーションがハングアップします。そのため、Discover 下でプログラムを実行していない場合は、これらの関数を無効にする必要があります。または、API 関数の空の定義を使用して動的ライブラリを作成し、それをプログラムとリンクすることができます。この場合、Discover を使用しないでプログラムを実行する場合は、ライブラリが使用されますが、Discover 下で実行する場合は、真の API 関数が自動的に呼び出されます。
Discover は、「「バイナリは正しく準備される必要がある」」の説明に従って準備されているコードのみを計測できます。注釈の付いていないコードは、バイナリにリンクされているアセンブリ言語コード、またはそのセクションに示されてるものより古いコンパイラまたはオペレーティングシステムでコンパイルされたモジュールから来ている場合があります。
準備から特別に除外されているのは、asm 文または .il テンプレートを含むアセンブリ言語モジュールおよび関数です。
Discover は機械コード上で動作します。ツールは、ロードやストアなどの機械命令でエラーを検出し、それらのエラーをソースコードと相互に関連付けます。一部のソースコード文は関連付けられている機械命令がないため、Discover は明白なユーザーエラーを検出していないように思われる場合があります。たとえば、次の C コードフラグメントを考えてみましょう:
int *p = (int *)malloc(sizeof(int)); int i; i = *p; /* compiler may not generate code for this statement */ printf("Hello World!\n"); return;
p で示されたアドレスに格納された値を読み取ることは、メモリーが初期化されていないため、潜在的なユーザーエラーとなります。ただし、最適化コンパイラは、変数 i が使用さていないことを検出するため、メモリーから読み取り、i に割り当てる文のコードは、生成されません。この場合、Discover は非初期化メモリーの使用 (UMR) を報告しません。
コンパイラの生成コードは常に想定どおりになるわけではありません。コンパイラが生成するコードは、-On 最適化オプションを含む、使用するコンパイラオプションによって異なるため、Discover によって報告されるエラーも異なる可能性があります。たとえば、-O1 最適化レベルで生成されたコードで報告されるレポートは、-O4 最適化レベルで生成されたコードによって非表示になる可能性があります。
システムライブラリは、オペレーティングシステムとともにインストール済みで、計測用に再度コンパイルできません。Discover は、標準の C ライブラリ (libc.so) からの一般的な関数に対するサポートを提供します。すなわち、Discover これらの関数によってどのメモリーにアクセスされ、どのメモリーが変更されるかを把握しています。ただし、アプリケーションが他のシステムライブラリを使用する場合、Discover レポートで擬陽性を検出する可能性がありあす。擬陽性が報告される場合、コードから Discover API を呼び出してそれらを削除できます。
Discover は、malloc()、calloc()、free()、operator new()、および operator delete() などの標準のプログラミング言語メカニズムによって割り当てられている場合にヒープメモリーを検出できます。
アプリケーションが標準の関数の最上部で動作するカスタムメモリー管理システム (たとえば、malloc() とともに実装されるプール割り当て管理) を使用する場合、Discover は機能しますが、適切なリークの報告や、解放されたメモリーへのアクセスは保証されていません。
Discover は、次のメモリーアロケータをサポートしていません:
brk(2)() または sbrk(2)() システム呼び出しを直接使用するカスタムヒープアロケータ
バイナリに静的にリンクされた標準のヒープ管理関数
mmap(2)() および shmget(2)() システム呼び出しを使用してユーザーコードから割り当てられたメモリー
sigaltstack(2)() 関数はサポートされていません。
配列範囲を検出するため Discover が使用するアルゴリズムのため、静的および自動 (ローカル) 配列の範囲外アクセスエラーを検出することはできません。動的に割り当てられた配列のエラーのみ検出できます。