GDBデバッガを利用してPHP内部の動きを知る - PHPソースコードリーディング入門その2
前回: PHPソースコードリーディング入門(とっかかり編) - id:anatooのブログ
PHPのソースコードを読んでいく際に、どうしてもソースコードを読むだけではよくわからない部分というのが出てくる。この記事ではPHPをデバッガで動かして内部の働きを明らかにする方法を書く。
デバッガで動かせるようにビルドする
余計な拡張は無しで、デバッガで動かせるようにビルドする。configure時に--enable-debugオプションを渡す。
$ cd php-src $ ./buildconf $ ./configure --disable-all --enable-debug $ make
GDBで動かす
makeした後、コマンドラインで動かせるバイナリはsapi/cli/phpにあるので以下のように起動する。まあ普通のGDBの使い方。
$ gdb sapi/cli/php
後はGDBのマニュアルなどを見つつ存分にデバッガを扱う。自分の場合は直接GDBを使わずにCGDBというGDBフロントエンドを利用しているがこのへんは好き好きで変える。
PHPの組み込み関数内にブレークポイントを設定する
PHPの組み込み関数や拡張で宣言されるPHPの関数は、PHP内部ではZEND_FUNCTIONというマクロを用いて宣言される。ZEND_FUNCITONマクロの中身を調べると、これらの関数は、実際には"zif_"というプレフィックスを付けられてC言語レベルでの関数として宣言されている。zifとは、zend internal functionの略と思われる。
実際にgdbでPHPの組み込み関数にブレークポイントを設定する例が以下。ここではstrlen関数にブレークポイントを張っている。
$ gdb sapi/cli/php (gdb) break zif_strlen Breakpoint 1 at 0x10022c759: file /path/to/myphp/Zend/zend_builtin_functions.c, line 482. (gdb) run -r "echo strlen('hoge');" Starting program: /path/to/myphp/sapi/cli/php -r "echo strlen(0);" Reading symbols for shared libraries ++. done Breakpoint 1, zif_strlen (ht=1, return_value=0x10064bfe0, return_value_ptr=0x0, this_ptr=0x0, return_value_used=1) at /path/to/myphp/Zend/zend_builtin_functions.c:482
CGDBだと以下みたいな画面になる。ZEND_FUNCTION(strlen)で宣言されたコードの中で止まっているのがわかる。
PHP用のGDBカスタムコマンドを利用する
PHPのソースコードには、GDBで利用できるPHP用のカスタムコマンドも含まれている。ソースコードリポジトリ直下の".gdbinit"というファイルがそれ。このカスタムコマンドでは、PHPをデバッガで動かす際に便利なコマンドが幾つか収められている。
このカスタムコマンド利用するには、.gdbinitがあるディレクトリ上でGDBを起動するか、もしくはホームディレクトリに.gdbinitをコピーしてGDBを起動する。もしくはGDBの--commandオプションで.gdbinitファイルのパスを渡す。以下のように。
$ gdb --command .gdbinit sapi/cli/php
以下は、.gdbinitに収められているカスタムコマンドの一部である。
コマンド名 | 概要 |
---|---|
printzv | zval構造体ポインタの中身をわかりやすく表示する。zval構造体とは、PHPで扱う値のC言語レベルでの表現。 |
zbacktrace | GDBのbacktraceコマンドがC言語のコールスタックを表示するのに対して、zbackraceコマンドはPHPレベルでのコールスタックを表示する |
ここで挙げたカスタムコマンド以外も.gdbinitには含まれているので覗いてみるとよい。現在の実装に追いついておらず使えないコマンドもいくつかあるようなので注意。
php-fpmをデバッガとともに動かす
コマンドラインで動かすPHP以外にも、実際にウェブアプリとして動いているPHPアプリケーションをデバッグしたい場合は、php-fpmの子プロセスの数を一つに設定した後起動し、プロセスIDを調べてデバッガでattachする。attachした直後は、php-fpmはブレークした状態になっている。ブレークポイントを設定した後、continueコマンドでブレーク状態から抜けてブラウザからPHPを実行するとブレークポイントを張ったところで止まってくれる。
例えば、cliでのデバッガの使用例と同じく、PHPで書いたウェブアプリ内の組み込み関数内でブレークしたい場合は、以下のようにすればよい。
$ # php-fpmの子プロセス数を1に設定する $ vim php-fpm.conf (中略) pm = static pm.max_children = 1 (中略) $ php-fpm $ ps A | grep php-fpm 58090 ?? Ss 0:00.01 sapi/fpm/php-fpm 58091 ?? S 0:00.00 sapi/fpm/php-fpm 58095 s000 R+ 0:00.00 grep php-fpm $ # 二番目のプロセスIDを指定してアタッチ $ gdb --pid 58091 (gdb) break zif_strlen (gdb) continue
それで、以下のようなスクリプトにブラウザからアクセスすれば、ブレークされる。ので、後はcliのバイナリをデバッガで動かす場合と同じ。
<?php echo strlen('hoge');
apacheモジュールで動かす場合は知らない。ググれば多分普通に出てくるはず。