Skip to content

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

Closed

Description

@leonelfranchellimdlz

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 include avx512f 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 from gdb o
  • The php binary is stripped (no symbols past execute_ex/zend_execute*). I can rebuild an unstripped / --enable-debug PHP 8.5.7 ZTS and re-run to provide bt full, disassemble, and the EG()/CG()
    map_ptr field 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 -r script.

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

  1. LamentXU123 commented on Jun 25, 2026

    @LamentXU123
    Member

    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.

  2. jnoordsij commented on Jun 25, 2026

    @jnoordsij

    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:

    • this is a regression compared to 8.5.6, which works fine; I've also just confirmed it is still present on 8.5.8RC1
    • I've observed this on an image derived from the Docker image php:8.5-fpm-alpine, which is NTS
    • the issue only occurs when I run it within the scope of my Digital Ocean Kubernetes cluster (unsure about actual CPU model), but does not on my local machine
    • most things for my Laravel apps work fine, but whenever I hit a Cache::remember call, which subsequently uses the Laravel file cache driver, the SIGSEGV appears; thus potentially some file system interaction may be involved here
  3. iliaal commented on Jun 25, 2026

    @iliaal
    Member

    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

  4. leonelfranchellimdlz commented on Jun 25, 2026

    @leonelfranchellimdlz
    Author

    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-debug so codegen matches
    the 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_CALL handler doing the polymorphic method-call
    run-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:

    mov    0x40(%r14),%rdx          ; rdx = run_time_cache base  (ZEND_MAP_PTR_GET(op_array.run_time_cache))
    mov    0x10(%r13),%r12          ; r12 = called_scope
    => cmp  (%rdx,%rax,1),%r12       ; read [run_time_cache + 0x8]  -> SIGSEGV
    

    rdx = 0x13a89 (≈ 80 KB) and rax = 0x8, so it dereferences 0x13a91. rdx is supposed to be a heap
    pointer (run_time_cache); instead it holds what looks like the raw zend_map_ptr offset with a base
    of ~0
    — i.e. ZEND_MAP_PTR_GET() returned base(≈0) + offset. So the per-process map_ptr base is
    unset/zero in this freshly-spawned CLI process when the tracing JIT is enabled.

    Symbolized backtrace

    Program received signal SIGSEGV, Segmentation fault.
    ZEND_INIT_METHOD_CALL_SPEC_UNUSED_CONST_HANDLER () at Zend/zend_vm_execute.h:35866
    35866   in Zend/zend_vm_execute.h
    
    #0  ZEND_INIT_METHOD_CALL_SPEC_UNUSED_CONST_HANDLER () at Zend/zend_vm_execute.h:35866
            fbc = 0x6085add39260
            called_scope = 0x6085add38af8
            obj = 0x77cb713b16c0
            function_name = 0x0
    #1  execute_ex (ex=0x6085af9163a0) at Zend/zend_vm_execute.h:120138
    #2  zend_execute (op_array=0x77cb75c98000, return_value=0x0) at Zend/zend_vm_execute.h:121944
    #3  zend_execute_script (type=8, retval=0x0, file_handle=0x7fffd96c7ff0) at Zend/zend.c:1978
    #4  php_execute_script_ex (primary_file=..., retval=0x0) at main/main.c:2641
    #5  php_execute_script (primary_file=...) at main/main.c:2681
    #6  do_cli (argc=3, argv=...) at sapi/cli/php_cli.c:951
    #7  main (argc=3, argv=...) at sapi/cli/php_cli.c:1374
    

    Faulting instruction + registers

    => 0x...<execute_ex+20311>: cmp    (%rdx,%rax,1),%r12
       0x...<execute_ex+20315>: jne    <execute_ex+28238>
       0x...<execute_ex+20321>: mov    0x8(%rdx,%rax,1),%rbx
    
    rax 0x8        rdx 0x13a89 (80521)     r12 0x6085add38af8 (called_scope)
    r13 0x77cb713b16c0   r14 0x77cb75c13a30 (execute_data)   rip <execute_ex+20311>
    ; AVX-512 mask regs populated: k0=0x80000000 k1=0xff k2=0x7ff   (ZTS: fs_base set)
    

    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:

    $ php artisan migrate:modules
       INFO  Nothing to migrate.
       INFO  Nothing to migrate.
    Segmentation fault (core dumped)     # exit 139
    

    Toggle (same binary, same process, only ini changed):

    opcache result
    jit=tracing, jit_buffer_size=256M ❌ SIGSEGV
    jit=disable, jit_buffer_size=0 (opcode cache still on) ✅ exit 0
    opcache.enable_cli=0 ✅ exit 0
    PHP 8.4 (same app/config) ✅ exit 0

    opcache 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

    • PHP 8.5.7 (cli) (ZTS); Zend Engine v4.5.7; Zend OPcache v8.5.7.
    • Built on dunglas/frankenphp:php8.5 (Debian/glibc, amd64).
    • Crashes: AMD EPYC 9V74 (cpu family 25, model 17), AVX-512 (avx512f/dq/bw/vl/vnni/bf16/vbmi/...),
      hypervisor, under Azure Container Apps. Short-lived CLI processes crash; the long-lived FrankenPHP
      server SAPI on the same host with the same JIT config serves fine (per-process map_ptr set up once).
    • Does NOT crash: Intel Core Ultra 7 255HX (no AVX-512), bare metal, same image/binary/config.
  5. iliaal commented on Jun 25, 2026

    @iliaal
    Member

    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:

    1. Can you test current PHP-8.5 branch 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.

    2. 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.

    3. Cheap one to bury the AVX theory: run the crashing binary with GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX512F,-AVX512VL and see if it still crashes.

    The faulting value (a run_time_cache base that resolves to a small offset, around 80 KB) points at the map_ptr base for that frame, but nothing in the 8.5.6..8.5.7 range 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.

  6. leonelvsc commented on Jun 25, 2026

    @leonelvsc

    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)

    docker build -t php85-jit-repro .
    docker run --rm php85-jit-repro
    #   done 5000 queries
    #   Segmentation fault (core dumped)   ->  exit 139

    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-debug

    Control (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 0

    What 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-laravel initialized (any DSN — a fake/invalid one is
    enough; no real account, network, or API call). Under opcache.jit=tracing the process SIGSEGVs; under
    opcache.jit=disable it's clean.

    Dockerfile.debug compiles git clone --branch PHP-8.5 php-src with -O2 -g (unstripped; not
    --enable-debug, to keep release codegen) and runs the repro under gdb:

    PHP 8.5.9-dev (cli) (built ...) (NTS)        # PHP-8.5 branch HEAD, commit 19441489f08dbf0ef7083c46df080ef28ecc6aeb
    
    Program received signal SIGSEGV, Segmentation fault.
    ZEND_INIT_METHOD_CALL_SPEC_UNUSED_CONST_HANDLER () at Zend/zend_vm_execute.h:35875
    #0  ZEND_INIT_METHOD_CALL_SPEC_UNUSED_CONST_HANDLER ()  Zend/zend_vm_execute.h:35875
    #1  execute_ex (ex=...)                                  Zend/zend_vm_execute.h:120168
    #2  zend_execute (op_array=..., return_value=0x0)        Zend/zend_vm_execute.h:121974
    #3  zend_execute_script (type=8, ...)                    Zend/zend.c:1978
    ...
    
    => <execute_ex+19573>: cmp (%rdx,%rax,1),%r12     ; rax=0x8  rdx=0x10939 (~64 KB)  ->  [rdx+8] SIGSEGV
    

    %rdx is the run_time_cache base from ZEND_MAP_PTR_GET(); instead of a heap pointer it holds a tiny
    value (~64 KB — just the slot offset), i.e. the map_ptr base for that frame resolved to ~0, so
    base + slot_offset faults. Deterministic, and identical value across machines/runs.

    Already ruled out

    Happy to run anything else on the affected setup. Hope it's useful!

  7. changed the title [-]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[/+] on Jun 25, 2026
  8. iliaal commented on Jun 25, 2026

    @iliaal
    Member

    Awesome thank you, I tracked the issue down now, so will have a PR soon ;-)

  9. added a commit that references this issue on Jun 25, 2026
    f4d3234
  10. leonelvsc commented on Jun 25, 2026

    @leonelvsc

    #22459 verified , thanks ! hope it lands soon

  11. added a commit that references this issue on Jun 26, 2026
    a0ddc2a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions