デバッグ(gdb)に関する世界観です。
デバッガは、確認のために使います。確認とは、内部データの確認のことです。
Webブラウザを作る際、HTMLを解析して内部にDOMツリーを作ります。コンパイラを作る際も、構文解析器がトークンを葉とするようなパースツリーを内部に作ります。
しかしながら、プログラムにバグがあって、こうした内部データがきちんと作られていなかった場合であっても、デバッガがなければ、それを確認する手段がありません。
デバッガは、このような時に、内部データが「今どのような値になっているか」を見ることができます。
また、デバッガは、プログラムの途中までを実行したり、ステップ実行でひとつひとつインタラクティブに実行することができます。「そこまで実行して、今のデータはどういう値になっているか」を知ることができます。
デバッガは、簡単なプログラムでは使うことがないかもしれませんが、高度なアプリケーションになってくると重要なソフトウェアです。
コマンドラインで使えるデバッガとしてGNUのgdbがあります。gccに-gオプションをつけて、デバッグ情報を追加したバイナリの動作を追跡・確認できます。
コマンドラインでない場合は、IDE(統合開発環境)についているデバッガが使えます。オープンソースのEclipseや、ジェットブレインズの製品、あるいはVisual StudioやVS-Codeなどでもデバッグ機能があります。特にジェットブレインズの製品は使いやすくて、デバッガを使った開発におすすめです。またGoogle ChromeやMozilla FirefoxのようなWebブラウザには、JavaScriptのデバッグ機能があります。
GNUによるデバッガ。
基本的に、gccに-gオプションをつけてバイナリをコンパイルする。警告オプションをすべて有効にする-Wall、最適化を行わない-O0オプションを付ける。
gcc -g -Wall -O0 -o hoge hoge.c
マクロを使えるようにするために-g3としてもよい。
そして、プログラム名を指定してgdbを実行する。
gdb ./hoge
そして、各種コマンドを実行する。
たとえば、bでブレークポイントを設定、runで実行、attachで実行中のプログラムのデバッグ、p 変数名で変数の値を表示、setで変数の値を書き換える、nでステップ実行、sで関数の中までステップ実行、cで次のブレークポイントまでを実行、などができる。
コマンド | 説明 |
---|---|
b 関数名 | 関数にブレークポイントを設定する。 |
b ファイル名:行数 | ファイルの中の特定の行にブレークポイントを設定する。 |
i b | ブレークポイントを一覧表示する。 |
d no | ブレークポイントを削除する。 |
run | 実行する。 |
attach pid | 実行中のプログラムをデバッグする。 |
p 変数名 | 変数の値を表示する。おそらく最もよく使うコマンド。 構造体のメンバはs.mで要素を、ポインタは*pで値を見れる。 |
set 書き込み先=式 | 変数の値を書き換える。 |
n | ステップ実行する。関数には入らない。 |
s | 関数の中までステップ実行する。 |
c | 次のブレークポイントまでを実行する。 |
f | 関数を抜けるまでを実行する。 |
u | ループを抜けるまでを実行する。 |
where | 現在どこを実行中かを表示する。 |
bt | バックトレース(関数の呼び出し経路を表示)。 |
l | その時点の関数ブロックのソースコードを表示する。 |
何も打たずにリターン | 直前のコマンドをもう一度実行する。 |
また、終了はcでプログラムを最後まで実行した上でqとする。
2023.02.05編集
詳しくは以下を参照のこと。
また、以下の書籍が参考になります。
物凄いマニュアルが以下にあります。
オープンソースのIDEであるEclipseでもブレークポイントを設定したデバッグが可能です。Eclipseを参照のこと。
Ruby on Railsの開発にはジェットブレインズ社のRubyMine、Pythonの開発には同社のPyCharmがおすすめです。
たとえば、関数やクラス名から、その宣言へとジャンプできますし、コンソールで対話的にコードを実行したり、ブレークポイントを設定して、ある特定の場所で変数の値がどうなっているかを知ることができます。
IntelliJ IDEAを参照のこと。
HTML, CSS, JavaScriptの開発なら、Google ChromeやMozilla Firefoxの検証機能を使いましょう。
Mozilla FirefoxやCSSやJavaScriptのツールも参照のこと。
また、マイクロソフト社のオープンソーステキストエディタVS-Codeでも、デバッグ機能を使うことができます。
VS-Codeを参照のこと。
ある意味、プログラムの基本とは、「何がどこで実行中か」ということがポイントだと思います。
いつ、そのプログラムがどこで何を実行しているのか、それが分かると、たとえばループの条件判断に関数を入れ込むなど、といった荒業でプログラムを書くことができます。
バグのないプログラムを参照のこと。
コマンドラインの対話的プログラムの場合、デバッグ用のビルドとリリース用のビルドを分けることで、たとえばgccではデバッグ情報を付加してコンパイルすることで、gdbなどのデバッガから、ブレークポイント(中断地点)まで途中まで実行したり、コマンドでステップ実行を繰り返したり、実行時のそれぞれのポイントの変数などの情報を追跡したりすることができる。
しかしながら、デーモンやサーブレット、あるいはマルチスレッドを用いた非対話型のプログラムでは、こうしたデバッガとの連携や、追跡やステップ実行が困難な時がある。
こうした時は、要所要所に「printステートメント」を挿入し、printf()で変数の内容を表示する「printf()デバッグ」が効果を発揮する。
Javaの場合、AspectJによるアスペクトを使うことで、複数のメソッドの実行の前後に、適切なステートメント(たとえばprintln())を実行するようにすることもできる。
また、C++のテンプレートには、違った意味でのデバッグの難しさが存在する。コードがコンパイル時点で冗長に生成されるため、デバッグが難しい(エラーメッセージも役に立たない)だけではなく、プログラムのサイズの肥大化にもつながる。JavaやC#にはもともとテンプレートは無かったが、最近はジェネリクスが導入されている。
(Code Reading ~オープンソースから学ぶソフトウェア開発技法~ (プレミアムブックス版)を参考にしています。多くは自分で執筆しました。)
なんらかの理由でgdbや統合開発環境のデバッガによる対話的なデバッグが使えない場合(たとえばサーバーアプリケーション)、トレースログを吐き出すことでバグの原因を突き止めることができる場合があります。
トレースログとは、プログラムの動きを追跡(トレース)した結果を記録するログのことです。
注意点は、本番環境でトレースログを吐き出さないようにすること。本番環境でトレースログを取るのはご法度です。if (DEBUG) { }や#ifdef DEBUG ~ #endifのような形で、必ず本番環境ではトレースログの記録が無効化されるようにしておきましょう。#ifdef DEBUG ~ #endifとした場合は、gcc -DDEBUGとすればマクロが有効になります。
また、トレースログには大量に情報が吐き出されることが一般的です。その中から具体的なバグの所在を見つけるのは難しいかもしれません。ですが、情報は多いに越したことはありません。原則として、ログに吐き出す内容には、できるだけ詳細な内容を吐き出すようにしましょう。
C言語(プリプロセッサとインラインアセンブラ)も参照のこと。
僕が思うに、常にOSの全プロセスのメモリの内容を、画面に出力し続ける、デバッグターミナルを作ってはどうかと思う。
プログラムをコンパイルする際に、デバッグ情報に加えて、今のメモリの内容を出力することができるような情報を付加し、OSがプログラムを実行した際に、全プログラムについて、メモリの中に格納されているデータの変数値などを常に表示し、更新する。
これは、一部のIDE(統合開発環境)では、変数の値を一覧して表示する機能があるが、OSとコンパイラのレベルで、今稼働しているすべてのプログラムのデータ情報を表示し続けるのである。
また、できることなら、そのデータの変更されていくログ情報もともに表示するオプションがあってもいいだろう。
これはバグを見つけるためのデバッグに有効であるだけではなく、今稼働しているシステムが果たして正常に働いているのかということを確認する際にも有効だと思う。「システム管理者の誰もが欲しい機能」ではないかと思う次第だ。
そして、この機能を作るのは原理的に簡単である。コンパイラとOSによって、変数値が変更された時に、ひとつ処理を介入して、その値をカーネルに通知するようなコードをマクロ的に追加し、その通知された内容をユーザープロセスが取得できるようなシステムコールがあればいい。そんなに実現の難しい仕組みではない。ただしパフォーマンスは遅くなるので、リリースする際には無効にするような、デバッグ用のオプション的機能としてつけるべきである。
Pythonには、assert文という、デバッグに使える専用の特殊な構文があります。
assert文は、組み込み定数__debug__がTrueである時にだけ有効になります。assert文は、与えられた条件式を評価して、もしTrueでなかった場合に例外AssertionErrorを吐きます。
これにより、必ずTrueになるはずの条件式が、そうなっていなかった場合を早く検知することができます。
Pythonも参照のこと。
また、Rubyでデバッグ用に変数の内容を詳細に出力(ダンプ)したい場合は、pメソッドを使うことができます。
これとは別に、Rubyにはppメソッドがあり、より見やすい形でダンプしてくれます。
また、loggerを使うことで、変数の値をログに記録することができます。
Rubyも参照のこと。
また、PHPにもvar_dump()関数があり、変数の値をダンプして詳細に出力できます。
PHPも参照のこと。
GNUツールチェインも参照のこと。
GDB