From 7857119fa441d2a36f2defff2c6c2c4bb0af97fa Mon Sep 17 00:00:00 2001 From: Pavel Gushcha Date: Fri, 9 Oct 2026 17:03:58 +0000 Subject: [PATCH 1/2] Fix preloaded functions JIT-compiled on load being overwritten after restart zend_jit_startup() records the JIT buffer position that zend_jit_restart() rewinds to (dasm_ptr[1]) before preloading runs. With a JIT trigger that compiles on script load (e.g. opcache.jit=function) preloaded functions are compiled during preloading, i.e. after that point. An OPcache restart (opcache_reset(), OOM, hash overflow) then rewinds the buffer below their code, while their handlers still point into it: the next JIT compilation overwrites the code and calling a preloaded function returns garbage or crashes. This happens in NTS with a single process as well. zend_jit_restart_preloaded_op_array() only resets the hot counters of preloaded functions; functions compiled on load cannot be restored. Record the restart point again after preloading, so code generated by preloading survives restarts. Traces and functions compiled at runtime are discarded as before. --- ext/opcache/ZendAccelerator.c | 3 + ext/opcache/jit/zend_jit.c | 11 +++ ext/opcache/jit/zend_jit.h | 1 + .../jit/preload_restart_function_jit.phpt | 82 +++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 ext/opcache/tests/jit/preload_restart_function_jit.phpt diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 79c7668ba5e9..3df9eed46ca0 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4755,6 +4755,9 @@ static zend_result accel_preload(const char *config, bool in_child) zend_shared_alloc_save_state(); accel_interned_strings_save_state(); +#ifdef HAVE_JIT + zend_jit_save_restart_point(); +#endif SHM_PROTECT(); HANDLE_UNBLOCK_INTERRUPTIONS(); diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index a958a2453c4e..45dc327fac16 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3908,4 +3908,15 @@ void zend_jit_restart(void) } } +/* zend_jit_restart() rewinds the buffer to this point: code generated before it + * (preloaded functions compiled on script load) must survive restarts */ +void zend_jit_save_restart_point(void) +{ + if (dasm_buf) { + zend_jit_unprotect(); + dasm_ptr[1] = dasm_ptr[0]; + zend_jit_protect(); + } +} + #endif /* HAVE_JIT */ diff --git a/ext/opcache/jit/zend_jit.h b/ext/opcache/jit/zend_jit.h index 9178d340a0ed..f2d2c8a7d184 100644 --- a/ext/opcache/jit/zend_jit.h +++ b/ext/opcache/jit/zend_jit.h @@ -167,6 +167,7 @@ void zend_jit_deactivate(void); ZEND_EXT_API void zend_jit_status(zval *ret); ZEND_EXT_API void zend_jit_blacklist_function(zend_op_array *op_array); void zend_jit_restart(void); +void zend_jit_save_restart_point(void); #define ZREG_LOAD (1<<0) #define ZREG_STORE (1<<1) diff --git a/ext/opcache/tests/jit/preload_restart_function_jit.phpt b/ext/opcache/tests/jit/preload_restart_function_jit.phpt new file mode 100644 index 000000000000..5571b52dfc25 --- /dev/null +++ b/ext/opcache/tests/jit/preload_restart_function_jit.phpt @@ -0,0 +1,82 @@ +--TEST-- +Preloaded functions JIT-compiled on script load must survive an OPcache restart +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=disable +opcache.jit_buffer_size=16M +--CONFLICTS-- +server +--FILE-- +> 2); + } + return $s; +} +function preloaded_str(string $x): string { + return strrev($x) . strlen($x); +} +PHP); + +file_put_contents($dir . '/call.php', <<<'PHP' + +--CLEAN-- + +--EXPECT-- +1502868 olleh5 +bool(true) +1289143 +1502868 olleh5 +1502868 olleh5 From e5ef3a41dcebb5893b14d31dbe62223f81e2a0ac Mon Sep 17 00:00:00 2001 From: Pavel Gushcha Date: Tue, 13 Oct 2026 17:29:48 +0000 Subject: [PATCH 2/2] Address review comments Reuse zend_jit_save_restart_point() in zend_jit_startup(), check dasm_ptr instead of dasm_buf, and describe what the function does in its comment. --- ext/opcache/jit/zend_jit.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 45dc327fac16..47313fd48d66 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3723,10 +3723,7 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) zend_jit_trace_startup(reattached); - zend_jit_unprotect(); - /* save JIT buffer pos */ - dasm_ptr[1] = dasm_ptr[0]; - zend_jit_protect(); + zend_jit_save_restart_point(); } void zend_jit_shutdown(void) @@ -3908,11 +3905,11 @@ void zend_jit_restart(void) } } -/* zend_jit_restart() rewinds the buffer to this point: code generated before it - * (preloaded functions compiled on script load) must survive restarts */ +/* Saves the current JIT buffer position as the one zend_jit_restart() rewinds to, + * so that the code generated so far is kept across restarts */ void zend_jit_save_restart_point(void) { - if (dasm_buf) { + if (dasm_ptr) { zend_jit_unprotect(); dasm_ptr[1] = dasm_ptr[0]; zend_jit_protect();