大統一Debian勉強会 2013 - gdb+python拡張を使ったデバッグ手法

大統一 Debian 勉強会 2013
gdb+python 拡張を使ったデバッグ手法
野島 貴英
[email protected]
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 の変数を
参照するにはそれなりの工夫がいると思われます。