From 23c710f719c22430f8dccd306f090f8a905626b0 Mon Sep 17 00:00:00 2001 From: Pavel Gushcha Date: Fri, 9 Oct 2026 18:09:43 +0000 Subject: [PATCH] Fix JIT stub veneers being reused after an OPcache restart on AArch64 On AArch64 a branch from JIT code to a stub may be out of range, and IR emits a veneer next to the new code. zend_jit_commit_veneers() then replaces the address of the stub in zend_jit_stub_handlers[] by the address of the veneer, so that later code reuses it. These veneers live after the JIT restart point: zend_jit_restart() rewinds the buffer below them, but zend_jit_stub_handlers[] keeps pointing to them. Code compiled after the restart branches to the old addresses, which are overwritten by new traces, and crashes (e.g. on an exception or when leaving a function through a stub). This needs more than 1MB of JIT code (the range of conditional branches) before the restart. Keep the original stub addresses after the pending veneers and restore them in zend_jit_restart(). --- ext/opcache/jit/zend_jit.c | 7 +++++-- ext/opcache/jit/zend_jit_ir.c | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index a958a2453c4e..e43f04f6be10 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3702,8 +3702,8 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) *dasm_ptr = (void**)*dasm_ptr + sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0]); #elif defined(IR_TARGET_AARCH64) zend_jit_stub_handlers = dasm_buf; - *dasm_ptr = (void**)*dasm_ptr + (sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0])) * 2; - memset(zend_jit_stub_handlers, 0, (sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0])) * 2 * sizeof(void*)); + *dasm_ptr = (void**)*dasm_ptr + (sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0])) * 3; + memset(zend_jit_stub_handlers, 0, (sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0])) * 3 * sizeof(void*)); #endif *dasm_ptr = (void*)ZEND_MM_ALIGNED_SIZE_EX(((size_t)(*dasm_ptr)), 16); zend_jit_protect(); @@ -3889,6 +3889,9 @@ void zend_jit_restart(void) /* restore JIT buffer pos */ dasm_ptr[0] = dasm_ptr[1]; +#if defined(IR_TARGET_AARCH64) + zend_jit_restore_stub_handlers(); +#endif zend_jit_trace_restart(); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 50851d75abb5..8e5678fee492 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -449,6 +449,24 @@ static void zend_jit_commit_veneers(void) } } } + +/* zend_jit_commit_veneers() replaces the address of a stub by the address of a veneer emitted + * after it. zend_jit_restart() rewinds the buffer below such veneers, so it restores the + * original addresses, kept after the pending veneers. */ +static void zend_jit_save_stub_handlers(void) +{ + int count = sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0]); + + memcpy(zend_jit_stub_handlers + count * 2, zend_jit_stub_handlers, count * sizeof(void*)); +} + +static void zend_jit_restore_stub_handlers(void) +{ + int count = sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0]); + + memcpy(zend_jit_stub_handlers, zend_jit_stub_handlers + count * 2, count * sizeof(void*)); + memset(zend_jit_stub_handlers + count, 0, count * sizeof(void*)); +} #endif static bool zend_jit_prefer_const_addr_load(zend_jit_ctx *jit, uintptr_t addr) @@ -2969,6 +2987,9 @@ static void zend_jit_setup_stubs(void) } zend_jit_free_ctx(&jit); } +#if defined(IR_TARGET_AARCH64) + zend_jit_save_stub_handlers(); +#endif } #define REGISTER_HELPER(n) \