大統一 Debian 勉強会 2013 gdb+python 拡張を使ったデバッグ手法 野島 貴英 nozzy@debian.or.jp 2013 年 6 月 29 日 Level Up Debian 質問タイィィィィーム 皆さん Debianって使ってますかー? 質問タイィィィィーム 皆さん Debianって使ってますかー? debian sid で unstable な生活してますかー? 質問タイィィィィーム 皆さん Debianって使ってますかー? debian sid で unstable な生活してますかー? *-dbg パッケージって知ってますかー? 質問タイィィィィーム 皆さん Debianって使ってますかー? debian sid で unstable な生活してますかー? *-dbg パッケージって知ってますかー? gdbって使ったことありますかー? 質問タイィィィィーム 皆さん Debianって使ってますかー? debian sid で unstable な生活してますかー? *-dbg パッケージって知ってますかー? gdbって使ったことありますかー? gdb の python 拡張使ったことありますかー? 回答 皆さん Debianって使ってますかー? →使ってない人はぜひ使ってみましょう! 回答 皆さん Debianって使ってますかー? →使ってない人はぜひ使ってみましょう! debian sid で unstable 生活してますかー? → level up を目指す人はぜひ debian sid へアップグレー ドして unstable な生活を送りましょう!楽しいよ! 回答 皆さん Debianって使ってますかー? →使ってない人はぜひ使ってみましょう! debian sid で unstable 生活してますかー? → level up を目指す人はぜひ debian sid へアップグレー ドして unstable な生活を送りましょう!楽しいよ! *-dbg パッケージって知ってますかー? →使ったことない人は、家に帰ったら aptitude install XXXX-dbg! (XXXX は何か好きなパッケージで) 回答 皆さん Debianって使ってますかー? →使ってない人はぜひ使ってみましょう! debian sid で unstable 生活してますかー? → level up を目指す人はぜひ debian sid へアップグレー ドして unstable な生活を送りましょう!楽しいよ! *-dbg パッケージって知ってますかー? →使ったことない人は、家に帰ったら aptitude install XXXX-dbg! (XXXX は何か好きなパッケージで) gdbって使ったことありますかー?/gdb の python 拡張 使ったことありますかー? →使ったことない人は、本資料公開してますので、ぜ ひ真似をしてみてー。 今回発表 の Debian 環境 今回の Debian 環境 項番 1 2 3 4 5 種類 Debian バージョン CPU OS 種別 php gdb 内容 debian sid (jessie/sid) amd64 (多分 i386 も一緒かな?) linux version 5.5.0 (debian sid から) version 7.6 (debian sid から) kFreeBSD とか、Hurd 環境とか、ARM とかの他の CPU の人 は... 大変申し訳ない... ちょ っと だけおさ らい *-dbg パッケージ今までの Debian 勉強会での発 表 *-dbg パッケージの技術的な話と、debian の*-dbg パッケー ジの現状については、 2012 年の大統一 Debian 勉強会の岩松さんの発表 「debug.debian.net」↓ http: // gum. debian. or. jp/ 2012/ に掲載されているプレゼン資料が大変参考になりますので、 こちらもあわせてご覧くださいませ。 (今回の発表はこちらの資料読んでなくても大丈夫なように 調整してます!) gdb+python の今までの Debian 勉強会での発表 gdb+python については、 第 98 回東京エリア Debian 勉強会↓ http: // tokyodebian. alioth. debian. org/ 2013-03. html に掲載されている勉強会資料に内部構造、コマンド拡張、 ブレークポイントの応用など載せてますので、こちらもあ わせてご覧くださいませ。 (今回の発表はこちらの資料読んでなくても大丈夫なように 調整してます!) *-dbg パッ ケージに ついてちょ っと *-dbg パッケージとは バイナリパッケージに対応して、デバッガが利用するデバッ グシンボルを別のファイルに分離してパッケージにまとめ たパッケージとなります。Debian に含まれているバイナリ をデバッグするときに大変便利です。 項番 1 2 種類 *-dbg パッケージ名 シンボルファイル名 内容 バイナリパッケージ名-dbg COMPATIBILITY LEVEL 9 未満のパッケージの場合: バイナリと同じファイル名 COMPATIBILITY LEVEL 9 以上のパッケージの場合: バイナリに埋め込まれた BuildID に対応したファイル名 3 インストール先 COMPATIBILITY LEVEL 9 未満のパッケージの場合: /usr/lib/debug/¡バイナリのインストール先のディレクトリ名¿ 例:/usr/bin/php5 だったら、/usr/lib/debug/usr/bin/php5 COMPATIBILITY LEVEL 9 以上のパッケージの場合: /usr/lib/debug/.build-id/以下 *-dbg パッケージとは 試しにどんな*-dbg パッケージがあるかみてみます。 $ cat /etc/debian_version jessie/sid $ aptitude search ’.*-dbg’ p 0ad-dbg p 389-ds-base-dbg p 389-ds-base-libs-dbg p 7kaa-dbg ... 中略... $ aptitude search ’.*-dbg’ 2272 $ Real-time strategy game of 389 Directory Server suite 389 Directory Server suite Seven Kingdoms Ancient | wc -l debian sid(jessie/sid) で 2272 パッケージ程度 *-dbg パッケージを早速使ってみる php5 をデバッグしてみます。 $ aptitude install php5-dbg $ apt-get source php5/sid $ ls php5-5.5.0+dfsg php5_5.5.0+dfsg-1.dsc php5_5.5.0+dfsg-1.debian.tar.gz php5_5.5.0+dfsg.orig.tar.xz $ pwd /home/yours/php5-src/ $ gdb --args /usr/bin/php5 -r ’phpinfo()’ (gdb) set substitute-path /tmp/buildd/ /home/yours/php5-src/ (gdb) b main (gdb) run (gdb) l 1197 #else 1198 int main(int argc, char *argv[]) 1199 #endif 1200 { 1201 #ifdef ZTS 1202 void ***tsrm_ls; COMPATIBILITY LEVEL Debian のソースパッケージは、debhelper の COMPATIBILITY LEVEL により構築に使われる debian/以下 のファイルのフォーマットや、実際にビルドされる時の挙 動がちょっと異なっています。現在の debian sid でも様々な LEVEL を持つパッケージが混在している状況です。確認に はソースパッケージを取ってきて debian/compat ファイルを 見るとわかります。 COMPATIBILITY LEVEL の見方↓ $ $ 5 $ $ 9 apt-get source php5 cat php5-5.5.0+dfsg/debian/compat apt-get source gstreamer0.10 cat gstreamer0.10-0.10.36/debian/compat COMPATIBILITY LEVEL 9 未満の*-dbg debhelper の COMPATIBILITY LEVEL 9 未満の形式で構築さ れているパッケージの*-dbg の中身: 例えば php5-dbg が該当 $ dpkg -L php5-dbg ... 中略... /usr/lib/debug /usr/lib/debug/usr /usr/lib/debug/usr/bin /usr/lib/debug/usr/bin/php5 /usr/lib/debug/usr/bin/php5-cgi /usr/lib/debug/usr/sbin /usr/lib/debug/usr/sbin/php5-fpm ... 中略... $ php5 コマンドは/usr/bin/php5 にあるので、インストール先 は/usr/lib/debug/usr/bin/php5 に、デバッグシンボルがイン ストールされています。 COMPATIBILITY LEVEL 9 以上の*-dbg debhelper の COMPATIBILITY LEVEL 9 以上の形式で構築さ れているパッケージの*-dbg の中身: 例えば libgstreamer0.10-0-dbg が該当 $ dpkg -L libgstreamer0.10-0-dbg ... 中略... /usr/lib/debug/.build-id/3f/bdb94562139d00e153a2fc6c 720772ca28acad.debug /usr/lib/debug/.build-id/86 /usr/lib/debug/.build-id/86/cc80bb6f2bdb31a2ed02973d 54530b3d99846f.debug /usr/lib/debug/.build-id/44 /usr/lib/debug/.build-id/44/ff321f11ffd750f8c351ffa3 f5d20028d2f6a6.debug /usr/share /usr/share/doc ... 中略... $ COMPATIBILITY LEVEL 9 以上の*-dbg どのシンボルファイルがどのバイナリに対応するかを確認 してみます↓ $ file /usr/lib/x86_64-linux-gnu/libgstreamer-0.10. so.0.30.0 /usr/lib/x86_64-linux-gnu/libgstreamer-0.10.so.0.30.0: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=44ff321f11ffd750f8c351ffa3f5d20028d2f6a6, stripped $ BuildID[sha1] の値から /usr/lib/debug/.buildid/44/ff321f11ffd750f8c351ffa3f5d20028d2f6a6.debug が libgstreamer-0.10.so.0.30.0 のシンボルファイルとなります。 ところで BuildIDって何? バイナリを一意に指定できるように埋め込んだ ID。バイナ リの構築時に、リンカがバイナリのハッシュ値を計算して 埋め込んでいます。 $ readelf -n /usr/lib/x86_64-linux-gnu/libgstreamer0.10.so.0 Notes at offset 0x000001c8 with length 0x00000024: Owner Data size Description GNU 0x00000014 NT_GNU_BUILD_ID Build ID: 44ff321f11ffd750f8c351ffa3f5d20028d2f6a6 BuildID について詳しくは、 BuildID 解説 http://fedoraproject.org/wiki/RolandMcGrath/ BuildID BuildID に関して binutils へ寄贈されたパッチ↓ http://sourceware.org/ml/binutils/2007-07/ msg00012.html どうやってシンボルファイルを見つける? gdb はバイナリに埋め込まれている .gnu_debuglink セク ションを見てシンボルファイルのファイル名を判断します。 $ readelf -x .gnu_debuglink /usr/bin/php5 Hex dump of section ’.gnu_debuglink’: 0x00000000 70687035 .. 中略..0dd10 php5.....‘.. $ readelf -x .gnu_debuglink /usr/lib/x86_64-linux-gnu/ libgstreamer-0.10.so.0.30.0 Hex dump of section ’.gnu_debuglink’: 0x00000000 66663332 .. 中略..06638 ff321f11ffd750f8 0x00000010 63333531 .. 中略..03238 c351ffa3f5d20028 0x00000020 64326636 .. 中略..00000 d2f6a6.debug.... 0x00000030 0da0b0f5 .... つまり、バイナリ php5 のシンボルファイル名は php5 とい うファイル名、バイナリ libgstreamer-0.10.so.0.30.0 のシンボ ルファイル名は、 ff321f11ffd750f8c351ffa3f5d20028d2f6a6.debug という事がわ かります。 ところでソースデバッグ出来ないんだけど? 先ほどの/usr/bin/php5 は gdb から’set substitute-path’ で、 ソースのありかを一発で指定できましたが、いつもうまく いくとは限りません。 うまくいかない例↓ $ gdb --args /usr/bin/gst-launch (gdb) set substitute-path /tmp/buildd/ /home/yours/gstreamer/ (gdb) b main (gdb) run Breakpoint 1, main ... 中略... at gst-run.c:318 318 gst-run.c: そのようなファイルやディレクトリはありません. あれ? ところでソースのデバッグ出来ないんだけど? gst-launch のソースである gstreamer0.10-0.10.36 を実際にビ ルドするとわかるのですが、gcc に渡されるソースファイル の指定にソースがフルパスで指定されていません。 (php5 は gcc にソースファイルが指定されるときフルパスで 指定されています) こういうときは、gdb の dir コマンドを利用してソースの 入っている可能性のある場所をすべて指定するとソースの デバッグができるようになります。 gdb の dir コマンドによるソースの指定 できた。 $ gdb (gdb) tools (gdb) (gdb) (gdb) 314 315 316 317 318 319 320 --args /usr/bin/gst-launch dir /home/yours/gstreamer/gstreamer0.10-0.10.36/ b main run l } int main (int argc, char **argv) { GHashTable *candidates; gchar *dir; dir コマンド注意点 gdb の dir でソースファイルを指定している時は、gdb は dir で登録されたパスをすべて検索して該当のソースファイル を見つけようとするため、万一同じファイル名のファイル が別のディレクトリで先に発見されると、間違ったソース ファイルを表示してしまうので注意がいります。 補足:2013/7/22(追記) 2013/7/20(土) の東京エリア Debian 勉強会の月刊 Debhelper の発表にて、dir コマンドで指定する場合でも set substitue-path を使ってソースコードを一発指定する方法を 見つけており、紹介させていただきましたので、ご参照く ださい。資料は以下から↓ http://tokyodebian.alioth.debian.org/2013-07.html gdb+python と gdb.Value クラス gdb+python 動かす gdb 内臓の python を動作させてみます。 なお、今回は、gdb 7.6 から搭載された 1 python-interactive コマンドを使って、python をインタラクティブモードで利 用してみます。 $ gdb (gdb) pi >>> import sys >>> print sys.version_info sys.version_info(major=2, minor=7, micro=5, releaselevel=’final’, serial=0) >>> 見ての通り、gdb を python で制御できるわけです。 1 gdb 7.6 released! http://lwn.net/Articles/548781/ gdb.Value クラス gdb はブレークポイントの他に、バイナリのソースコード側 で定義した変数を参照できます。 こちらを python 側で参照したり、応用きかせたりするの には、gdb.Value クラスがとても便利です。 便利な機能一覧: 1 デバッグ対象の変数が structure の場合、gdb.Value 型オ ブジェクトは structure のメンバ変数をそのまま連想配 列にマップしてくれます。 2 デバッグ対象の変数が配列なら、python 上も配列とし てアクセスできます。 3 ポインタか即値かは自動で判定されて適宜問題無いよ うに扱われるため、structure のポインタを辿ったりす るのが非常に簡潔に書けます。 4 関数ポインタの呼び出しもサポート済みです。 gdb.Value クラスで変数アクセス 値の参照の例: $ sudo aptitude install php5-dbg $ cat ./phpinfo.php <?php phpinfo() ?> $ gdb --args /usr/bin/php5 ./phpinfo.php (gdb) b zend_vm_execute.h:356 (gdb) run (gdb) pi >>> edata=gdb.parse_and_eval( ... "executor_globals.current_execute_data") >>> print edata.address 0xe60920 <executor_globals+1120> >>> print edata.dereference() {opline = 0x7ffff7fbd778, function_state = {function = 0x7ffff7fbd498, arguments = 0x0}, ... 中略...,call = 0x0} >>> print edata[’op_array’][’filename’] 0x7ffff7fbd640 "/home/yours/phpinfo.php" >>> [Ctrl+D] gdb.Value クラスで関数ポインタ 関数ポインタ呼び出しの例: $ gdb --args /usr/bin/php5 ./phpinfo.php (gdb) b zend_vm_execute.h:356 (gdb) run (gdb) pi >>> phpfile=(gdb.parse_and_eval( ... "zend_get_executed_filename")).dereference() >>> print phpfile {const char *(void)} 0x6c7600 <zend_get_executed_filename> >>> print phpfile() 0x7ffff7fc4418 "/home/yours/phpinfo.php >>> [Ctrl+D] (gdb) gdb.Value クラスに文字列格納時の注意点 gdb.Value クラスに文字列へのポインタが代入された場合、 こちらの文字列を str() で取り出そうとすると、どうしても gdb で”p pointer” した時とまったく同じフォーマットで文字 列に変換されてしまいます。 このとき、” アドレス 文字列” というフォーマットにされ てしまうので、str.partition(" ") 等を使って、欲しい文 字列だけ取り出す必要があります。 $ gdb --args /usr/bin/php5 ./phpinfo.php (gdb) b zend_vm_execute.h:356 (gdb) run (gdb) pi >>> phpfile=(gdb.parse_and_eval( ... "zend_get_executed_filename")).dereference() >>> filename=phpfile() >>> print str(filename) 0x7ffff7fc4418 "/home/yours/phpinfo.php" >>> print (str(filename).partition(" "))[2] "/home/yours/phpinfo.php" php5.5.0 内 部構造 php5.5.0 の中間コード実行部分 php5.5.0 では、中間コードを実行する部分が2つあります。 execute_ex()(Zend/zend_vm_execue.h) ユーザが定義した関数を実行するときに呼ばれる execute_internal()(Zend/zend_execute.c) php 本体に C 等で実装されている関数を実行するとき に呼ばれる 今回 gdb を仕掛ける場所 今回、ユーザ定義の関数の実行トレースを取ってみたいの で、execute_ex() の中間コード実行部分直前に、gdb を仕 掛け、どこを実行しているかを表示してみます。 Zend/zend_vm_execute.h 330 ZEND_API void execute_ex(zend_execute_data *execute_data TSRMLS_DC) 331 { 332 DCL_OPLINE 333 zend_bool original_in_execution; ... 中略... 356 if ((ret = OPLINE->handler( <---ココ execute_data TSRMLS_CC)) > 0) { 357 switch (ret) { php5.5.0 の便利な API php5.5.0 から、Zend Engine は現在どこを実行中なのかにつ いて情報を得る為に、便利な API が用意されています。 zend_get_executed_filename() (Zend/zend_execute_API.c) 現在実行中の php ファイル名を得る get_active_function_name() (Zend/zend_execute_API.c) 現在実行中の関数スコープ名を得る zend_get_executed_lineno() (Zend/zend_execute_API.c) 現在実行中の行番号を得る php のソー スの実行 トレース を 取って みる gdb を使って php のソースをデバッグする事に ついて 通常、php のデバッグをするのであれば、xdebug を導入 して、vim-nox+debugger plugin/netbean/eclipse 等でデバッ グするのが手軽ですし、強力なデバッグが可能です。 しかしながら、バックグラウンドで動作するような php プログラムを書いた際に、運用中、突然暴走するなどが あったとき、何が起きているのかをデバッグするには、 xdebug の環境だけでは厳しいものがあります。 ここで、gdb が使えれば、暴走中の php プロセスに直接ア タッチしてデバッグする事が可能も可能になるので、デバッ グの方法が広がります。 実行トレースを取ってみた 今回の勉強会資料には、今まで説明したものを組み合わせ て、php ソースレベルでの実行トレースを取った例を載せて いますので、お試しあれ! 7/22(追記): 勉強会資料は↓からダウンロードできます。 http://tokyodebian.alioth.debian.org/pdf/ debianmeetingresume2013-gum.pdf 今回紹介の方法の問題点 今回の gdb+python で、php ソースレベルのトレースを取る やり方は、実は、以下の問題点があります。 中間コードが1つ実行される度に gdb 側に逐一ブレー クが入り python スクリプトが動くため、php の実行速 度が極端に遅くなります。大きい php プログラムにつ いて、gdb を用いたデバッグをするにはもっと別のアプ ローチを検討する必要があるかもしれません。 php の変数の値を参照するには、php の内部変数である executor_globals に登録されているシンボル テーブルからデータを引っ張ってくる必要があります。 しかしながら、現行の php5.5.0 の Zend エンジンの API には、 php の変数の値をデバッガ側から簡単に参照で きるような API が存在しない為、gdb から php の変数を 参照するにはそれなりの工夫がいると思われます。
© Copyright 2024 Paperzz