Win32 プログラムのデバッグTips (1)
ひとつのプログラムを完成させるまでには、多くの場合「デバッグ」という作業が必要です。 まったくバグのないプログラムを一気に書き上げるのは難しいことですから、 プログラミングの際には実行時のエラーを見つけやすくするための工夫が必要ですし、 実際にエラーが発生した場合にはできるだけ手際よく対処したいものです。
デバッグを行う上での最初の目標である「原因の特定」を効率的に行うための ツールや流儀はプラットフォームや使用言語により一様ではありませんが、 ここではネイティブな Windows プログラムをデバッグする上で役に立つ小技をいくつかとり上げてみたいと思います。
今回は、自作のプログラムの実行中にプロセスが異常終了する状況において、 問題箇所を手早く探すための方法のひとつをご紹介します。
記事では Microsoft Visual C++ を開発環境と想定しています。
■ 概要
あまり見たくないメッセージですね。
どうやらプログラムの実行中に内部で捕捉されない例外が発生したようです。
原因をつきとめるためにはトレースを行ったりプログラムの出力するログを確認したりと いろいろな手立てが考えられますが、こういった場合にはクラッシュダンプを利用する方法が 非常に有効な場合があります。
そのためには、プログラムのビルド時に VC++ が出力した *.pdb ファイルを保存して おくことが必要となります。
この両方が揃っていれば、簡単にソースコード上の問題箇所を特定することができる 可能性もあります。以下、その手順をまとめてみます。
■ *.pdb ファイルについて
pdb(Progmam Database)ファイルにはデバッグ用のシンボル情報が含まれています。
プログラムのビルドの際に VC++ コンパイラに /Zi オプション、リンカに /DEBUG オプションを
指定することで、
前者は型情報を vc*.pdb ファイルへ出力し、後者はシンボル情報を <実行ファイル名>.pdb ファイルへ
出力します。
# 上記の内容は新規プロジェクト作成時の Release ビルド構成でのデフォルトです
# ビルドずみの実行ファイルをバイナリダンプすると、開発時のプロジェクトディレクトリの
フルパスつきで「<実行ファイル名>.pdb」の名前が埋め込まれていることがわかります
*.pdb ファイルは実行ファイルのビルド時に毎回生成されます。たとえソースコードに変更がなくても、
実行ファイル本体のビルド時に同時に生成されたものでなければまったく役に立ちませんので、
実行ファイルと同様に大切に保存しておく必要があります。
■ クラッシュダンプについて
□ Windows XP/ 2000 の場合
Windows XP または Windows 2000 の場合は、以下の手順で「ワトソン博士」設定することにより
プログラム異常終了時に自動的にクラッシュダンプが出力されるようになります。
1: [ファイル名を指定して実行] より 「drwtsn32 -i」を実行
2:続けて、 [ファイル名を指定して実行] より 「drwtsn32」を実行して設定を行う
ダンプファイルは「クラッシュ ダンプ」欄のパスへ出力されます。
※ XP の場合は上図のように「クラッシュダンプの種類」を選択できますが、「完全」にすると プロセスメモリが丸ごとダンプされるためプログラムによっては出力ファイルが巨大になります。 通常は「最小」を指定しておき、発生した問題を詳細に分析したい場合にのみ「完全」を 指定するとよいでしょう
□ Windows Vista の場合
Windows Vista には drwtsn32.exe が存在しません。16 ビット版 Windows の頃から聴診器を片手にシステムディレクトリに佇んでいたワトソン博士もとうとう引退ということでしょうか。
Vista では以下の手順でクラッシュダンプを採取することができます。
1)プログラムエラーが発生
2)上記のエラーダイアログを開いたままの状態で、タスクマネージャ上の所定のプロセス名を右クリックして 「ダンプファイルの作成」を選択
3)出力終了(ログオン中のユーザフォルダの配下へ出力されます)
参照:
Windows Vista でユーザー モード プロセス ダンプを取得する方法
■ 異常終了発生
首尾よくエラーが再現したら、まずクラッシュダンプを確認してみましょう。
所定のフォルダに「user.dmp」(XP, 2000の場合)が存在していれば OK です。
ダンプファイルをダブルクリックすると VC++ の IDE が起動します。
IDE が起ち上がったら [デバッグ] メニューからデバッグを「開始」して下さい。
エラーメッセージが表示されたらその内容を確認して「中断」ボタンをクリックします。
※ エラーの一例
下図の要領で「呼び出し履歴」(コールスタック)の先頭には最終的に例外を起こした コードの位置が関数名またはアドレスで表示されます。 ここで先頭が自作のコードを示していない場合には、自作のコード名が 示されているフレームまで遡ってダブルクリックして下さい。 ソースコードが表示されれば OK です。
ここでは異常発生時のプロセスの状況が再現されており変数の内容を確認することもできます。 実例を以下に示します。(クリックで拡大)
このコードでは、MyDLL.dll を LoadLibrary して MyFunc という関数のアドレスを 取得していますが、取得に失敗して NULL が返されたにも関わらず そこへジャンプしたためにエラーが発生したことがわかります。 さらに変数 hMod も NULL であり、そもそも LoadLibrary に失敗しているようですね。 返値のチェックもしていないとは困ったものです・・・ と、いささかわざとらしいサンプルでしたが クラッシュダンプと pdb を使えばこういう要領での分析が可能となるわけです。
もちろんこういう単純なケースばかりとは限らず、たとえばある処理地点で ヒープを壊したことがまったく別の処理に影響を及ぼすといったケースも 珍しいものではありません。従って、常に問題解決の道筋を示唆してくれるとは限りませんが、 この方法を使ったことのない方は一度試してみてはどうでしょう。 配布ずみの実行ファイルがユーザ環境でエラーを起こした場合などには特に有用です。
・クラッシュダンプには pdb ファイルのフルパスが格納されており、pdb ファイルには ビルド時点のプロジェクトのパスが埋め込まれています。従って、クラッシュダンプを 解析にかける際にはビルド時点と同一のプロジェクトディレクトリ構成を再現して おくべきです。
・Windows 2000 環境で出力されるクラッシュダンプを Visual C++ 2005 以上で 扱うことはできません。Visual C++ .NET 2003 であれば大丈夫です。

