検索
連載

KGDBを使って、Android組み込みボードをリモートデバッグしよう!【前編】〜KGDBの仕組みを理解する〜実践しながら学ぶ Android USBガジェットの仕組み(2)(3/3 ページ)

「AndroidのUSB機能」をテーマに、Android搭載の組み込みボードを実際に用いながら、その仕組みなどについて詳しく解説する連載。第2回となる今回は、Linuxカーネルデバッガ「KGDB」の仕組みについて詳しく解説する。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
前のページへ |       

KGDBをデバッグしてみよう!

 KGDBのプログラムの動作を知るためには、その処理の流れを調べる必要があります。そのため、KGDBをデバッグして動作確認したいところではありますが、さすがにデバッガをデバッグするデバッガはありませんので、KGDBのソースにログ(デバッグログ)を仕込んで、動作を確認することにしました。


デバッグログを仕込む
 デバッグログは、適切な箇所に埋め込まれているとバグ検出の強力な手助けとなります。今回は、KGDBの仕組みを理解することがメインとなりますので、KGDBソースの中で、ソフトウェア割り込みやブレークポイント設定箇所、そして、GDBとの通信箇所にアタリを付けて、処理の全体的な流れを推測しながら、その推測が正しいことを見るためのログを埋めることにしました。具体的には、以下を選択しました。

  • KGDBのソフトウェア割り込みの入口と出口
  • ブレークポイントの設定と解除契機
  • GDBとの通信(送信/受信)パケット情報をログ
ログを仕込むポイント KGDBソースに追加したデバッグログ 説明
KGDBのソフトウェア割り込みの入口と出口 kgdb_handle_exception pc=<PC> enter KGDBのソフトウェア割り込み処理関数(kgdb_handle_exception())の入口に仕込んだログ。<PC>は、割り込み発生時のプログラムカウンタ
kgdb_handle_exception exit KGDBのソフトウェア割り込み処理関数(kgdb_handle_exception())の出口に仕込んだログ
ブレークポイントの設定と解除契機 kgdb_activate_sw_breakpoints: addr=<アドレス> ブレークポイントに未定義命令を埋め込む処理関数(kgdb_activate_sw_breakpoints())に仕込んだログ。<アドレス>は、ブレークポイント設定アドレス
kgdb_deactivate_sw_breakpoints: addr=<アドレス> ブレークポイントに元の命令を埋め込む処理関数(kgdb_deactivate_sw_breakpoints())に仕込んだログ。<アドレス>は、ブレークポイント設定アドレス
GDBとの通信(送信/受信)パケット情報 get_packet packet=<パケット> KGDBのパケット受信処理関数(get_packet())に仕込んだログ。<パケット>は、GDBからKGDBに対して送られてきたパケット(文字列)
put_packet packet=<パケット> KGDBのパケット送信処理関数(put_packet())に仕込んだログ。<パケット>は、KGDBからGDBに対して送るパケット(文字列)
表7 デバッグログ

いざKGDBをデバッグ!
 それでは、KGDBソースへのデバッグログの仕込み、ソースをコンパイルしたカーネルイメージを組み込みボードのROMに焼き込みして、再起動すれば準備完了です。この具体的な方法については、【後編】で解説しますので、今回は、KGDBをどうやってデバッグしたのかに注力していきたいと思います。

 KGDBをデバッグするためには、当然、KGDBを動作させる必要があり、カーネルのデバッグ操作を行う必要があります。そのため、今回デバッグする対象の関数として、さまざまなアプリケーションが使用するread(2)システムコールを使い、この関数の先頭にブレークポイントを設定することにしました(注7)。

デバッグ対象関数
図8 デバッグ対象関数
※注7:ブレークポイントを設定する箇所は関数の入口である373行目です。


 なお、この関数のアセンブラ命令は以下の通りで、実際のブレークポイントの設定箇所はメモリ上のアドレスであるc00a9d9c番地です(注8)。

sys_read()のアセンブラ命令
図9 sys_read()のアセンブラ命令
※注8:アセンブラ命令は、カーネルをコンパイルしてできるvmlinuxを逆アセンブルして出力させました。


 それでは、これからGDBコマンドをたたきながら、仕込んだログの内容を確認していきましょう。

ブレークポイントを設定し、コンティニューするまで

 デバッグ操作として、まずはsys_read()関数にブレークポイントを設定し、コンティニューしてみます(図10)。この操作を実行したときのログを以下に示します。

ブレークポイントを設定しコンティニューする(デバッグログ)
図10 ブレークポイントを設定しコンティニューする(デバッグログ)

 図10の左側の列がKGDBのデバッグログで、右側の列がGDB端末のログです。このログは、ブレークポイントを設定して、コンティニュー命令を実行するまでのKGDBの仕組みを理解するためには非常に大切なログです。それでは、このログの内容を処理のシーケンスに書いて、1つずつその仕組みを確認していきましょう。

ブレークポイントを設定しコンティニューする(シーケンス図)
図11 ブレークポイントを設定しコンティニューする(シーケンス図)

 まず面白いのは、ブレークポイントを設定したとき(1)には、GDBからブレークポイントの設定要求がでない点です。実際に設定されるのは、ユーザーがコンティニュー要求をしたタイミング(2)です。これは、ユーザーのコマンド操作では複数のブレークポイントを設定したり、途中でキャンセルしたりする可能性があり、そのような操作で無駄な通信が発生するのを避けているのだと思います。

 ユーザーがコンティニュー操作をすると、GDBは要求されたブレークポイントの設定要求とコンティニュー要求を連続してKGDBに送信します(3)(4)。これを受けて、KGDBは、コンティニュー要求の契機で、ブレークポイント(c00a9d9c)に未定義命令を設定していることが分かります(5)。その後、KGDBはソフトウェア割り込み処理を終了し、CPUは元の処理を継続します(6)

 これで、ユーザーが指定したブレークポイントに未定義命令が埋め込まれましたので、いずれCPUがこのアドレス番地を踏むと、ソフトウェア割り込みが発生して、KGDBに処理が移ってくるということが読み取れました。

ブレークポイントに到達する!

 その後、CPUが処理を継続すると、read(2)システムコールの先頭(c00a9d9c番地)の未定義命令を実行し、期待通りブレークポイントでソフトウェア割り込みが発生しました(図12)。

ブレークポイントに到達する(デバッグログ)
図12 ブレークポイントに到達する(デバッグログ)
ブレークポイントに到達する(シーケンス図)
図13 ブレークポイントに到達する(シーケンス図)

 最初に確認するポイントとしては、KGDBの割り込み処理の先頭で未定義命令を消し、元の命令コードに戻していることが分かります(2)

 その後、ブレークポイントに到達した旨をGDBに通知します(3)。それを受けたGDBは、詳細情報を取得するために、レジスタ値を取得します(4)(5)。そして、ブレークポイントに到達したメッセージをユーザーに出力し、ユーザーからのコマンド入力待ちになります(6)

 なお、GDBは、KGDBが実際に未定義命令を消したかどうかは知らないので、z0コマンドでブレークポイントの解除要求を行っています(7)

 さて、ここまでで、ブレークポイントがどのように実現されているか分かりました。あれ? でもちょっと待ってください。ブレークポイントの設定情報が消えてしまっているので、次のコンティニュー命令をしたときにはブレークポイントで止まらなくなりますよ。うーん、どうするのでしょうか……。論より証拠です。次のコンティニューのログを確認してみましょう。

再度、コンティニューする!

再度コンティニューする(デバッグログ)
図14 再度コンティニューする(デバッグログ)
再度コンティニューする(シーケンス図)
図15 再度コンティニューする(シーケンス図)

 コンティニューすると(1)、あれれ? GDBは、コンティニュー要求を受けると、ブレークポイントの次のアドレス(c00a9da0)にブレークポイント設定要求をしていますね(2)。その後、コンティニュー要求を送って、処理を継続させています(3)。KGDBは、その要求通り、次のアドレスに未定義の命令を埋め込んで(4)、処理を継続します(5)

 なぜ、こんなことをしているのでしょう? 不思議ですねぇ〜。とにかく、次のログを追い掛けてみましょう。

ブレークポイント復活の秘密が明らかに

ブレークポイント復活の秘密が明らかに(デバッグログ)
図16 ブレークポイント復活の秘密が明らかに(デバッグログ)
ブレークポイント復活の秘密が明らかに(シーケンス図)
図17 ブレークポイント復活の秘密が明らかに(シーケンス図)

 CPUは、次の命令コードを実行すると、当然ながら未定義命令実行により、ソフトウェア割り込みが発生します(1)。この際、KGDBはコードを元に戻し(2)、GDBに通知します(3)。それを受けたGDBは、自分が埋め込んだブレークポイントであることは承知の上のことですので、ユーザーには何も知らせず、そのブレークポイントの解除(6)に加えて、元のブレークポイントの設定要求(7)を行い、コンティニューしているようです(8)。そして、この要求によって、元のブレークポイントのアドレスに未定義命令が復活しました(9)

 なるほど、GDBが次の命令コードにブレークポイントをわざわざ設定していたのは、元のブレークポイントを復旧するのが目的だったのですね。

 以上、まとめると、ブレークポイントでCPUを停止させるKGDBの仕組みは、以下の通りです。

  1. ブレークポイントには、命令コードに特殊な命令(ARMの場合は未定義命令)を埋め込む
  2. CPUは、その特殊な命令を実行すると、そのアドレスでCPUの実行が停止し、ソフトウェア割り込みが発生する
  3. ソフトウェア割り込みが発生すると、ブレークポイント(特殊な命令)の設定は解除される
  4. その復旧には、GDBが故意に(ユーザーには内緒で)次のアドレスの命令コードにブレークポイントを設定し、そこで発生したブレークで元のブレークポイントを設定している

 普段、当たり前のように使っているデバッガの機能もその裏側ではいろいろと頑張ってくれているのですねぇ〜。



 さて、今回は、自分が使うデバッガの仕組みを理解するために、KGDBをしっかりとデバッグしてみました。次回は、このKGDBを使うための方法を紹介します。ご期待ください!! (次回に続く)

Copyright © ITmedia, Inc. All Rights Reserved.

前のページへ |       
ページトップに戻る