ブレークポイントにより、バグの出現箇所の前でプログラムを停止したり、何が間違っているかを検出するためにコードをステップスルーしたりできます。
まだ行なっていない場合は、「出力」ウィンドウの連結を解除します。
以前はこのプログラムをコマンド行から実行しました。dbxtool でプログラムを実行して、バグを再現します。
ツールバーの「再起動」ボタン
をクリックするか、「デバッガ・コンソール」ウィンドウで run と入力します。
「デバッガ・コンソール」ウィンドウで Return キーを押します。
警告ボックスに、SEGV に関する情報が表示されます。
警告ボックスで、「破棄して一時停止」をクリックします。
「エディタ」ウィンドウで、Interp::find() 内の strcmp() の呼び出しが再度強調表示されます。
ツールバーの「呼び出し元を現在に設定」ボタン
をクリックして、以前に Interp::dispatch() に表示された不明なコードに移動します。
次のセクションでは、find() の呼び出し箇所の少し前にブレークポイントを設定し、コードをステップスルーして不具合の理由を特定できるようにします。
ブレークポイントは、行ブレークポイントや関数ブレークポイントなど、いくつかの方法で設定できます。次のリストで、ブレークポイントを作成する複数の方法について説明します。
行ブレークポイントの設定
127 行目の横の左マージン内をクリックして、行ブレークポイントを切り替えます。
関数ブレークポイントの設定
関数ブレークポイントを設定します。
「エディタ」ウィンドウで、「Interp::dispatch」を選択します。
「デバッグ」->「新規ブレークポイント」を選択するか、右クリックして「新規ブレークポイント」を選択します。
「新規ブレークポイント」ダイアログボックスが表示されます。
選択した関数の名前が「関数」フィールドに表示されています。
「OK」をクリックします。
コマンド行でのブレークポイントの設定
関数ブレークポイントを設定するもっとも簡単な方法は、dbx コマンド行を使用することです。「デバッガ・コンソール」ウィンドウに stop in コマンドを入力します。
(dbx) stop in dispatch (4) stop in Interp::dispatch(char*) (dbx)
Interp::dispatch と入力する必要がなかったことに注目してください。関数名を指定するだけで十分です。
「ブレークポイント」ウィンドウとエディタは、通常次のように表示されます。
エディタ内が乱雑になるのを避けるため、「ブレークポイント」ウィンドウを使用します。
「ブレークポイント」タブをクリックします (または前に最小化している場合は最大化します)。
行ブレークポイントおよび関数ブレークポイントの 1 つを選択して、右クリックして「削除」を選択します。
ブレークポイントの詳細は、Oracle Developer Studio 12.5: dbx コマンドによるデバッグ の 第 6 章, ブレークポイントとトレースの設定を参照してください。
エディタで切り替えて、行ブレークポイントを設定することは直感的である場合があります。ただし、多くの dbx ユーザーは、次の理由で関数ブレークポイントの方を好みます。
「デバッガ・コンソール」ウィンドウに si dispatch と入力すると、エディタでファイルを開き、ブレークポイントを配置する行までスクロールする必要がなくなります。
エディタ内のテキストを選択すれば関数ブレークポイントを作成できるようになるため、ファイルを開かなくても、関数の呼び出し側で関数にブレークポイントを設定できます。
alias si stop in alias sa stop at alias s step alias n next alias r run
.dbxrc ファイルと dbxenv 変数のカスタマイズの詳細は、Oracle Developer Studio 12.5: dbx コマンドによるデバッグ の dbxenv 変数の設定を参照してください。
関数ブレークポイントの名前は、「ブレークポイント」ウィンドウに表示されています。行ブレークポイントの名前は説明的なものではありませんが、「ブレークポイント」ウィンドウの行ブレークポイントを右クリックし、「ソースへ移動」を選択するか、そのブレークポイントをダブルクリックすると、127 行目の内容を特定できます。
関数ブレークポイントの方が、より持続します。dbxtool はブレークポイントを永続させるため、コードを編集したりソースコード制御のマージを行なったりすると、行ブレークポイントが簡単にずれてしまうことがあります。関数名の方が編集に耐えられます。
これで Interp::dispatch() にブレークポイントが 1 つできたので、「デバッガ・コンソール」ウィンドウで「再起動」
を再度クリックして Return キーを押すと、実行可能コードを含む dispatch() 関数の最初の行でプログラムが停止します。
find() に渡される argv[0] の問題が特定されたので、argv に対してウォッチポイントを使用します。
「エディタ」ウィンドウで argv のインスタンスを選択します。
右クリックして「新規ウォッチポイント」を選択します。選択したテキストを含む「新規ウォッチ」ダイアログボックスが表示されます。
「OK」をクリックします。
「ウォッチ」ウィンドウを開くには、「ウィンドウ」->「ウォッチ」(Alt+Shift+2) を選択します。
「ウォッチ」ウィンドウで、argv を展開します。
argv は初期化されておらず、argv はローカル変数であるため、前の呼び出しでスタックに残されたランダムな値を「継承」している可能性があることに注意してください。これは問題の原因でしょうか。
緑色の PC の矢印が int argc = 0; を指すまで、「ステップ・オーバー」(F8)
を 2 回クリックします。
argc は argv のインデックスになるため、argc のウォッチポイントも作成します。argc も現時点では初期化されておらず、意図しない値が含まれている可能性があることに注意してください。
argc のウォッチポイントは、argv のウォッチポイントのあとで作成したため、「ウォッチ」ウィンドウでは 2 番目に表示されます。
ウォッチポイント名をアルファベット順に並べるには、「名前」列見出しをクリックして列をソートします。次の図のソートの三角印を確認してください。
「ステップ・オーバー」(F8)
をクリックします。
argc は、初期化された値である 0 を示し、値がちょうど変更されたことを示す太字で表示されます。
アプリケーションが strtok() を呼び出します。
「ステップ・オーバー」をクリックして、関数をステップオーバーし、たとえば、バルーン式を使用して、token が NULL であることを監視します。
strtok() 関数は、たとえば文字列をいずれかの DELIMITERS で区切られたトークンに分割するのに役立ちます。詳細は、strtok(3) のマニュアルページを参照してください。
「ステップ・オーバー」を再度クリックして、トークンを argv に割り当てます。次に、ループ内で strtok() の呼び出しが行われます。
ステップオーバーすると、ループには入らずに (これ以上トークンがないため)、代わりに NULL が割り当てられます。
サンプルプログラムがどこでクラッシュしたかを特定するため、その割り当てもステップオーバーすると呼び出しのしきい値に達します。
プログラムがこの時点でクラッシュすることをダブルチェックするため、find() の呼び出しをステップオーバーします。
「シグナルがキャッチされました」警告ボックスが再度表示されます。
以前のように「破棄して一時停止 」をクリックします。
Interp::dispatch() で停止したあとの find() の最初の呼び出しが、本当に不具合のある場所です。
最初に find() を呼び出した場所にすばやく戻ることができます。
「呼び出し元を現在に設定」
をクリックします。
find() の呼び出しサイトで行ブレークポイントを切り替えます。
「ブレークポイント」ウィンドウを開いて、Interp::dispatch() 関数ブレークポイントを無効にします。
dbxtool の表示は次の図のようになります。
下矢印は、2 つブレークポイントが 141 行に設定され、それらの 1 つが無効であることを示しています。
「デバッガ・コンソール」ウィンドウで「再起動」
をクリックし、Return キーを押します。
プログラムが find() の呼び出しの前に戻ります。「再起動」ボタンは再起動を実行します。デバッグ中は、初期の起動より再起動の方が頻繁に行われます。
バグはどこにあるでしょうか。ふたたびウォッチポイントをみてみましょう。
行が空であり、トークンを持っていなかったため、strtok() の最初の呼び出しが NULL を返したため、argv[0] が NULL であることに注意してください。
必要に応じて、このチュートリアルの残りに進む前にこのバグを修正してください。
デバッガでプログラムを実行する場合は、ブレークポイントスクリプトを使用してコードにパッチを適用するの説明に従って、デバッガでコードにパッチを適用できます。
コード例の開発者は、この条件に対しておそらくテストし、Interp::dispatch() の残りを省略しているはずです。
この例は、誤動作するプログラムを不具合が発生する前の時点で停止し、コードの意図と実際のコードの動作を比較しながらコードをステップスルーする、もっとも一般的なデバッグパターンを示しています。
次のセクションでは、この例で使用したステップ動作とウォッチポイントの一部を回避するためにブレークポイントを使用する高度な技術について説明します。