Invalid library (maybe not a PHP library)

phpデバッグビルドしたのは後々に役立つだろうということの他に、当面の問題として"Invalid library (maybe not a PHP library)"が何なのかを調べようという趣旨があったりしました。

php.iniに"extension=libmysqlclinet.so"と書いて、"/usr/lib/php/modules"にlibmysqlclient.soを放りこめばウマクイクと思ったのに、"php -v"すると以下のように怒られるわけです。

mir@t43:~/php$ php -v
PHP Warning:  PHP Startup: Invalid library (maybe not a PHP library) 'libmysqlclient.so'  in Unknown on line 0

"Invalid library"でgrepすると、ext/standard/dl.cのphp_dl関数の中でエラーとなっているみたい。

    if (!get_module) {
        DL_UNLOAD(handle);
        php_error_docref(NULL TSRMLS_CC, error_type, "Invalid library (maybe not a PHP library) '%s' ", Z_STRVAL_P(file));
        RETURN_FALSE;
    }

というわけでphp_dl関数にbreakpointを突っ込んでデバッガを動かしてみる。

まず気になるポイント1個目。DL_LOADはLinuxだとシステムコールdlopenが実行されるマクロ。libpathの型はchar*で、値は"/usr/lib/php/modules/libmysqlclient.so"。ここでdlopenの戻り値のhandleは値が設定されていたのでこの時点では問題無し。

    /* load dynamic symbol */
    handle = DL_LOAD(libpath);
    if (!handle) {
        php_error_docref(NULL TSRMLS_CC, error_type, "Unable to load dynamic library '%s' - %s", libpath, GET_DL_ERROR());
        GET_DL_ERROR(); /* free the buffer storing the error */
        efree(libpath);
        RETURN_FALSE;
    }  

次に気になるポイント。DL_FETCH_SYMBOLはLinuxだとシステムコールdlsymが実行されるマクロ。最初に"get_module"というシンボルを取得しようとし、それがダメなら"_get_module"というシンボル名でリトライ。しかしこれらの行を実行した後に変数get_moduleを見たらnullだった。

    get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "get_module");
    if (!get_module)
        get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "_get_module");

というわけで、get_module=0x0のまま処理が進んで、ここでエラーになっていたということですな。

    if (!get_module) {
        DL_UNLOAD(handle);
        php_error_docref(NULL TSRMLS_CC, error_type, "Invalid library (maybe not a PHP library) '%s' ", Z_STRVAL_P(file));
        RETURN_FALSE;
    }

つまり、dlopenしたlibmysqlclient.soにシンボルget_moduleが無かったのが今回のエラーの原因ということですね。で、PHPの仕様を確認したわけではないけれども、拡張モジュールとしてロード可能なdynamic linkライブラリはシンボル"get_module"が含まれていないといけないということらしい。

この"get_module"だけれども、以下のように定義されているので、引数無しで戻り値がzend_module_entryへのポインタである関数ということになる。

    zend_module_entry *(*get_module)(void);

zend_module_entryは"_zend_module_entry"という名前でZend/zend_modules.hに定義されている構造体。ふむ。要するにget_module関数がないのが原因と。

get_module関数がある場合には、次のステップで以下のようにzend_module_entryへのポインタを取得する。

    module_entry = get_module();

で、まとめると、自分でビルドしたlibmysqlclient.soとかはextensionsでPHPに読み込ませて使うのは無理ということですな。これphp界隈の人には常識? orz

ついでに

もうちょい調べてみる。

libmysqlclient.soに対してnmとかstringsとかをやって"get_module"でgrepしてももちろん入っていない。

続いてMySQLのソースディレクトリに対して"get_module"でgrepしても、やはり入っていない。

ということはMySQLのソースからだけではphp用のモジュールは作れないってことかなぁ。phpに詳しいnkjm氏曰く、通常はphpのビルド時に--with-mysqlでsharedは指定しないのでlibmysqlclientはstatic linkされるから無問題、ということなので自分で作ったMySQLのクライアントライブラリをphpで使おうとおもったらその方法になるのかなあ。

でもこれだと、MySQL側を弄ってリコンパイルする度に、PHPもリコンパイルしないといけないよね。困るー。

mysql.soってPHPのソースに含まれてるのね

phpのソースをfindしたらでてきた。

mir@t43:~/php/php-5.2.3$ find . -name "mysql.so"
./ext/mysql/.libs/mysql.so
./modules/mysql.so

mir@t43:~/php/php-5.2.3/modules$ nm mysql.so | grep get_module
0000408c T get_module

あ、そっか・・・。mysql_connectというPHP関数を実装しているのはphpのソースに含まれるmysql.soとかmysqli.soとかで、こいつらがさらにlibmysqlclient.soに依存する、というフォーメーションなのねん。

mysql.soをコピればok

/usr/lib/php/modulesに放りこんで、/usr/local/php.iniにextention=mysql.soをついかしたらphpスクリプトが動くようになった。

mysql.soをlddする

とりあえず/usr/lib/mysql/libmysqlclient.so.15を見ているらしい。

mir@t43:~/php/php-5.2.3/modules$ ldd mysql.so
        linux-gate.so.1 =>  (0x00bf7000)
        libmysqlclient.so.15 => /usr/lib/mysql/libmysqlclient.so.15 (0x001a3000)
        libc.so.6 => /lib/i686/nosegneg/libc.so.6 (0x00305000)
        libcrypt.so.1 => /lib/libcrypt.so.1 (0x00113000)
        libnsl.so.1 => /lib/libnsl.so.1 (0x00dc5000)
        libm.so.6 => /lib/i686/nosegneg/libm.so.6 (0x00f3b000)
        libssl.so.6 => /lib/libssl.so.6 (0x00141000)
        libcrypto.so.6 => /lib/libcrypto.so.6 (0x00446000)
        libz.so.1 => /usr/lib/libz.so.1 (0x00d83000)
        /lib/ld-linux.so.2 (0x00b74000)
        libgssapi_krb5.so.2 => /usr/lib/libgssapi_krb5.so.2 (0x00f93000)
        libkrb5.so.3 => /usr/lib/libkrb5.so.3 (0x00929000)
        libcom_err.so.2 => /lib/libcom_err.so.2 (0x00c23000)
        libk5crypto.so.3 => /usr/lib/libk5crypto.so.3 (0x00578000)
        libresolv.so.2 => /lib/libresolv.so.2 (0x00186000)
        libdl.so.2 => /lib/libdl.so.2 (0x00c39000)
        libkrb5support.so.0 => /usr/lib/libkrb5support.so.0 (0x00c91000)

ということはこれを新しく作成したlibmysqlclient.so.15で上書きするかあるいは環境変数LD_LIBRARY_PATHを使えばいけそう。/home/mir/phpに新しくつくったlibmysqlclient.soを置いているとして、、

mir@t43:~/php$ LD_LIBRARY_PATH=/home/mir/php ldd php-5.2.3/modules/mysql.so
        linux-gate.so.1 =>  (0x004ee000)
        libmysqlclient.so.15 => /home/mir/php/libmysqlclient.so.15 (0x00d11000)
        libc.so.6 => /lib/i686/nosegneg/libc.so.6 (0x00110000)
        libcrypt.so.1 => /lib/libcrypt.so.1 (0x00685000)
        libnsl.so.1 => /lib/libnsl.so.1 (0x0056f000)
        libm.so.6 => /lib/i686/nosegneg/libm.so.6 (0x00251000)
        /lib/ld-linux.so.2 (0x00b74000)

イケター! これでphp環境でのlibmysqlclient.soのリビルドし放題ですねぃ。