***本記事にはプロモーションが含まれています。***
この記事は、
Windows 7上のVMware Playerにgdbでリモート接続する方法
Windows 7上のVMware PlayerでLinuxのデバイスドライバをリモートデバッグする方法(1) - デバッグするカーネルモジュールを用意する
Windows 7上のVMware PlayerでLinuxのデバイスドライバをリモートデバッグする方法(2) - リモートデバッグする
Windows 7上のVMware PlayerでLinuxのデバイスドライバをリモートデバッグする方法(3) - 自動化する
の1つです。
1. カーネルモジュールとそのソースコードをWindowsホストに送り込む
gdbを使ってカーネルモジュールをリモートデバッグするためには、モジュールそのものであるtestmodule.koとそのソースファイルであるtestmodule.cをWindows上で参照する必要があります。testmodule.koが持つシンボル情報、関数の位置が必要になることと、ソースコードを表示するためにソースコードが必要になるためです。
今回は、共有フォルダーを使って、その2つをWindowホストに送り込みます。VMware Playerの設定でkazuというディレクトリがCygwinの/home/kazuを指し示しているとします。Ubuntu 13.04上でファイルを2つコピーします。共有フォルダは/mnt/hgfs/kazuにマウントされているとします。
ubuntu$ cp testmodule.ko /mnt/hgfs/kazu/
ubuntu$ cp testmodule.c /mnt/hgfs/kazu/
testmodule.koというモジュールは、gdbを起動するディレクトリに置きます。
また、testmodule.cというソースは、Cygwin上でLinuxのソースディレクトリと全く同じ位置に置きます。
つまり、Ubuntu 13.04上で/home/kazu/testというディレクトリでモジュールを作った場合、Windows上のCygwinで/home/kazu/testというディレクトリにtestmodule.cはなくてはいけません。
cygwin$ cp testmodule.c /home/kazu/test/
このとき、今回はやっていませんが、共有フォルダをうまく使うとWindows上でコピーする手間が省けます。
2. カーネルモジュールのロードされた位置情報を確認する
ゲストOS上で、/sys/module/モジュール名/sections/ディレクトリにある.text、.data、.bssというファイルにカーネルモジュールの各セクションの開始アドレスが保存されています。それを確認します。このときのポイントは、スーパーユーザーでファイルを見ることです。そうしないと、0x0000000000000000という値になって確認できません。
ubuntu$ sudo cat /sys/module/testmodule/sections/.{text,data,bss}
0xffffffffa030c000
0xffffffffa030e000
0xffffffffa030e360
上から順に、.text、.data、.bssセクションのアドレスになります。.textセクションには、関数の開始アドレスが入っています。C言語のソースコードで、関数の外側にあるグローバル変数は.bssセクションに値が保存されます。また、初期値が設定してあるものは.dataセクションに値が保存されます。そのため、gdbで値を表示するためには、それらのセクションの先頭アドレスを指定しないと値を表示することができません。
3. gdbでリモートデバッグを開始する
今度は、ホストOSのCygwinのターミナルで、gdbを起動します。set architectureでターゲットとなるアーキテクチャーが64bitであることを設定します。target remoteで接続します。ポート番号は、最初は8864で、2回目からは8865になります。
cygwin$ gdb
(gdb) set archi i386:x86-64
The target architecture is assumed to be i386:x86-64
(gdb) tar rem :8864
Remote debugging using :8864
0xffffffff8104d386 in ?? ()
次に、add-symbol-fileというgdbのコマンドで、カーネルモジュールのシンボル情報をロードします。このとき、-sオプションを使って先に確認した.dataと.bssセクションの開始アドレスを使います。アドレスや、セクション名を間違えやすいので注意してください。
(gdb) add-symbol-file testmodule.ko 0xffffffffa030c000 -s .data 0xffffffffa030e000 -s .bss 0xffffffffa030e360
Reading symbols ...done.と表示されたら成功です。
add symbol table from file "testmodule.ko" at
.text_addr = 0xffffffffa030c000
.data_addr = 0xffffffffa030e000
.bss_addr = 0xffffffffa030e360
(y or n) y
Reading symbols from /cygdrive/c/mingw64/msys/1.0/home/kazu/testmodule.ko...done.
(gdb)
次に、停止させたい関数の名前をブレークポイントに登録します。今回は、my_ioctlで停止させます。
(gdb) break my_ioctl
Breakpoint 1 at 0xffffffffa030c0f1: file /home/kazu/test/testmodule.c, line 51.
(gdb)
ブレークポイントが設定されました。
この状態でcontinueでゲストOSに制御を移します。
(gdb) continue
ゲストOSでの操作が可能になったと思います。そして、Ubuntuの中でtestdriverを動かしてみます。testdriverは、ioctl()を使ってカーネルモジュールを呼び出します。
Ubuntu$ ./testdriver
すると、ゲストOSの実行が停止して、ホストOSのCygwin上で次のように表示されます。
Breakpoint 1, my_ioctl (file=0xffff880027111ec0, count=32,
buf=18446612133350149392) at /home/kazu/test/testmodule.c:51
warning: Source file is more recent than executable.
51 {
(gdb)
ブレークポイントで停止します。このとき、ソースコードをコピーして使っているためにwarningがででいますが、無視してください。これで、カーネルのソースコードを表示したり変数を確認することが可能になりました。
4. カーネルの変数を確認する
まず、listコマンドでソースコードを表示してみます。
(gdb) list
46 printk("<1>\nRemovind %s \n", modname);
47 }
48
49 // ユーザーモードのプログラムで、ioctl()を使ったときに呼び出されます。
50 long my_ioctl( struct file *file, unsigned int count, unsigned long buf)
51 {
52 int i;
53
54 i = 1;
55 global_data = 2;
(gdb)
では、1行ずつ実行してみましょう。nextまたは、nで実行できます。1行実行した前後で、countの値を見てみます。
(gdb) print count
$1 = 32
(gdb) next
54 i = 1;
(gdb) print count
$2 = 100
(gdb)
ブレークポイントで止まった直後は、引数の値が設定されていないために、countは32でした。1行実行して関数の中に入って100に変わりました。この100は、testdriver.cでioctl()を呼んだとき、100バイトと指定してあったものです。また、bufの内容を見てみましょう。x/sと打ち込みます。xは、メモリーを検査するコマンドで、sで文字列として表示します。
(gdb) x/s buf
0x7fff0e9c3b10: "Hello!"
(gdb)
testdriver.cで設定したHello!の文字が見られます。もしくは、print (char *)bufとすることでも表示できます。(char *)にキャストするのは、my_ioctlでbufは、unsigned longになっているためです。
(gdb) print (char *)buf
$15 = 0x7fff0e9c3b10 "Hello!"
(gdb)
つぎに、ローカル変数iとグローバル変数global_dataの変化を調べます。まず、iとglobal_dataを表示しても値が設定されていないので値は何が入っているかわかりません。
(gdb) print i
$2 = -1
(gdb) print global_data
$3 = 0
(gdb)
1行実行すると、iに値が設定されていきます。このとき、55行目のソースコードが表示されますが、これは現在実行したコードではなく、次に実行するコードという意味です。
(gdb) next
55 global_data = 2;
(gdb) print i
$4 = 1
(gdb)
iが1に設定されました。また実行すると、global_dataに値が設定されます。
(gdb) next
56 printk("<1>\n%s is called! \n", modname);
(gdb) print global_data
$5 = 2
(gdb)
global_dataが2になりました。このように1行ずつ実行して値の変化を調べることができます。
4. リモートデバッグの終了
disconnectコマンドで、接続を切断してquitで終了します。
(gdb) disconnect
(gdb) quit
で終了できます。
5. 再接続
add-symbol-fileは、シンボルの再ロードに対応していないようです。ソースコードを修正してもう1度デバッグをするためには、gdbを終了してからもう一度接続する必要があります。
ソースコードを変更した後は、コンパイルします。その後、ロードされているモジュールをアンロードしてから新しいモジュールをロードします。セクション情報を確認します。
Ubuntu$ make
Ubuntu$ sudo rmmod testmodule
Ubuntu$ sudo insmod testmodule.ko
Ubuntu$ sudo cat /sys/module/testmodule/sections/.{text,data,bss}
Ubuntu$ cp testmodule.ko /mnt/hgfs/kazu/
Ubuntu$ cp testmodule.c /mnt/hgfs/kazu/
ソースコードを配置した後、gdbを立ち上げてもう一度同じことをします。ソースコードを変更してカーネルモジュールを再ロードすると、add-symbol-fileのセクション情報が毎回変わってきますので注意してください。
$ cp testmodule.c /home/kazu/test/
$ gdb
(gdb) set archi i386:x86-64
(gdb) tar rem :8865
(gdb) add-symbol-file testmodule.ko 0xffffffffa030c000 -s .data 0xffffffffa030e000 -s .bss 0xffffffffa030e360
このような流れになります。
このときVMware Playerのバグで、8865番になっていることに注意してください。一度8865番になると次は変わらないようです。接続できないときは、起動したゲストOS用の.logファイルを確認してください。
6. うまくリモートデバッグできないときに確認すること
まず、gdbがうまく接続できているかどうか。
.vmxファイルにgdbstubの設定がされているか。
gdbを動かしたときのポート番号があっているか.logファイルで確認する。
ソースコードがgdb上でうまく表示されないときに確認すること。
ソースコードが-g -O0オプションつきでコンパイルしているか。Ubuntu 12.04とUbuntu 12.10では、-O0オプションをつけてコンパイルすることはできませんでした。
ソースコードはコンパイルしているか。
カーネルモジュールはホストOSにちゃんとコピーできているか。
ソースコードを変更したときは、ゲストOSからホストOSにちゃんとコピーできているか。
ソースコードは、LinuxとCygwinで同じ位置に置かれているか。
add-symbol-fileでセクション情報を正確に設定できているか。ソースを変更したときは、gdbを再起動しないといけません。セクション情報も変わっているはずです。
いろいろと落とし穴があります。このようなことをチェックしてください。
次に続きます。
Windows 7上のVMware PlayerでLinuxのデバイスドライバをリモートデバッグする方法(3) - 自動化する