日本-日本語 |
|
|
|
OpenVMS マニュアル |
|
HP OpenVMS
|
目次 | 索引 |
本章では,特殊な場合のデバッグ方法について説明します。
省略時の設定では,プログラムの実行速度を向上させるためにコードを最適化するコンパイラが多くあります。最適化は,実行時に一度だけ評価されればすむよう不変式が DO ループから除かれる,メモリ記憶位置のいくつかがプログラム内の異なる場所で異なる変数に割り当てられる,いくつかの変数がデバッグ中にアクセスしなくてすむように削除される,などの方法で行われます。
最終的には,デバッグ時に実行されているコードは,画面モードのソース・ディスプレイ( 第 7.4.1 項 を参照)で表示されるソース・コードやソース・リスト・ファイルに記述されているソース・コードとは一致しないことがあります。
最適化されたコードをデバッグする際の問題を回避するために,多くのコンパイラではコンパイル時に/NOOPTIMIZE,またはそれに等しいコマンド修飾子を指定することができます。この修飾子を指定すると,コンパイラによる最適化がほとんど抑止され,それによって最適化のために発生するソース・コードと実行可能なコードとの相違を減らすことができます。
このオプションが利用できない場合,または最適化されているコードのデバッグが明らかに必要な場合は,本章を参照してください。本章では,最適化されたコードのデバッグ方法と,最適化されたコードの典型的な例を使用して混乱の潜在的な原因を示します。また,最適化されたコードをデバッグするときに,このような混乱を減らす目的で使用する機能についても説明します。
この機能を十分活かした上で,最適化されたコードのデバッグ効率を向上させるためには,使用している言語コンパイラの最新版を入手しなければなりません。必要なコンパイラのバージョンについては,コンパイラのリリース・ノートやその他のマニュアルなどを参照してください。
また最適化されたコードをデバッグするためには,イメージのサイズの増加に対応するため,通常の必要ディスク領域に比べて,約1/3の領域が余分に必要になります。
最適化されたコードをデバッグする場合,定義済みディスプレイのINSTなどの画面モード機械語命令ディスプレイを使用して,プログラムのデコード済み命令ストリーム( 第 7.4.4 項 を参照)を表示します。機械語命令ディスプレイは,実際に実行されているコードを表示します。
画面モードでは,KP7を押すとSRC表示とINST表示が比較しやすいように並べて表示されます。または,コンパイラが生成した機械語コード・リストを調べることもできます。
さらに,プログラムを命令レベルで実行し,命令を検査するには, 第 4.3 節 で説明されている方法を使用してください。
これらの方法を使用すると,実行可能なコード・レベルで何が起きているかを明確にし,ソース・ディスプレイとプログラム動作の相違を解決することができます。
コンパイラは,実行中のさまざまな時点で変数を永久にまたは一時的に削除することによってコードを最適化することがあります。たとえば,最適化によってアクセスできなくなった変数Xを検査しようとすると,デバッガは次のメッセージのいずれかを表示します。
14.1.1 削除された変数
%DEBUG-W-UNALLOCATED, entity X was not allocated in memory (was optimized away) %DEBUG-W-NOVALATPC, entity X does not have a value at the current PC |
次のPascalの例は,この状況が発生する様子を示しています。
PROGRAM DOC(OUTPUT); VAR X,Y: INTEGER; BEGIN X := 5; Y := 2; WRITELN(X*Y); END. |
このプログラムを/NOOPTIMIZE,またはそれに等しい修飾子を付けてコンパイルすると,デバッグのときに次のような正常な動作が得られます。
$ PASCAL/DEBUG/NOOPTIMIZE DOC $ LINK/DEBUG DOC $ DEBUG/KEEP . . . DBG> RUN DOC . . . DBG> STEP stepped to DOC\%LINE 5 5: X := 5; DBG> STEP stepped to DOC\%LINE 6 6: Y := 2; DBG> STEP stepped to DOC\%LINE 7 7: WRITELN(X*Y); DBG> EXAMINE X,Y DOC\X: 5 DOC\Y: 2 DBG> |
このプログラムを/OPTIMIZE,またはそれに等しい修飾子を付けてコンパイルすると,XとYの値は初期値の割り当て以降は変更されないため,コンパイラはX*Yを計算して値(10)を保存し,XとYの記憶域を割り当てません。そのため,デバッグの開始後,STEPコマンドは第5行ではなく直接第7行に移ります。さらに,XやYを検査することはできません。
$ PASCAL/DEBUG/OPTIMIZE DOC $ LINK/DEBUG DOC $ DEBUG/KEEP . . . DBG> RUN DOC . . . DBG> EXAMINE X,Y %DEBUG-W-UNALLOCATED, entity X was not allocated in memory (was optimized away) DBG> STEP stepped to DOC\%LINE 7 7: WRITELN(X*Y); DBG> |
これに対して,次の行はWRITELN文の最適化されていないコードを表示します。
DBG> STEP stepped to DOC\%LINE 7 7: WRITELN(X*Y); DBG> EXAMINE/OPERAND .%PC DOC\%LINE 7: MOVL S^#10,B^-4(FP) B^-4(FP) 2146279292 contains 62914576 DBG> |
Line Source Code ---- ----------- 5 DO 100 I=1,10 6 A(I) = 1/X 7 100 CONTINUE |
最適化は次の手順で行われます。コンパイラがソース・プログラムを処理するとき,Xの逆数はソース・コードで指定されている10回ではなく, 1回だけ計算すればよいと判断されます。 Xの値はDOループの中では変化しないからです。コンパイラはこのように,次に示すコード・セグメントと等しい最適化されたコードを作成します。
Line Optimized Code Equivalent ---- ------------------------- 5 TEMP = 1/X DO 100 I=1,10 6 A(I) = TEMP 7 100 CONTINUE |
コンパイラの機能によって,移動したコードがループの第1行と関連したり,元の行番号を維持したり(Alphaシステムで共通)することがあります。
相違が発生しても,その相違は表示されるソース行を見るだけでは明らかではありません。さらに,Xがゼロのために1/Xの計算が失敗したとき,ソース表示を入念に調べることによって,除算が全く含まれていないソース行でゼロによる除算が行われたことが明らかになります。
#include <stdio.h> #include <stdlib.h> int main(unsigned argc, char **argv) { int w, x, y, z=0; x = atoi(argv[1]); printf("%d\n", x); x = 5; y = x; if (y > 2) { /* always true */ printf("y > 2"); } else { printf("y <= 2"); } if (z) { /* always false */ printf("z"); } else { printf("not z"); } printf("\n"); } |
次の2つの例は,最適化したdoct2プログラムによって,行単位でステップ実行した場合の例と,セマンティク・イベントによりステップ実行した場合の例で,上記と対照をなしています。
$ doct2:=$sys$disk:[]doct2 $ doct2 6 Debugger Banner and Version Number Language:: Module: Doct2: GO to reach DBG> go break at routine DOCT2\main 654: x = atoi(argv[1]); DBG> step stepped to DOCT2\main\%LINE 651 651: int main(unsigned argc, char **argv) { DBG> step stepped to DOCT2\main\%LINE 654 654: x = atoi(argv[1]); DBG> step stepped to DOCT2\main\%LINE 651 651: int main(unsigned argc, char **argv) { DBG> step stepped to DOCT2\main\%LINE 654 654: x = atoi(argv[1]); DBG> step stepped to DOCT2\main\%LINE 655 655: printf("%d\n", x); DBG> step stepped to DOCT2\main\%LINE 654 654: x = atoi(argv[1]); DBG> step stepped to DOCT2\main\%LINE 655 655: printf("%d\n", x); DBG> step 6 stepped to DOCT2\main\%LINE 661 661: printf("y > 2"); DBG> step y > 2 stepped to DOCT2\main\%LINE 671 671: printf("not z"); DBG> step not z stepped to DOCT2\main\%LINE 674 674: printf("\n"); DBG> step stepped to DOCT2\main\%LINE 675 675: } DBG> step 'Normal successful completion' DBG> |
$ doct2:=$sys$disk:[]doct2 $ doct2 6 Debugger Banner and Version Number Language:: Module: Doct2: GO to reach DBG> set step semantic_event DBG> go break at routine DOCT2\main 654: x = atoi(argv[1]); DBG> step stepped to DOCT2\main\%LINE 654+8 654: x = atoi(argv[1]); DBG> step stepped to DOCT2\main\%LINE 655+12 655: printf("%d\n", x); DBG> step 6 stepped to DOCT2\main\%LINE 661+16 661: printf("y > 2"); DBG> step y > 2 stepped to DOCT2\main\%LINE 671+16 671: printf("not z"); DBG> step not z stepped to DOCT2\main\%LINE 674+16 674: printf("\n"); DBG> step stepped to DOCT2\main\%LINE 675+24 675: } DBG> step stepped to DOCT2\__main+104 DBG> step 'Normal successful completion' DBG> |
セマンティク・ステップ実行の動作は,行単位のステップ実行と比べてはるかに滑らかで,直線的な方法になります。またセマンティク・ステップ実行では,問題のある重要な箇所で停止します。一般的に,最適化したコードを行単位でステップ実行する場合に特徴的な,コードが前後に「動き回」ることによる混乱は,セマンティク・ステップ実行の場合,激減するかまたは完全になくなります。ソース・プログラムの順序変更により,実行特性が向上することもありますが,通常フローは上から下に向かって移動します。
ステップ実行の密度は,行単位のステップ実行とセマンティク・ステップ実行とで異なります。行単位の方が大きくなることもあり,セマンティクの方が大きくなることもあります。たとえば,セマンティクの性質によりセマンティク・イベントを構成する文は,その文が完全に最適化されている場合,セマンティク・ステップ実行で出てくることはありません。このように,セマンティク・リージョンは複数の行にまたがっており,最適化された行はスキップされるということになります。
コンパイラは,1つの式の値は2つの出現の間では変化しないものと判断し,その値をレジスタに保存することがあります。このような場合,コンパイラは次の出現では値の再計算は行わず,レジスタに保存されている値が有効であるとみなします。
プログラムをデバッグしている間に,式中の変数の値を変更するためにDEPOSITコマンドを使用しても,レジスタに保存されている対応する値は変更されません。したがって,処理を続行する場合,式中の変更された値ではなくレジスタの値が使用され,予期しない結果になることがあります。
さらに,非静的変数( 第 3.4.3 項 を参照)の値がレジスタに保存されている場合,メモリ内にあるその値は通常,無効になります。したがって,このような状況下では,変数に対してEXAMINEコマンドを入力すると誤った値が表示されることがあります。
最適化してコンパイルするとき,コンパイラが変数を,別個に割り当てることのできるいくつかの部分変数に「分割」して,その変数で存在期間分割による分析を実行することがあります。その結果,元の変数が,別々の時間に別々の場所(場合によってはレジスタ,場合によってはメモリ,場合によってはどこにもない)に置かれているということも可能になります。異なる部分変数を使用すれば,それぞれの変数を同時にアクティブにすることもできます。
Alpha プロセッサでは, EXAMINEコマンドを使用すると,プログラムのどの場所で変数が定義されているかが表示されます。変数が不適切な値のとき,この位置の情報により,変数の値がどこで割り当てられているか判断することができます。また/DEFINITION S修飾子により,この位置の数を省略時の5から別の値に変更できるようになっています。
存在期間分割による分析は,スカラ変数およびパラメータのみに適用されます。配列,レコード,構造体,その他の集合体には適用されません。
次の例は,存在期間分割処理の例を示しています。最初の例は,小さなCプログラムですが,ここでは,左の段の数字が行番号を表しています。
14.1.4 レジスタの使用
14.1.5 存在期間分割変数
385 doct8 () { 386 387 int i, j, k; 388 389 i = 1; 390 j = 2; 391 k = 3; 392 393 if (foo(i)) { 394 j = 17; 395 } 396 else { 397 k = 18; 398 } 399 400 printf("%d, %d, %d\n", i, j, k); 401 402 } |
最適化されているプログラムで,デバッグのために,コンパイル,リンク,実行を行うとき,次のようなダイアログが表示されます。
$ run doct8 |
. . . DBG> step/into stepped to DOCT8\doct8\%LINE 391 391: k = 3; DBG> examine i %W, entity 'i' was not allocated in memory (was optimized away) DBG> examine j %W, entity 'j' does not have a value at the current PC DBG> examine k %W, entity 'k' does not have a value at the current PC |
変数 i のメッセージが,変数 j, k のメッセージと異なることに注意してください。変数 i は,メモリ (レジスタ,コア,その他) に全く割り当てられていないため,その値を再度テストする意味はありません。比較のために見てみると, j と k には,この時点で「現在の PC」に値がありません。これ以降のプログラムの中で出てきます。
もう1行ステップ実行すると,次のようになります。
DBG> step stepped to DOCT8\doct8\%LINE 385 385: doct8 () { |
これを見ると,1ステップ戻っているように見えます。最適化(スケジューリング )したコードで共通の現象です。この問題については, 第 14.1.2 項 の「セマンティク・ステップ実行モード」の項で説明しています。さらにステップ実行を続けると,次のようになります。
DBG> step 5 stepped to DOCT8\doct8\%LINE 391 391: k = 3; DBG> examine k %W, entity 'k' does not have a value at the current PC DBG> step stepped to DOCT8\doct8\%LINE 393 393: if (foo(i)) { DBG> examine j %W, entity 'j' does not have a value at the current PC DBG> examine k DOCT8\doct8\k: 3 value defined at DOCT8\doct8\%LINE 391 |
ここでは j がまだ定義されていませんが,k には3という値があります。この値は391行目で割り当てられているものです。
ソースでは,k の前に j に値が割り当てられている (390行目)ため,これはすでに表示されていなければなりません。ここでも最適化(スケジューリング)したコードで共通の現象が発生しています。
DBG> step stepped to DOCT8\doct8\%LINE 390 390: j = 2; |
ここで j の値が表示されます。
DBG> examine j %W, entity 'j' does not have a value at the current PC DBG> step stepped to DOCT8\doct8\%LINE 393 393: if (foo(i)) { DBG> examine j DOCT8\doct8\j: 2 value defined at DOCT8\doct8\%LINE 390 |
400行目のprint文までスキップし,j の値をテストします。
DBG> set break %line 400 DBG> g break at DOCT8\doct8\%LINE 400 400: printf("%d, %d, %d\n", i, j, k); DBG> examine j DOCT8\doct8\j: 2 value defined at DOCT8\doct8\%LINE 390 value defined at DOCT8\doct8\%LINE 394 |
j の値を定義した位置が複数あります。IF 節で選択されるパスにより,どちらかの位置が適用されます。この機能を使用すれば,変数が明らかに不適切な値の場合,その場所を調べることができ,同時に値がどこから来ているのかについても調べることができます。
次の例のようにSHOW SYMBOL/ADDRESSコマンドを使用すると,あるシンボルの存在期間分割情報を表示することができます。
DBG> show symbol/address j data DOCT8\doct8\j between PC 131128 and 131140 PC definition locations are at: 131124 address: %R3 between PC 131144 and 131148 PC definition locations are at: 131140 address: %R3 between PC 131152 and 131156 PC definition locations are at: 131124 address: %R3 between PC 131160 and 131208 PC definition locations are at: 131124, 131140 address: %R3 |
目次 | 索引 |
|