View Issue Details

IDProjectCategoryView StatusLast Update
0002332XdebugCode Coveragepublic2025-03-27 13:26
Reporterclue Assigned Toderick  
PrioritynormalSeveritycrashReproducibilityalways
Status confirmedResolutionopen 
Product Version3.4.2 
Summary0002332: Segmentation fault for code coverage with nested fibers
Description

A segmentation fault happens when getting code coverage information when using nested fibers (PHP 8.1+). In particular, this is currently noticeable when using PHPUnit with code coverage report for any project using ReactPHP v3+. Using async fibers is one of the core features of ReactPHP v3+, so this currently affects a larger number of projects (fortunately only) during development.

We've started seeing these segfaults consistently a couple of weeks ago and could track this down to a segfault that only happens as of Xdebug 3.4.2 and none of the earlier versions. In other words, I don't think we've introduced any other new code that would trigger this behavior and these errors started showing up seemingly randomly in newer test runs.

I can consistently reproduce the exact same segmentation fault on any PHP version that supports fibers (PHP 8.1, PHP 8.2, PHP 8.3 and PHP 8.4) across multiple operating systems (Linux x64 and Windows x64 at least).

The segmentation fault only occurs with latest Xdebug 3.4.2, it does not occur in any earlier versions. Taking a look at the diff between 3.4.1 and 3.4.2 (https://github.com/xdebug/xdebug/compare/3.4.1...3.4.2), I believe this might be related to https://github.com/xdebug/xdebug/commit/97719d96600e29b98dbe3848685e300b03d35266 and earlier https://github.com/xdebug/xdebug/commit/d3a8a6b0d6feac962187825479238c00f71e48eb.

Thank you for your excellent work on Xdebug!

Steps To Reproduce

The provided script crashes consistently on PHP 8.1+ when code coverage is enabled:

$ php -d zend_extension=xdebug.so -d xdebug.mode=coverage fibers.php 
bool(true)
Segmentation fault (core dumped)

The provided script is the minimal reproducible example I could come up with and does indeed look quite contrived. However, it does not crash when Xdebug or code coverage is disabled or when using an earlier Xdebug 3.4.1.

A more realistic example would be the test suite of reactphp/async v4 (or pretty much any project that depends on it):

$ git clone https://github.com/reactphp/async.git
$ cd async
$ composer install
$ php -d zend_extension=xdebug.so -d xdebug.mode=coverage vendor/bin/phpunit --coverage-text
…
.....Segmentation fault (core dumped)
Additional Information

Here's the minimal Docker environment to reproduce this:

$ docker run -it --rm -v `pwd`:/data --workdir /data --entrypoint bash php:8.1 # same problem for later versions
$ pecl install xdebug-3.4.2 # works for earlier versions

$ php -d zend_extension=xdebug.so -v
PHP 8.1.28 (cli) (built: May 14 2024 13:38:19) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.28, Copyright (c) Zend Technologies
    with Xdebug v3.4.2, Copyright (c) 2002-2025, by Derick Rethans
$ apt install gdb -yq
$ gdb --args php -d zend_extension=xdebug.so -d xdebug.mode=coverage fibers.php
GNU gdb (Debian 13.1-3) 13.1
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...
(No debugging symbols found in php)
(gdb) run
Starting program: /usr/local/bin/php -d zend_extension=xdebug.so -d xdebug.mode=coverage fibers.php
warning: Error disabling address space randomization: Operation not permitted
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
bool(true)

Program received signal SIGSEGV, Segmentation fault.
0x00007a08a6074f99 in xdebug_create_key_for_path (path=path@entry=0x62e7bdd11a40, str=str@entry=0x7a08a5e00c40) at /tmp/pear/temp/xdebug/src/coverage/branch_info.c:313
313     /tmp/pear/temp/xdebug/src/coverage/branch_info.c: No such file or directory.
(gdb) bt full
#0  0x00007a08a6074f99 in xdebug_create_key_for_path (path=path@entry=0x62e7bdd11a40, str=str@entry=0x7a08a5e00c40) at /tmp/pear/temp/xdebug/src/coverage/branch_info.c:313
        i = 0
        temp_nr = "\240\367\320\275\347b\000\000\002\000\000\000\000\000\000"
#1  0x00007a08a6076fd2 in xdebug_code_coverage_end_of_function (op_array=<optimized out>, filename=filename@entry=0x62e7bdd08980, function_name=function_name@entry=0x62e7bdd0f600 "{closure:/data/fibers.php:5-8}")
    at /tmp/pear/temp/xdebug/src/coverage/code_coverage.c:730
        str = {l = 0, a = 0, d = 0x0}
        path = 0x62e7bdd11a40
#2  0x00007a08a60775ac in xdebug_coverage_execute_ex_end (fse=<optimized out>, op_array=<optimized out>, tmp_filename=0x62e7bdd08980, tmp_function_name=0x62e7bdd0f600 "{closure:/data/fibers.php:5-8}")
    at /tmp/pear/temp/xdebug/src/coverage/code_coverage.c:1072
No locals.
#3  0x00007a08a606145b in xdebug_execute_user_code_end (execute_data=0x62e7bdd13de0, retval=0x62e7bdd13148) at /tmp/pear/temp/xdebug/src/base/base.c:814
        op_array = 0x62e7bdbdf848
        fse = 0x62e7bdd0d8e0
#4  0x000062e7a33c88e6 in zend_observer_fcall_end ()
No symbol table info available.
#5  0x000062e7a303a77c in ?? ()
No symbol table info available.
#6  0x000062e7a3397b63 in execute_ex ()
No symbol table info available.
#7  0x000062e7a33252bf in zend_call_function ()
No symbol table info available.
#8  0x000062e7a33ca53d in ?? ()
No symbol table info available.
#9  0x000062e7a33cb4ce in ?? ()
No symbol table info available.
#10 0x000062e7a32ccedf in make_fcontext ()
No symbol table info available.
Backtrace stopped: Cannot access memory at address 0x7a08a5e01000
$ apt install valgrind -yq
$ valgrind php -d zend_extension=xdebug.so -d xdebug.mode=coverage fibers.php > valgrind.log 2>&1
TagsNo tags attached.
Attached Files
valgrind.log (7,841 bytes)   
==8169== Memcheck, a memory error detector
==8169== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==8169== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==8169== Command: php -d zend_extension=xdebug.so -d xdebug.mode=coverage fibers.php
==8169== 
==8169== Warning: client switching stacks?  SP change: 0x1ffeffcd28 --> 0x8d12fb8
==8169==          to suppress, use: --max-stackframe=137274236272 or greater
==8169== Warning: client switching stacks?  SP change: 0x8d12b88 --> 0x8f13fb8
==8169==          to suppress, use: --max-stackframe=2102320 or greater
==8169== Warning: client switching stacks?  SP change: 0x8f13ee8 --> 0x8d12b88
==8169==          to suppress, use: --max-stackframe=2102112 or greater
==8169==          further instances of this message will not be shown.
bool(true)
==8169== Invalid read of size 8
==8169==    at 0x8633FBD: xdebug_code_coverage_end_of_function (code_coverage.c:729)
==8169==    by 0x86345AB: xdebug_coverage_execute_ex_end (code_coverage.c:1072)
==8169==    by 0x861E45A: xdebug_execute_user_code_end (base.c:814)
==8169==    by 0x6D08E5: zend_observer_fcall_end (in /usr/local/bin/php)
==8169==    by 0x34277B: ??? (in /usr/local/bin/php)
==8169==    by 0x69FB62: execute_ex (in /usr/local/bin/php)
==8169==    by 0x62D2BE: zend_call_function (in /usr/local/bin/php)
==8169==    by 0x6D253C: ??? (in /usr/local/bin/php)
==8169==    by 0x6D34CD: ??? (in /usr/local/bin/php)
==8169==    by 0x87BE44F: ???
==8169==  Address 0x88301b8 is 8 bytes inside a block of size 24 free'd
==8169==    at 0x484417B: free (vg_replace_malloc.c:872)
==8169==    by 0x8633FF6: xdebug_code_coverage_end_of_function (code_coverage.c:737)
==8169==    by 0x86345AB: xdebug_coverage_execute_ex_end (code_coverage.c:1072)
==8169==    by 0x861E45A: xdebug_execute_user_code_end (base.c:814)
==8169==    by 0x6D08E5: zend_observer_fcall_end (in /usr/local/bin/php)
==8169==    by 0x34277B: ??? (in /usr/local/bin/php)
==8169==    by 0x69FB62: execute_ex (in /usr/local/bin/php)
==8169==    by 0x62D2BE: zend_call_function (in /usr/local/bin/php)
==8169==    by 0x6D253C: ??? (in /usr/local/bin/php)
==8169==    by 0x6D34CD: ??? (in /usr/local/bin/php)
==8169==    by 0x882130F: ???
==8169==  Block was alloc'd at
==8169==    at 0x48465EF: calloc (vg_replace_malloc.c:1328)
==8169==    by 0x8631CF9: xdebug_path_new (branch_info.c:208)
==8169==    by 0x8633EB3: xdebug_code_coverage_start_of_function (code_coverage.c:701)
==8169==    by 0x863445D: xdebug_coverage_execute_ex (code_coverage.c:1051)
==8169==    by 0x861F71A: xdebug_execute_user_code_begin (base.c:784)
==8169==    by 0x6D05BA: ??? (in /usr/local/bin/php)
==8169==    by 0x62D2B0: zend_call_function (in /usr/local/bin/php)
==8169==    by 0x6D253C: ??? (in /usr/local/bin/php)
==8169==    by 0x6D34CD: ??? (in /usr/local/bin/php)
==8169==    by 0x882130F: ???
==8169== 
==8169== Invalid read of size 8
==8169==    at 0x8631D74: xdebug_path_free (branch_info.c:222)
==8169==    by 0x8633FF6: xdebug_code_coverage_end_of_function (code_coverage.c:737)
==8169==    by 0x86345AB: xdebug_coverage_execute_ex_end (code_coverage.c:1072)
==8169==    by 0x861E45A: xdebug_execute_user_code_end (base.c:814)
==8169==    by 0x6D08E5: zend_observer_fcall_end (in /usr/local/bin/php)
==8169==    by 0x34277B: ??? (in /usr/local/bin/php)
==8169==    by 0x69FB62: execute_ex (in /usr/local/bin/php)
==8169==    by 0x62D2BE: zend_call_function (in /usr/local/bin/php)
==8169==    by 0x6D253C: ??? (in /usr/local/bin/php)
==8169==    by 0x6D34CD: ??? (in /usr/local/bin/php)
==8169==    by 0x87BE44F: ???
==8169==  Address 0x88301b8 is 8 bytes inside a block of size 24 free'd
==8169==    at 0x484417B: free (vg_replace_malloc.c:872)
==8169==    by 0x8633FF6: xdebug_code_coverage_end_of_function (code_coverage.c:737)
==8169==    by 0x86345AB: xdebug_coverage_execute_ex_end (code_coverage.c:1072)
==8169==    by 0x861E45A: xdebug_execute_user_code_end (base.c:814)
==8169==    by 0x6D08E5: zend_observer_fcall_end (in /usr/local/bin/php)
==8169==    by 0x34277B: ??? (in /usr/local/bin/php)
==8169==    by 0x69FB62: execute_ex (in /usr/local/bin/php)
==8169==    by 0x62D2BE: zend_call_function (in /usr/local/bin/php)
==8169==    by 0x6D253C: ??? (in /usr/local/bin/php)
==8169==    by 0x6D34CD: ??? (in /usr/local/bin/php)
==8169==    by 0x882130F: ???
==8169==  Block was alloc'd at
==8169==    at 0x48465EF: calloc (vg_replace_malloc.c:1328)
==8169==    by 0x8631CF9: xdebug_path_new (branch_info.c:208)
==8169==    by 0x8633EB3: xdebug_code_coverage_start_of_function (code_coverage.c:701)
==8169==    by 0x863445D: xdebug_coverage_execute_ex (code_coverage.c:1051)
==8169==    by 0x861F71A: xdebug_execute_user_code_begin (base.c:784)
==8169==    by 0x6D05BA: ??? (in /usr/local/bin/php)
==8169==    by 0x62D2B0: zend_call_function (in /usr/local/bin/php)
==8169==    by 0x6D253C: ??? (in /usr/local/bin/php)
==8169==    by 0x6D34CD: ??? (in /usr/local/bin/php)
==8169==    by 0x882130F: ???
==8169== 
==8169== Invalid free() / delete / delete[] / realloc()
==8169==    at 0x484417B: free (vg_replace_malloc.c:872)
==8169==    by 0x8633FF6: xdebug_code_coverage_end_of_function (code_coverage.c:737)
==8169==    by 0x86345AB: xdebug_coverage_execute_ex_end (code_coverage.c:1072)
==8169==    by 0x861E45A: xdebug_execute_user_code_end (base.c:814)
==8169==    by 0x6D08E5: zend_observer_fcall_end (in /usr/local/bin/php)
==8169==    by 0x34277B: ??? (in /usr/local/bin/php)
==8169==    by 0x69FB62: execute_ex (in /usr/local/bin/php)
==8169==    by 0x62D2BE: zend_call_function (in /usr/local/bin/php)
==8169==    by 0x6D253C: ??? (in /usr/local/bin/php)
==8169==    by 0x6D34CD: ??? (in /usr/local/bin/php)
==8169==    by 0x87BE44F: ???
==8169==  Address 0x88301b0 is 0 bytes inside a block of size 24 free'd
==8169==    at 0x484417B: free (vg_replace_malloc.c:872)
==8169==    by 0x8633FF6: xdebug_code_coverage_end_of_function (code_coverage.c:737)
==8169==    by 0x86345AB: xdebug_coverage_execute_ex_end (code_coverage.c:1072)
==8169==    by 0x861E45A: xdebug_execute_user_code_end (base.c:814)
==8169==    by 0x6D08E5: zend_observer_fcall_end (in /usr/local/bin/php)
==8169==    by 0x34277B: ??? (in /usr/local/bin/php)
==8169==    by 0x69FB62: execute_ex (in /usr/local/bin/php)
==8169==    by 0x62D2BE: zend_call_function (in /usr/local/bin/php)
==8169==    by 0x6D253C: ??? (in /usr/local/bin/php)
==8169==    by 0x6D34CD: ??? (in /usr/local/bin/php)
==8169==    by 0x882130F: ???
==8169==  Block was alloc'd at
==8169==    at 0x48465EF: calloc (vg_replace_malloc.c:1328)
==8169==    by 0x8631CF9: xdebug_path_new (branch_info.c:208)
==8169==    by 0x8633EB3: xdebug_code_coverage_start_of_function (code_coverage.c:701)
==8169==    by 0x863445D: xdebug_coverage_execute_ex (code_coverage.c:1051)
==8169==    by 0x861F71A: xdebug_execute_user_code_begin (base.c:784)
==8169==    by 0x6D05BA: ??? (in /usr/local/bin/php)
==8169==    by 0x62D2B0: zend_call_function (in /usr/local/bin/php)
==8169==    by 0x6D253C: ??? (in /usr/local/bin/php)
==8169==    by 0x6D34CD: ??? (in /usr/local/bin/php)
==8169==    by 0x882130F: ???
==8169== 
bool(true)
==8169== 
==8169== HEAP SUMMARY:
==8169==     in use at exit: 6,908 bytes in 18 blocks
==8169==   total heap usage: 24,658 allocs, 24,641 frees, 3,685,051 bytes allocated
==8169== 
==8169== LEAK SUMMARY:
==8169==    definitely lost: 24 bytes in 1 blocks
==8169==    indirectly lost: 0 bytes in 0 blocks
==8169==      possibly lost: 0 bytes in 0 blocks
==8169==    still reachable: 6,884 bytes in 17 blocks
==8169==         suppressed: 0 bytes in 0 blocks
==8169== Rerun with --leak-check=full to see details of leaked memory
==8169== 
==8169== For lists of detected and suppressed errors, rerun with: -s
==8169== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
valgrind.log (7,841 bytes)   
fibers.php (210 bytes)   
<?php

xdebug_start_code_coverage(XDEBUG_CC_UNUSED);

$fiber = new \Fiber(function () {
    $fiber = new \Fiber(function () { });
    var_dump($fiber->start() === null);
});
var_dump($fiber->start() === null);
fibers.php (210 bytes)   
Operating System
PHP Version8.1-dev

Activities

derick

2025-03-27 13:26

administrator   ~0007221

Hi,

I had a look at this, and I can indeed reproduce this with your minimal example.

I also know what the problem is, I think.

Each fiber has their own stack, and Xdebug switches its internal stack when execution switches to another fiber (or the main thread): https://github.com/xdebug/xdebug/blob/master/src/base/base.c#L1200

The code coverage functionality also uses a stack to keep track about how functions are called, but this stack is (incorrectly) shared with all fibers.

In your case, when the execution changes to a different fiber, the code tries to find the path for the current function using the stack size of the current execution unit. But if this is the same level as in the previous fiber, this information and memory structure is already freed through xdebug_code_coverage_end_of_function (https://github.com/xdebug/xdebug/blob/master/src/coverage/code_coverage.c#L723).

The fix here is to also have separate stacks for code coverage, and this is not as trivial of a fix as I would like. It doesn't trigger with 3.4.1 and earlier, because of the memory leak, but the code coverage result would also still not be accurate.

For other reasons, I have a new implementation for coverage (https://github.com/derickr/xdebug/tree/improve-code-coverage) where I would rather fix this. But this branch is currently quite a bit slower (0000023:0000030%) and I haven't figured out how to address that yet (nor do I understand why it is slower).

In short, this is going to take a while I'm afraid.

cheers,
Derick

Issue History

Date Modified Username Field Change
2025-03-25 19:29 clue New Issue
2025-03-25 19:29 clue File Added: valgrind.log
2025-03-25 19:29 clue File Added: fibers.php
2025-03-27 13:26 derick Assigned To => derick
2025-03-27 13:26 derick Status new => confirmed
2025-03-27 13:26 derick Note Added: 0007221