このセクションでは、ブレークポイントを使用するためのいくつかの高度な技術について説明します。
ブレークポイントカウントの使用法
境界ブレークポイントの使用法
役立つブレークポイントカウントのピッキング
ウォッチポイント
ブレークポイント条件の使用法
ポップを使用したマイクロ再実行
修正と継続機能の使用法
このセクション、およびプログラム例は、ここで紹介する手順とほぼ同じものを使用して dbx で実際に検出されたバグを基にしています。
ソースコードには in というサンプル入力ファイルが含まれており、これを使用してプログラム例でバグを発生させます。in には次のコードが含まれています。
display nonexistent_var # should yield an error display var stop in X # will cause one "stopped" message and display stop in Y # will cause second "stopped" message and display run cont cont run cont cont
この入力ファイルでプログラムを実行すると、出力は次のようになります。
$ a.out < in > display nonexistent_var error: Don't know about 'nonexistent_var' > display var will display 'var' > stop in X > stop in Y > run running ... stopped in X var = { a = '100' b = '101 c = '<error>' d = '102 e = '103' f = '104' } > cont stopped in Y var = { a = '105' b = '106' c = '<error>' d = '107' e = '108' f = '109' } > cont exited > run running ... stopped in X var = { a = '110' b = '111' c = '<error>' d = '112' e = '113' f = '114' } > cont stopped in Y var = { a = '115' b = '116' c = '<error>' d = '117' e = '118' f = '119' } > cont exited > quit Goodby
この出力を見て量が多いと思われるかもしれませんが、この例では、プログラムが長大で複雑なためコードをステップスルーまたは追跡することが困難な場合に使用する技術を解説することを主眼としています。
c フィールドの値を表示する箇所で、値が <error> になっていることに注目してください。フィールドに不正なアドレスが含まれていると、このような状況が発生することがあります。
プログラムを 2 度実行した場合に、最初の実行時に取得しなかった追加のエラーメッセージを取得していることに注目してください。
error: cannot get value of 'var.c'
error() 関数では、特定の状況でのサイレントエラーメッセージに変数 err_silent を使用します。たとえば、エラーメッセージを表示するのではなく、表示コマンドの場合に、問題が c = '<error>' として表示されます。
最初のステップでは、デバッグターゲットを設定し、「再起動」
をクリックしてバグを簡単に繰り返すことができるようにターゲットを構成します。
次のようにプログラムのデバッグを開始します。
プログラム例をコンパイルしていない場合は、プログラム例の説明に従って実行してください。
「デバッグ」->「実行可能ファイルをデバッグ」を選択します。
「実行可能ファイルをデバッグ」ダイアログボックスで、実行可能ファイルへのパスを参照または入力します。
「引数 」フィールドで、次のものを入力します。
< in
実行可能ファイルのパスのディレクトリ部分が「作業ディレクトリ」フィールドに表示されます。
「デバッグ」をクリックします。
現実の状況で、「環境」フィールドに同様に入力したい場合があります。
プログラムをデバッグするときに、dbxtool がデバッグターゲットを作成します。「デバッグ」->「最近行なったデバッグ」を選択してから、目的の実行可能ファイルを選択すると、同じデバッグ構成を使用できます。
これらのプロパティーの多くを dbx コマンド行で設定できます。これらはデバッグターゲット構成に格納されます。
次の技術は、簡単な再現方法を維持するのに役立ちます。ブレークポイントを追加したときは、途中のさまざまなブレークポイントで「継続」をクリックしなくても、「再起動」をクリックすると目的の場所にすばやく移動できます。
エラーメッセージが出力される場合は、error() 関数の内部に最初のブレークポイントを置きます。このブレークポイントは、33 行目の行ブレークポイントとなります。
より大きなプログラムでは、たとえば「デバッガ・コンソール」ウィンドウで、次のように入力すると「エディタ」ウィンドウの現在の関数を簡単に変更できます。
(dbx) func error
ラベンダーストライプに、func コマンドで検出された一致が示されます。
33 番目の上の「エディタ 」ウィンドウの左マージンをクリックして、行ブレークポイントを作成します。
「再起動」
をクリックしてプログラムを実行し、ブレークポイントにヒットすると、in ファイル内のシミュレートされたコマンドによって生成されたエラーメッセージがスタックトレースに表示されます。
> display var # should yield an error
error() の呼び出しは想定された動作です。
「継続」
をクリックしてプロセスを継続すると、再度ブレークポイントにヒットします。
予期しないエラーメッセージが表示されます。
コマンドによるブレークポイントの最初のヒットの後で、「継続 」をクリックする必要なく、実行ごとに繰り返してこの場所に到着する方がよいでしょう。
> display var # should yield an error
プログラムまたは入力スクリプトを編集して、最初の問題を引き起こす表示コマンドを削除できます。ただし、使用している入力シーケンスはこのバグを再現する鍵となる可能性があるため、入力を変更する必要はありません。
再度このブレークポイントに到達するには、カウントを 2 に設定します。
「ブレークポイント 」ウィンドウで、ブレークポイントを右クリックして、「カスタマイズ 」を選択します。
「ブレークポイントをカスタマイズ 」で、「カウンタの上限値 」フィールドに「2」を入力します。
「OK」をクリックします。
これで、目的の場所に繰り返し到達できます。
この場合は、カウントとして 2 を選択したので特に問題はありませんでした。しかし、目的の場所が何度も呼び出される場合もあります。適切なカウント値を簡単に選択するには、ステップ 7: カウント値の確認を参照してください。ここでは、error() での停止を呼び出しでのみ行う別の方法について説明します。
error() 内のブレークポイントの「ブレークポイントをカスタマイズ」ダイアログボックスを開き、「数の制限」のドロップダウンリストから「常に停止」を選択して、ブレークポイントのカウントを無効にします。
プログラムを再実行します。
error() で 2 回停止するときに、スタックトレースを確認します。1 回目の error() での停止は、次のような画面になります。
2 回目の error() での停止は、次のような画面になります。
runProgram (フレーム [7]) から呼ばれるときにこのブレークポイントで停止するように調整するため、「ブレークポイントをカスタマイズ」ダイアログボックスを再度開き、「While In」フィールドを runProgram に設定します。
err_silent が 0 より大きくないため、意図しないエラーメッセージが出力されます。バルーン評価を使用して err_silent の値を調べます。
カーソルを 31 行目の err_silent に置いて、表示される値を待機します。
スタックを追跡して、err_silent が設定された場所を確認します。
「呼び出し元を現在に設定」
を 2 回クリックすると、evaluateField() に到達し、この関数はすでに evaluateFieldPrepare() を呼び出し、err_silent を操作している可能性がある複雑な関数をシミュレートします。
「呼び出し元を現在に設定」を再度クリックすると、printField() に到達し、err_silent が増分されます。printField() はすでに printFieldPrepare() を呼び出し、err_silent を操作している可能性のある複雑な関数もシミュレートします。
一部のコードが err_silent++ と err_silent-- に囲まれていることを注目してください。
err_silent は、printFieldPrepare() または evaluateFieldPrepare() のいずれかで不正になったか、printField() に制御が到達したときにすでに不正だった可能性があります。
printField() の呼び出し前と呼び出し後のどちらで err_silent が不正になったかを特定するため、printField() にブレークポイントを置きます。
printField() を選択し、「新規ブレークポイント」を右クリックして、選択します。
新しいブレークポイントのタイプが事前に選択され、「関数」フィールドに printfield が事前に入力されます。
「OK」をクリックします。
「再起動」
をクリックします。
ブレークポイントに最初にヒットするのは、最初の実行中に最初に停止したときの最初のフィールド var.a 上です。err_silent は 0 で、これは問題ありません。
「継続 」をクリックします。
err_silent は引き続き問題ありません。
ふたたび「継続 」をクリックします。
err_silent は引き続き問題ありません。
意図しないエラーメッセージが発生する printField() の呼び出しに到達するまで、しばらくかかります。printField ブレークポイントでブレークポイントカウントを使用する必要があります。しかしカウントをどのように設定したらよいでしょうか。この簡単な例では、表示される実行、停止、およびフィールドをカウントすることもできますが、実際のプロセスはもっと複雑である可能性があります。カウントを半自動的に確認する方法があります。
printField() 上のブレークポイントの「ブレークポイントをカスタマイズ 」を開いて、「カウンタの上限値」フィールドを infinity に設定します。
この設定は、このブレークポイントで停止しないことを意味します。ただし、依然として数えています。
カウントなどの追加のプロパティーが表示されるように「ブレークポイント」ウィンドウを設定します。
「ブレークポイント」ウィンドウの右上隅にある「表示可能項目の変更」ボタン
をクリックします。
「カウント」、「制限」、および「指定関数内」を選択します。
「OK」をクリックします。
プログラムを再度実行します。error() 内の、runProgram() を境界とするブレークポイントにヒットします。
printField() 上のブレークポイントのカウントを確認します。
カウントは 15 です。
再度「ブレークポイントをカスタマイズ」ウィンドウで、「数の制限」列のドロップダウンリストをクリックし、「現在のカウントの値を使用」を選択して現在のカウントを「数の制限」に転送し、「OK」をクリックします。
ここでプログラムを実行すると、意図しないエラーメッセージの前に最後に呼び出された printField() で停止します。
バルーン評価を使用して、ふたたび err_silent を検査します。現在は -1 です。もっとも可能性の高い原因は、printField() に到達する前に、ある err_silent-- の実行回数が多すぎたか、ある err_silent++ の実行回数が少なすぎたことです。
このような小さいプログラムでは、コードを入念に検査することで、この err_silent のミスマッチのペアを特定できます。ただし大規模なプログラムには、膨大な数の次のペアが含まれている可能性があります。
err_silent++; err_silent--;
ミスマッチのペアをより速く検索する方法として、ウォッチポイントの利用があります。
エラーの原因が、err_silent++; と err_silent--; のセットのミスマッチではなく、err_silent の内容を上書きする不正なポインタである可能性もあります。ウォッチポイントはそのような問題をとらえるのにより効果的でしょう。
err_silent でウォッチポイントを作成するには、次の手順に従います。
err_silent 変数を選択し、「新規ブレークポイント」を右クリックして、選択します。
アクセスするブレークポイントの種類を設定します。
「設定」セクションが変更され、「アドレス」フィールドが & err_silent になることに注意してください。
「いつ」フィールドの「後」を選択します。
「演算 」フィールドの「書き込み 」を選択します。
「OK」をクリックします。
プログラムを実行します。
init() で停止します。err_silent が 1 に増分され、そのあと実行が停止しました。
「継続 」をクリックします。
ふたたび init() で停止します。
ふたたび「継続 」をクリックします。
ふたたび init() で停止します。
ふたたび「継続 」をクリックします。
ふたたび init() で停止します。
ふたたび「継続 」をクリックします。
ここで、stopIn() で停止します。ここでも -1 にはならず、問題はありません。
err_silent が -1 に設定されるまで何度も「継続」をクリックする代わりに、ブレークポイント条件を設定できます。
ウォッチポイントに条件を追加するには、次の手順に従います。
「ブレークポイント」ウィンドウで、「後」の「書き込み」ブレークポイントを右クリックして、「カスタマイズ」を選択します。
「時間」フィールドで「後」が選択されていることを確認します。
「後」を選択すると、err_silent の変更後の値を確認できます。
「条件」フィールドを err_silent == -1 に設定します。
「OK」をクリックします。
プログラムを再度実行します。
checkThings() で停止します。ここではじめて err_silent が -1 に設定されます。マッチングしている err_silent++ を探すにつれて、バグのように見えるものを確認します。err_silent は関数の else 部分でのみ増分されます。
これはあなたが探しているバグでしょうか。
関数の else ブロックから実際に進んでいることをダブルチェックする方法の 1 つは、checkThings() にブレークポイントを設定し、プログラムを実行することです。しかし、checkThings() は何度も呼び出される可能性があります。ブレークポイントカウントまたは境界ブレークポイントを使用して checkThings() の正しい呼び出しに到達できますが、最近実行された処理をよりすばやく再現するには、スタックをポップします。
「デバッグ」->「スタック」->「最上位の呼び出しをポップ」を選択します。
「最上位の呼び出しをポップ」がすべてを元に戻さないことに注意してください。特に、データデバッグからコントロールフローデバッグに切り替えているため、err_silent の値はすでに間違っています。
プロセスの状態は、checkThings() の呼び出しを含む行の最初に戻ります。
「ステップイン」
をクリックすると、checkThings() が再度呼び出されるたびに監視できます。
checkThings() をステップスルーするにつれて、プロセスが if ブロックを実行することを確認でき、ここで err_silent は増分されず、-1 に減らされます。
プログラミングエラーが見つかったようですが、それをトリプルチェックすることをお勧めします。
コードを適切に修正し、バグが実際に解決されたことを確認します。
err_silent++ が if 文の上に来るようにコードを修正します。
「デバッグ」>「コード変更の適用」を選択するか、「コード変更の適用」ボタン
を押します。
printField ブレークポイントおよびウォッチポイントを無効にしますが、error() のブレークポイントは有効なままにします。
プログラムを再度実行します。
error() でブレークポイントをヒットすることなくプログラムが完了し、出力が想定どおりであることを確認してください。
この例は、ブレークポイントとステップ動作の使用法の終わりで説明したのと同じパターンを示します。つまり、誤動作するプログラムを不具合が発生する前の時点で停止し、コードの意図と実際のコードの動作を比較しながらコードをステップスルーします。主な相違は、調子が悪くなる前にポイントを検出することが少し関連しています。