pのメモ

技術系のお話が中心になると思います。おかしなところがありましたら、ご指摘いただければ幸いです。

NDKのデバッグ方法(breakpointの設置方法)

いままでNDKは扱ったことはなかったので
NDKで作られたモジュールのデバッグ方法について、ちょっと調べてみました。

ADT r20よりNDKのデバッグが(前に比べれば)楽になったようなので、
eclipse+r20以降(NDKはr8を使用)をベースに話をします。


前提:
・NDKのインストール、プロジェクトのビルドはできていること。ここでは触れません。

eclipseの設定:
eclipse にNDK pluginsを入れておきます。もちろんADTもr20以上のものを。
・NDK pluginsを入れたら、eclipseの[環境設定…]より[Android]-[NDK]画面を開き、
 [NDK location]パスを設定します。

プロジェクト毎の設定:
eclipseの対象プロジェクトのメニューを開き、[Android Tools]-[Add Native Support]を選択。すると、ダイアログが表示されますので、ここに出力するlibの名前を入力してください。
f:id:systemp:20130506101349p:plain
・プロジェクトのPropertiesより、[C/C++ Build]画面を開く。ここのBuilder設定の[Use default build command]のチェックを外し、[Build command:]に「ndk-build APP_OPTIM=debug NDK_DEBUG=1」と入力。(アプリリリースするときは、元にもどしておいたほうがよいでしょう)
f:id:systemp:20130506101407p:plain
・AndroidManifest.xmlタグに「android:debuggable="true"」属性を追加(これもアプリリリースするときは、外しておいたほうがよいでしょう)

で、実際に(ブレークポイントを貼りつつ)デバッグ実行する方法(2種類):

1. eclipse上で操作
予めeclipse上でC/C++ソースに対してブレークを貼っておき、
[Debug As]-[Android Native Application]でNativeのデバッグを開始。
プログラム起動後、数秒ほど経つとndk-gdbが実行され、シンボルが読み込まれ、
あとは、Javaアプリと同じようにブレークポイントに来た時点で停止、eclipse上で
デバッグができる。

ただ、このアプリ起動からシンボルロードまでのラグが曲者で、この間に走ってしまう処理を
ブレークさせたい場合は、waitを置くなり工夫してください。もしくは、次の方法で
ブレークさせます。

2. コマンドラインで操作
実際breakさせたいタイミングの前に通るJava側のソースコードにbreakを貼っておきます(Java側ですよ)。まあ、”Nativeライブラリがロードされたあと”のActivityのonCreateとかですね。
その後、[Debug As]-[Android Application]でアプリをデバッグ実行させます。(Nativeじゃないですよ)
先ほどbreakしておいたところで止まったら、ターミナルから対象プロジェクトのルートディレクトリへ移動し、以下実行。

$ ndk-gdb

Nativeデバッグモードに入りますので、あとは止めたいところにbreakpointを起きます。(以下コマンド参照)
breakを貼り終えたら、

(gdb) c

とコマンドを打って、gdbを続行状態にします。
その後、eclipse上で止められていたJava側の処理を続行させます。
するとgdbで置いておいたbreakpointに到達した時点で、breakされ、あとはコマンドラインでデバッグできます。

まあ、アプリインストールしたあとで、いきなり

(gdb) ndk-gdb --start

としてgdb起動でも、いい場合もあるかもしれません。

デバッグコマンドはgdbと同様ですが、簡単なところだけ以下に記載します。
ステップオーバー

(gdb) n

ステップイン

(gdb) s

変数参照

(gdb) p (変数名)

ブレークポイントの列挙

(gdb) info b

プレークポイントの設置

(gdb) b (ファイル名):(行番号)
とか
(gdb) b (ファイル名):(関数名)

プレークポイントの撤去

(gdb) delete (プレークポイントのインデックス)

終了

(gdb) q

以上、こんな感じでデバッグできるかと思います。

なお冒頭でこんな感じのメッセージがでてもビビらないように、大丈夫です。
「warning: Could not load shared library symbols for 49 libraries, e.g. /system/bin/linker.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?」

注意点:
・上記2通りの方法ともデバッグを停止しても、プログラムは終了せずに突っ走ります。
・1の方法は、まだ安定していないようです。変数が見れなかったり、思うような位置でbreakできなかったり。
・この方法でも上手くシンボルが読み込めなかったり、ブレークできないプロジェクトがありました。原因は特定できていません。
 方法は上記のものだけではありません。ネット上、他の方法を取られている方もいますので、そちらもご参考になってください。
・Java側とNative側との間をデバッグで行き来することはできません。
 Java側のデバッグは「Android Application」で、Native側のデバッグは「Android Native Application」で行う必要があります。
・実機を使ったデバッグについて、端末によってはbreakできない(gdbの要求を無効)場合があります。
 (シャープと東芝(富士通)の端末でbreakできなことが確認されています。
 最悪、端末シミュレータでも実行はできますが、シミュレータ自体が遅いので、そこでNDK叩いてもなあ。という気にさせられます・・・。

トラブルシュート:
・C/C++ソースコードで、問題ないはずなのに「X is not resolved.」「X cannont be resolved.」エラーが大量発生していた場合は、Code Analysis機能の誤検知の可能性があります。
eclipseの環境設定の[C/C++]-[Code Analysis]画面から該当する項目をOFFにして、チェックを無効にしてください。
 あと、同じく環境設定の[C/C++]-[Indexer]画面の[Indexer options]の[Skip files larger than]で入力されている数値を上げてみる(デフォルト8MB から32MBとか)と解消したという情報もありますが、私のところでは効果ありませんでした。
(参考:Android NDKでシンボル名が解決できない - HolyOresamaEmpire::blog() - Yahoo!ブログ

・ndk-gdbから「No symbol table is loaded. Use the "file" command.」と言われた場合、

(gdb) file (soファイル名).so

と入れてみてください。symbolが読み込まれるかもしれません。
(soファイルは、ちゃんとビルドできていますか?)


いや〜ここまで辿りつくのに、時間かかっちゃったんですけど(数時間でチョチョイとはいかなかった)皆さんハマったりしないんですかね。
で、実際にNDK使った実装は、今後勉強していきます。