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 ServerJavaのシステムでも使っているのでデータが悪い訳ではないと思う。
ちなみに、PDOでやってみると落ちはしないがodbcで落ちるデータを取ると空文字になる。

いろいろやってみると、SQL ServerでカラムがVARCHAR(24)に全角8文字入れると落ちないが
VARCHAR(23)に全角8文字入れると落ちることに気づいた。けど、ググっても全然情報出てこない。

しょうがないから、PHP5.5、5.6とかubuntu14でも試したけど状況かわらず。
これは、PHPODBCのレイヤが悪いんじゃなかろうかと。
どうせ暇だし、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入れ替えたら落ちなくなった。まあいいや。
どうせ同じ職場の人間しか使わないし。

けど、この調査ホントに疲れた!!近くに相談できる人でもいればなー。

けど、この現象ってうちの会社の環境が特殊だからなのか?WindowsPHPでは試してないけど、
LinuxPHPSQL Server使うことが珍しいの?
PHPerの人は、こういうこと普通にしとるんだろうか・・・。誰か教えてください