Skip to content

JIT Bug: EXC_BAD_ACCESS on ARM64 with property hooks + Fibers #21359

@azjezz

Description

@azjezz

Description

The following code:

<?php
/**
 * JIT bus error reproduction — PHP 8.5.2, arm64, opcache.jit=tracing
 *
 * Run:
 *   php -dopcache.enable_cli=1 -dopcache.jit=tracing -dopcache.jit_buffer_size=64M jit-repro.php
 *
 * Requires: azjezz/psl 5.1.0
 *
 * Crashes ~40% of the time on arm64 with JIT tracing mode.
 * The JIT miscompiles ZEND_FETCH_OBJ_FUNC_ARG on a property hook,
 * returning `call_leave_op` (a data-segment pointer in __DATA_CONST)
 * as the next opcode handler. ARM64 W^X policy prevents executing
 * data pages → EXC_BAD_ACCESS (code=2).
 */
declare(strict_types=1);

require __DIR__ . '/../../vendor/autoload.php';

use Psl\Async;
use Psl\IO;
use Psl\TLS;

enum Kind: int { case A = 1; }

final class Rec {
    public Kind $kind { get => Kind::A; }
    public function __construct(public readonly string $name) {}
}

const Q = "\x00\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\x01\x00\x01";

function dnsq(string $srv, string $name): array {
    $c = TLS\connect($srv, 853);
    $c->writeAll(pack('n', strlen(Q)) . Q);
    $c->readAll(unpack('n', $c->readAll(2))[1]);
    $c->close();
    return [new Rec($name)];
}

function race(string $n): array {
    return Async\first(array_map(fn($srv) => Async\run(fn() => dnsq($srv, $n)), ['1.1.1.1', '8.8.8.8']));
}

$tasks = [];
foreach (['a.com', 'b.com', 'c.com'] as $d) {
    $tasks[$d] = Async\run(static fn() => Async\concurrently([
        0 => fn() => race($d), 1 => fn() => race($d), 2 => fn() => race($d),
    ]));
}
foreach (Async\all($tasks) as $responses) {
    foreach ($responses[0] as $r) { IO\write_line('[%s]: %s', $r->name, $r->kind->name); }
    foreach ($responses[1] as $r) { IO\write_line('[%s]: %s', $r->name, $r->kind->name); }
    foreach ($responses[2] as $r) { IO\write_line('[%s]: %s', $r->name, $r->kind->name); }
}
echo "OK\n";

Resulted in this output:

[a.com]: A
[a.com]: A
[a.com]: A
[b.com]: A
[b.com]: A
[b.com]: A
[c.com]: A
[1]    8610 bus error  php -dopcache.enable_cli=1 -dopcache.jit=tracing -dopcache.jit_buffer_size=64

But I expected this output instead:

[a.com]: A
[a.com]: A
[a.com]: A
[b.com]: A
[b.com]: A
[b.com]: A
[c.com]: A
[c.com]: A
[c.com]: A
OK

Crash: EXC_BAD_ACCESS (code=2, address=0x101409048) at call_leave_op
Crash site: Line 36 - IO\write_line('[%s]: %s', $r->name, $r->kind->name) where $r->kind is a property hook (public Kind $kind { get => Kind::A; })

Stack trace:

➜  leaf git:(main) ✗ lldb php examples/dns/jit-repro.php
(lldb) target create "php"
Current executable set to '/Users/azjezz/.phpbrew/php/8.5.2/bin/php' (arm64).
(lldb) settings set -- target.run-args  "examples/dns/jit-repro.php"
(lldb) run
Process 14499 launched: '/Users/azjezz/.phpbrew/php/8.5.2/bin/php' (arm64)
[a.com]: A
[a.com]: A
[a.com]: A
[b.com]: A
[b.com]: A
[b.com]: A
[c.com]: A
[c.com]: A
[c.com]: A
OK
Process 14499 exited with status = 0 (0x00000000)
(lldb) run
Process 14550 launched: '/Users/azjezz/.phpbrew/php/8.5.2/bin/php' (arm64)
[a.com]: A
[a.com]: A
[a.com]: A
[b.com]: A
[b.com]: A
[b.com]: A
[c.com]: A
[c.com]: A
Process 14550 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x101409048)
    frame #0: 0x0000000101409048 php`call_leave_op
php`call_leave_op:
->  0x101409048 <+0>:  <unknown>
    0x10140904c <+4>:  udf    #0x1
    0x101409050 <+8>:  udf    #0x0
    0x101409054 <+12>: udf    #0x0
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x101409048)
  * frame #0: 0x0000000101409048 php`call_leave_op
    frame #1: 0x000000010061cf44 php`execute_ex(ex=<unavailable>) at zend_vm_execute.h:116172:12 [opt]
    frame #2: 0x000000010062f32c php`zend_execute.cold.1 at zend_vm_execute.h:121884:2 [opt]
    frame #3: 0x00000001004eab18 php`zend_execute(op_array=<unavailable>, return_value=<unavailable>) at zend_vm_execute.h:121867:48 [opt]
    frame #4: 0x00000001005c5830 php`zend_execute_script(type=8, retval=0x0000000000000000, file_handle=0x000000016fdfe140) at zend.c:1977:3 [opt]
    frame #5: 0x0000000100437d50 php`php_execute_script_ex(primary_file=0x000000016fdfe140, retval=0x0000000000000000) at main.c:2641:13 [opt]
    frame #6: 0x0000000100437eac php`php_execute_script(primary_file=<unavailable>) at main.c:2681:9 [opt] [artificial]
    frame #7: 0x00000001005c7e0c php`do_cli(argc=<unavailable>, argv=<unavailable>) at php_cli.c:951:5 [opt]
    frame #8: 0x00000001005c6af8 php`main(argc=<unavailable>, argv=0x0000000102f9c9b0) at php_cli.c:1362:18 [opt]
    frame #9: 0x000000019b551d54 dyld`start + 7184
(lldb)

PHP Version

PHP 8.5.2 (cli) (built: Feb 11 2026 07:55:48) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.2, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.2, Copyright (c), by Zend Technologies

Operating System

MacOS / ARM64

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions