PHP 8系に移行できるだろうか?(7)
原因が見えてきましたよ
Apacheの子プロセスがSIGBUSで異常終了するのは 0x00000010 という変なアドレスにアクセスしているのが直接原因と思われるというのが前回までのお話。では、この変な文字列アドレスが何処から "main/main.c" の php_error_cb() に渡されて来るのか?呼び出し元を辿って行って行くと、出所は "zend/zend.c" 内の
ZEND_API ZEND_COLD void zend_error_zstr(int type, zend_string *message) {
const char *filename;
uint32_t lineno;
get_filename_lineno(type, &filename, &lineno);
zend_error_impl(type, filename, lineno, message);
}
〜略〜
static ZEND_COLD void get_filename_lineno(int type, const char **filename, uint32_t *lineno) {
/* Obtain relevant filename and lineno */
switch (type) {
case E_CORE_ERROR:
case E_CORE_WARNING:
*filename = NULL;
*lineno = 0;
break;
case E_PARSE:
case E_COMPILE_ERROR:
case E_COMPILE_WARNING:
case E_ERROR:
case E_NOTICE:
case E_STRICT:
case E_DEPRECATED:
case E_WARNING:
case E_USER_ERROR:
case E_USER_WARNING:
case E_USER_NOTICE:
case E_USER_DEPRECATED:
case E_RECOVERABLE_ERROR:
if (zend_is_compiling()) {
*filename = ZSTR_VAL(zend_get_compiled_filename());
*lineno = zend_get_compiled_lineno();
} else if (zend_is_executing()) {
*filename = zend_get_executed_filename();
if ((*filename)[0] == '[') { /* [no active file] */
*filename = NULL;
*lineno = 0;
} else {
*lineno = zend_get_executed_lineno();
}
} else {
*filename = NULL;
*lineno = 0;
}
break;
default:
*filename = NULL;
*lineno = 0;
break;
}
if (!*filename) {
*filename = "Unknown";
}
}
|
のようです(ソースは PHP 8.0.27 のものです)。get_filename_lineno() 関数で第二引数 filename に渡しているアドレスですね。どうやらJITコンンパイラがコンパイルしたファイル名を返そうとしているように見えます(赤字の部分)。
そう言えば PHP 8.0 の新機能は PHP構文をネイティブコードに変換して次回以降は変換済みのコードを実行することで処理を高速化するという JIT(Just In Time)コンパイラの実装が一つの目玉でしたね。でもウチの PHP 8.0って JIT機能を有効にしていたっけか?
調べてみたら PHP 8.0以降で JIT機能を有効にするには "php.ini" に少なくとも以下の設定を追加する必要があるそうです。
opcache.jit = [disable | off | tracking/on | function]
opcache.jit_buffer_size = 0 | nnn[K | M | G]
|
詳細はこちらを参照
もしかしたら JIT機能を有効化したら問題解決する?そう思って "php.ini" に以下を追加してみました。
[opcache]
opcache.enable=1 ; こちらは PHP 7から指定してある
opcache.enable_cli=1 ; Opcacheの有効化設定
〜略〜
opcache.jit = tracking
opcache.jit_buffer_size = 32M
|
有効になったか確認してみます。
$ /usr/local/php8/bin/php -i | grep opcache.jit
$
|
あれぇ?全然有効になってない。何故?もしかして PHP8.0のビルド時にも JIT機能を有効にしておかないとダメとかですか?ビルドで作られた "config.log" を確認してみたところ、気になる内容が出力されていました。
configure:51253: checking whether to enable JIT
configure:51267: result: yes
configure:51288: WARNING: JIT not supported by host architecture
|
ウチの Mac mini G4(PPCアーキテクチャ)では JIT機能はサポートされないってことですか?現時点で対応しているのはインテル x86, x64アーキテクチャだけみたいですね。そりゃそうか・・・
なのでウチの Webサーバの PHP 8.0 では JIT機能は無効化されていると言うことになります。同じ理由で、ARM, MIPS, Sparc, RISC-V などにも対応できてないわけで、Apple M1/M2チップ採用の Mac等も(ARMア−キテクチャですから)非対応ってことになりますね。
こんな単純なことに何日も気付いてなかったワタシって本当に元プログラマか?・・・
ちょっと待てよ。JIT機能は無効なのに、何故コンパイルしたファイル名を得ようとしているんだ?コンパイルできてないのだから、そんなファイル名が得られるわけないじゃないか。これが原因か?!
これは想像なのだけど、JIT機能(正常系)の実装に手一杯で、JIT機能が無効になっているときの異常(エラー)系が考慮不足、テスト不足なのでは?そのため、JIT機能が無効の場合には処理(コンパイル)中のファイル名が設定漏れになる不具合があり、エラーがあったファイル名をエラーメッセージに埋め込もうとすると、設定されていないファイル名アドレスを参照する事になってしまい、SIGBUSを起こすことになったのではないかと。であれば、これは PHP 8.0のバグでしょうね。
何となく原因らしきものが見えてきました。であれば、この辺りの処理を修正すれば SIGBUSが起きないようにすることができるかもしれませんね。さて?
|