デバッグメモ

1. デバッグのための非最適化

ここにあるパッチは、プログラムを遅くしますが、gdb上でプログラムを一行ごとに実行して変数の検査をできるようにします。Fedora Core 4上で、使えます。i386-softmmuターゲットだけのものです。

Windowsホストのパッチ。
qemu-20061108-debug-on-windows.patch

Linuxホストのパッチ。
qemu-20061108-debug-on-linux.patch

AIOが使われるようになってから、SIGALRMとSIGUSR2がLinuxホストでは使われます。しかし、gdbにとっては、デバッグの障害になります。上のパッチは、SIGUSR2を使わなくし、AIOがSIGALRMを使うようにしています。また、タイマー割り込みをしなくしています。
Windowsホストでは、シグナルの問題はありませんので、デバッグはやさしいです。

gccの-O0オプションで最適化を行わないようにします。-fomit-frame-pointerを取ることで、通常の関数呼び出しを用いて、gdb上でのデバッグを容易にできます。ただ、これらはOP_CFLAGSには使えません。op.oというオブジェクトファイルは、コンパイル時にdyngenによって解析されるので使えません。-O2オプションのみでは他のソースコードに対して最適化されすぎますので、-O2 -fno-gcse もしくは -O1 オプションが作成環境(gccのバージョンなど)によりつかえます。

-O0オプションを使うには、exec.hにあるenvとT0からT3までの変数をレジスタ変数に指定しなくする必要があります。これで、struct CPUX86Stateを調べたりできます。

target-i386/op.c内のASM_SOFTMMUは使えません。プログラムがコンパイルできないので。

以下に、Linuxホストでのデバッグ方法を示します。

(1)現在のCVSのコードにパッチをあて、makeします。

$ cvs -z3 -d:pserver:anonymous@cvs.savannah.nongnu.org:/sources/qemu co qemu

$ cd qemu
qemu$ patch -p0 <../qemu-20061108-debug-on-linux.patch
qemu$ ./configure --target-list=i386-softmmu --cc=gcc32
qemu$ make
(2) ディレクトリをi386-softmmuに変え、.gdbinitファイルを編集します。
qemu$ cd i386-softmmu
i386-softmmu$ vi .gdbinit
以下に.gdbinitファイルを示します。
----------------------
file qemu
set args -L ../pc-bios -hda ../../linux.img
b main

define hook-stop
handle SIGALRM nopass
end

define hook-run
handle SIGALRM pass
end

defilen hook-continue
handle SIGALRM pass
end

run
-----------------

AIOがSIGALRMを使いますので、gdbが無視するようにしています。

(3) gdbをスタートします。
i386-softmmu$ gdb
すると、プログラムはmainルーチンで止まります。

もし、Fedora Core 4ホストで、"Couldn't get registers: No such process"というエラーが出る場合は、gdb 6.0のバグです。そのときは、次のようにしてください。
[Fedora Core 4] LD_ASSUME_KERNEL=2.2.5
[Fedora Core 4] gdb

2. gdb上でのデバッグの仕方

上記のパッチをあてて、プログラムを作ったあと次のように起動します。

i386-softmmu$ gdb qemu.exe
GNU gdb 5.2.1
Copyright 2002 Free Software Foundaton, Inc.
etc.
(gdb)

set args で起動オプションと、b(break)でブレークポイントを設定します。

(gdb) set args -L ../pc-bios -hda ../linux.img
(gdb) b main

r(run)でプログラムを起動し、n(next)で1行ずつ実行し、p(print)で変数を検査できます。

(gdb)r
Starting prgram: C:\qemu\i386-softmmu\qemu.exe -L ../pc-bios -hda ../linux.img

Breakpoint 1, main (argc=5, argv=0x1584a08)
	at C:/qemu/vl.c:2758
2758         DisplayState *ds = &display_state;   (これが次に実行されるソースコードです。)
(gdb)p display_state           (display_stateの内容が表示されます。)
$1 = {data = 0x0, linesize =0, depth = 0, width = 0, height = 0,
   dpy_update = 0, dpy_size = 0, dpy_refresh = 0}
(gdb)p &display_state          (display_stateのメモリーアドレスが表示されます。)
$2 = (DisplayState *) 0x4b6018      (この値は環境により異なります。)
(gdb)p ds                      (dsの値が表示されます。)
$3 = (DisplayState *) 0x7800bd6     (この値は今は初期化されてません。)
(gdb)n                         (vl.cの2758行目が実行されます。)
(gdb)p ds
$4 = (DisplayState *) 0x4b6018      (この値はdisplay_stateのメモリーアドレスと同じ値になります。)
(gdb)p *ds
$5 = {data = 0x0, linesize =0, depth = 0, width = 0, height = 0,
   dpy_update = 0, dpy_size = 0, dpy_refresh = 0}
                                    (これらの値は、display_stateの内容と同じです。)

これで、display_stateのメモリーアドレスがdsに入ったことを確認することができます。

あと、main_loopという関数の先頭にブレークポイントを設定し、そこまで実行するにはc(continue)。ソースコードの表示には、l(list)。

(gdb)b main_loop
Breakpoint 2 at 0xxxxxx: file C:/qemu/vl.c, line 2459.
(gdb) c
Contnuing.

Breakpoint 2, main_loop () at C:/qemu/vl.c, line 2459
2459        CPUState *env = global_env;
(gdb) list
2454    }
2455
2456    int main_loop(void)
2457    {
2458         int ret, timeout;
2459         CPUState *env = global_env;
2460
(gdb) n
2462              if (vm_running) {
(gdb) p env
$7 = (CPUX86State *) 0xa520048         (CPUStateは、マクロによりCPUX86Stateになっています。)
(gdb) p *env
$8 = {regs = {0, 0, 1536, etc.....

vl.cの2463行目にブレークポイントを設定したりするには、b vl.c:2463を使います。関数に入るには、s(step)で入ることができます。finishで関数の終わりまで実行して抜け出せます。

(gdb) b vl.c:2463
Breakpoint 3 at 0x403f5e: file c:/qemu/vl.c, line 2463.
(gdb) c
Continuing.

Breakpoint 3, main_loop () at C:/qemu/vl.c:2463
2463                   ret = cpu_exec(env);
(gdb) s
cpu_x86_exec (env1=0xa520048) at C:/qemu/cpu-exec.c:113
113           saved_T0 = T0;
(gdb) finish
Run till exit from #0 cpu_x86_exec (env1=0xa520048)
      at C:/qemu/cpu-exec.c:113
0x00403f69 in main_loop () at C:/qemu/vl.c:2463
2463                    ret = cpu_exec(env);
Value returned is $10 = 256
(gdb)

ブレークポイントの表示は、i(info) b(break)。設定解除は、del(delete) number。有効化、無効化には、enab(enable) number、 dis(disable) number。

(gdb) i b
(gdb) del 1
(gdb) enab 1
(gdb) dis 1

プログラムを終了するには、q(quit)。

(gdb) q
The program is running. Exit anyway? (y or n) y

よく使うのはこんなとこだと思います。あとは、h(help)で調べてみてください。

(gdb) h
(gdb) h running

.gdbinitというファイルを置くことでgdbの初期化時の設定をすることができます。

file qemu.exe
set args -L ../pc-bios -hda ../linux.img
b main
r

3. デバッグプリント

NoConsoleというプログラムが、既にある実行ファイルにコンソールウィンドウをつけたりとったりできます。
http://lists.gnu.org/archive/html/qemu-devel/2005-04/msg00437.html
http://lists.gnu.org/archive/html/qemu-devel/2005-10/msg00326.html

ソースコードはこちら。
http://dliboon.freeshell.org/?view=programming.bcx

4. windbgを使ったリモートデバッグ

Windows 2000ゲストをWindows XPホストからデバッグする場合について紹介します。

4.1 windbgのインストール

Debugging Tools for Windowsをマイクロソフトのサイトからダウンロードして、ホストOSにインストールします。

4.2 windbgの設定

windbgを起動して、[File]->[Symbol File Path]メニューを選びます。これで、ゲストOSのシンボルパスを設定します。
ダイアログに、srv*c:\tmp*http://msdl.microsoft.com/download/symbolsを設定します。これで、マイクロソフトのシンボルサーバからゲストOSのシンボルをダウンロードし、c:\tmpフォルダに保存することになります。もし、c:\tmpフォルダがなければ、作成してください。

4.3 windbgのショートカットをデスクトップに作成

デスクトップにwindbgのショートカットを作ります。スタート−>すべてのプログラム−>Debugging Tools for Windows−>windbgと選んで、その上で右クリックをします。そして、送る−>デスクトップ(ショートカットを作成)を選びます。

作成したショートカット上で右クリックし、プロパティを選びます。

リンク先というところに、-k com:pipe,port=\\.\pipe\com_1,resets=0,reconnect というスタートオプションを付け加えます。com_1というのが、名前つきパイプの名前です。

"C:\Program Files\Debugging Tools for Windows\windbg.exe" -k com:pipe,port=\\.\pipe\com_1,resets=0,reconnect

4.4 ゲストOSのセッティング

ゲストOSがWindows 2000の場合は、起動して、コマンドプロンプトに行きます。boot.iniの属性を変更して、notepad.exeで開きます。
C:\> attrib -r -s -h boot.ini
C:\> notepad boot.ini

そして、エントリをコピーして/debug /debugport=com1 /baudrate=115200を加えます。

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows 2000 Professional" /fastdetect
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows 2000 Professional" /fastdetect /debug /debugport=com1 /baudrate=115200
それぞれのエントリは1行です。
エントリを追加した後、ゲストOSを終了します。

4.5 ゲストOSの起動と、ホストOSからの接続

-serial pipe:com_1オプションでゲストOSを起動します。com_1というのは、上で説明した名前つきパイプの名前です。

qemu.exe -L . -hda win2k.img -serial pipe:com_1

Qemuは、windbgの接続を待って止まります。


そのあと、windbgのショートカットをクリックします。


Windows 2000の画面に、[debugger enabled]を選択できるメニューが表示されるはずです。
[debugger enable]を選択します。

Microsoft Windows 2000 Professional
Microsoft Windows 2000 Professional [debugger enabled]


すると、windbgの画面に接続されたというメッセージが現れます。もし、このメッセージがでない場合は、ゲストOSとwindbgを閉じて、もう一度やり直してください。

Connected to Windows 2000 2195 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.
Symbol search path is: srv*c:\tmp*http://msdl.microsoft.com/download/symbols
Executable search path is:


しばらく後に、ntoskernl.exeがロードされて次のようなメッセージが現れます。

Windows 2000 Kernel Version 2195 UP Free x86 compatible
Kernel base = 0x80400000 PsLoadedModuleList = 0x8046a4c0
System Uptime: not available


[Debug]->[break]メニューを選択することで、ゲストOSの実行を一時停止することができます。
一時停止するまでに、少し時間がかかります。

4.6 メモ

4.6.1 ntoskrnl.exeのロード時にブレーク

まず、windbgをスタートします。そして、Ctrl-Alt-kを2回押します。そうすると、次のようなメッセージが出ます。

Will request initial breakpoint at next boot.
Will breakin on first symbol load at next boot.
そのあと、ゲストOSをスタートします。すると、ntoskrnl.exeのロード時にブレークすることができます。

4.6.2 コマンド

ドライバのリスト。

kd> lm t n
モジュールのリスト。
kd> x *!
モジュールのシンボルのリスト(たとえば、ntoskrnl.exe)。
kd> x nt!*
逆アセンブル。
kd> u
リスタート。
kd> g

5 gdbでのDLLのデバッグ

MinGWで、dllのデバッグをしようとすると、ブレークポイントを設定するのにちょっとしたコツが必要になります。

具体的には、QEMUでSDL.dllのデバッグをしようとしたのですが、まず、-gオプション付でSDLのコンパイルをします。

SDL-1.2.9>$ CFLAGS="-O0 -g" configure
SDL.dllをQEMUのある場所にコピーします。そして、gdbでのデバッグを開始します。

ここで、QEMUを走らせてからDLL内の関数を指定する方法と、走らせる前に指定する方法の2つがあります。

[その1]QEMUを走らせてから設定

QEMUのオプションを設定し、いったんmain関数で実行を止めて、ブレークポイントを設定します。

$ gdb qemu
(gdb) set args -L ../pc-bios -hda ../../linux.img
(gdb) b main
(gdb) run
止まったら、調べたい関数を指定します。

(gdb) b SDL_VideoInit
(gdb) continue
これで、DLL内で止まると思います。


[その2]QEMUを走らせる前に設定

QEMUのオプションを設定し、dll-symbolsでSDL.dllを指定します。
$ gdb qemu
(gdb) set args -L ../pc-bios -hda ../../linux.img
(gdb) dll-symbols SDL.dll
listで調べたい関数を表示します。
(gdb) list SDL_VideoInit
141     
142     /*
143      * Initialize the video and event subsystems -- determine native pixel format
144      */
145     int SDL_VideoInit (const char *driver_name, Uint32 flags)
146     {
147             SDL_VideoDevice *video;
148             int index;
149             int i;
150             SDL_PixelFormat vformat;
表示された行番号をブレークポイントに指定します。
(gdb) break 145

(gdb) run
これで、runをすればブレークポイントで止まるはずです。ソースコードが表示されない場合、directoryでソースコードの位置を指定する必要があるかもしれません。
(gdb) directory ~/sdl-1.2.9/src/video/
なぜQEMUを実行してからでないと、ブレークポイントを設定できないかというと、SDL.dllがメモリーにロードされないとアドレスが解決できないせいだと思います。


ホーム