php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81256 Assertion `zv != ((void *)0)' failed for "preload" with JIT
Submitted: 2021-07-14 03:49 UTC Modified: 2021-07-20 09:52 UTC
From: hao dot sun at arm dot com Assigned:
Status: Closed Package: JIT
PHP Version: 8.1.0alpha3 OS: Ubuntu 20.04
Private report: No CVE-ID: None
 [2021-07-14 03:49 UTC] hao dot sun at arm dot com
Description:
------------
I suppose tracing JIT is tested by the "community_job.yml" in Azure pipeline in upstream. I further tested with functional JIT.

This bug was found when running the "Symfony demo" with functional JIT, i.e. opcache.jit=1205, in NTS+DEBUG+HYBRID+ASAN.

Note: this bug only occurred in HYBRID VM mode, not CALL VM mode.
Note: this bug occurred in both JIT/arm64 and JIT/x86.
Note: this bug occurred with ZEND_JIT_LEVEL_OPT_SCRIPT optimization level. If we run the test with "opcache.jit=1255", this bug showed up as well.

Here is the error msg:
# php -d opcache.preload=var/cache/dev/App_KernelDevDebugContainer.preload.php public/index.php
php: /opt/php-8.1.0alpha3/Zend/zend_vm_execute.h:62757: zend_get_opcode_handler_func: Assertion `zv != ((void *)0)' failed.


One child process would be created to preload the scripts (See function accel_finish_startup()) and zend_jit_script() would be invoked under ZEND_JIT_LEVEL_OPT_SCRIPT.
The assertion occurs when generating the JIT code.

Here the backtrace info for this child process.

(gdb) bt
#0  0x00007ffff568a18b in raise () from /usr/lib/x86_64-linux-gnu/libc.so.6
#1  0x00007ffff5669859 in abort () from /usr/lib/x86_64-linux-gnu/libc.so.6
#2  0x00007ffff5669729 in ?? () from /usr/lib/x86_64-linux-gnu/libc.so.6
#3  0x00007ffff567af36 in __assert_fail () from /usr/lib/x86_64-linux-gnu/libc.so.6
#4  0x0000555556f9b22e in zend_get_opcode_handler_func (op=0x41af3f48) at /opt/php-8.1.0alpha3/Zend/zend_vm_execute.h:62757
#5  0x00007fffefcd55eb in zend_jit_handler (Dst=0x7fffffffdc60, opline=0x41af3f48, may_throw=1) at /opt/php-8.1.0alpha3/ext/opcache/jit/zend_jit_x86.dasc:3649
#6  0x00007fffefda686d in zend_jit (op_array=0x41af3d78, ssa=0x6120001659e8, rt_opline=0x0) at /opt/php-8.1.0alpha3/ext/opcache/jit/zend_jit.c:4040
#7  0x00007fffefe1d8c5 in zend_jit_script (script=0x41b01e40) at /opt/php-8.1.0alpha3/ext/opcache/jit/zend_jit.c:4511
#8  0x00007fffefc5d4c2 in zend_accel_script_persist (script=0x41b01e40, for_shm=1) at /opt/php-8.1.0alpha3/ext/opcache/zend_persist.c:1379
#9  0x00007fffefc34619 in preload_script_in_shared_memory (new_persistent_script=0x615000069800) at /opt/php-8.1.0alpha3/ext/opcache/ZendAccelerator.c:4423
#10 0x00007fffefc38442 in accel_preload (config=0x607000000b98 "var/cache/dev/App_KernelDevDebugContainer.preload.php", in_child=true)
    at /opt/php-8.1.0alpha3/ext/opcache/ZendAccelerator.c:4840
#11 0x00007fffefc395c1 in accel_finish_startup () at /opt/php-8.1.0alpha3/ext/opcache/ZendAccelerator.c:5038
#12 0x00007fffefc2b39b in accel_post_startup () at /opt/php-8.1.0alpha3/ext/opcache/ZendAccelerator.c:3309
#13 0x0000555556dae1d3 in zend_post_startup () at /opt/php-8.1.0alpha3/Zend/zend.c:1078
#14 0x0000555556c46c9c in php_module_startup (sf=0x5555588bd720 <cli_sapi_module>, additional_modules=0x0, num_additional_modules=0) at /opt/php-8.1.0alpha3/main/main.c:2277
#15 0x0000555557166405 in php_cli_startup (sapi_module=0x5555588bd720 <cli_sapi_module>) at /opt/php-8.1.0alpha3/sapi/cli/php_cli.c:409
#16 0x000055555716aa36 in main (argc=4, argv=0x604000000250) at /opt/php-8.1.0alpha3/sapi/cli/php_cli.c:1333



Test script:
---------------
Following the "community_job.xml", download "Symfony" test case, prepare the "Symfony_demo" and 

run "php -d opcache.preload=var/cache/dev/App_KernelDevDebugContainer.preload.php public/index.php"

The version of Symfony I used is 

commit 39e9d75c95598d6e09428f201f6f1921a40503f5
Author: Nicolas Grekas <nicolas.grekas@gmail.com>
Date:   Mon Jul 12 16:15:46 2021 +0200

    Merge branch '5.3' into 5.4
    
    * 5.3:
      skip Bootstrap 4 tests that do not apply to the Bootstrap 5 form theme


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-07-15 09:40 UTC] hao dot sun at arm dot com
I'd like to share some of my debugging results. Hope that this info could help to diagnose this bug.

I suspected this is because the same function is compiled by JIT for two times.

In the first time, the handler of one opline(in my test, the opcode is #164, and is in function trigger_deprecation() at file symfony_demo/vendor/symfony/deprecation-contracts/function.php) is replaced by the address of JIT-ed code.

Hence, in the second time, during generating the JIT code, the original handler of this opline, i.e. the interpreter handler, is needed by zend_jit_handler(), however, it's not a valid interpreter handler any longer, resulting in the failure in function zend_get_opcode_handler_func().

"Preload" mainly works in function accel_preload().
1) it firstly parses all functions and classes using the filename "$PRELOAD$. See https://github.com/php/php-src/blob/php-8.1.0alpha3/ext/opcache/ZendAccelerator.c#L4780-L4823
2) and then each script would be parsed. See https://github.com/php/php-src/blob/php-8.1.0alpha3/ext/opcache/ZendAccelerator.c#L4836-L4841

In my debugging, I found one opline is parsed by step 1) and step 2) respectively.
 [2021-07-17 15:05 UTC] hao dot sun at arm dot com
Here is one workaround, disabling the JIT process for opcache.jit=1205 if one function has already been preloaded before.
But I don't think it's a good fix. I guess we should add some check in the "preload" module to filter out duplicate functions.

Besides, this workaround only works for jit=1205. 
We should take care of other trigger options, i.e. 1215, 1225, 1235 and 1255. In my local test, if we add similar check before https://github.com/php/php-src/blob/master/ext/opcache/jit/zend_jit.c#L4470, use after free error would be raised.



diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index d700004442..26e5a6d762 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -4514,6 +4514,9 @@ ZEND_EXT_API int zend_jit_script(zend_script *script)
                for (i = 0; i < call_graph.op_arrays_count; i++) {
                        info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
                        if (info) {
+                               if (call_graph.op_arrays[i]->fn_flags & ZEND_ACC_PRELOADED) {
+                                       continue;
+                               }
                                if (zend_jit(call_graph.op_arrays[i], &info->ssa, NULL) != SUCCESS) {
                                        goto jit_failure;
                                }
 [2021-07-20 09:52 UTC] dmitry@php.net
The problem is caused by attempt to JIT the same op_array, wrapped by "if", twice.

The simplified test-case:

preload.php
===========
<?php
$a = true;
if ($a) {
        function foo(...$arg) {
                echo "Hello\n";
        }
}
?>

$ php -dopcache.preload=preload.php -d opcache.jit=1205 -r 'foo();'
php: /home/dmitry/php/php-master/Zend/zend_vm_execute.h:62562: zend_get_opcode_handler_func: Assertion `zv != ((void *)0)' failed.
Aborted (core dumped)
 [2021-07-20 12:28 UTC] git@php.net
Automatic comment on behalf of dstogov
Revision: https://github.com/php/php-src/commit/1e4095f03dea465dc1f2e04861a0e9422bdcb6f9
Log: Fixed bug #81256 (Assertion `zv != ((void *)0)' failed for &quot;preload&quot; with JIT)
 [2021-07-20 12:28 UTC] git@php.net
-Status: Open +Status: Closed
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Sun Nov 28 09:03:14 2021 UTC