WinDbgのメモ

はじめに
デバッグコンパイル
WinDbgを起動する
ブレークポイントの作り方
コールスタック
コマンドライン引数
マルチスレッドアプリのデバッグ
Watch
DLLのデバッグ
コマンドを使う

はじめに

VisualC++(VC)等の環境に強力なGUIデバッガがくっついているので、話題のないWinDbg。
あってもドライバのデバッグに関するページが大半のWinDbg。
ドライバ開発とか、リモートデバッグとかそんな高度なことはしない&出来ない&分からないけど、
普通にプログラムをデバッグしたい。

そこで、そんなデバッガWinDbgを使って、普通のCソースモードデバッグする方法をメモっておくことにしました。
WinDbgの簡単な使い方が分かればそれで良しとする。デバッガよりも、自分の作ったプログラムの方が大事だしね。
私は英語ぜんぜん出来ないので、ヘルプがろくに読めません。でもがんばる。
デバドラとかそういった用途は Windows XP デバイスドライバプログラミング[入門と実践](技術評論社) を読んでください。

実行環境
ソフトバージョン
OSWinXP HomeEdition SP2
VisualC++ToolKit 1.01.00(cl,linkともに 13.10.3077)
WinDbg6.6.0003.5

デバッグコンパイル

デバッガで動くプログラムを作るために、デバッグコンパイルというのをしなければなりません。
デバッグコンパイルをすると、デバッグ中に変数の値を見ることや、ソースを1行ずつ追うことが出来るようになります。
私の使っているVisualC++ ToolKit(以下VCTK)では、デバッグコンパイルのオプションがたくさんあります。

/Zdobj埋め込み、グローバル シンボル、外部シンボル、行番号情報だけが含まれる
/Z7obj埋め込み、詳細なデバッグ情報が含まれる
/Zipdb生成、型情報、変数や関数の名前と型、および行番号が含まれる
/ZIpdb生成、上に加えエディット コンティニュ機能をサポートする

WinDbgではエディット コンティニュ機能は利用できないようなので、通常は/Ziで問題ないと思います。
ランタイムライブラリは、/MTdとか/MDdのようにdを付けるとデバッグバージョンが使われます。

とりあえず、何の意味も無いコードです。

#include<stdio.h>
main()
{
    int a , b, c;
    a = 10;
    b = 20;
    c = 30;
    a = b + c;
    
    printf("test C / a=%d\n" , a);
    
}
cl *.c /Od /Zi

でOKのはずです。pdbファイルが出来ると思います。/Odは必要ないと思いますが一応付けてみました。
ライブラリ関数の中に入ってもしょうがないので、デバッグ版ランタイムライブラリはリンクしていません。(リンクするならcl *.c /Zi /MLd)
これで、準備が出来ました。あとはデバッガを操作するだけです。

WinDbgを起動する

とりあえず、WinDbgを起動してみました。 んで、File→OpenExecutable...で先ほど作ったexeを開いてみました。
コンソールウインドウが出てくるだけなので、 Open→SourceFileでソースを開き、 ツールバー右上から、CommandとLocalsとRegisters、あわせて4画面を並べてみました。

WinDbgスクリーンショット
ヘルプの「Debugging in Source Mode」を見ると、 ブレークポイントを作る必要があるらしい。で、ヘルプと同じようにコマンドを打ってみました。 commandウインドウの下の0:000>のボックスにbp mainと打って、さらにgと打ってみました。

WinDbgスクリーンショット

なんと、ソースに紫のマーカーが!ついでにlocalsのところに変数名が表示されました。 でも初期化していないため値が凄いことになっています。
あとは、ステップインとかステップオーバーとかをするだけです。1行ずつ進みます。

go [Go]をクリックすれば、次のブレークポイントまで、ブレークポイントが無ければプログラムの終わりまで進みます。

step inStep In1行ずつ進む。関数があったらその中に入る。
step overStep Over1行ずつ進むが、関数の中には入らない。
step outStep Out今いる関数を抜ける。

ブレークポイントの作り方

ブレークポイントはデバッグ中にプログラムが停止する場所です。
これが無いと、デバッグを実行してもプログラムが終了するまで停止しません。
デバッグ中にブレークポイントがあると、 デバッグが停止し、プログラムの位置や、変数の名前、値などを見ることが出来ます。
作成方法は以下のようになります。

プログラムに埋め込む

//Win32API
#include<windows.h>
VOID DebugBreak(VOID);

//コンパイラ組み込み
void __debugbreak(void);

;アセンブラ
int 3

だそうです。MSDN参照。

デバッグ実行時に設置する

ツールバーの手のボタンbreakpointで、ブレークポイントを設置します。
1,ソースのブレークポイントを入れたいところをクリックします。(|カーソルがあればよい)
2,手のボタンbreakpointをクリックします。
3,ソースの選択した行が、赤くなります。(オプション設定で変わるかも)
4,ブレークポイントを解除するときは、解除したい行に|カーソルを持って行き、手のボタンbreakpointをクリック。
ブレークポイントをGUIで設定

ツールバー左から5つめにあるgo([Go]ボタン)を押すと、確かにブレークポイントで停止します。(紫色になりました。)
もちろんコマンドウインドウでbpコマンドを使ってブレークポイントを設置できます。

コールスタック

コールスタック(Call Stack)を使うと、呼び出した関数の階層がわかるようです。

/*
    debug その2 Call Stack
*/

int sub2(int oz)
{
    return ++oz;
}

int sub(int pg)
{
    return sub2(pg);
}

main()
{
    int n;
    
    n = sub(sub(5));
    
    printf("n=%d\n" , n);
}

printfは戻り値がintなので、Cであれば、#include<stdio.h>は無くても動きます。
WinDbgしてみました。今回は、Callsウインドウを出してみました。今回はすべてステップインstep inです。
Callsウインドウ
ちゃんと関数の階層が表示されています。main→sub→sub2

コマンドライン引数

コンソールアプリや、関連付け起動などコマンドライン引数を扱うプログラムをデバッグする場合、
WinDbgでは以下のようにすると引数を与えることが出来ます。
WinDbgを起動して、メニューのFile→Open Executableと進み、以下のようなダイアログを出します。
引数に「test テス㌧」と入れる
で赤で囲んだ部分にコマンドライン引数を入れます。[test テス㌧]と入れてみました。
実際に実行すると、以下のようになりました。(ブレークポイントはmain関数の閉じカッコに設置)
WinDbg実行結果
引数が渡されているのを確認できました。

マルチスレッドアプリのデバッグ

何の意味もないプログラムです。
今回はブレークポイントをプログラムに埋め込んでみました。

#include<process.h>
#include<stdlib.h>
#include<stdio.h>

int flag = 1;

void thre(void*arg)
{
    __debugbreak();
    printf("スレッドだよ♪\n");
    flag = 0;
    _endthread();
}


main()
{
    __debugbreak();
    _beginthread(thre , 0 , NULL);
    
    while(flag){
         _sleep(100);
    }
    printf("終了です。\n");
    __debugbreak();
}

今回はコンパイル時に/MTd等のマルチスレッドランタイムを使います。
さもないと_beginthread()、_endthread()が使えません。
go([Go]ボタン)を押すと、最初のブレークポイントで止まり、次はスレッド関数内のブレークポイントで停止しました。
最後に、main関数から抜ける前のポイントで停止しました。
ただ、最適化コンパイルしたとき、同じように動作するかどうかは分かりません。

Watch

Watch(ウォッチ)は、変数の値を見る[Locals]と似て非なるものです。Localsは局所変数の読み書きしか出来ません。 しかしWatchは式の計算、レジスタの値の表示等を行うことが可能です。
ウッチウインドウ
内部外部変数どちらも表示できます。関数も入れることができます。このときアドレスが表示されます。
式を入れることも出来ます。このとき、計算結果が表示されます。
レジスタの値を表示するときは頭に@を付けます。レジスタの値は変更できません。

DLLのデバッグ

まず、DLLとEXEを作成します。DLL側に2つの値を交換する関数DLLswapを作ります。EXE側でそれを呼び出しています。

DLLのコード (dllfunc.c)

#include<windows.h>

__declspec(dllexport) void WINAPI DLLswap(int *a , int *b)
{
    int tmp;
    
    tmp = *a;
    *a = *b;
    *b = tmp;
    return ;
}

EXEのコード (dlltest.c)

#include<windows.h>

__declspec(dllexport) void WINAPI DLLswap(int *, int *);

main()
{
    int a = 1988;
    int b = 1006;
    
    DLLswap(&a , &b);
    printf("a=%d , b=%d\n" , a , b);
    return ;
}

それぞれをデバッグコンパイルします。

cl dllfunc.c /Zi /LD
cl dlltest.c dllfunc.lib /Zi

次にWinDbgを起動します。EXEとDLL2つのソースを表示します。
DLL側の出口のカッコにブレークポイントを作り実行させてみました。
DLLのデバッグ
ちゃんとDLL内部の関数でストップしました。ローカル変数も出ました。

コマンドを使う

WinDbgは当然コマンドが使えます。 コマンドを使うと、今までのボタン操作では不可能な細かい操作が可能になります。 たとえば、ブレークポイントを命令単位で設置したり、スレッドをフリーズ、サスペンドさせる、 といったことが出来ます。

ここでは簡単なコマンドのみのせておきます。 詳しくはヘルプのDebuggers→Debugger Reference→Debugger Commandsにあります。

ブレークポイントの設置はbpコマンドです。 bpコマンドを使うと、ソース1行単位でなく、バイト単位でブレークポイントを設置できます。 ただ命令以外のところには設置できません。

書き方は

[~Thread] bp[ID] [Options] [Address [Passes]] ["CommandString"]

です(ヘルプより)。でも私はbp [アドレス]くらいしか使いません。

WinDbg:bpコマンドでブレークポイント設置

他のコマンド

bl [ID]ブレークポイントを確認するにはblです。bl[ID]で指定IDのポイントを、blだけで全てのポイントの情報を見ることが出来ます。
bc [ID]ブレークポイントの削除はbcコマンドです。 bc[ID]で指定IDのポイントを、bl *で全てのポイントを削除します。
f [アドレス範囲] [パターン]アドレス範囲をパターンで埋めます。
m [アドレス範囲] [宛先]メモリをコピーします。
g [スタートアドレス] [停止アドレス]デバッグ実行します。
pステップ実行。
.cls画面をクリア。
.restartデバッグ再スタート
~ [スレッド番号]スレッドの状態を表示
~f [スレッド番号]スレッドをフリーズさせる
~u [スレッド番号]スレッドのフリーズを解除する