Linux版PHP5.4以降のODBCエクステンションがsegmentation fault
院内独自開発システム用のサーバがDebianでPHP5.2を使ってる。PostgreSQLも入っている。
俺の前の上司が一人で作った環境。
前にサーバトラブルがあってPostgreSQLのデータファイルが壊れているらしく自動バキュームが
全く働いてない。
どうもpostgresデータベースというかシステムデータベースの、忘れたけどpg_xxxxxxとか
いうテーブルが開けないみたい。
そのせいか、PostgreSQLのディレクトリの容量がデータ量に見合ってないぐらいデカい。
まあ、そのサーバのVMイメージをコピーして、ググっていろいろやったが全くダメ。
なんか壊れている領域を強制的にゼロで埋めるコマンドとかもやったが全く改善しない。
これは、サーバ移行せんといかんかなー。と考えたのが先週の月曜日。
仮想サーバにCentOS7サーバを準備して、apache/php/pgsql/unixODBC/FreeTDSなど
いろいろインストール。画像やphpファイルはrsyncで持ってきた。さあ動作確認。
あるシステムの最初のページは表示された。
が、次のページに行くと「受信したデータが・・・」みたいなエラー。
apacheのerror_logを見るとsegmentation faultでPHPが落ちてるじゃないですか。
調べてみると、たわいもないコードのところで落ちている。
こんなコードでも落ちる。
<?php $con = odbc_connect("xxx.xxx.xxx.xxx", "xxx", "xxx"); if(empty($con)) { echo "error"; die; }else{ echo "connecting success"; } echo "\n"; // STAFFはテーブルでもビューでも落ちる $sql = "SELECT KANJINAME FROM STAFF"; $result = odbc_exec($con, $sql); while(odbc_fetch_row($result)) { echo "test:"; echo odbc_result($result, "KANJINAME"); echo "\n"; }
SQL Serverのデータを取りにいくところで落ちる。稼働中のサーバでは動いているし、
このSQL ServerはJavaのシステムでも使っているのでデータが悪い訳ではないと思う。
ちなみに、PDOでやってみると落ちはしないがodbcで落ちるデータを取ると空文字になる。
いろいろやってみると、SQL ServerでカラムがVARCHAR(24)に全角8文字入れると落ちないが
VARCHAR(23)に全角8文字入れると落ちることに気づいた。けど、ググっても全然情報出てこない。
しょうがないから、PHP5.5、5.6とかubuntu14でも試したけど状況かわらず。
これは、PHPのODBCのレイヤが悪いんじゃなかろうかと。
どうせ暇だし、PHPのソース改造してでもやるかい。あー、けどすごいめんどくせー。
とりあえず、gdbでトレースとった結果がこれ。
#0 __memcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/memcpy-sse2-unaligned.S:116 #1 0x00000000006c8d58 in _estrndup () #2 0x00007f7bbbcd0f4f in zif_odbc_result (ht=<optimized out>, return_value=0x7f7bbeea3630, return_value_ptr=<optimized out>, this_ptr=<optimized out>, return_value_used=<optimized out>) at /root/php_dev/php5.5-201409112230/ext/odbc/php_odbc.c:2186 #3 0x00000000006dd9bb in dtrace_execute_internal () #4 0x000000000079da15 in ?? () #5 0x0000000000717748 in execute_ex () #6 0x00000000006dd8b9 in dtrace_execute_ex () #7 0x00000000006ef340 in zend_execute_scripts () #8 0x000000000068f225 in php_execute_script () #9 0x000000000079f9ee in ?? () #10 0x0000000000461de0 in main ()
あー、なんかメモリ破壊してる。まあ、どこでアロックするサイズ間違えてるか知らんが
多めに取れば問題ないでしょ。と思って、メモリアロックしてる箇所を探すことにした。
ソースは適当にこれを使った
http://snaps.php.net/php5.5-201409112230.tar.gz
メモリ破壊している部分はここ(修正後の行数だから大体の目安で見て)
RETURN_STRINGLマクロで_estrndupが使われていた。そこで落ちる。ちなみに_estrndupで
アロックするサイズは第2引数だった。
2189 if (result->values[field_ind].vallen == SQL_NULL_DATA) { 2190 RETURN_NULL(); 2191 } else { 2192 RETURN_STRINGL(result->values[field_ind].value, result->values[field_ind].vallen, 1); 2193 }
アロックしている部分はここだった
989 case SQL_CHAR: 990 case SQL_VARCHAR: 991 #if defined(ODBCVER) && (ODBCVER >= 0x0300) 992 case SQL_WCHAR: 993 case SQL_WVARCHAR: 994 colfieldid = SQL_DESC_OCTET_LENGTH; 995 #else 996 charextraalloc = 1; 997 #endif 998 default: 999 rc = SQLColAttributes(result->stmt, (SQLUSMALLINT)(i+1), colfieldid, 1000 NULL, 0, NULL, &displaysize); 1001 /* Workaround for Oracle ODBC Driver bug (#50162) when fetching TIMESTAMP column */ 1002 if (result->values[i].coltype == SQL_TIMESTAMP) { 1003 displaysize += 3; 1004 } 1005 1006 if (charextraalloc) { 1007 /* Since we don't know the exact # of bytes, allocate extra */ 1008 displaysize *= 4; 1009 } 1010 /********* ここから *************/ 1011 #if 1 /* 修正後 */ 1012 result->values[i].value = (char *)emalloc(displaysize + 4); 1013 rc = SQLBindCol(result->stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, result->values[i].value, 1014 displaysize + 4, &result->values[i].vallen); 1015 #else /* 修正前 */ 1016 result->values[i].value = (char *)emalloc(displaysize + 1); 1017 rc = SQLBindCol(result->stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, result->values[i].value, 1018 displaysize + 1, &result->values[i].vallen); 1019 #endif 1010 /********* ここまで *************/
1008行目で(カラムサイズ×4)バイト分のメモリを取ろうとしているけど、修正前の1018行目では
1バイト分しか余分に取ってなかったが、修正後は4バイト余分に取るようにした。
この修正後、ビルドしてodbc.soだけ入れ替えたらテーブルのVARCHAR(24)は落ちなくなった。
ちなみに、phpソースからphp_odbc.soを作成する方法は以下のコマンド
./configure --with-unixODBC=shared --enable-so make cp -f modules/odbc.so /usr/lib/php5/20121212/
が、ビューではやっぱり落ちる。まあ、今考えたら当たり前か。この修正では_estrndupに渡される値は
何もかわらないし。
テーブルの時はSQLBindColの第6引数のvallenにカラムサイズが入ってくるけど、ビューの時は-4が
入ってくる。-4はSQL_NO_TOTALを指すが何のこっちゃさっぱりだわ。
ビューだからカラムサイズとれないのかなー?
まあ、RETURN_STRINGLマクロの第2引数に−4(SQL_NO_TOTAL)が指定されているから
落ちるということがわかった。
で、このように修正した。
2189 if (result->values[field_ind].vallen == SQL_NULL_DATA) { 2190 RETURN_NULL(); 2191 /********* ここから *************/ 2192 }else if (result->values[field_ind].vallen == SQL_NO_TOTAL) { 2193 RETURN_STRINGL(result->values[field_ind].value, strlen(result->values[field_ind].value), 1); 2194 /********* ここまで *************/ 2195 } else { 2196 RETURN_STRINGL(result->values[field_ind].value, result->values[field_ind].vallen, 1); 2197 }
めちゃくちゃ姑息!!でもこれ以外の方法が思いつかなかった。
configure/makeしてodbc.so入れ替えたら落ちなくなった。まあいいや。
どうせ同じ職場の人間しか使わないし。
けど、この調査ホントに疲れた!!近くに相談できる人でもいればなー。
けど、この現象ってうちの会社の環境が特殊だからなのか?Windows版PHPでは試してないけど、
Linux版PHPでSQL Server使うことが珍しいの?
PHPerの人は、こういうこと普通にしとるんだろうか・・・。誰か教えてください