GDB/GDBserverによるクロスターゲットのリモートデバッグ

 Linuxベースの組み込みシステムで動くアプリケーションのデバッグは厄介な仕事だが、理論上はGDB(GNUデバッガ)を使えば、ゆとりで片付けられるはずである。だが実際には、そのためのGDBのセットアップがやや難関となる。現実に作業が発生するし、克服すべき技術的な障害も存在するからだ。とはいえ、当て推量に頼らずプログラムを一定の方法で系統的にデバッグすることのメリットは、この作業にかかる手間を補って余りある。この作業で生ずる困難を軽減するヒントをいくつか紹介しよう。

 ターゲットプラットフォームでフル装備のGDBを動かさなくても GDBserver を使う手がある。別のマシンでGDBを実行できるようにするプログラムだ。GDBserverを使う利点は、GDBの消費するターゲットリソースのほんの一部しか消費しないことにある。デバッガの低レベルの機能(ブレークポイントの設定、ターゲットプロセッサのレジスタ操作、アプリケーションメモリの読み書き)しか実装されていないからだ。GDBserverはデバッグ対象アプリケーションの制御を奪い、開発ワークステーション上のリモートGDBからの指示を待つ。

 いつものことだが、開発ワークステーションとターゲットプラットフォームでは搭載プロセッサの種類が異なる(前者がi686クラスのプロセッサなら、後者はARMやPowerPCといったところだろう)。これはワークステーションにインストールされているGDBバイナリがそのままでは使えないことを意味する。クロスターゲットデバッガが必要なのだ。つまり、GDBをソースコードから別途ビルドする必要がある。

GDBのビルド

 以下の例ではターゲットプロセッサとして7450 PowerPCを使用する。

作業に入る前にGDBを動かすPCとターゲットプラットフォームの間の通信インターフェースを準備する必要がある。シリアルリンクでもよいが、できればEthernetネットワークで接続するのが望ましい。クロスターゲット・ツールチェーンとしてGNU Cコンパイラも必要だ(加えて、Cランタイムライブラリと、binutils、すなわちバイナリユーティリティ群も)。これを開発ワークステーション上で動かしてターゲットプロセッサ用の実行可能プログラムを生成するわけだ。GDBのソースコードから次の二組のバイナリをビルドすることになる。

  • クロスターゲット ─ ホストは開発ワークステーション、ターゲットはターゲットプロセッサ
  • ネイティブ ─ ホストとターゲットはどちらもターゲットプロセッサ

 まず、GDBのソースコード圧縮アーカイブをダウンロードし、展開する:

mkdir -p ~/work/cross/gdb/downloads
cd ~/work/cross/gdb/downloads
wget http://ftp.gnu.org/gnu/gdb/gdb-6.7.1.tar.bz2
cd ..
tar xvjf downloads/gdb-6.7.1.tar.bz2

 GDBソースパッケージはGNUビルドシステムを使用しており、バイナリの生成は通常なら数個のコマンド(./configure ; make ; make install)で完了する。しかし、今回のケースは少し注意が必要だ。2種類のホストプロセッサについてのバイナリをビルドし、各バイナリを別の場所にインストールしたいからである。そのため、各バイナリを、ソースパッケージを展開したディレクトリ内ではなく、それぞれ独自のディレクトリ内でビルドすることにする。さらに、configureスクリプトのコマンドラインオプションでターゲットおよびホストのプロセッサと、インストールディレクトリプレフィックスも指定する。

 クロスターゲットのバイナリをビルドするには、--targetオプションでターゲットアーキテクチャを指定する。アーキテクチャ識別子(powerpc-7450-linux-gnu)が、すべてのクロスツールチェーン・バイナリのプレフィックスとなる(クロスコンパイラのバイナリはpowerpc-7450-linux-gnu-gcc)。

mkdir -p ~/work/cross/gdb/build/host_x86
cd ~/work/cross/gdb/build/host_x86
../../gdb-6.7.1/configure --prefix=/opt/gdb/powerpc-7450-linux-gnu/cross --target=powerpc-7450-linux-gnu
make
make install

 ターゲットネイティブ・バイナリのビルドはもっとややこしい。一つにはホストアーキテクチャ(ターゲットアーキテクチャpowerpc-7450-linux-gnuと同じ)を指定する必要があるからだ。もう一つの問題は欠けているライブラリだ。一部のライブラリがクロスツールチェーンで使えない可能性があり、ターゲットネイティブGDBのビルドに取りかかる前にビルドしておく必要がある。次の例は、ターゲットネイティブtermcapライブラリが欠けているとわかったときの対処法を示している(クロスビルドには多少の違いがあるので、不確かなときは「./configure --help」で調べること)。

cd ~/work/cross/gdb/downloads
wget ftp://ftp.gnu.org/gnu/termcap/termcap-1.3.1.tar.gz
cd ..
tar xvzf downloads/termcap-1.3.1.tar.gz
mkdir -p ~/work/cross/gdb/build/termcap
cd ~/work/cross/gdb/build/termcap
export CC=powerpc-7450-linux-gnu-gcc
export RANLIB=powerpc-7450-linux-gnu-ranlib
../../termcap-1.3.1/configure --host=powerpc-7450-linux-gnu --prefix=$HOME/work/cross/termcap
make
make install

 検討すべき問題がもう一つある。バイナリを静的にリンクする必要があるかどうかだ。静的リンクが必要になるのは、GDBとGDBserverの運用に必要な共有ライブラリの一部が欠けている場合だが、それで生成される実行可能プログラムは動的リンクの場合よりもずっと大きくなる。静的リンクを指定するには、オプション-staticLDFLAGS環境変数に追加し、その後にconfigureを実行する。追加ライブラリは、次のようにLDFLAGSCPPFLAGSの両方で指定する必要がある。

export LDFLAGS="-static -L$HOME/work/cross/termcap/lib"
export CPPFLAGS="-I$HOME/work/cross/termcap/include"
../../gdb-6.7.1/configure --prefix=/opt/gdb/powerpc-7450-linux-gnu/native --host=powerpc-7450-linux-gnu --target=powerpc-7450-linux-gnu
make
make install

 ビルドプロセスの途中でGNUリンカから警告が出るはずだ。静的にリンクするアプリケーションで一部の関数(たとえば、dlopen、gethostbyname、その他いくつか)が使われているときは、実行時にリンク用としてGNU Cランタイムライブラリ版の共有ライブラリが必要になるからだ。それらのライブラリをターゲットプラットフォームにインストールしなければならないだろう。

 GDBserverの実行可能プログラムを生成したら、ターゲットプラットフォームにコピーする。powerpc-7450-linux-gnu-stripユーティリティでデバッグ情報を取り除いておけば、ストレージを節約できる。

 このビルド手続きを別の種類のターゲットプロセッサ用に書き換えるときは、アーキテクチャ識別子を変更するだけでよい。

デバッグの実際

 デバッグをするためには、コンパイラ/リンカに-gコマンドラインオプションを指定して、デバッグ情報付きでアプリケーションをコンパイルする必要がある。その結果、生成される実行可能ファイルが大きくなりすぎてターゲットプラットフォームのストレージスペースに入り切らないことがある。その場合は、ファイルを移動する前にpowerpc-7450-linux-gnu-stripでデバッグ情報を取り除き、デバッグ情報除去後のファイルをターゲットプラットフォームに置けばよい。除去後のファイルがターゲットプラットフォームのGDBserverで実行され、除去前のファイルが開発ワークステーションのGDBに読み込まれる。

 リモートデバッグについては、特に複雑なところはない。ターゲットプラットフォームで、GDBserverを用いてアプリケーションを起動し、その際、TCP接続からの情報をリスンするホストとポートを指定する。

gdbserver HOST:PORT PROG [ARGS ...]

 続いて開発ワークステーションで、クロスターゲットGDBを起動する。

powerpc-7450-linux-gnu-gdb PROG

 デバッグ情報を取り除く前の実行可能ファイルを指定すること。GDBコンソールで次のように入力する。

target remote HOST:PORT
break main
continue

 これらのコマンドは、GDBをターゲットプラットフォームのGDBserverに接続し、プログラムの先頭にブレークポイントを設定し、最初のブレークポイントに達するまでプログラムを走らせる。

 次のようにすることで、GDBserverを既に動作しているプロセスにアタッチすることもできる。

gdbserver HOST:PORT --attach PID

 その後、プロセスは停止するので、リモートのGDBを用いてデバッグすることができる。

 GDBのコマンドはリモートアプリケーションをデバッグするときも期待したように動くが、いくつか例外がある。顕著な違いは、runコマンドを使わない点だ。デバッグセッションの開始時にプログラムが既に動いているからだ。また、プログラムを最後まで実行できるようにした場合、プログラムと共にリモートGDBserverも終了し、さらにリモートセッションが終了することも奇異に感ずるだろう。

 実用的なリモートデバッグ環境をセットアップするのは、「結局、printfが一番頼りになる」と宣う御仁には荷が重すぎるかもしれない。しかし、GDBを使えばコードやデータフローの追跡だけでなく修正も可能なので、コード自体に何も手を加えずにコードの挙動を詳しく調べることができる。バグを解決する魔法ではないが、頼りになることは間違いない。

Avi Rozenは、機械視覚を用いた製品を開発する企業の研究開発担当シニアエンジニア。

Linux.com 原文