Xdebug にパッチを送ってみた。
Xdebug を導入するだけで、 var_dump はHTMLでハイライトされ、例外やエラーが発生したときは見やすく彩られたバックトレースが表示されます。また、適切な設定と eclipse (vimでも可能らしい)があれば、ソースレベルのリモートデバッグが可能になります。さらには、プロファイリングもできるので、ボトルネックの検出にも一役買います。
一度使うと、これ無しに開発するなど考えられないライブラリです。
そんな Xdebug を使っている時に、バグらしき現象に遭遇しました。
xdebug の設定に、 xdebug.show_local_vars
という設定があります。
デフォルトでは 0
になっているのですが、これを 0
以外の値にしてやると、バックトレースが表示されるときに、ローカル変数の値を出力してくれるようになります。
これが便利で、基本的に有効にしているのですが、PHP5.2系では問題なく出力されていたのですが、PHP5.3 にしてから 変数名は列挙されるものの、すべての値が Undefined
と表示されてしまっていました。
そのとき利用していたバージョンは、Xdebug 2.1.0 beta1 。まぁ、まだベータだし、そのうち直るだろう。なんて思っていたのですが、RC1 がリリースされても直る気配がない。仕方ないので、ソースコードを追ってみることにしました。
まずは、 Xdebug のソースコードをダウンロード。このときは RC1のソースコードで追うことにしました。
最初に探したのが、 Variables in local scope
の文字列。検索してみると、 xdebug_stack.c にありました。 static char* html_formats[12]
という変数の一部です。なので今度は、それを利用している箇所を html_formats
で検索。利用しているのは、以下の関数。
- xdebug_append_error_head
- xdebug_append_error_description
- xdebug_append_printable_stack
- dump_used_var_with_contents
- xdebug_append_error_footer
名前から dump_used_var_with_contents が怪しいので、処理を追ってみると、どうやら、xdebug_get_php_symbol で取得した値を xdebug_get_zval_value_fancy (あるいは xdebug_get_zval_value)で整形して出力しているらしいということが分かりました。
xdebug_get_zval_value_fancy は、渡された値を整形して出力するだけなので、値を取得できないのは xdebug_get_php_symbol で適切に値が取得できていないからということになります。というわけで、xdebug_get_php_symbol 関数を精査することに。
この関数は xdebug_var.c に定義されていました。
この関数は、
- XG(active_symbol_table)
- EG(active_op_array)->static_variables
- &EG(symbol_table)
に対して、zend_hash_find 関数を使い、該当する変数値を検索して、なければ NULL を返す(= Undefined として表示される)ようになっています。
いろいろ調べてみたところ、上から順に ローカル変数、(ローカルで定義された)static 変数、 グローバル変数が格納されたオブジェクトのようです。このうち、ローカル変数を保持するオブジェクトである XG(active_symbol_table) は、呼び出し前に
XG(active_symbol_table) = EG(active_symbol_table);
とされていることから、EG(active_symbol_table) と等価であることが分かります。
EG(active_symbol_table) は確かに、ローカル変数を保持するシンボルテーブルらしい(php 本体のソースコードを参照)のですが、どうやら、ここから変数値が取得できていないのが原因のようでした。
ですが、何が原因なのかさっぱり分かりません。
ここで、ふと思いつきました。
(PHPの) compact 関数は、ローカル変数の値を元に配列を構築する関数だよな……。
ということは、PHP 本体の compact 関数に相当する部分のソースコードが参考になるんじゃないかと。
PHP の関数は、PHP_FUNCTION マクロで構築されるので、検索対象文字列は PHP_FUNCTION(compact)
です。
ext/standard.array.c にありました。
ここで妙なものを発見。
if (!EG(active_symbol_table)) {
zend_rebuild_symbol_table(TSRMLS_C);
}
Xdebug のソースコードでは、 EG(active_symbol_table) すなわち XG(active_symbol_table) が必ず存在するかのように扱っていたのですが、compact 関数のソースコードを見ると、 EG(active_symbol_table) が存在しない場合があるかのようなコードが記述されている。
これは妙だと思い、この部分が付け足されたコミットログを追ってみると、ビンゴ。
php5.3 のリポジトリ rev.258703 において、EG(active_symbol_table) の初期を必要になるまで遅延する修正がコミットされていました。
これにより、EG(active_symbol_table) は、常に存在はせず、必要に応じて zend_rebuild_symbol_table を呼ぶ必要が出来たらしいです。
そんな訳で、EG(active_symbol_table) を利用する前に、zend_rebuild_symbol_table を呼ぶようにしてみたら、見事ローカル変数が出力されるようになりました。
ざっと調べてみたところ、 xdebug_debug_zval と xdebug_debug_zval_stdout もこれに依存して正常に動いていなかったようなので、以下のようなパッチを作り、本家に投げました。
-------------------------------------------------------------------- --- xdebug.c.original 2010-04-15 15:39:56.000000000 +0900 +++ xdebug.c 2010-04-15 16:40:06.000000000 +0900 @@ -1457,7 +1457,13 @@ efree(args); WRONG_PARAM_COUNT; } - + +#if PHP_VERSION_ID >= 50300 + if (!EG(active_symbol_table)) { + zend_rebuild_symbol_table(TSRMLS_C); + } +#endif + for (i = 0; i < argc; i++) { if (Z_TYPE_PP(args[i]) == IS_STRING) { XG(active_symbol_table) = EG(active_symbol_table); @@ -1498,7 +1504,13 @@ efree(args); WRONG_PARAM_COUNT; } - + +#if PHP_VERSION_ID >= 50300 + if (!EG(active_symbol_table)) { + zend_rebuild_symbol_table(TSRMLS_C); + } +#endif + for (i = 0; i < argc; i++) { if (Z_TYPE_PP(args[i]) == IS_STRING) { XG(active_symbol_table) = EG(active_symbol_table); --- xdebug_stack.c.original 2010-04-15 15:45:52.000000000 +0900 +++ xdebug_stack.c 2010-04-15 16:39:21.000000000 +0900 @@ -90,6 +90,12 @@ return; } +#if PHP_VERSION_ID >= 50300 + if (!EG(active_symbol_table)) { + zend_rebuild_symbol_table(TSRMLS_C); + } +#endif + tmp_ht = XG(active_symbol_table); XG(active_symbol_table) = EG(active_symbol_table); zvar = xdebug_get_php_symbol(name, strlen(name) + 1); --------------------------------------------------------------------
英語は苦手なので、 excite 翻訳と、友人の力を借りつつ、なんとかBTSに投稿。
待つこと数日。
拙い英語ながらも通じたらしく、
- http://svn.xdebug.org/cgi-bin/viewvc.cgi/xdebug/trunk/xdebug_stack.c?view=markup&revision=3263&root=xdebug
- http://svn.xdebug.org/cgi-bin/viewvc.cgi/xdebug/trunk/xdebug.c?view=markup&revision=3265&root=xdebug
投稿したパッチを取り込んでもらえました。
まだ、これが反映されたバージョンはリリースされていませんが、 2.1.0 の正式版では反映されてるのではないかと期待してます。
OSS の世界に自分の名前が残るって嬉しいですね!