デバッガで動作を追跡する
前口上
- デバッガは文字通りデバッグをするためのものだが, 実際の機能はプログラムを, その状態 (どの文を実行しているか, 変数の値がどうなっているか) を表示しながら少しずつ実行する, というものである
- したがってバグがない状態でも プログラムの動作・仕組み を知るのに最適なツールである
- 特に, まだ仕組みがわからないのプログラムが, どこでなにをしているか, 自分が実現したいことに関係した機能はどこ (どのファイルのなんという関数) で行われているかを調べることができる
- ある機能がどこで行われているかを探るのに, 勘 (ファイル名や関数名) や静的なソースコード検索 (grepなど) で探すこともひとつの方法ではあるが, 一見関係しそうで実は無関係とか, その環境では全く使われていないコードなどが見つかってしまうこともよくある
- デバッガは 実際に実行されているコード に限定して追跡ができ, 静的な検索よりも確実性高く, 関連するコードを突き止めることができる
- この演習で, デバッガを使ったコードの追跡をぜひ身につけてください
デバッガの機能
基本はプログラムを「少しずつ, 止めながら実行する」
-
そのやり方に何種類かある
-
デバッガやデバッグ環境によって微妙に呼び方が異なる場合があるが, 概念はどれもほとんど同じ
-
next
またはstep over
: 次の行まで実行する. ただしその行に関数呼び出しが含まれていたらそれらすべてを飛ばす. つまり文字通りプログラム上の次の行まで実行する (関数呼び出しの最後の文の場合はその関数の終わりまで) -
step
またはstep into
: 次の行まで実行するが, その行に関数呼び出しが含まれていたらその関数の中に入っていく (呼び出している関数の先頭まで実行する) -
finish
,return
またはstep out
: 現在実行中の関数の終了まで実行する -
continue
: 明示的に設定した「次に止めるポイント (breakpoint)」まで実行する -
breakpoint
: continueを実行したときに止めるポイントを設定する -
どれも目的は同じで, プログラムの実行を前に進める それだけ
-
どこまで進めるかが違うだけ
例え話:
next
,step over
= 各駅停車 : 各行 (駅) で止まるstep
,step into
= 途中下車の旅 : 各行 (駅) で止まるのみならず, 各駅で下車して寄り道をする (呼び出している関数の中に入る). やや例えが苦しいが勘弁continue
= 急行 : 次の停車駅まで止まらずに通過する
変数や式の値を調べる
-
環境によってやり方は異なり, 明示的にコマンドを打って表示する環境もあれば, 統合環境のように画面を分けて局所変数の値を一覧にしてくれる環境もある
-
print <式>
: 明示的にコマンドを打って表示する環境では,print
などのコマンド名で式 (特別な場合として変数) を表示できることが通常 -
統合環境 (VS Codeなど) では画面を分けて局所変数の値を自動的に一覧にしてくれる
- それでも単なる変数値だけでなく, 配列の特定の要素値や, 複雑な計算をした値を表示したい場合もあるので, それを能動的にできるようにすることが重要
-
up
,down
: 今実行が停止している関数だけでなく, それを呼び出している関数, 更にそれを呼び出している関数, ... に焦点を切り替えて, 変数や式を表示することができるup
は焦点を caller (今焦点が当たっている関数を呼び出した関数) 側にひとつずらすdown
は焦点を callee (今焦点が当たっている関数から呼び出された関数) 側にひとつずらす- どちらの場合も
up
,down
をした後はprint <式>
を通常通り実行できる (使える変数が違うだけ)
-
VS Codeのような統合環境ではスタックトレース (今実行が停止ている関数, それを呼び出した関数, それを呼び出した関数, ... ) が自動的に表示されておりその中から焦点を当てたい関数を選べば同じ効果が得られる
デバッガを使ったソースコードの追跡・調査 --- 思考過程
- デバッガを使って, ソースコードのどこで自分の興味がある機能が実現されているかを追跡・調査することができる
- 基本は,
- プログラムの先頭 (例: C/C++であれば main関数の先頭, PythonやJavaScriptであればファイルの先頭) で実行を止める
- そこから「少しずつ実行」していき, 関係がありそうなコードにたどり着く
- 問題は,「少しずつ実行」していくといっても文字通り1行ずつ実行して行くと永遠に時間がかかってしまうこと
- そこで以下では興味ある場所を発見するための「作戦」「思考過程」を, もう少しそれらしく言語化する
- 当初は next (step over) を基本にして, 関数呼び出しは飛ばしながら実行する
- つまりC/C++言語であればmain関数中の各行, PythonやJavaScriptであればファイルトップレベルの各行に停車してくイメージ
- 今止まっている行 (次に実行する行) で呼び出されている関数が, 関数名などから明らかに自分の興味に関係しそうであれば step (step into) で関数の中に入っていく
- step (step into) で入ってみて違うなと思ったら finish (step out) で脱出
- 逆に, next (step over) で飛ばしたら実は興味ある関数を飛ばしてしまった (行き過ぎた), ということもある
- そうなったことは典型的には, 外から観測できるプログラムの出力で判断できる. 例えばプログラムが "hello" と出力するのを変更したいのに, "hello" と表示されてしまったら明らかに行き過ぎたことになる
- 行き過ぎが判明したら, 基本はもう一度プログラムの先頭からの実行をやり直す
- 同じところまで next (step over) でたどり着いて今度は step (step into) するのが原始的なやり方
- 慣れてきたらそうする代わりに, 行き過ぎた行に breakpoint を設定してそこまで continue で一気に実行(急行) するのがよい
- これを通して「どの関数の中で興味ある処理が実行されているか」ということに関する知識が, 一段精度高く得られたことになる
- あとは同じことをその関数の中でも繰り返していき, 徐々にその精度を高めていく (大きな配列中から目当ての値を2分探索しているイメージと言えば伝わるか?)
ひとたびどの関数で興味ある処理が行われているかの当たりがついてきたら, その関数の中の処理の流れや, データの流れを見ていく
- 特に, 必要な情報がデータ (変数, 配列, 構造体など) としてどう表現されているかを突き止め, それがどう計算されているかの元をたどっていくことが主な目標になる
- 目当てのデータがどの変数や配列に入っていそうかを調べるには端末のデバッガであれば
print
コマンド, VS Codeであればの変数表示のペインやDebug Console で式の値を表示する機能を活用して当たりをつける - 変数に当たりがついたらそれがどこから渡ってきているかを, ソースコードを見ながら考える
- 見ている関数内で計算されている場合もあれば, 引数として渡されている場合もある
- 前者であれば, それを計算するのに使われている別の変数 (「親」の変数とでも言うべきか) が見つかるかもしれない
- 後者であれば, 呼び出している関数 (親関数) の中を観察することが重要になる
- それには, 端末内のデバッガであれば
up
コマンド, VS Code であればスタックトレースからcallerを選択することで, 親関数の状態が観測可能になる - 親関数にたどり着いたら同様に, そのデータを親関数自身が計算しているか, さらに親から渡されているかなどを追跡していく
- このようなことを繰り返して, 変更したいデータの発生から完成に至るまでの流れを突き止めていくことができる (ことが多い)
実際は実践あるのみ
- 以上がデバッガを使ってソフトウェアの動作を解明していくときの「思考過程」
- 抽象的で, 読むだけですぐに腑に落ちることは少ないと思うので, 以降のページの実例を見て, 自分でも実践して, 習得してください