JavaScript is required to for searching.
ナビゲーションリンクをスキップ
印刷ビューの終了
Oracle Solaris Studio 12.2 dbxtool チュートリアル
search filter icon
search icon

概要

プログラム例

dbxtool の設定

コアダンプの診断

ブレークポイントとステップ動作の使用法

ブレークポイントを設定する

関数ブレークポイントの利点

ウォッチポイントとステップ動作の使用法

ディスカッション

高度なブレークポイント技術の使用法

問題

ステップ 1: 再現性

ステップ 2:最初のブレークポイント

ステップ 3:ブレークポイントカウント

ステップ 4:境界ブレークポイント

ステップ 5: 原因の調査

ステップ 6: より多くのブレークポイントカウント

ステップ 6: 原因の範囲を限定する

ステップ 7: ウォッチポイントの使用法

ステップ 8:ブレークポイントの条件

ステップ 9:スタックをポップすることによる診断の確認

ステップ 10:診断をさらに確認するための修正の使用法

ディスカッション

ブレークポイントスクリプトを使用してコードにパッチを適用する

Oracle Solaris Studio 12.2 dbxtool チュートリアル

2010 年 9 月

このチュートリアルでは、バグを含んだプログラム例を使用して、dbx デバッガ用の独立グラフィカルユーザーインタフェース (GUI) である dbxtool の効果的な使用方法について説明します。最初に基本的な機能について説明し、その後、より詳細な機能について説明していきます。

概要

このチュートリアルでは、「バグを含んだ」プログラム例を使用して、dbx デバッガ用のスタンドアロングラフィカルユーザーインタフェース (GUI) である dbxtool の効果的な使用方法について説明します。最初に基本的な機能について説明し、その後、より詳細な機能について説明していきます。

プログラム例

このチュートリアルでは、dbx デバッガの単純で、やや擬似的なシミュレーションを使用します。まず、C++ で書かれたこのプログラムのソースコードを、インストールされた Oracle Solaris Studio 12.2 ソフトウェアの examples/debugger/debug_tutorial ディレクトリから入手します。

  1. ディレクトリを、ユーザー専用の作業領域にコピーします。次に例を示します。

    cp -r /opt/solstudio12.2/examples/debugger/debug_tutorial ~/debug_tutorial
  2. プログラムを構築します。

    make
    CC -g   -c  main.cc
    CC -g   -c  interp.cc
    CC -g   -c  cmd.cc
    CC -g   -c  debugger.cc
    CC -g   -c  cmds.cc
    CC -g   main.o interp.o cmd.o debugger.o cmds.o -o a.out

プログラムは次のモジュールから構成されます。

cmd.h
cmd.cc
Cmd クラス、デバッガコマンドを実装するためのベース
interp.h
interp.cc
Interp クラス、簡単なコマンドインタプリタ
debugger.h
debugger.cc
Debugger クラス、デバッガの主要なセマンティクスの模倣
cmds.h
cmds.cc
さまざまなデバッグコマンドの実装
main.h
main.cc
main() 関数とエラー処理Interp をセットアップし、さまざまなコマンドを作成して、それらのコマンドを Interp に割り当てます。Interp を実行します。

プログラムを実行して、dbx コマンドをいくつか試します。

$ a.out
> exec date
Sun Jun 21 16:13:06 PDT 2009
> display var
will display 'var'
> stop in X
> run running ...
stopped in X
var = {
        a = '100'
        b = '101'
        c = '<error>'
        d = '102'
        e = '103'
        f = '104'
}
> quit
Goodby
$

dbxtool の設定

次のように入力して、dbxtool を起動します。

installation_directory/bin/dbxtool

最初に dbxtool を起動したときに、ウィンドウは次のように表示されます。

dbxtool ウィンドウ

このチュートリアルを Web ブラウザで参照している場合は、おそらくそれだけで画面の半分が使用されるので、dbxtool のアプリケーションのサイズを画面の半分になるようにカスタマイズすると便利です。

次に、dbxtool のさまざまなカスタマイズ例を示します。

コアダンプの診断

使用状況に合うように dbxtool を設定しました。次に、バグをいくつか見つけてみましょう。

プログラム例をもう一度実行します。ただし、今回はコマンドを入力しないで改行キーを押します。

$ a.out
> display var
will display 'var'
> 
Segmentation Fault (core dumped)
$

ここで、実行可能ファイルおよびコアファイルを指定して dbxtool を起動します。

$ dbxtool a.out core

ヒント - dbxtool コマンドは dbx コマンドと同じ引数を受け入れていることに注目してください。


dbxtool は、次のような内容を表示します。

dbx コンソールウィンドウおよび strcmp への呼び出し場所を表示する dbxtool ウィンドウ

ここでいくつかの注意事項を挙げます。

関数は、パラメータとして不正な値を渡される場合は通常失敗します。ここに、strcmp() に渡された値を確認するためのいくつかの方法があります。

ここで、name の値が NULL であるべきではないことは非常に明白です。しかし、どのコードがこの不正な値を Interp::find() に渡したのでしょうか。これを調べるには、次の操作を行います。

このコードは未知です。また、少し見ただけでは、argv[0] の値が NULL であること以外はわかりません。

ブレークポイントおよびステップ動作を使用して、この問題を動的にデバッグする方がよい場合があります。

ブレークポイントとステップ動作の使用法

ブレークポイントにより、バグを示す少し前でプログラムを停止したり、何が間違っているかを検出するためにコードをステップスルーすることができます。

「プロセス入出力 (Process I/O)」ウィンドウの合体をまだ解除していなければ、このタイミングで行うとよいでしょう。

以前はこのプログラムをコマンド行から実行しました。ここで、dbxtool でプログラムを実行して、バグを再現します。

  1. ツールバーで「実行 (Run)」ボタン 「実行 (Run)」ボタンをクリックするか、「Dbx コンソール (Dbx Console)」ウィンドウで run と入力します。

  2. 「プロセス入出力 (Process I/O)」ウィンドウで改行キーを押します。このとき、警告ボックスが SEGV について知らせます。

    SEG を表示する「シグナル捕獲 (Signal Caught)」警告ボックス
  3. 警告ボックスで、「破棄して一時停止 (Discard and Pause)」をクリックします。エディタ (Editor) ウィンドウで、Interp::find()strcmp() への呼び出しが再度強調表示されます。

  4. ツールバーの「呼び出し元を現在に設定 (Make Caller Current)」ボタン 「呼び出し元を現在に設定 (Make Caller Current)」ボタンをクリックして、Interp::dispatch() に以前に表示された不明なコードに移動します。ここで、find() への呼び出しの少し前にブレークポイントを設定できます。後で、間違っている理由を調べるため、コードをステップスルーできます。

ブレークポイントを設定する

ブレークポイントを設定するには、いくつかの方法があります。行番号が表示されていなければ、最初に左マージン内を右クリックし、「行番号を表示 (Show Line Numbers)」チェックボックスをオンにして、エディタで行番号を有効にします。

現在は、エディタが乱雑になっています。

乱雑な「エディタ (Editor)」ウィンドウ

「ブレークポイント (Breakpoints)」ウィンドウを使用して、この乱雑さをクリーンアップできます。

  1. 「ブレークポイント (Breakpoints)」タブをクリックします (または前に最小化している場合は最大化します)。

  2. 行ブレークポイントおよび関数ブレークポイントの 1 つを選択して、右クリックして「削除 (Delete)」を選択します。

関数ブレークポイントの利点

エディタで切り替えて、行ブレークポイントを設定することは直感的である場合があります。ただし、多くの dbx ユーザーは、次の理由で関数ブレークポイントの方を好みます。

ウォッチポイントとステップ動作の使用法

したがって、現在、Interp::dispatch() に単一のブレークポイントがあります。ふたたび「実行 (Run)」 「実行 (Run)」ボタンをクリックし、「プロセス入出力 (Process I/O)」ウィンドウの改行キーを押すと、プログラムは実行可能コードを含む dispatch() 関数の最初の行で停止します。

122 行目で実行が停止した「エディタ (Editor)」ウィンドウ

culprit が find() へ渡されている argv[0] であることがすでにわかっているため、ウォッチポイントを使用して argv を監視します。

  1. 「エディタ (Editor)」ウィンドウで argv のインスタンスを選択します。

  2. 「デバッグ (Debug)」 > 「新規ウォッチポイント (New Watch)」を選択するか、または右クリックして「新規ウォッチポイント (New Watch)」を選択します。「新規ウォッチポイント (New Watch)」ダイアログボックスが選択したテキストで表示されます。

    「新規ウォッチポイント (New Watch)」ダイアログボックス
  3. 「OK」をクリックします。

  4. 「ウィンドウ (Window)」 > 「ウォッチポイント (Watches)」を選択して、「ウォッチポイント (Watches)」ウィンドウを開きます。

  5. argv を展開します。

    argv が展開された「ウォッチポイント (Watches)」ウィンドウ

    このガベージはいったい何でしょう。argv が初期化されておらず、ローカル変数であるため、前の呼び出しからスタック上に残っているランダムな値を「継承」していることに注意してください。これは問題の原因でしょうか。先走りせずに系統的に進めましょう。

  6. 「ステップオーバー (Step Over)」 「ステップオーバー (Step Over)」ボタンを 2 回、緑色の PC の矢印が int argc = 0; を示すまでクリックします。

  7. argcargv の索引であることは明白であるため、同様にそれに注意して、そのウォッチポイントも作成します。また、今は初期化されておらず、ガベージ値が含まれている可能性もあるので注意してください。

  8. argc 秒にウォッチポイントを作成したため、「ウォッチポイント (Watches)」ウィンドウの argv 下に表示されます。ウィンドウの最初の行に表示されれば問題ありません。ウォッチポイントを削除して、希望の順序で再入力できます。ただし、この場合、使用可能なクイックトリックがあります。「名前 (Name)」列ヘッダーをクリックすると、列がソートされます。次のような内容が取得されるまでクリックします (ソートトライアングルに注意)。

    ウォッチポイントがソートされた「ウォッチポイント (Watches)」ウィンドウ
  9. 「ステップオーバー (Step Over)」 「ステップオーバー (Step Over)」ボタンをクリックします。argc が、その初期化された値の 0 をどのように表示するか注目してください。太字は、値がちょうど変更されたことを示しています。

    argc 値が太字で表示された「ウォッチポイント (Watches)」ウィンドウ
  10. アプリケーションが strtok() を呼び出します。「ステップオーバー (Step Over)」をクリックして、関数をステップオーバーし、たとえば、バルーン式を使用して、トークンが NULL であることを監視します。


    ヒント - strtok() は何を行いますか。strtok (3) マニュアルページを参照できますが、簡単に言えば、DELIMETERS/ の 1 つによって区切られたトークンに line などの文字列を分割するのに役立ちます。


  11. 「ステップオーバー (Step Over)」をもう一度クリックすると、argv にトークンを割り当て、次にループに strtok() への呼び出しがあります。ステップオーバーするにつれて、ループには入らずに (これ以上トークンがない)、逆に NULL が割り当てられます。その割り当てもステップオーバーすると、検出するための呼び出しのしきい値となります。リコールする場合、これはプログラムがクラッシュした場所です。

  12. find() に呼び出しをステップオーバーすることによって、プログラムがここでクラッシュすることをダブルチェックします。実際には、「シグナル捕獲 (Signal Caught)」警告ボックスがふたたび表示されます。

    「シグナル捕獲 (Signal Caught)」警告ボックス

    以前のように「破棄して一時停止 (Discard and Pause)」をクリックします。

  13. したがって、Interp::dispatch で停止した後で、find() への最初の呼び出しは、実際は不正な場所です。

    これは明白であったかもしれませんが、重要な点は、元の場所にすばやく戻れることを示すことです。ここで、方法を説明しましょう。

    1. 「呼び出し元を現在に設定 (Make Caller Current)」 「呼び出し元を現在に設定 (Make Caller Current)」ボタンをクリックします。

    2. find() の呼び出しサイトで行ブレークポイントを切り替えます。

    3. 「ブレークポイント (Breakpoints)」ウィンドウを開いて、Interp::dispatch () 関数ブレークポイントを無効にします。

      dbxtool は、このように見えるはずです。

      1 つを無効にし、2 つのブレークポイントを示す「エディタ (Editor)」ウィンドウ
    4. 下矢印は、2 つブレークポイントが 141 行に設定され、それらの 1 つが無効であることを示しています。

  14. 「実行 (Run)」 「実行 (Run)」ボタン をクリックして「プロセス入出力 (Process I/O)」ウィンドウの改行キーを押すと、プログラムが find() への呼び出しの前に戻って終了します。(「実行 (Run)」ボタンがいかに再起動するかに注意してください。デバッグ時には、さらに頻繁に再起動します。)


    ヒント - たとえば、プログラムを再構築する場合、バグを検出して修正した後で、dbxtool を終了して、再起動する必要はありません。「実行 (Run)」ボタンをクリックすると、dbx がプログラム (またはその構成要素) が再コンパイルされていることを検出し、再ロードします。

    したがって、デバッグに関する問題で使用しやすいように、デスクトップ上で、おそらく最小化して dbxtool を簡単に保持するのがより効率的です。


  15. それではバグはどこですか。ふたたびウォッチポイントをみてみましょう。

    「ウォッチポイント (Watches)」ウィンドウ

    ここで、最初の strtok() へ呼び出しが NULL を返し、つまり行が空でありトークンを持っていなかったため、argv[0] が NULL であるすばらしい直感的なリープを作成できます。


    ヒント - このチュートリアルの残りに進む前に、このバグを修正できますか。

    できます。また、改行キーを押さずに、空の行を作成しないことも選択できます。

    または、主に、デバッガ下でプログラムを実行している場合、「ブレークポイントスクリプトを使用してコードにパッチを適用する」 で説明されるように、デバッガで「パッチを適用」することができます。


コード例の開発者は、この条件に対しておそらくテストし、Interp::dispatch() の残りを省略しているはずです。

ディスカッション

上記の例では、調子が悪くなる前の時点で誤動作しているプログラムを停止し、コードが実際に動作している方法でコードの意図を比較してコードをステップスルーする最も一般的なデバッグパターンを示しています。

あらゆるステップ動作と監視なしで、より直接的にバグを検出することができていましたか。実際はできていましたが、最初にブレークポイントを使用するための技術をもっと学習する必要があるでしょう。

高度なブレークポイント技術の使用法

この節では、ブレークポイントを使用するためのいくつかの高度な技術について説明します。

この節、およびプログラム例は、ここで紹介する手順とほぼ同じものを使用して 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'
error: cannot get value of 'var.c'
      c = '<error>'
      d = '112'
      e = '113'
      f = '114'
      }
> cont 
stopped in Y
var = {
      a = '115'
      b = '116'
error: cannot get value of 'var.c'
      c = '<error>'
      d = '117'
      e = '118'
      f = '119'
      }
> cont 
exited

Goodby

この出力を見て、多いなと思われるかもしれませんが、この例では、プログラムが長大で複雑なためコードを 1 行ずつ見ては追跡することが困難な場合に使用すべき手法を解説することを主眼にしています。

c フィールドの値を表示する箇所で、値が <error> になっていることに注目してください。フィールドに不正なアドレスが含まれていると、このような状況が発生することがあります。

問題

プログラムを 2 度実行した場合に、最初の実行時に取得しなかった追加のエラーメッセージを取得していることに注目してください。

error: cannot get value of 'var.c'

error() 関数は、特定の状況におけるサイレントエラーメッセージに変数 err_silent を使用します。たとえば、エラーメッセージを表示するのではなく、表示コマンドの場合に、問題が c = '<error>' として表示されます。

ステップ 1: 再現性

最初のステップは、デバッグターゲットをセットアップし、「実行 (Run)」 「実行 (Run)」ボタンをクリックしてバグを簡単に繰り返すことができるように設定することです。

次のようにプログラムのデバッグを開始します。

  1. プログラム例をコンパイルしていない場合は、「プログラム例」 の説明に従って実行してください。

  2. 「デバッグ (Debug)」 > 「実行可能ファイルをデバッグします (Debug Executable)」を選択します。

  3. 「実行可能ファイルをデバッグします (Debug Executable)」ダイアログボックスで、実行可能ファイルへのパスを参照するか、入力します。

  4. 「引数 (Arguments)」フィールドで、次のものを入力します。

    < in
  5. 実行可能ファイルのパスのディレクトリ部分を「実行ディレクトリ (Run Directory)」フィールドにコピー&ペーストします。

  6. 「デバッグ (Debug)」をクリックします。

    「実行可能ファイルをデバッグします (Debug Executable)」ダイアログボックス

現実の状況で、「引数 (Arguments)」フィールドまたは「環境 (Environment)」フィールドに同様に入力したい場合があります。

「デバッグ (Debug)」 > 「現在のセッションを設定 (Configure Current Session)」を選択して、設定のプロパティをすべて変更できます。

プログラムをデバッグするときに、dbxtool がデバッグターゲットを作成します。「デバッグ (Debug)」 > 「最近行なったデバッグ (Debug Recent)」を選択し、次に目的の実行可能ファイルを選択して、常に同じデバッグ設定を使用できます。

dbx コマンドラインからこれらのプロパティの多くを設定する方が簡単な場合があります。これらはデバッグターゲット設定に格納されます。

次の内容の多くの目的は、さまざまな中間ブレークポイントで「継続 (Continue)」をクリックする必要なく、「実行 (Run)」をクリックすることによって、興味の場所に常に移動できるように、ブレークポイントを追加するにつれて容易な再現性を維持することです。

ステップ 2:最初のブレークポイント

エラーメッセージを出力する場合には、error() 関数内にブレークポイントを置きましょう。このブレークポイントは、33 行目の行ブレークポイントとなります。

より大きなプログラムでは、たとえば「dbx コンソール (Dbx Console)」ウィンドウで、次のように入力して「エディタ (Editor)」ウィンドウの現在の関数を簡単に変更できます。

(dbx) func error

ラベンダーストライプに、func コマンドで検出された一致が示されます。

  1. 33 番目の上の「エディタ (Editor)」ウィンドウの左マージンをクリックして、行ブレークポイントを作成します。

    31 行目にラベンダーストライプ、33 行目にブレークポイントが表示された「エディタ (Editor)」ウィンドウ
  2. 「実行 (Run)」 「実行 (Run)」ボタン をクリックしてプログラムを実行し、ブレークポイントをヒットすると、スタックトレースが表示されます。 in ファイルのシミュレートされたコマンドによって発行されているエラーメッセージが表示されます。

    > display var    # should yield an error

    error() への呼び出しは想定された動作です。

    エラーメッセージフレームが表示された「呼び出しスタック (Call Stack)」ウィンドウ
  3. 「継続 (Continue)」 「継続 (Continue)」ボタン をクリックしてプロセスを継続し、ふたたびブレークポイントをヒットします。今回は予期しないエラーメッセージを受け取ります。

    エラーメッセージフレームが表示された「呼び出しスタック (Call Stack)」ウィンドウ

ステップ 3:ブレークポイントカウント

コマンドによるブレークポイントの最初のヒットの後で、「継続 (Continue)」をクリックする必要なく、実行ごとに繰り返してこの場所に到着する方がよいでしょう。

> display var # should yield an error

プログラムまたは入力スクリプトを編集して、最初の問題を引き起こす表示コマンドを削除できます。ただし、作業している特定の入力順序は、このバグを再現する鍵となる可能性があるため、状況を混乱させないようにしましょう。

このブレークポイントに到着する 2 回目に興味があるため、カウントを 2 に設定しましょう。

  1. 「ブレークポイント (Breakpoints)」ウィンドウで、ブレークポイントを右クリックして、「カスタマイズ (Customize)」を選択します。

  2. 「ブレークポイントをカスタマイズ (Customize Breakpoint)」で、「カウンタの上限値 (Count Limit)」フィールドに「2」を入力します。

  3. 「OK」をクリックします。

    「ブレークポイントをカスタマイズ (Customize breakpoint)」ダイアログボックス

これで、興味のある場所に繰り返し到着できます。

ステップ 4:境界ブレークポイント

この場合、カウント 2 を選択することは簡単でした。しかし、停止に興味のある場所は何度も呼び出される場合があります。後で、適切なカウント値をどのように簡単に選択できるかわかるでしょう。しかし現在、興味のある呼び出しでのみ、error() で停止する別の方法を調査しましょう。

error() 内のブレークポイントの前のように「ブレークポイントをカスタマイズ (Customize Breakpoint)」を開いて、「カウンタの上限値 (Count Limit)」のドロップダウンリストから「常に停止 (Always stop)」を選択して、ブレークポイントカウントを無効にします。

ここで、「実行 (Run)」をクリックして、error() で 2 回停止するときに、スタックトレースに注意を払います。1 回目は次のように表示されます。

「呼び出しスタック (Call Stack)」ウィンドウ

2 回目は次のように表示されます。

「呼び出しスタック (Call Stack)」ウィンドウ

runProgram (フレーム [7]) から最後と呼ばれるときに、このブレークポイントで停止するように調整します。これを行うには、ふたたび「ブレークポイントをカスタマイズ (Customize Breakpoint)」ダイアログボックスを開いて、「指定関数内 (While In)」フィールドを runProgram に設定します。

「ブレークポイントをカスタマイズ (Customize Breakpoint)」ウィンドウ

ここで、再び、興味のあるポイントに繰り返し、普通に到着できます。

ステップ 5: 原因の調査

不要なエラーメッセージが発行されているのは何故ですか。明らかに、 err_silent が > 0 であるためです。バルーン評価を使用して err_silent の値を見てみましょう。カーソルを 31 行目の err_silent に置いて、表示される値を待機します。

err_silent = 0 のバルーン評価が表示された「エディタ (Editor)」ウィンドウ

err_silent が設定される場所を確認するために、スタックに従いましょう。「呼び出し元を現在に設定 (Make Caller Current)」 「呼び出し元を現在に設定 (Make Caller Current)」ボタンを 2 回クリックすると evaluateField() に到着し、すでに evaluateFieldPrepare() を呼び出していて、err_silent を操作している可能性のある複雑な関数をシミュレートします。

evaluateField にラベンダーストライプが表示された「エディタ (Editor)」ウィンドウ

「呼び出し元を現在に設定 (Make Caller Current)」をもう一度クリックすると、printField() に到着します。ここで err_silent は増やされています。printField() はすでに printFieldPrepare() を呼び出していて、err_silent を操作している可能性のある複雑な関数もシミュレートします。

printField にラベンダーストライプが表示された「エディタ (Editor)」ウィンドウ

err_silent++ および err_silent-- がどのようにいくつかのコードを囲むかに注意してください。

printFieldPrepare() または evaluateFieldPrepare() のいずれかで、err_silent が間違っているか、またはコントロールが printField() に到着するときにすでに間違っていることがあります。

printField () にブレークポイントを置くことによって printField() への呼び出し前後が間違っているかどうかがわかるでしょう。

ステップ 6: より多くのブレークポイントカウント

printField() にブレークポイントを設定します。

  1. printField() を選択し、「新規ブレークポイント (New Breakpoint)」を右クリックして、選択します。

  2. 新しいブレークポイントタイプが事前に選択され、「関数 (Function)」フィールドに printfield で事前入力されています。

  3. 「OK」をクリックします。

    「新規ブレークポイント (New Breakpoint)」ダイアログボックス
  4. 「実行 (Run)」 「実行 (Run)」ボタンをクリックします。ブレークポイントをヒットする初回は、最初の実行時、最初の停止時、および最初のフィールド var.a 上です。err_silent は 0 で、これは問題ありません。

    バルーン評価が表示された「エディタ (Editor)」ウィンドウ
  5. 「継続 (Continue)」をクリックします。err_silent は依然として問題ありません。

  6. ふたたび「継続 (Continue)」をクリックします。err_silent は依然として問題ありません。

特定の printField() への呼び出しに達し、その結果、不要なエラーメッセージが発生するまでしばらくかかります。printField ブレークポイントでブレークポイントカウントを使用する必要があります。しかしカウントをどのように設定したらよいでしょうか。この簡単な例では、表示されている実行、停止、およびフィールドを数えようとしますが、実際には非常に予測可能ではない可能性があります。ただし、カウントが半自動的であるかを推測する方法があります。

  1. printField() 上のブレークポイントの「ブレークポイントをカスタマイズ (Customize Breakpoint)」を開いて、「カウンタの上限値 (Count Limit)」フィールドを infinity に設定します。

    「ブレークポイントをカスタマイズ (Customize Breakpoint)」ダイアログボックス

    この設定は、このブレークポイントで停止しないことを意味します。ただし、依然として数えています。

  2. この時点で、「ブレークポイント (Breakpoints)」ウィンドウに、カウントなどのより多くのプロパティを表示することが望ましいでしょう。

    1. 「ブレークポイント (Breakpoints)」ウィンドウの右上角にある「表示項目を変更 (Change Visible Columns)」ボタン 「表示項目を変更 (Change Visible Columns)」ボタン をクリックします。

    2. 「カウント (Count)」、「制限 (Limit)」、および「指定関数内 (While In)」を選択します。

    3. 「OK」をクリックします。

      「表示項目を変更 (Change Visible Columns)」ダイアログボックス
  3. プログラムを再実行します。error() 内のブレークポイント、runProgram() によって境界を付けられるブレークポイントをヒットします。

  4. ここで、printField() 上のブレークポイントのカウントを見てみましょう。

    「ブレークポイント (Breakpoints)」ウィンドウ

    15 で、すなわち、ほしいカウントです。

  5. 「カウンタの上限値 (Count Limit)」列のドロップダウンリストをクリックして、「現在のカウント値を使用 (Use current Count value)」を選択し、現在のカウントをカウンタの上限に転送して、改行キーを押します。

ここで、printField() で停止するプログラムを実行すると、最後が不要なエラーメッセージの前に呼び出されます。

ステップ 6: 原因の範囲を限定する

バルーン評価を使用して、ふたたび err_silent を検査します。現在は -1 です。printField() に到達する前に、1 つの err_silent-- が必要以上に、あるいは 1 つの err_silent++ が必要以下に実行されたという可能性は最も高いといえます。

err_silent のこのミスマッチのペアをどのように検索できますか。この例のような小さなプログラムでは、注意深いコード検査によって行うことができます。しかし、大きなプログラムでは、法外な数のペアがある可能性があります。

err_silent++;
err_silent--;

ミスマッチのペアをより速く検索する方法として、ウォッチポイントの利用があります。


ヒント - err_silent++; および err_silent--; のミスマッチのセットではなく、err_silent のコンテンツを上書きする不正ポインタである場合もあります。ウォッチポイントはそのような問題をとらえるのにより効果的でしょう。


ステップ 7: ウォッチポイントの使用法

err_silent でウォッチポイントを作成するには、次の手順に従います。

  1. err_silent 変数を選択し、「新規ブレークポイント (New Breakpoint)」を右クリックして、選択します。

  2. アクセスするブレークポイントの種類を設定します。「設定 (Settings)」セクションがどのように変更され、「アドレス (Address)」フィールドがどのように & err_silent であるか注意してください。

  3. 「いつ (When)」フィールドの「後 (After)」を選択します。

  4. 「演算 (Operation)」フィールドの「書き込み (Write)」を選択します。

  5. 「OK」をクリックします。

    「新規ブレークポイント (New Breakpoint)」ウィンドウ
  6. ここでプログラムを実行します。init() で停止します。ここでは問題ないのようです。つまり、err_silent は 1 に増分され、その後実行が停止しました。

  7. 「継続 (Continue)」をクリックします。ふたたび init() で停止します。

  8. ふたたび「継続 (Continue)」をクリックします。ふたたび init() で停止します。

  9. ふたたび「継続 (Continue)」をクリックします。ふたたび init() で停止します。

  10. ふたたび「継続 (Continue)」をクリックします。ここで、stopIn() で停止します。ここでも問題ないのようで、つまり -1s ではありません。

err_silent が -1 に設定されるまでしばらく時間がかかることがありますが、視界が霞んで実際に -1 に変わった瞬間を見逃すまいと「継続 (Continue)」ボタンを何十回もクリックしたくもありません。しかしさらによい方法があります。

ステップ 8:ブレークポイントの条件

ウォッチポイントに条件を追加するには、次の手順に従います。

  1. 「ブレークポイント (Breakpoint)」ウィンドウで、「後 (After)」の「書き込み (write)」ブレークポイントを右クリックして、「カスタマイズ (Customize)」を選択します。

  2. 「後 (After)」が「とき (When)」フィールドで選択されていることを確認します。


    ヒント - err_silent の値が何に変更されたか知りたいため、「後 (After)」を選択することは重要です。


  3. 「条件 (Condition)」フィールドを err_silent == -1 に設定します。

  4. 「OK」をクリックします。

    「ブレークポイントをカスタマイズ (Customize Breakpoint)」ダイアログボックス

これでプログラムを再実行します。checkThings() で停止します。これは err_silent が -1 に設定されている最初です。マッチングしている err_silent++ を探すにつれて、バグのように見えるものを確認します。err_silent は関数の else 部分でのみ増分されます。

checkThings で停止したプログラムを表示する「エディタ (Editor)」ウィンドウ

これはあなたが探しているバグでしょうか。

ステップ 9:スタックをポップすることによる診断の確認

関数の else ブロックから実際に進んでいることをダブルチェックしましょう。

これを行う 1 つ方法は、checkThings() でブレークポイントを設定し、プログラムを実行することです。しかし、checkThings() は何度も呼び出される可能性があります。checkThings() の正しい呼び出しに達するにはブレークポイントカウントまたは境界ブレークポイントを使用できますが、最近実行されたものを再実行するすばやい方法があります。

checkThings() への呼び出しに気付きます。実際、プロセスの状態は、checkThings() への呼び出しを含む行の最初に戻されています。

ここで、「ステップイン (Step Into)」 「ステップイン (Step Into)」ボタン をクリックし、ふたたび checkThings() が呼び出されるたびに監視します。 checkThings()() をステップスルーするにつれて、実際にプロセスが if ブロックを実行することを確認できます。err_silent は増分されず、-1 に減らされます。

checkThings の if セクションで停止したプログラムを表示する「エディタ (Editor)」ウィンドウ

プログラミングエラーを検出しているように見えます。しかし、それをトリプルチェックしましょう。

ステップ 10:診断をさらに確認するための修正の使用法

コードを適切に修正し、バグが実際に解決されたことを確認します。

  1. err_silent++ が if 文の上に来るようにコードを修正します。結果は次のようになります。

    err_silent++ が移動したことを表示している「エディタ (Editor)」ウィンドウ
  2. 「デバッグ (Debug)」 > 「コード変更を適用 (Apply Code Changes)」を選択します。

  3. printField ブレークポイントおよびウォッチポイントを無効にしますが、error() のブレークポイントは有効なままにします。

    「ブレークポイント (Breakpoints)」ウィンドウ
  4. プログラムを再実行します。

error() でブレークポイントをヒットすることなくプログラムが完了し、出力が想定どおりであることに注意しましょう。

「プロセス入出力 (Process I/O)」ウィンドウ

ディスカッション

上記は、以前として「ブレークポイントとステップ動作の使用法」の終わりで説明したのと同じパターンを示しており、つまり、調子が悪くなる前の時点の誤動作プログラムを停止し、実際にコードが動作する方法でコードの意図を比較してコードをステップスルーします。主な相違は、調子が悪くなる前にポイントを検出することが少し関連しています。

ブレークポイントスクリプトを使用してコードにパッチを適用する

「ブレークポイントとステップ動作の使用法」 で、空の行が NULL の最初のトークンを生成し、SEGV の原因となるバグを検出しました。ここにすばやく解決する方法があります。

  1. 前の節で作成したブレークポイントのすべてを削除します。

  2. 「実行可能ファイルをデバッグします (Debug Executable)」ダイアログボックスの <in 引数を削除します。

  3. interp.cc の 130 行目の行ブレークポイントを切り替えます。

    130 行目にブレークポイントが表示された「エディタ (Editor)」ウィンドウ
  4. 「ブレークポイント (Breakpoints)」ウィンドウで、ちょうど作成したブレークポイントを右クリックし (より新しいブレークポイントは一番下に追加される)、「カスタマイズ (Customize)」を選択します。

  5. 「ブレークポイントをカスタマイズ (Customize Breakpoint)」ダイアログボックスの、「条件 (Condition)」フィールドで token == 0 と入力します。

  6. 「アクション (Action)」ドロップダウンリストから「スクリプトの実行 (Run Script)」を選択します。

  7. 「スクリプト (Script)」フィールドで、assign token = line と入力します。


    ヒント - assign token = "dummy" はどうですか。dbx はデバッグプロセスの dummy 文字列を割り当てることはできません。その一方で line"" に等しいことが知られています。


    ダイアログボックスは次のように表示されるはずです。

    「カスタムブレークポイント (Custom breakpoint)」ダイアログボックス
  8. 「OK」をクリックします。

ここで、プログラムを実行し、クラッシュではなく、空の行を入力すると、次のように動作します。

「プロセス入出力 (Process I/O)」ウィンドウ

dbxtooldbx に送られたコマンドで表示される場合はどのように明白に機能するでしょうか。

when at "interp.cc":130 -if token == 0 { assign token = line; }