WindowsでVisual Studio Community 2013とDebugger Tools for Windowsをインストールし、コンパイラ一式とデバッガを使えるようにする。 また、簡単なC言語コードを書き、コマンドラインからのコンパイルとデバッグをやってみる。
環境
Windows 8.1 Pro 64ビット版
コマンドプロンプトからsysteminfoコマンドを実行することで、システム情報が確認できる。
>systeminfo OS 名: Microsoft Windows 8.1 Pro OS バージョン: 6.3.9600 N/A ビルド 9600 OS ビルドの種類: Multiprocessor Free システムの種類: x64-based PC プロセッサ: 1 プロセッサインストール済みです。 [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz
Visual Studio Community 2013のインストール
Visual Studioでは、個人開発者を対象にCommunity版が無償で提供されているので、これを利用する。 ここでは、標準構成にてインストールする。
開発用コンソールの準備
後述するCLI版コンパイラなどを利用するためには、アプリ一覧の「Visual Studio Tools」の中にある開発用コンソール(Developer Command Prompt for VS2013)を使う必要がある。 そのまま起動した場合カレントディレクトリがコンパイラのあるフォルダとなるが、毎回作業フォルダに移動するのは面倒なので、次のようにして開発用フォルダをカレントディレクトリとするショートカットを準備する。
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts
にある「Developer Command Prompt for VS2013」のショートカットを開発用フォルダにコピーする- コピーしたショートカットの「作業フォルダー」を空欄に変更する
WinDbg/CDBのインストール
Windowsで使えるデバッガにはOllyDbg、Immunity Debugger、x64dbgなどがあるが、ここではMicrosoftが提供するWinDbgおよびそのCLI版であるCDBを利用する。 これらはWindows 8.1 SDKにおいて「Debugging Tools for Windows」として提供されており、インストール中の選択画面より単独でインストールすることができる。
環境変数の登録
コマンドプロンプトからCDBを使えるようにするために、PATH環境変数にCDBの置かれたパスを追加する。 また、必要に応じてシンボル情報を取得できるよう、_NT_SYMBOL_PATH環境変数にMicrosoft public symbol serverを設定する。
「システムの詳細設定」から「環境変数」を選択し、ユーザー環境変数として次を設定する。 PATH環境変数がすでに設定されている場合は、既存のパスの後ろにセミコロンを挟んで追記すればよい。
PATH C:\Program Files (x86)\Windows Kits\8.1\Debuggers\x86 _NT_SYMBOL_PATH srv*C:\Symbols*http://msdl.microsoft.com/download/symbols
コマンドラインからのコンパイル・デバッグをやってみる
動作確認も兼ねて、コマンドラインからclでプログラムをコンパイルし、cdbでデバッグしてみる。
C言語でメッセージボックスを表示するプログラムを書いてみる
次のようなコードを書き、開発用フォルダにhello.cとして保存する。
/* hello.c */ #include <windows.h> #pragma comment(lib, "user32") int main() { MessageBox(NULL, "Hello, world!", "Hello, caption", MB_OK); return 0; }
MessageBox関数などのWindows APIの定義は、MSDNから調べることができる。
上のドキュメントよりMessageBox関数がuser32.dllにて実装されていることがわかるが、clは標準で対応するインポートライブラリuser32.libをリンクしない。 そこで、ここではソースコード中にpragma commentを書くことにより、リンカにuser32.libをリンクするよう指定している。
開発用コンソールからC言語コードをコンパイルするには、clを使う。
ここでは、/nologo
オプションでコピーライトメッセージを非表示にし、/Zi
オプションを指定することでデバッグ用のシンボルファイル(pdb)も書き出すようにする。
>cl /Zi hello.c Microsoft (R) C/C++ Optimizing Compiler Version 18.00.31101 for x86 Copyright (C) Microsoft Corporation. All rights reserved. hello.c Microsoft (R) Incremental Linker Version 12.00.31101.0 Copyright (C) Microsoft Corporation. All rights reserved. /out:hello.exe /debug hello.obj
同一フォルダにhello.exeが作られるので実行してみる。
>hello.exe
指定した内容のメッセージボックスが表示されることが確認できる。
CDBでデバッグしてみる
デバッガには、GUIのwindbgとCLIのcdb(と新しいコンソールでcdbを開くntsd)がある。
cdbのもとでプログラムを実行するには次のようにする。 なお、初回起動時は各DLLのシンボル情報を環境変数で設定したシンボルサーバから取得するため、多少時間がかかる。
>cdb hello.exe Microsoft (R) Windows Debugger Version 6.3.9600.17298 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: hello.exe ************* Symbol Path validation summary ************** Response Time (ms) Location Deferred srv*C:\Symbols*http://msdl.micros oft.com/download/symbols Symbol search path is: srv*C:\Symbols*http://msdl.microsoft.com/download/symbols Executable search path is: ModLoad: 00f90000 00fbe000 hello.exe ModLoad: 77e60000 77fc7000 ntdll.dll ModLoad: 76700000 76840000 C:\WINDOWS\SysWOW64\KERNEL32.DLL ModLoad: 75980000 75a50000 C:\WINDOWS\SysWOW64\KERNELBASE.dll ModLoad: 76250000 7639f000 C:\WINDOWS\SysWOW64\USER32.dll ModLoad: 76850000 76957000 C:\WINDOWS\SysWOW64\GDI32.dll (4450.2b24): Break instruction exception - code 80000003 (first chance) eax=00000000 ebx=00000000 ecx=916f0000 edx=00000000 esi=7f16b000 edi=00000000 eip=77f0e67f esp=00def60c ebp=00def638 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 ntdll!LdrpDoDebuggerBreak+0x2b: 77f0e67f cc int 3 0:000>
?
コマンドでコマンドの一覧が確認できる。
0:000> ? Open debugger.chm for complete debugger documentation B[C|D|E][<bps>] - clear/disable/enable breakpoint(s) BL - list breakpoints BA <access> <size> <addr> - set processor breakpoint BP <address> - set soft breakpoint D[type][<range>] - dump memory DT [-n|y] [[mod!]name] [[-n|y]fields] [address] [-l list] [-a[]|c|i|o|r[#]|v] - dump using type information DV [<name>] - dump local variables E[type] <address> [<values>] - enter memory values G[H|N] [=<address> [<address>...]] - go K <count> - stacktrace KP <count> - stacktrace with source arguments LM[k|l|u|v] - list modules LN <expr> - list nearest symbols P [=<addr>] [<value>] - step over Q - quit R [[<reg> [= <expr>]]] - view or set registers S[<opts>] <range> <values> - search memory SX [{e|d|i|n} [-c "Cmd1"] [-c2 "Cmd2"] [-h] {Exception|Event|*}] - event filter T [=<address>] [<expr>] - trace into U [<range>] - unassemble version - show debuggee and debugger version X [<*|module>!]<*|symbol> - view symbols ? <expr> - display expression ?? <expr> - display C++ expression $< <filename> - take input from a command file (snip)
bp
コマンドでhello.exeのmain関数にブレークポイントをセットし、g
コマンドで実行を継続する。
ブレークポイントを設定する際には、モジュール名の後に!
をつけた後シンボル名を指定する必要があることに注意。
0:000> bp hello!main *** WARNING: Unable to verify checksum for hello.exe 0:000> g ModLoad: 766d0000 766f5000 C:\WINDOWS\SysWOW64\IMM32.DLL ModLoad: 76150000 76247000 C:\WINDOWS\SysWOW64\MSCTF.dll ModLoad: 75f40000 75ffe000 C:\WINDOWS\SysWOW64\msvcrt.dll Breakpoint 0 hit eax=0103d068 ebx=00000000 ecx=00000001 edx=00000000 esi=00000000 edi=00000000 eip=00f91010 esp=00defa68 ebp=00defaac iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 hello!main: 00f91010 55 push ebp
コールスタックを表示するにはk
コマンドを使う。
RetAddrはその関数からのリターンアドレスであり、一つ下の段の関数内のアドレスが入る。
また、kb
とするとそれぞれの関数の第3引数までの値も表示することができる。
0:000> k ChildEBP RetAddr 00defa64 00f91136 hello!main 00defaac 7671919f hello!__tmainCRTStartup+0xfe 00defab8 77eb0bbb KERNEL32!BaseThreadInitThunk+0xe 00defafc 77eb0b91 ntdll!__RtlUserThreadStart+0x20 00defb0c 00000000 ntdll!_RtlUserThreadStart+0x1b 0:000> kb ChildEBP RetAddr Args to Child 00defa64 00f91136 00000001 01037060 0103d068 hello!main 00defaac 7671919f 7f16b000 00defafc 77eb0bbb hello!__tmainCRTStartup+0xfe 00defab8 77eb0bbb 7f16b000 31fdcc26 00000000 KERNEL32!BaseThreadInitThunk+0xe 00defafc 77eb0b91 ffffffff 77e9c9c0 00000000 ntdll!__RtlUserThreadStart+0x20 00defb0c 00000000 00f911f9 7f16b000 00000000 ntdll!_RtlUserThreadStart+0x1b
レジスタの値を調べるにはr
コマンド、メモリ上のデータを調べるにはdc
コマンドを使う。
dc
のc
は「dword and Char」を意味し、db
(byte)とするとバイト単位での表示、da
(ascii)とするとASCII文字列として表示することができる。
0:000> r eax=0103d068 ebx=00000000 ecx=00000001 edx=00000000 esi=00000000 edi=00000000 eip=00f91010 esp=00defa68 ebp=00defaac iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 hello!main: 00f91010 55 push ebp 0:000> dc esp 00defa68 00f91136 00000001 01037060 0103d068 6.......`p..h... 00defa78 46f3564c 00000000 00000000 7f16b000 LV.F............ 00defa88 ffffe001 00000000 00000000 00defa78 ............x... 00defa98 00000000 00defaec 00f92800 46d6f120 .........(.. ..F 00defaa8 00000000 00defab8 7671919f 7f16b000 ..........qv.... 00defab8 00defafc 77eb0bbb 7f16b000 31fdcc26 .......w....&..1 00defac8 00000000 00000000 7f16b000 d4423dd8 .............=B. 00defad8 ffffe001 10804710 fffff580 00defac4 .....G.......... 0:000> dc 01037060 01037060 01037068 00000000 6c6c6568 78652e6f hp......hello.ex 01037070 abab0065 abababab feeeabab feeefeee e............... 01037080 00000000 00000000 3d790746 1900f861 ........F.y=a... 01037090 554c4c41 53524553 464f5250 3d454c49 ALLUSERSPROFILE= 010370a0 505c3a43 72676f72 61446d61 ab006174 C:\ProgramData.. 010370b0 abababab 00ababab 00000000 00000000 ................ 010370c0 3b7a0743 0000f860 01036d28 010300c0 C.z;`...(m...... 010370d0 3f790744 1800f865 010374f8 01036730 D.y?e....t..0g.. 0:000> da 01037068 01037068 "hello.exe"
上の結果から、argv = {"hello.exe", NULL}
となっていることを確認できる。
また、poi()
関数を使うとそのアドレスに入っている値を取り出す、すなわち、ポインタとみなしてdereferenceすることができる(gdbにおける{void *}
に対応)。
これを使うと、上と同じ操作をアドレスを直接指定することなく行うことができる。
0:000> dc esp 00defa68 00f91136 00000001 01037060 0103d068 6.......`p..h... 00defa78 46f3564c 00000000 00000000 7f16b000 LV.F............ 00defa88 ffffe001 00000000 00000000 00defa78 ............x... 00defa98 00000000 00defaec 00f92800 46d6f120 .........(.. ..F 00defaa8 00000000 00defab8 7671919f 7f16b000 ..........qv.... 00defab8 00defafc 77eb0bbb 7f16b000 31fdcc26 .......w....&..1 00defac8 00000000 00000000 7f16b000 d4423dd8 .............=B. 00defad8 ffffe001 10804710 fffff580 00defac4 .....G.......... 0:000> dc poi(esp+8) 01037060 01037068 00000000 6c6c6568 78652e6f hp......hello.ex 01037070 abab0065 abababab feeeabab feeefeee e............... 01037080 00000000 00000000 3d790746 1900f861 ........F.y=a... 01037090 554c4c41 53524553 464f5250 3d454c49 ALLUSERSPROFILE= 010370a0 505c3a43 72676f72 61446d61 ab006174 C:\ProgramData.. 010370b0 abababab 00ababab 00000000 00000000 ................ 010370c0 3b7a0743 0000f860 01036d28 010300c0 C.z;`...(m...... 010370d0 3f790744 1800f865 010374f8 01036730 D.y?e....t..0g.. 0:000> da poi(poi(esp+8)) 01037068 "hello.exe"
ディスアセンブル結果を表示するには、u
コマンドを使う。
引数がない場合は、eipからの結果が表示される。
0:000> u hello!main: 00f91010 55 push ebp 00f91011 8bec mov ebp,esp 00f91013 6a00 push 0 00f91015 680070fb00 push offset hello!__rtc_tzz <PERF> (hello+0x27000) ( 00fb7000) 00f9101a 681070fb00 push offset hello!__rtc_tzz <PERF> (hello+0x27010) ( 00fb7010) 00f9101f 6a00 push 0 00f91021 ff1594b1fb00 call dword ptr [hello!_imp__MessageBoxA (00fbb194)] 00f91027 33c0 xor eax,eax
p
コマンドでstep over、t
コマンドでtrace intoができる。
前者は関数の呼び出し先に入らず、後者は入る。
つまり、それぞれgdbにおけるnexti
、stepi
に対応する。
0:000> p eax=0103d068 ebx=00000000 ecx=00000001 edx=00000000 esi=00000000 edi=00000000 eip=00f91011 esp=00defa64 ebp=00defaac iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 hello!main+0x1: 00f91011 8bec mov ebp,esp 0:000> [ENTER] hello!main+0x3: 00f91013 6a00 push 0 0:000> [ENTER] hello!main+0x5: 00f91015 680070fb00 push offset hello!__rtc_tzz <PERF> (hello+0x27000) ( 00fb7000) 0:000> [ENTER] hello!main+0xa: 00f9101a 681070fb00 push offset hello!__rtc_tzz <PERF> (hello+0x27010) ( 00fb7010) 0:000> [ENTER] hello!main+0xf: 00f9101f 6a00 push 0 0:000> [ENTER] hello!main+0x11: 00f91021 ff1594b1fb00 call dword ptr [hello!_imp__MessageBoxA (00fbb194)] ds:002b:00fbb194={USER32!MessageBoxA (762c27ce)} 0:000> t eax=0103d068 ebx=00000000 ecx=00000001 edx=00000000 esi=00000000 edi=00000000 eip=762c27ce esp=00defa50 ebp=00defa64 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 USER32!MessageBoxA: 762c27ce 8bff mov edi,edi 0:000> kb ChildEBP RetAddr Args to Child 00defa4c 00f91027 00000000 00fb7010 00fb7000 USER32!MessageBoxA 00defa64 00f91136 00000001 01037060 0103d068 hello!main+0x17 00defaac 7671919f 7f16b000 00defafc 77eb0bbb hello!__tmainCRTStartup+0xfe 00defab8 77eb0bbb 7f16b000 31fdcc26 00000000 KERNEL32!BaseThreadInitThunk+0xe 00defafc 77eb0b91 ffffffff 77e9c9c0 00000000 ntdll!__RtlUserThreadStart+0x20 00defb0c 00000000 00f911f9 7f16b000 00000000 ntdll!_RtlUserThreadStart+0x1b
コマンドを何も指定せずにENTERを押した場合は、前のコマンドの繰り返しとなる。
また、dc
やu
コマンドでは、引数なしのコマンドを繰り返すとさらに先に進めた結果が表示される。
g
コマンドで実行を継続しメッセージボックスが表示されるのを確認した後、q
コマンドでCDBを終了する。
0:000> g ModLoad: 75640000 7571b000 C:\WINDOWS\SysWOW64\uxtheme.dll ModLoad: 76580000 766ce000 C:\WINDOWS\SysWOW64\combase.dll ModLoad: 76440000 764f0000 C:\WINDOWS\SysWOW64\RPCRT4.dll ModLoad: 75880000 7589d000 C:\WINDOWS\SysWOW64\SspiCli.dll (snip) eax=00000000 ebx=77f5a820 ecx=00000000 edx=00000000 esi=00000000 edi=01039a54 eip=77e9a7bc esp=00def924 ebp=00def9f0 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!NtTerminateProcess+0xc: 77e9a7bc c20800 ret 8 0:000> q quit:
関連リンク
- Walkthrough: Compiling a C Program on the Command Line
- Debugging Using CDB and NTSD (Windows Debuggers)
- [VC++] リンクするライブラリファイルをソースコード内に記述する | あみだがみねのもろもろ備忘録
- C言語系/memos/VC++/04, Win32のEXE,LIB,DLL開発入門(C言語) - Glamenv-Septzen.net
- Open Security Research: Getting Started with WinDBG - Part 3
- Common WinDbg Commands (Thematically Grouped)
- WinDbg cheat sheet « The Art of Dev
- windbg と gdb に関係した基本操作メモ。 | みむらの手記手帳