前へ | 次へ | 目次 | 索引 |
本章では,特殊な場合のデバッグ方法について説明します。
省略時の設定では,プログラムの実行速度を向上させるためにコードを最適化するコンパイラが多くあります。最適化は,実行時に一度だけ評価されればすむよう不変式が DO ループから除かれる,メモリ記憶位置のいくつかがプログラム内の異なる場所で異なる変数に割り当てられる,いくつかの変数がデバッグ中にアクセスしなくてすむように削除される,などの方法で行われます。
最終的には,デバッグ時に実行されているコードは,画面モードのソース・ディスプレイ(第 7.4.1 項 を参照)で表示されるソース・コードやソース・リスト・ファイルに記述されているソース・コードとは一致しないことがあります。
最適化されたコードをデバッグする際の問題を回避するために,多くのコンパイラではコンパイル時に/NOOPTIMIZE,またはそれに等しいコマンド修飾子を指定することができます。この修飾子を指定すると,コンパイラによる最適化がほとんど抑止され,それによって最適化のために発生するソース・コードと実行可能なコードとの相違を減らすことができます。
このオプションが利用できない場合,または最適化されているコードのデバッグが明らかに必要な場合は,本章を参照してください。本章では,最適化されたコードのデバッグ方法と,最適化されたコードの典型的な例を使用して混乱の潜在的な原因を示します。また,最適化されたコードをデバッグするときに,このような混乱を減らす目的で使用する機能についても説明します。
この機能を十分活かした上で,最適化されたコードのデバッグ効率を向上させるためには,使用している言語コンパイラの最新版を入手しなければなりません。必要なコンパイラのバージョンについては,コンパイラのリリース・ノートやその他のマニュアルなどを参照してください。
また最適化されたコードをデバッグするためには,イメージのサイズの増加に対応するため,通常の必要ディスク領域に比べて,約1/3の領域が余分に必要になります。
最適化されたコードをデバッグする場合,定義済みディスプレイのINSTなどの画面モード機械語命令ディスプレイを使用して,プログラムのデコード済み命令ストリーム(第 7.4.4 項 を参照)を表示します。機械語命令ディスプレイは,実際に実行されているコードを表示します。
画面モードでは,KP7を押すとSRC表示とINST表示が比較しやすいように並べて表示されます。または,コンパイラが生成した機械語コード・リストを調べることもできます。
さらに,プログラムを命令レベルで実行し,命令を検査するには,第 4.3 節 で説明されている方法を使用してください。
これらの方法を使用すると,実行可能なコード・レベルで何が起きているかを明確にし,ソース・ディスプレイとプログラム動作の相違を解決することができます。
14.1.1 削除された変数
コンパイラは,実行中のさまざまな時点で変数を永久にまたは一時的に削除することによってコードを最適化することがあります。たとえば,最適化によってアクセスできなくなった変数Xを検査しようとすると,デバッガは次のメッセージのいずれかを表示します。
%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> |
VAX プロセッサでは,最適化されたプログラム内でどんな値が使用されているかを調べるには,EXAMINE/OPERAND .%PCコマンドを使用し,現在のPC値の機械語コードをすべてのオペランドの値とシンボルを含めて表示します。たとえば,次の行はPC値がWRITELN文にある場合の最適化されたコードを表示します。
DBG> STEP stepped to DOC\%LINE 7 7: WRITELN(X*Y); DBG> EXAMINE/OPERAND .%PC DOC\%LINE 7: PUSHL S^#10 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> |
最適化の手法の中には,ソース・コードで指定されたシーケンスとは異なるシーケンスで操作を行うという方法をとるものがあります。場合によってはコードがすべて削除されてしまうこともあります。
その結果,デバッガが表示するソース・コードと,実際に実行されているコードが完全には対応しなくなります。
例を使用して説明するために,Fortranプログラムのソース・コードのセグメントを示します。これはコンパイラ・リストや画面モードのソース表示に表示されるものです。このコード・セグメントは配列Aの最初の10個の要素を1/Xの値に設定します。
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行と関連したり(VAX システムで共通),元の行番号を維持したり(Alphaシステムで共通)することがあります。
相違が発生しても,その相違は表示されるソース行を見るだけでは明らかではありません。さらに,Xがゼロのために1/Xの計算が失敗したとき,ソース表示を入念に調べることによって,除算がまったく含まれていないソース行でゼロによる除算が行われたことが明らかになります。
このようなソース・コードと実行可能なコードの明らかな不一致は,最適化されたプログラムをデバッグするときにしばしば見られます。前の例のようなループから抜ける場合のコード動作が原因になるだけでなく,他の多くの最適化の手法も原因になります。
14.1.3 セマンティク・ステップ実行(Alpha のみ)
セマンティク・ステップ実行(Alphaシステムのみで使用可能)を行うことにより,最適化されたコードをステップ実行するとき,混乱を少なくすることができます。セマンティク・ステップ実行モードは,従来の行単位ステップ・モード,命令単位ステップ・モードを補足するものです。セマンティク・ステップ実行に使用するコマンドとして,SET STEP SEMANTIC_EVENTとSTEP/SEMANTIC_EVENTの2種類のコマンドがあります。
最適化されたコードをステップ実行するときの問題として,明らかなソース・プログラムの位置が前後に「動き回り」,同じ行が何度も現れるということがあります。実際,STEP LINEモードでコードを前方向に実行している場合でも,STEP コマンドのたびに同じ命令が繰り返されることもあります。
この問題は,セマンティク・イベントの注釈命令によって対応できます。セマンティク・イベントは,次の2つの理由で重要です。
セマンティク・イベントは,以下のいずれかになります。
ただしセマンティク・イベントは,必ずしもイベント割り当て,制御の転送,呼び出しに限られるものではありません。例外として次のようなものがあります。
このようなルーチンにステップ移動するときは,次のいずれかを行う。
SET STEP SEMANTIC_EVENTコマンドは,省略時のステップ実行モードをセマンティクとして確立します。
STEP/SEMANTIC_EVENTコマンド(セマンティク・モードになっているときは単にSTEPでも同等)は,次のセマンティク・イベントが割り当て,制御の転送,呼び出しのいずれであっても,そのイベントにブレークポイントをセットします。そのイベントに移るまで,実行が進みます。さまざまな行/文があれば,そのうちのいくつかがプロセスを阻害しない方法で実行されます。セマンティク・イベントに移る(つまりそのイベントに対応する命令に移ったが,まだ実行されていない状態)と,実行が停止します(STEP/LINEを使用しているときに次の行に移るのと同様)。
次に示すCプログラムdoct2の内容は,最適化の場合の注意事項を示しています。
#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> |
セマンティク・ステップ実行の動作は,行単位のステップ実行と比べてはるかに滑らかで,直線的な方法になります。またセマンティク・ステップ実行では,問題のある重要な箇所で停止します。一般的に,最適化したコードを行単位でステップ実行する場合に特徴的な,コードが前後に「動き回」ることによる混乱は,セマンティク・ステップ実行の場合,激減するかまたは完全になくなります。ソース・プログラムの順序変更により,実行特性が向上することもありますが,通常フローは上から下に向かって移動します。
ステップ実行の密度は,行単位のステップ実行とセマンティク・ステップ実行とで異なります。行単位の方が大きくなることもあり,セマンティクの方が大きくなることもあります。たとえば,セマンティクの性質によりセマンティク・イベントを構成する文は,その文が完全に最適化されている場合,セマンティク・ステップ実行で出てくることはありません。このように,セマンティク・リージョンは複数の行にまたがっており,最適化された行はスキップされるということになります。
14.1.4 レジスタの使用
コンパイラは,1つの式の値は2つの出現の間では変化しないものと判断し,その値をレジスタに保存することがあります。このような場合,コンパイラは次の出現では値の再計算は行わず,レジスタに保存されている値が有効であるとみなします。
プログラムをデバッグしている間に,式中の変数の値を変更するためにDEPOSITコマンドを使用しても,レジスタに保存されている対応する値は変更されません。したがって,処理を続行する場合,式中の変更された値ではなくレジスタの値が使用され,予期しない結果になることがあります。
さらに,非静的変数(第 3.4.3 項 を参照)の値がレジスタに保存されている場合,メモリ内にあるその値は通常,無効になります。したがって,このような状況下では,変数に対してEXAMINEコマンドを入力すると誤った値が表示されることがあります。
14.1.5 条件コードの使用(VAX のみ)
最適化の方法の中に,VAXプロセッサの条件コードが設定される方法を利用したものが1つあります。次のPascalのソース・コードを例に考えてみます。
X := X + 2.5; IF X < 0 THEN ... |
分岐するかどうかを判断するためにXの新しい値をテストする代わりに,最適化されたコードではXに2.5が加えられたあとの条件コード設定に判断の基準が置かれます。したがって,IF文にブレークポイントを設定してXに異なる値を格納しようとすると,条件コードはもはやXの値を反映しなくなるため,意図した結果を得ることができなくなります。すなわち,分岐の判定は変数に格納された値に関係なく行われます。
ここでも,期待する結果を得るために,EXAMINE/OPERAND .%PCコマンドを使用して正しい格納位置を明確にすることができます。
14.1.6 存在期間分割変数
最適化してコンパイルするとき,コンパイラが変数を,別個に割り当てることのできるいくつかの部分変数に「分割」して,その変数で存在期間分割による分析を実行することがあります。その結果,元の変数が,別々の時間に別々の場所(場合によってはレジスタ,場合によってはメモリ,場合によってはどこにもない)に置かれているということも可能になります。異なる部分変数を使用すれば,それぞれの変数を同時にアクティブにすることもできます。
Alpha プロセッサでは,EXAMINEコマンドを使用すると,プログラムのどの場所で変数が定義されているかが表示されます。変数が不適切な値のとき,この位置の情報により,変数の値がどこで割り当てられているか判断することができます。また/DEFINITION S修飾子により,この位置の数を省略時の5から別の値に変更できるようになっています。
存在期間分割による分析は,スカラ変数およびパラメータのみに適用されます。配列,レコード,構造体,その他の集合体には適用されません。
次の例は,存在期間分割処理の例を示しています。最初の例は,小さなCプログラムですが,ここでは,左の段の数字が行番号を表しています。
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行目)ため,これはすでに表示されていなければなりません。ここでも最適化(スケジューリング)したコードで共通の現象が発生しています。
前へ | 次へ | 目次 | 索引 |