たまたま講読している php-dev というPHPの実装を日本語で議論するメーリングリストで「mbstring の新関数」というスレッドがあった。新規にmb_list_encodings_alias_names()という関数を追加したらしいのだが、既存のmb_list_encodings()とどう違うのかどうかという議論がわきおこっている。
ここでは、mb_list_encodings()を題材にどうやってphpの実装を理解していくか、そのプロセスを記述してみたい。もちろんこの方法がベストであるとか、この方法でなければいけないとか、いつでもこの方法が適用可能だなんてことを主張するつもりは一切ないが、一つの例として大規模ソフトウェアの微視的理解方法を理解いただきたい。
1) mb_list_encodingsがどこで利用されているかを知る。
$ cd /usr/src/php-5.1.4
$ time find -type f|xargs egrep -l mb_list_encodings_alias_names
real 0m21.909s
user 0m0.088s
sys 0m0.606s
新規に追加された関数mb_list_encodings_alias_names()は現在のバージョンでは利用されていない。(あたりまえだ)
$ time find -type f|xargs egrep -l mb_list_encodings
./ext/mbstring/mbstring.c
./ext/mbstring/mbstring.h
./ext/mbstring/.libs/mbstring.o
./sapi/cli/php
./libs/libphp5.so
./.libs/libphp5.so
real 0m0.450s
user 0m0.180s
sys 0m0.248s
既存のmb_list_encodingsは、mbstring.{c|h}で利用されている。。*.oとかもgrepでひっかかる。実行時間が最初は21.909秒、今回は0.450秒で激減しているのは、ページキャッシュに当該ファイルが載ってしまったためと考えられる。
2) ソースファイルを読む。mbstring.c
/* {{{ proto array mb_list_encodings()
Returns an array of all supported encodings */
PHP_FUNCTION(mb_list_encodings)
{
const mbfl_encoding **encodings;
const mbfl_encoding *encoding;
int i;
array_init(return_value);
i = 0;
encodings = mbfl_get_supported_encodings();
while ((encoding = encodings[i++]) != NULL) {
add_next_index_string(return_value, (char *) encoding->name, 1);
}
}
3) 実行してみる。
$ php -r ' print_r(mb_list_encodings());'
Array
(
[0] => pass
[1] => auto
[2] => wchar
[3] => byte2be
[4] => byte2le
[5] => byte4be
[6] => byte4le
[7] => BASE64
[8] => UUENCODE
[9] => HTML-ENTITIES
[10] => Quoted-Printable
[11] => 7bit
[12] => 8bit
...
)
4) デバッガで実行する。
ブレークポイントをどこに設定するか?
当該ソースコードは
PHP_FUNCTION(mb_list_encodings)
となっていて、PHP_FUNCTION()というマクロを利用してmb_list_encodings()を定義している。PHP_FUNCTIONの定義を丹念におっていけば、どのように展開されていくかが分るのだが、もっと直接的な方法でしる。
先のfind |xargs egrepというイディオムで当該関数を含んだファイル一覧が得られたが、いくつかのバイナリファイルにも含まれていることがわかる。そこで力づくで
$ strings ./sapi/cli/php|egrep mb_list_encodings
zif_mb_list_encodings
mb_list_encodings
とするとzif_mb_list_encodings()がどうも当該関数名だということがわかる。
(gdb) b main
Breakpoint 1 at 0x82824e0: file /usr/src/php-5.1.4/sapi/cli/php_cli.c, line 581.
取り合えづmainにブレークポイントを設定した。
(gdb) b mb_list_encodings
Function "mb_list_encodings" not defined.
Make breakpoint pending on future shared library load? (y or [n])
予想どおりmb_list_encodingsという関数はない。
(gdb) b zif_mb_list_encodings
Breakpoint 2 at 0x8102931: file /usr/src/php-5.1.4/ext/mbstring/mbstring.c, line 2327.
ビンゴである。
実行する。
(gdb) run -r ' print_r(mb_list_encodings());'
Starting program: /usr/local/bin/php -r ' print_r(mb_list_encodings());'
[Thread debugging using libthread_db enabled]
[New Thread -1208068416 (LWP 4872)]
[Switching to Thread -1208068416 (LWP 4872)]
Breakpoint 1, main (argc=3, argv=0xbfe1e994)
at /usr/src/php-5.1.4/sapi/cli/php_cli.c:581
(gdb) c
Continuing.
Breakpoint 2, zif_mb_list_encodings (ht=0, return_value=0xbfe1e580,
return_value_ptr=0x0, this_ptr=0x0, return_value_used=1)
at /usr/src/php-5.1.4/ext/mbstring/mbstring.c:2327
当該ブレークポイントで停止した。
スタックフレームを見ると呼び出し系列が一目瞭然でわかる。
(gdb) bt
#0 zif_mb_list_encodings (ht=0, return_value=0xbfe1e580,
return_value_ptr=0x0, this_ptr=0x0, return_value_used=1)
at /usr/src/php-5.1.4/ext/mbstring/mbstring.c:2327
#1 0x082222a8 in zend_do_fcall_common_helper_SPEC (execute_data=0xbfe1e580)
at /usr/src/php-5.1.4/Zend/zend_vm_execute.h:200
#2 0x08221c8d in execute (op_array=0x950635c)
at /usr/src/php-5.1.4/Zend/zend_vm_execute.h:92
#3 0x0820057c in zend_eval_string (str=0x950635c "\004", retval_ptr=0x0,
string_name=0x841e72f "Command line code")
at /usr/src/php-5.1.4/Zend/zend_execute_API.c:1116
#4 0x082006c8 in zend_eval_string_ex (
str=0xbff0f932 " print_r(mb_list_encodings());", retval_ptr=0x0,
string_name=0x841e72f "Command line code", handle_exceptions=1)
at /usr/src/php-5.1.4/Zend/zend_execute_API.c:1150
#5 0x082831b2 in main (argc=3, argv=0xbfe1e994)
at /usr/src/php-5.1.4/sapi/cli/php_cli.c:1181
あとはステップ実行なりなんなりして微視的な理解を深めるだけである。
ここまでに到達するのに30分とかからない。
最近のコメント