デバッガによるプログラムデバッグの環境
デバッガを用いたプログラムデバッグの実際について学ぶ
株式会社三菱総合研究所
情報技術研究センター 飯尾 淳
プログラムの作成は一筋縄ではいきません。作成したコードが一発で予定通り動作することは稀です。とくにプログラムが複雑になればなるほど、エラーが混入する確率が高まります。今回は、デバッガを使って効率よくC言語プログラムのデバッグを行う方法を学習します。
基本的なデバッグの方法
デバッグとは、プログラムに潜むバグ(不具合やエラーの原因)を探り出し、それを排除することによってプログラムが正常に動作するように修正する作業です。デバッグ作業を説明する前に、バグにはどのようなものがあるか考えてみましょう。
ここに挙げているように、バグの種類には、コンパイルエラー、ランタイムエラー、論理エラーがあります。最も単純で取り除きやすいエラーはコンパイルエラー(リンクエラーを含む)です。プログラムのビルド時に見つけることができます。コンパイルそのものが実施できず、その原因はスペルミスや文法エラーといった単純な原因がほとんどといえます。
次に複雑なものはランタイムエラーです。ランタイムエラーは実行時になり初めて明らかになるエラーです。代表的なランタイムエラーには、計算途中で0による除算が発生する場合や、スタックオーバーフロー、あるいは不適切なポインタ処理で生じることが多いセグメンテーション違反などがあります。このようなエラーが生じると、プログラムは途中で止まってしまいます。
最もややこしいエラーは論理エラーです。プログラムの設計や仕様と異なる動作をしてしまうといった種類のエラーです。コーディングの際に誤りが紛れ込むケースです。一見、きちんと処理が進むように見えるので、このようなエラーを見つけることは簡単ではありません。これはプログラマ側の問題であり、コンピュータが自動で見つけることは難しい種類のエラーです(論理エラーを自動で発見するためには、テストコードを用意して、期待した動作と異なる結果かどうかを判断する作業が必要です)。
ここで問題です。レッスン1で話題に出した初期化忘れのエラーは、上記のどのエラーに分類されるでしょうか(答え:論理エラーです。ただし、このような初期化忘れの結果、ランタイムエラーが引き起こされるケースは多々あります)。
先に説明した3種類のエラーのうち、コンパイルエラーは比較的簡単に修正することが可能です。実際に、どの行にエラーが存在するかをコンパイラがメッセージで教えてくれるからです。ただし、C言語で複雑なマクロ定義をしている場合には見つけにくいこともあるので注意が必要です。
一方、リンク時のエラーは多少、複雑です。よくあるリンクエラーの原因は、ライブラリのバージョン相違による不整合です。例えばライブラリは存在するものの使っている関数が含まれていないとか、関数の引数が変更されていて型が合わないといったケースです。これらはnmコマンドを用いてライブラリそのものをチェックしたり、関連するドキュメントを精査して使い方を間違えていないかを確認したりといった作業により修正しなければなりません。
衝動音楽無料または私はそれをダウンロードしなければならないのです。
ランタイムエラーや論理エラーに対しては、既に見てきたようにデバッグプリント等の手段による検証が基本です。これから説明するデバッガを用いてデバッグ作業を進めると作業効率を劇的に向上させることができます。またコンパイル時に-Wallや-Wuninitializedといったオプションを用いることで、動作させることなく静的に確認することも可能です。
トレース機能
デバッグの基本はプログラムのトレースです。ここではそのトレース機能を説明します。
論理エラーを追及する作業は辛い作業です。間違ったプログラムは間違った通りに実行されます。それを追いかけていくことによって、予定した処理ではない処理が行われている箇所を突き止める作業がデバッグの重要な作業です。
プログラムのソースコードに従って、1行1行、順次実行していく機能がプログラムのトレース機能です。1行ごとに処理を進めていき、その際に変数の値はどうか、メモリの状況はどうかといったアプリケーションの状態を確認していきます。それらの作業によりバグの原因を突き止めます。分岐がある場合は、分岐の条件となっている式の値を確認します。考えた通りの内容になっているかどうか、なっていない場合はなぜそのようになっているかを考えながら、プログラムのトレース作業を進めます。
ところでプログラムを頭から1行ずつ進めていく作業は効率的ではありません。もしプログラムのバグが、プログラムのほとんど終わりの部分に潜んでいたとしたら、それまで辿りつくために無駄な確認作業を行わなければならないでしょう。そのために、ブレークポイントと呼ばれる機能がデバッガには用意されています。
ブレークポイントは、プログラムの動作を止める地点を指定するための機能です。図では、プログラムの4行めにブレークポイント(赤いマーク)が指定されています。デバッガを用いてプログラムを走らせると、4行めに到達した時点でプログラムの実行が停止します。そこで変数の中身やメモリの状況を確認することができます。図において緑の矢印は現在実行中の場所を示します。図は、ブレークポイントで停止してから2行進めた状態を表しています。
なお特定の変数やメモリの内容に着目し、指定した状況が発生した場合にプログラムの実行を停止するという機能もデバッガの主要な機能です。これをウォッチポイントといいます。
それではDDD (Data Display Debugger)というデバッガを使ってデバッグ作業の演習に挑戦してみましょう。まず、次のスライドに示すデバッグ対象のソースコード(1から4までの和を求める)を、コンパイラオプション(-g)付きでコンパイルします。実際に実行するとどうなるか、試してみましょう。結果は10になりましたか?
このプログラムにあるバグを、デバッグして直しましょう。アプリケーションメニューもしくはコマンドラインからDDDを起動します。次に、DDDのファイルメニューで「Open Program」を選び、実行ファイルを選択します。ソースコードではなく実行ファイルを選ぶところがポイントです。
algarythmは何ですか
ソースコードが表示されるはずなので、ブレークポイントを設定したい行で右クリック、コンテキストメニューを表示させてみましょう。このメニューからブレークポイントを指定することができます。DDDのメニューを活用して、ブレークポイントまでの実行や、停止箇所から1行ずつの実行を試してみましょう。変数名の上で右クリックし変数の内容を確認することもできます。マウスを変数の上にしばらく置いておくと、ツールチップで値が表示されるので、それで確認することもできるでしょう。またステータスバーにも表示されるので、それで変数の内容を確認することもできます。
バグの原因は何でしょうか(回答:変数jが初期化されていないので、jに代入した時点で値がおかしくなることがあります。環境によってはたまたま変数jが0になっている場合もあるので、逆にそのようなときには潜在的なバグを見つけにくくなっているともいえるでしょう)。
デバッグ対象のソースコードを図に示します。1から4までの総和を求める単純なプログラムです。
アプリケーションのデバッグ
アプリケーションには様々な形態があります。馴染み深いものとしては、パソコンで直接操作するデスクトップアプリケーション、Webブラウザから操作しサーバ側で動作するWebアプリケーション、組み込み機器で個別に動作する組み込みアプリケーションなど、いろいろな形のアプリケーションソフトウェアが存在します。
またアプリケーションの裏側でデータベースと連携しているデータベースアプリケーションと呼ばれるカテゴリも存在します。これらのアプリケーションのデバッグは、それぞれ固有のデバッグ技術に支えられています。
デスクトップアプリケーションのデバッグは、これまでに説明したようなデバッグプリントやデバッガの利用が基本です。とくにデスクトップアプリケーションでは直接デバッガを動作させたり端末にデバッグプリントを出力されたりといったデバッグ作業がやりやすい恵まれた環境ともいえます。
一方で、デスクトップアプリケーションでもデバッグしにくい状況があり得ます。例えば画像処理アプリケーションで画像が思ったように処理されないようなケースが考えられます。図に示したように画像が崩れてしまうようなケースでは、デバッグプリントを行ったり、デバッガでメモリの内容を確認してもよく分からない場合も多いでしょう。このようなケースでは、メモリ上に存在する中間段階のデータから画像ファイルを生成し目視で確認できるようにするための、デバッグ専用コードを用意します。面倒ですが、ソフトウェア品質向上には不可欠の作業です。
データベース(DB)を使うアプリケーションで、とくにDBに対してデータのやりとりを行う部分のデバッグはどうやればよいでしょうか。ここではその作業について考えてみます。
まずDBからデータを取得する方法をおさらいしてみましょう。データの取得は、1.SQLを用意、2.SQLをDBMS(DB管理システム)に送信、3.DBMSからデータを取得、といった手順で行います。これはデータの書き込みも同様の手順で実施します。したがって、アプリケーションのデバッグでもこの手順に従って考えればよいわけです。
Perlスクリプトは何ですか
データベースアプリケーションでは、既にSQLが固定で決められているケースと、実行時にSQLを生成してそのSQLでDBとやりとりするケースが考えられます。前者のデバッグは比較的簡単です。DBMSを対話的に使用し、想定されているSQLを使って実際にやりとりを行ってみます。その結果を確認することで正しく動作しているかどうかが分かります。動的にSQLを作成するケースはその応用です。動的に作成されるSQLのバリエーションを想定し、それらを使って同じように確認してみましょう。ただし、動的にSQLを作成するケースでは、想定される全てのバリエーションをカバーできるかどうかが鍵を握ります。詳しくは後のレッスンで紹介するテスト・カバレージを参照して下さい。
さて次は組み込みアプリケーションのデバッグです。
写真は、あるDSP(Digital Signal Processor)ボードの写真です。右下の黒い四角がDSP、通常のコンピュータではCPU(Central Processing Unit)に相当する部分です。プログラムはこの内部で実行されます。このボードは、実際には電源とセンサ、コントローラが接続されて利用されます。情報の表示装置はありません。したがってデバッグプリントやデバッガの利用といったこれまで説明したテクニックは使えません。
このような組み込みデバイスで動作するアプリケーションを、どうやってデバッグすればよいでしょうか。
組み込みアプリケーションは、メモリの容量、CPUの計算速度、表示画面の有無や画面サイズといった条件(リソースと呼ばれます)に制約がある場合がほとんどです。これらは比較的小さく、先に述べたように表示画面が無い場合もあります。
このような組み込みアプリケーションの開発は、通常のコンピュータ(ホストと呼びます)でプログラム開発を行い、コンパイルした後の実行コードを実機に送り込んで実行するという手順をとることが一般的です。実際には実機へ転送する前に、ホスト上のエミュレータを利用して動作を確認するという手順が入ります。
実機での確認がなかなか困難な場合もあります。携帯端末のような組み込み機器で動作するアプリケーションの確認は比較的容易なものの、大規模プラントでのシステムを制御するアプリケーションや、人命に係わるシステムの検証は難しいといえるでしょう。
表示装置がある機器や、何らかの手段によって内部状態を確認することができる場合は、デバッグプリントで確認を行いながらデバッグを進めることが可能です。よくあるケースとしては、古くはRS-232C、最近ではUSBといったシリアル接続でホストと対象機器を接続し、それらの通信手段を用いてデバッグを行います。
ホスト側からはプログラムの転送や実行の制御を行います。組み込み機器側では、実際に実行した結果のデバッグ情報をホスト側に返します。それらを確認しつつ、ホスト側で再コンパイル、再転送、再実行、といった手順を繰り返すことで組み込みアプリケーションのデバッグ作業を行うことが可能です。
組み込み機器の環境によっては、デバッグ対象のアプリケーションから直接デバッグ情報を取得できないようなケースもあり得ます。そのようなときに、ホストPCとアプリケーションの間に入り、デバッグ情報をアプリケーションから取得してホスト側のデバッガに返送するための仕組みが用意されることもあります。そのような仕組みを「デバッグモニター」といいます。
デバッグモニターはソフトウェアによるデバッグ情報の取得でした。組み込み機器の場合はそれに限らず、ハードウェア的にデバッグ情報を取得するケースも一般的です。ICE(In Circuit Emulator)は、組み込み機器に直接介入してバスや信号の状態を参照できるようにするためのツールです。またJTAGと呼ばれる端子に接続して、PC上のデバッガに情報を吸い上げるような仕組みもよく利用されています。
さらにはオシロスコープでボード上の信号を直接確認してデバッグを行う場合もあります。このようなデバッグにはハードウェアの高度な知識が求められます。いずれにしても、組み込みアプリケーションの開発にはソフトウェアとハードウェア、両方の知識を十分に理解していることが必要です。
最後にWebアプリケーションのデバッグを説明します。Webアプリケーションの特徴は、入出力がクライアントであるWebブラウザで行われる一方で実際の処理はサーバ上で行われるという点にあります。アプリケーションの処理が手元で行われないという観点からみると組み込みアプリケーションの動作に似ているとも考えられるでしょう。
Webアプリケーションではネットワークを介してデバッグ情報を取得することができます。したがってデバッグプリントで動作を確認する基本は変わりません。またWebアプリケーションの記述によく利用されるプログラミング言語では、デバッグ用の関数も用意されていることがあります。例えばPHPでは、var_dumpという変数内容をダンプする関数が用意されています。
ただしやはりネットワークを介してやりとりしなければならないため、デスクトップアプリケーションのデバッグよりはどうしても作業効率が悪くなってしまいます。それを少しでも補おうと、IDE(Integrated Development Environment)には様々なデバッグ機能が用意されています。代表的なものに、Eclipse WTP(Web Tools Platform)やEclipseのEPICプラグイン、PDPプラグイン、そして次で説明するFirebugなどがあります。
Firebugは、JavaScriptで記述されたプログラムをデバッグするFirefoxのアドオン(拡張機能)です。FirefoxのアドオンWebサイトからダウンロードしてFirefoxに組み込むことにより利用できます。
Firebugを使えば、Webブラウザ内で動作するJavaScriptに対してブレークポイントを設定したり、変数の内容を参照したり、ときにはデバッグ作業中に内容を変更したりして、JavaScriptプログラムのデバッグをとても効率的に進めることができます。昨今のWebアプリケーションはクライアント側の機能をJavaScriptで制御することが一般的(Ajaxと呼ばれる仕組みにはJavaScriptが不可欠です)で、そのようなWebアプリケーションのデバッグには、Firebugのような、クライアント側のデバッガがたいへん効果的な役割を担います。
この回では、デバッグ作業の概要と、様々なアプリケーションに対するデバッグ方法を解説しました。プログラム作成作業のうち、多くの時間をデバッグ作業が占めています。したがってデバッグを効率的にできるかどうかが全体の作業効率を左右するといっても過言ではないでしょう。本講義を振り返って、デバッグの重要性を再確認して下さい。
0 コメント:
コメントを投稿