Linux
gdb

gdbのリモートデバッグのextended-remoteモードを試す(2)

前回の続き。

従来のremoteモードではできなくて、extended-remoteモードで可能になったこと

それは、gdb側からの操作で新たなデバッグ対象プロセスを起動することです。つまり run コマンドの実行。
また、デバッグ対象プロセスをまだ起動していない状態で、gdbとgdbserverの間でファイルを転送することができます。
この2つを合わせると、「修正された実行ファイルをターゲットに送信して、それを起動してデバッグを開始する」というのがgdb内で完結できます。

リモートデバッグのサイクル

以下の1-5を繰り返す。

  1. ソースコードを修正する
  2. コンパイル、リンクして実行ファイルを作成する
  3. 実行ファイルをターゲットに転送する
  4. 実行ファイルを起動する
  5. ブレークポイントをかけたり、変数を見たりしてデバッグ

従来のremoteモードでは3はscpで転送したりNFSにしたり別の方法での実現が必要でした。
4はターゲット側でgdbserverを起動する操作が必要。
ホスト側のgdbで操作できるのは5だけでした。

extended-remoteモードでは、3,4,5をホスト側のgdbの操作で実現できるようになります。
リモートデバッグの環境構築がずっと楽になりますね。

実際にやってみる

ARM Linux側でgdbserverをmultiモードで起動する

ARM# gdbserver --multi :1234
Listening on port 1234

最初に一回行うだけですむはず。(gdbがハングアップしない限り)
デバッグ対象プロセスの標準出力、標準エラー出力がここに出るので、バックグランドで実行させるのでなくて、telnetやsshのttyをひとつ割り当てるのがよいでしょう。

gdbから実行ファイルを転送して実行、デバッグ開始する

gdb起動

gdbは8.0.1を使用しています。ビルド方法
.gdbinit は前回の記事と同じものを使用しています。

$ arm-buildroot-linux-gnueabi-gdb 
GNU gdb (GDB) 8.0.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=arm-buildroot-linux-gnueabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) 

シンボル情報を読んで、実行ファイルをターゲットに転送、起動。

(gdb) file loop
Reading symbols from loop...done.
(gdb) set remote exec-file /root/loop
(gdb) remote put loop /root/loop
Successfully sent file "loop".
(gdb) run
Starting program: /home/koba/work/at1/gdbtest/loop 

ARM Linux側のgdbserverのttyには以下のようにデバッグ対象プロセスの標準出力が出ています。

Process /root/loop created; pid = 402
cnt=0
cnt=1
cnt=2

コントロールCで停止させ、バックトレースを確認してみます。

^C
Program received signal SIGINT, Interrupt.
0xb6f2ebac in nanosleep ()
   from ../buildroot/buildroot-2016.05/output/host/usr/arm-buildroot-linux-gnueabi/sysroot/lib/libc.so.6
(gdb) bt
#0  0xb6f2ebac in nanosleep ()
   from ../buildroot/buildroot-2016.05/output/host/usr/arm-buildroot-linux-gnueabi/sysroot/lib/libc.so.6
#1  0xb6f2eaa4 in sleep ()
   from ../buildroot/buildroot-2016.05/output/host/usr/arm-buildroot-linux-gnueabi/sysroot/lib/libc.so.6
#2  0x0001042c in func2 () at loop.c:16
#3  0x0001045c in func1 () at loop.c:7
#4  0x00010468 in main () at loop.c:23
(gdb)

修正された実行ファイルを転送し直して、再度実行、デバッグ開始する

ソースファイル loop.c を以下のように修正してビルドし、実行ファイルを更新します。

--- loop.c.org  2018-01-11 14:26:09.350034678 +0900
+++ loop.c  2018-01-11 14:17:20.618129370 +0900
@@ -14,7 +14,7 @@
 func2()
 {
    sleep(2);
-   printf("cnt=%d\n", cnt);
+   printf("v2: cnt=%d\n", cnt);
    cnt++;
 }

更新された実行ファイルを動かすために、デバッグ対象プロセスを停止させてから、シンボル情報を読み直し、実行ファイルをターゲット側に転送してから、再度実行開始します。

(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) file loop
Load new symbol table from "loop"? (y or n) y
Reading symbols from loop...done.
(gdb) remote put loop /root/loop
Successfully sent file "loop".
(gdb) run
Starting program: /home/koba/work/at1/gdbtest/loop 

ARM Linux側のgdbserverでは以下のように出ました。更新内容が反映されています。

Process /root/loop created; pid = 403
v2: cnt=0
v2: cnt=1
v2: cnt=2

ここまでの動作を全てホスト側のgdbから操作することができました。

トラブルシューティング

ファイルの転送がUnknown remote I/O error 9999になる

(gdb) remote put loop /root/loop
Unknown remote I/O error 9999
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) remote put loop /root/loop
Successfully sent file "loop".
(gdb) 

デバッグ対象のプロセスがまだ生きているときに、それを上書きするようなファイル転送をするとこのエラーになるようです。
killコマンドでデバッグ対象のプロセスを終了させてから、再度転送するとうまくいきます。

まとめ

想定した動作は実際にできそうです。
「修正された実行ファイルをターゲットに送信して、それを起動してデバッグを開始する」というのがgdb内で完結できることが確認できました。

ビルドして実行ファイルを更新した後は必ず、kill, remote put, file コマンドを実行してからrun コマンドを実行する必要があり、なにか手順をとばすとgdbが不安定な状態になってしまうことがありました。
その場合はgdbを終了させ、ARM Linux側のgdbserverもkillしてからやり直します。
操作ミスをなくすためには、マクロコマンドを定義するなど工夫が必要そうです。
User-defined Commands

続きを書きました。
gdbのリモートデバッグのextended-remoteモードを試す(3)