KGDBを使って、Android組み込みボードをリモートデバッグしよう!【前編】〜KGDBの仕組みを理解する〜:実践しながら学ぶ Android USBガジェットの仕組み(2)(2/3 ページ)
「AndroidのUSB機能」をテーマに、Android搭載の組み込みボードを実際に用いながら、その仕組みなどについて詳しく解説する連載。第2回となる今回は、Linuxカーネルデバッガ「KGDB」の仕組みについて詳しく解説する。
KGDBの仕組み
KGDBの仕組みを理解する上で、最初にKGDBでデバッグするための構成を説明します(図2)。
- ユーザーの作業
ユーザーはホストPC上にインストールされたGDBに対してコマンドを送り、その結果を取得することでデバッグ作業を行います。ユーザーが使う代表的なGDBのコマンドは以下の通りです(注4)。
No. | ユーザーの操作 | ユーザーがGDBに送るコマンド | コマンドの概要 |
---|---|---|---|
1 | ブレークポイント設定 | break <ブレークポイント> | <ブレークポイント>には、関数名、行番号などを指定し、ブレークポイントを設定します |
2 | ブレークポイント解除 | delete <ブレークポイント番号> | <ブレークポイント番号>には、設定したブレークポイントの番を指定し、ブレークポイントを解除します |
3 | データ参照 | info registers | レジスタの値を参照します |
print <変数名> | <変数名>の値を参照します | ||
4 | データ変更 | set $<レジスタ>=<値> | <レジスタ>に<値>を設定します |
set variable <変数名>=<値> | <変数名>に<値>を設定します | ||
5 | コンティニュー | continue | プログラムの実行を再開します。 |
表4 ユーザーがGDBに送るコマンド |
- ホストPC
ホストPCでは、GDBがユーザー操作を受け付けたとき、KGDBに対してGDBコマンド(表5)を送り、その結果を表示してユーザーに伝えます。GDBがKGDBに送るコマンドは以下の通りです。
No. | ユーザーの操作 | GDBがKGDBに送るコマンド | コマンドの概要 |
---|---|---|---|
1 | ブレークポイント設定 | Z0,<アドレス>,<アドレス長> | <アドレス>番地にソフトウェアブレークポイントを設定する |
2 | ブレークポイント解除 | z0,<アドレス>,<アドレス長> | <アドレス>番地にソフトウェアブレークポイントを削除する |
3 | データ参照 | g | GDBで定義されているレジスタ値の配列で各レジスタの値を取得する |
m<アドレス>,<アドレス長> | <アドレス>番地のメモリを参照する | ||
4 | データ変更 | G<レジスタ値> | GDBで定義されているレジスタ値の配列を渡すことで、それらの値が各レジスタに設定される |
M<アドレス>,<アドレス長>:<DATA> | <アドレス>番地のメモリに<DATA>を設定する | ||
5 | コンティニュー | c<アドレス> | <アドレス>番地からプログラムを再開する。<アドレス>を省略すると、現在の実行アドレス番地から再開する |
表5 GDBがKGDBに送るコマンド |
- ターゲットボード
ターゲットボードとホストPCとの接続はシリアルもしくはイーサーネットで行います。そして、KGDBがGDBから要求を受け付けたとき、Linuxカーネルコードに対する「デバッグ操作」を行い、その結果をGDBに伝えます。
さて、ここでKGDBの役割として、さらっと“デバッグ操作”をすると書きましたが、具体的にどのような操作をするとCPUを強制的に停止させ、また再開させるというような魔法を実現できるのか、気になりませんか?
KGDBのデバッガ機能の実現方法
デバッグ操作のうち、ブレークポイントの実現方法については、デバッガ機能の肝となるものだと思います。先ほどの説明で、GDBからの通信でKGDBにブレークポイントの設定要求を送ることは分かりました。では、その要求を受けたKGDBは、一体、どのようにブレークポイントの機能を実現するのでしょうか?
KGDBがブレークポイントの機能を実現するためには、少なくとも以下の機能要件を満たす必要があります。
- CPUの実行が、設定されたブレークポイントに到達したとき、GDBに処理が移ること
- GDBからのコンティニュー要求により、CPUの処理が継続すること
- 設定したブレークポイントを解除できること
これらの機能要件をどのように実現しているか、実験用に購入したAndroid搭載の組み込みボード(図3)を用い、KGDBのソース(注5)(表6)をデバッグしながら調査しました。
No. | ファイルパス | 説明 |
---|---|---|
1 | kernel/kgdb.c | KGDBのメイン処理 |
2 | include/linux/kgdb.h | KGDBのヘッダファイル |
3 | drivers/serial/kgdboc.c | ホストPCとシリアル通信するためのドライバ |
4 | drivers/net/kgdboe.c | ホストPCとイーサーネット通信するためのドライバ |
5 | arch/arm/kernel/kgdb.c | KGDBのCPU依存(ARM)処理 |
6 | arch/arm /include/asm/kgdb.h | KGDBのCPU依存(ARM)ヘッダファイル |
表6 KGDBソース |
機能要件1.:CPUが設定されたブレークポイントに到達したとき、GDBに処理が移ること
まず、1.の機能要件に対しては、ソフトウェア割り込みを利用することで実現しています。GDBからブレークポイントの設定要求があったときに、KGDBはブレークポイント設定アドレスに対して、特殊な命令を埋め込みます(図4)。
そして、CPUがそのアドレスの命令を実行すると、ソフトウェア割り込みが発生します(図5)。
ARMアーキテクチャのCPUでは、割り込みが発生すると、その種別に応じた割り込み処理を実行します。ここで、KGDBが埋め込む特殊な命令とは、「未定義命令(0xe7ffdeff)」と呼ばれるもので、CPUが未定義命令を実行すると、自動的に未定義命令用の割り込み処理が実行されます。KGDBは、この割り込み処理を実装しており、この割り込み処理でGDBとの通信が可能となるわけです。
ここで、既存のアドレスに特殊な命令を埋め込むと説明しましたが、元の命令が消えてしまって大丈夫でしょうか? ある意味、プログラムが破壊された状態といえますよね……。この点に気付かれた方、とても鋭いです。実は、KGDBがブレークポイントを設定するとき、元の命令はちゃんと退避しているのです。そして、特殊命令を実行したときに、KGDBの割り込み処理において退避していたコードを元に戻しているのです。
機能要件2.:GDBからコンティニュー要求により、CPUの処理が継続すること
次に、2.の機能要件に対しては、GDBがコンティニュー要求を送ったときに、KGDBがソフトウェア割り込み処理を終了させることで実現できます。割り込み処理は、その処理が終了すると、割り込みが発生する前の箇所から処理を継続させますので、単に割り込み処理を終了させるだけでよいのです(注6)(図7)。
あれ? でも、元のコードには未定義命令が入っていたので、処理を再開するとまた割り込みが発生するのでは?
と思われるかもしれませんが、これについては先述の通り、割り込み発生時に未定義命令は消え、元のコードに戻っているため、問題はありません。
うーん。でも、設定していたブレークポイントが消えてしまって困るのでは?
と思われた方、とても鋭いです! この辺の仕組みは、KGDBを実際に動作させながら、実際に見ていきましょう。
機能要件3.:設定したブレークポイントを解除できること
最後に、3.の機能要件に対しては、既に説明していますが、KGDBが退避していたコードを元に戻すことで実現できます。
これで、ブレークポイントの設定に関するKGDBの仕組みの概要説明は終わりです。ここまでの情報は、KGDBを実際に動作させてまとめたものでしたが、以降では、KGDBの仕組みを理解するために、試行錯誤しながら行った調査内容になります。
Copyright © ITmedia, Inc. All Rights Reserved.