Repository navigation
Tracing JIT: SIGSEGV in execute_ex (ZEND_INIT_METHOD_CALL) — run_time_cache map_ptr base ~0 on megamorphic calls, regression since 8.5.7 #22443
Description
Description
Description
PHP 8.5.7 ZTS with OPcache tracing JIT crashes with SIGSEGV in short-lived CLI processes on an AMD EPYC 9V74 (Zen 4, AVX-512) host under Azure Container Apps (ACA). The same image, the same php binary, and the same opcache config run fine on an Intel Core Ultra 7 255HX (no AVX-512) dev machine and on a self-managed Linux VM.
The crash is in the interpreter (execute_ex), not in JIT-generated code, at the tail of a trivial run (artisan migrate:modules → prints "Nothing to migrate." twice → segfaults on exit). It is 100% reproducible on the affected host. Disabling only the JIT (keeping opcode caching) eliminates it.
Reproduction
On the ACA container (PHP 8.5.7 ZTS, OPcache tracing JIT on):
$ php artisan migrate:modules; echo "exit=$?"
INFO Nothing to migrate.
INFO Nothing to migrate.
Segmentation fault (core dumped)
exit=139
Same binary/process, only ini flags changed:
| OPcache config | Result |
|---|---|
| opcache ini removed (no OPcache) | ✅ exit 0 |
opcache.enable=1, opcache.jit=tracing, opcache.jit_buffer_size=256M |
❌ SIGSEGV (139) |
opcache.enable=1, opcache.jit=disable, opcache.jit_buffer_size=0 |
✅ exit 0 |
opcache.enable_cli=0 |
✅ exit 0 |
→ The tracing JIT is the trigger; the opcode cache alone is fine.
Same image + identical opcache config across environments:
| Host CPU | AVX-512 | Runtime | Result |
|---|---|---|---|
| AMD EPYC 9V74 (Zen 4) | yes | Azure Container Apps (virtualized) | ❌ SIGSEGV |
| Intel Core Ultra 7 255HX (Arrow Lake) | no | local Docker, bare metal | ✅ works |
| x86-64 VM, Docker Compose | — | self-managed VM | ✅ works |
Crash details
gdb on the deterministic in-container repro (binary is stripped; execute_ex/`zend_exec
Program received signal SIGSEGV, Segmentation fault.
0x000056a6bb3df512 in execute_ex ()
#0 0x000056a6bb3df512 in execute_ex ()
#1 0x000056a6bb3da62d in zend_execute ()
#2 0x000056a6bb448bbe in zend_execute_script ()
#3 0x000056a6bb2d28f4 in php_execute_script_ex ()
#4 0x000056a6bb44aead in ?? ()
#5 0x000056a6bae5fb42 in ?? ()
#6 0x00007a18905ebca8 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#7 0x00007a18905ebd65 in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#8 0x000056a6bae60f41 in _start ()
Faulting instruction and registers:
=> 0x56a6bb3df512 <execute_ex+13762>: cmp (%rdx,%rax,1),%r12
rax 0x8 rdx 0x13949 (80201) rip execute_ex+13762
effective read = rdx + rax*1 = 0x13951 (~80 KB) -> SIGSEGV
%rdx (0x13949 ≈ 80,201) is used as a base pointer but holds a tiny value — every othis a normal 0x56a6…/0x7a18… address. The read at base + 8 ≈ 0x13951 is thesignature of a run-time-cache / zend_map_ptr base that resolved to ~0, so base + slot_offset collapses to just slot_offset (~80 KB). The fault address is consistently in the ~0x138xx–0x139xx range
across runs (deterministic, not random heap corruption).
Working theory: with the tracing JIT enabled, the per-process run-time-cache / map_pt in **freshly-spawned CLI processes on this CPU/runtime**, and the interpreter then dereferences map_ptr_base(≈0) + offset. Disabling JIT (opcode cache still on) avoids it uild (ZTS + JIT is less exercised), and the standout hardware difference between crashingand non-crashing hosts is **AVX-512 / Zen 4** (plus the virtualized Azure host). The faulting instruction itself is a plain cmp(not an AVX instruction), so this looks like a JIT codegen /map_ptr`-setup interaction with the CPU/environment rather than an illegal-instruction fault.
Consistent asymmetry: a long-lived server SAPI (FrankenPHP worker) on the same ACA hosonfig serves requests fine — only freshly-spawned CLI processes crash, matching aper-process map_ptr base set up wrong once per CLI invocation.
Environment
- PHP: 8.5.7 (cli) (built: Jun 24 2026) (ZTS); Zend Engine v4.5.7; Zend OPcache v8.5.7 (
Built by https://github.com/docker-library/php). - OPcache at crash:
enable=1,enable_cli=1,memory_consumption=1024M,max_accele_timestamps=0,jit=tracing,jit_buffer_size=256M. - Image:
dunglas/frankenphp:php8.5(embeds docker-library php 8.5.7 ZTS), amd64. - Crashing host: AMD EPYC 9V74 80-Core (cpu family 25, model 17, stepping 1,
micrposed,hypervisorflag set,bugs: ... srso tsa. Flags includeavx512f avx512dq
avx512bw avx512vl avx512vnni avx512_bf16 avx512vbmi avx512_vbmi2 avx512ifma avx512cd avx51s vpclmulqdq sha_ni`. Runtime: Azure Container Apps. - Non-crashing host (identical image/binary/config): Intel Core Ultra 7 255HX (Arr12**, bare metal, local Docker. Also reproduced clean on a self-managed x86-64 VM.
Limitations / further diagnostics I can run
- I can't
dmesg/inspect the host on ACA (managed runtime); the backtrace is fromgdbo - The
phpbinary is stripped (no symbols pastexecute_ex/zend_execute*). I can rebuild an unstripped /--enable-debugPHP 8.5.7 ZTS and re-run to providebt full,disassemble, and theEG()/CG()
map_ptrfield values — happy to, just say what you'd like. - Three variables differ between crashing and non-crashing hosts (CPU vendor/µarch, AVX-512, virtualization), so I can't isolate the cause from two machines. Isolation tests I can run on the ACA host:
disable glibc's AVX-512 ifunc routines (GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX512F,...) wic memcpy/memmove vs. PHP JIT codegen apart; and try another AVX-512 host outside Azure. - No framework-free minimal repro yet (only reproduces in-app on the affected host, though the workload is trivial — no migrations actually run); I can try to reduce it to a plain
php -rscript.
PHP Version
8.5.7 (ZTS)
Operating System
Linux amd64 — AMD EPYC 9V74 (Zen 4) under Azure Container Apps
PHP Version
8.5.7 (ZTS)
Operating System
No response
Activity
LamentXU123 commentedon Jun 25, 2026
Seems valid (we have some of those known issues #22269) but I don't have the machine to reproduce. Might need to wait for others to help.
jnoordsij commentedon Jun 25, 2026
This seems very similar to the SIGSEGV's I've been observing on my applications with 8.5.7. For reference here's what I've found on my end, which may provide some additional context:
php:8.5-fpm-alpine, which is NTSCache::remembercall, which subsequently uses the Laravel file cache driver, the SIGSEGV appears; thus potentially some file system interaction may be involved hereiliaal commentedon Jun 25, 2026
It would be helpful to get a trace with debug symbols are some idea of the code that can be used to reproduce the issue, right now there is simply not enough info to investigate this further
leonelfranchellimdlz commentedon Jun 25, 2026
Symbolized backtrace + reproduction (debug-symbol build, as requested).
PHP 8.5.7 ZTS built from source with release codegen + DWARF symbols (
CFLAGS="-O2 -g -fno-omit-frame-pointer",--enable-zts --enable-opcache, no--enable-debugso codegen matchesthe crashing release build), running on the affected host (AMD EPYC 9V74, Zen 4, AVX-512, Azure
Container Apps). Crash is in the
ZEND_INIT_METHOD_CALLhandler doing the polymorphic method-callrun-time-cache lookup, under the tracing JIT (hybrid VM).
Diagnosis
The faulting instruction is the cached-scope check
CACHED_PTR(opline->result.num) == called_scope:rdx = 0x13a89(≈ 80 KB) andrax = 0x8, so it dereferences0x13a91.rdxis supposed to be a heappointer (
run_time_cache); instead it holds what looks like the rawzend_map_ptroffset with a baseof ~0 — i.e.
ZEND_MAP_PTR_GET()returnedbase(≈0) + offset. So the per-processmap_ptrbase isunset/zero in this freshly-spawned CLI process when the tracing JIT is enabled.
Symbolized backtrace
Faulting instruction + registers
Reproduction
Not reducible to a standalone script so far — small hot JIT loops and even ~5000 distinct method-call
sites run clean on the same host; it only triggers under a full framework boot (a Laravel app), which
allocates a large number of distinct method-call run-time-cache slots. Trivially deterministic there:
Toggle (same binary, same process, only ini changed):
jit=tracing,jit_buffer_size=256Mjit=disable,jit_buffer_size=0(opcode cache still on)opcache.enable_cli=0opcache config at crash:
enable=1 enable_cli=1 memory_consumption=1024M max_accelerated_files=20000 validate_timestamps=0 jit=tracing jit_buffer_size=256M.Environment
dunglas/frankenphp:php8.5(Debian/glibc, amd64).avx512f/dq/bw/vl/vnni/bf16/vbmi/...),hypervisor, under Azure Container Apps. Short-lived CLI processes crash; the long-lived FrankenPHPserver SAPI on the same host with the same JIT config serves fine (per-process
map_ptrset up once).iliaal commentedon Jun 25, 2026
The hardware angle is probably a red herring. @jnoordsij above hits the same crash on an NTS alpine image on unrelated hardware (DO k8s), so this looks like a tracing-JIT regression in 8.5.7, not anything AVX-512 or Azure specific. 8.5.6 and 8.4 both working line up with that.
A few things that would pin it down:
Can you test current
PHP-8.5branch HEAD, not 8.5.8RC1? There's a JIT run-time-cache fix (Tracing JIT can dispatch the observer "begin" handler through the wrong run_time_cache slot (NULL deref) on megamorphic calls #22158) that landed after RC1 and isn't in any release yet. If your crash is gone on the branch, that's the answer.Is any observer/APM extension loaded in the crashing process (ddtrace, Sentry profiler, Tideways, etc.)? The Tracing JIT can dispatch the observer "begin" handler through the wrong run_time_cache slot (NULL deref) on megamorphic calls #22158 fix only matters if the fcall observer API is in use, so this tells us whether it applies to you.
Cheap one to bury the AVX theory: run the crashing binary with
GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX512F,-AVX512VLand see if it still crashes.The faulting value (a
run_time_cachebase that resolves to a small offset, around 80 KB) points at the map_ptr base for that frame, but nothing in the8.5.6..8.5.7range touches that code directly, so it's an indirect JIT codegen regression. I haven't been able to reduce it to a standalone script yet, even with large synthetic megamorphic method-call workloads under tracing JIT, so a smaller repro from your side would help a lot.leonelvsc commentedon Jun 25, 2026
I put together a self-contained, one-command reproduction
👉 https://github.com/leonelvsc/php85-tracing-jit-segfault
Reproduce against the current 8.5 release (
php:8.5-cli)Symbolized backtrace on PHP-8.5 branch HEAD (built with
-g+ gdb)docker build -f Dockerfile.debug -t php85-jit-repro-debug . docker run --rm php85-jit-repro-debugControl (proves it's the JIT): same image,
opcache.jit=disable→ exits cleanly.docker run --rm php85-jit-repro php -dopcache.jit=disable artisan repro # done, exit 0What it is
The whole trigger is a hot loop of trivial
DB::select('select N as n')queries (in-memory SQLite),in a stock Laravel app that has
sentry/sentry-laravelinitialized (any DSN — a fake/invalid one isenough; no real account, network, or API call). Under
opcache.jit=tracingthe process SIGSEGVs; underopcache.jit=disableit's clean.Dockerfile.debugcompilesgit clone --branch PHP-8.5 php-srcwith-O2 -g(unstripped; not--enable-debug, to keep release codegen) and runs the repro under gdb:%rdxis therun_time_cachebase fromZEND_MAP_PTR_GET(); instead of a heap pointer it holds a tinyvalue (~64 KB — just the slot offset), i.e. the
map_ptrbase for that frame resolved to ~0, sobase + slot_offsetfaults. Deterministic, and identical value across machines/runs.Already ruled out
Zend OPcacheis loaded; no ddtrace/Tideways/New Relic/Excimer. So the fcall-observer path isn't what's hit here.loop, no Sentry) does not crash.
Happy to run anything else on the affected setup. Hope it's useful!
[-]SIGSEGV in execute_ex with tracing JIT on PHP 8.5.7 ZTS, AMD EPYC (Zen 4 / AVX-512) under Azure Container Apps — run-time-cache / zend_map_ptr base resolves to ~0[/-][+]Tracing JIT: SIGSEGV in execute_ex (ZEND_INIT_METHOD_CALL) — run_time_cache map_ptr base ~0 on megamorphic calls, regression since 8.5.7[/+]iliaal commentedon Jun 25, 2026
Awesome thank you, I tracked the issue down now, so will have a PR soon ;-)
JIT: Fix run_time_cache offset stored without map_ptr dereference
leonelvsc commentedon Jun 25, 2026
#22459 verified , thanks ! hope it lands soon
JIT: Fix run_time_cache offset stored without map_ptr dereference