Skip to content

[ php-wasm ] Add xdebug shared extension to @php-wasm/node ASYNCIFY #2326

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

mho22
Copy link
Collaborator

@mho22 mho22 commented Jul 4, 2025

Motivation for the change

This is a pull request to dynamically load Xdebug in @php-wasm Node ASYNCIFY.

Roadmap

Related issues and pull requests

Relative to XDebug

Relative to dynamic library

Implementation details

Additional steps for asyncify are implemented :

  • new nx commands
  • new WITH_JSPI and WITH_DEBUG arguments in Xdebug Dockerfile
  • new xdebug.so files are compiled and dynamically loaded

Zend extensions and PHP versions

Important

Each version of PHP requires its own version of Xdebug because each version of PHP has a specific Zend Engine API version. Consequently, each version of Xdebug is designed to be compatible with a particular Zend Engine API version.

Example using PHP 8.2 with Xdebug 8.3 :

Xdebug requires Zend Engine API version 420230831.
The Zend Engine API version 420220829 which is installed, is outdated.

Testing Instructions

Two files are needed. A PHP file to test the step debugger. A JS file to run PHP 8.4 node ASYNCIFY.

php/xdebug.php

<?php

$test = 42; // Set a breakpoint on this line

echo "Hello Xdebug World\n";

scripts/node.js

import { PHP } from '@php-wasm/universal';
import { loadNodeRuntime } from '@php-wasm/node';


const php = new PHP( await loadNodeRuntime( '8.4', { withXdebug : true } ) );

await php.runStream( { scriptPath : `php/xdebug.php` } );

To achieve the test, you first need to start debugging [ with F5 or Run > Start debugging in VSCode ], then run :

node scripts/node.js

@mho22 mho22 marked this pull request as draft July 4, 2025 10:28
@mho22 mho22 mentioned this pull request Jul 4, 2025
11 tasks
@mho22 mho22 force-pushed the add-xdebug-support-to-php-wasm-node-asyncify branch from c07a975 to 7874d17 Compare July 7, 2025 14:26
@mho22
Copy link
Collaborator Author

mho22 commented Jul 8, 2025

I didn't expect Xdebug asyncify to be implemented so easily.

In a comment from the previous pull request, I stated this :

I managed to run step debugging with PHP 8.4 Asyncify! It required a lot of self control to find the complete list of ASYNCIFY_ONLY functions, both for php and for xdebug.

For php:

+ zend_extension_statement_handler
+ zend_llist_apply_with_argument
+ ZEND_EXT_STMT_SPEC_HANDLER
+ zend_post_deactivate_modules
+ call
+ zend_observer_fcall_begin_prechecked
+ zend_observer_fcall_begin

For Xdebug :

+ zm_post_zend_deactivate_xdebug
+ xdebug_debugger_post_deactivate
+ xdebug_dbgp_deinit
+ xdebug_execute_begin
+ xdebug_execute_user_code_begin
+ xdebug_init_debugger
+ xdebug_debug_init_if_requested_at_startup
+ xdebug_dbgp_init
+ xdebug_execute_ex
+ xdebug_statement_call
+ xdebug_debugger_statement_call
+ xdebug_dbgp_breakpoint
+ xdebug_dbgp_cmdloop
+ xdebug_fd_read_line_delim

We must keep in mind that these lists are certainly incomplete, since I just tested the step debugging of my simple $test = 42; breakpoint example. But its a start.

But, after I decided to check if all these functions were indeed still needed, I found out Xdebug didn't have to specify ASYNCIFY_ONLY functions to be compiled correctly and usable by PHP. Nevertheless, PHP still needed only 4 functions :

+ zend_extension_statement_handler
+ ZEND_EXT_STMT_SPEC_HANDLER
+ zend_observer_fcall_begin_prechecked // PHP8.4 only
+ zend_observer_fcall_begin

That's good news, but I don't know what changed in the project that made me compile Xdebug in Asyncify mode without the need of ASYNCIFY_ONLY. And if we can avoid that, what about making PHP compile the same way to avoid ASYNCIFY_ONLY too? I know this sounds silly but I am just thinking out loud.

I will still try to run the Xdebug tests with the php-wasm binary in a separate project first, and share my findings here.

Ah! FYI, Xdebug Asyncify tests passed successfully

@adamziel
Copy link
Collaborator

adamziel commented Jul 8, 2025

Oh, how lovely! Should we mark this as ready for review, then?

That's good news, but I don't know what changed in the project that made me compile Xdebug in Asyncify mode without the need of ASYNCIFY_ONLY. And if we can avoid that, what about making PHP compile the same way to avoid ASYNCIFY_ONLY too? I know this sounds silly but I am just thinking out loud.

Asyncify detects all the functions it needs to instrument. ASYNCIFY_ONLY actually disables that autodetection and tells the compiler to only instrument specific functions.

For core PHP, it is a must. The autodetection misses some important functions and also instruments a lot of unimportant functions, which produces a much larger (+10MB AFAIR) and slower (~5s to startup) binary. That's why we use the leaner way.

For XDebug, however, we're dealing with a much smaller codebase. If the difference in size and speed is not overly noticeable, relying on autodetection should be fine.

See also other Asyncify build options, maybe something will pick your interest.

@mho22
Copy link
Collaborator Author

mho22 commented Jul 8, 2025

Thank you for your highlights. Indeed we need ASYNCIFY_ONLY in PHP. I am currently trying to optimize xdebug compilation in ASYNCIFY and also trying to run Xdebug's php-xdebug-tests.php file that loads the Xdebug tests but I run into a new null function or function signature mismatch with PHP8.4 [ unrelated to the pointer casts ] :

RuntimeError: null function or function signature mismatch
    at php.wasm.php_auto_globals_create_get (wasm://wasm/php.wasm-13e3abe6:wasm-function[8442]:0x740f04)
    at php.wasm.zend_activate_auto_globals (wasm://wasm/php.wasm-13e3abe6:wasm-function[9497]:0x7db3db)
    at php.wasm.php_hash_environment (wasm://wasm/php.wasm-13e3abe6:wasm-function[8440]:0x740d48)
    at php.wasm.php_request_startup (wasm://wasm/php.wasm-13e3abe6:wasm-function[8253]:0x731fdf)
    at php.wasm.do_cli (wasm://wasm/php.wasm-13e3abe6:wasm-function[12645]:0x979bb7)
    at php.wasm.main (wasm://wasm/php.wasm-13e3abe6:wasm-function[12640]:0x970a0d)
    at php.wasm.run_cli (wasm://wasm/php.wasm-13e3abe6:wasm-function[12051]:0x9057b3)
    at async PHP.cli

I will add this in this issue.

@adamziel
Copy link
Collaborator

adamziel commented Jul 8, 2025

php_auto_globals_create_get is not in the ASYNCIFY_ONLY list right now - maybe that's related

@mho22
Copy link
Collaborator Author

mho22 commented Jul 8, 2025

Unfortunately the error occurs also in JSPI mode. The error occurs with this script :

import { PHP } from '@php-wasm/universal';
import { loadNodeRuntime } from '@php-wasm/node';

const script = `<?php

	$ch = curl_init( "https://github.com/xdebug/xdebug/archive/refs/tags/3.4.4.tar.gz" );
	$fp = fopen( "xdebug.tar.gz", 'wb' );

	curl_setopt( $ch, CURLOPT_FILE, $fp );
	curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
	curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // Use with caution
	curl_exec( $ch );

	if( curl_errno( $ch ) )
	{
		echo "Curl error: " . curl_error( $ch );
	}

	curl_close( $ch );

	fclose( $fp );

	echo "Download complete\n";

	$phar = new PharData( "xdebug.tar.gz" );

	$phar->extractTo( '/' );

	echo "Extraction complete\n";

	unlink( "xdebug.tar.gz" );

	echo "Archive deletion complete\n";
`;

const php = new PHP( await loadNodeRuntime( '8.4' ) );

php.chdir( '/' );

const result = await php.runStream( { code : script } );

await result.exitCode;

console.log( php.listFiles( 'xdebug-3.4.4' ) );

const response = await php.cli( [ 'php', 'xdebug-3.4.4/run-xdebug-tests.php' ] );

response.stderr.pipeTo( new WritableStream( { write( chunk ){ process.stderr.write( chunk ) } } ) );

response.stdout.pipeTo( new WritableStream( { write( chunk ){ process.stdout.write( chunk ) } } ) );

I think this is probably related to the way I am switching from runStream to cli.

I decided to upload manually the Xdebug repository and ony run the CLI and tada :

node --experimental-wasm-jspi scripts/test.js

Deprecated: explode(): Passing null to parameter #2 ($string) of type string is deprecated in /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php on line 896

Call Stack:
    0.0406    1055768   1. {main}() /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php:0
    0.0408    1055800   2. main() /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php:4057
    0.0440    1062144   3. write_information() /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php:702
    0.0602    1069224   4. explode($separator = ',', $string = NULL) /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php:896

PHP Deprecated:  explode(): Passing null to parameter #2 ($string) of type string is deprecated in /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php on line 896
=====================================================================
PHP         : /internal/shared/bin/php
CWD         : /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4
Extra dirs  :

VALGRIND    : Not used
=====================================================================
TIME START 2025-07-08 14:42:27
=====================================================================
PHP Stack trace:
PHP   1. {main}() /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php:0
PHP   2. main() /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php:4057
PHP   3. write_information() /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php:702
PHP   4. explode($separator = ',', $string = NULL) /Users/mho/Work/Projects/Development/Web/Professional/xdebug/template/xdebug-3.4.4/run-xdebug-tests.php:896
FAIL Check for xdebug presence [tests/base/001.phpt]
FAIL Test for crash with a destructor [tests/base/bug00001.phpt]
FAIL Test for bug #241: Crash in xdebug_get_function_stack() [tests/base/bug00241.phpt]
FAIL Test for bug #419: make use of P_tmpdir if defined instead of hardcoded '/tmp' [tests/base/bug00419-sunos.phpt]
FAIL Test for bug #419: make use of P_tmpdir if defined instead of hardcoded '/tmp' [tests/base/bug00419.phpt]
FAIL Test for bug #558: PHP segfaults when running a nested eval [tests/base/bug00558-001.phpt]
FAIL Test for bug #665: xdebug does not respect display_errors=stderr [tests/base/bug00665.phpt]
FAIL Test for bug #723: xdebug is stricter than PHP regarding Exception property types [tests/base/bug00723.phpt]
FAIL Test for bug #787: Segmentation Fault with PHP header_remove() [tests/base/bug00787.phpt]
FAIL Test for bug #801: Segfault with streamwrapper, unclosed $fp and xdebug on destruction [tests/base/bug00801.phpt]
FAIL Test for bug #823: Single quotes are escaped in var_dumped string output [tests/base/bug00823.phpt]
FAIL Test for bug #913: "Added debug info handler to DOM objects" not supported (< PHP 8.1) [tests/base/bug00913-php80.phpt]
FAIL Test for bug #913: "Added debug info handler to DOM objects" not supported (>= PHP 8.1) [tests/base/bug00913-php81.phpt]
FAIL Test for bug #931: Crash with exception in shut-down stage [tests/base/bug00931.phpt]
FAIL Test for bug #978: Inspection of array with negative keys fails [tests/base/bug00978-002.phpt]
FAIL Test for bug #1048: Can not get $GLOBAL variable by property_value on function context [tests/base/bug01048-002.phpt]

Tests are all failing by the way. For the moment.

For the record, Xdebug is enabled in PHP :

PHP 8.4.0-dev (cli) (built: Jul  8 2025 10:18:59) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.0-dev, Copyright (c) Zend Technologies
    with Xdebug v3.4.5-dev, Copyright (c) 2002-2025, by Derick Rethans

@adamziel
Copy link
Collaborator

adamziel commented Jul 8, 2025

Was there a point where it worked? Like before when the asyncify xdebug tests have passed? If yes, let's just use that. And if this is a new crash and also happened then and happens now in trunk then yes, let's debug it. Perhaps that doesn't have to block this PR, though.

@adamziel
Copy link
Collaborator

adamziel commented Jul 8, 2025

I think this is probably related to the way I am switching from runStream to cli.

Yeah, you can't do that. Once either of these was called, the php instance is no longer good for the other one. That could be the source of the error you're seeing.

@mho22
Copy link
Collaborator Author

mho22 commented Jul 8, 2025

Ok! I focus on the ASYNCIFY optimization and let's mark this as ready for review.

@mho22
Copy link
Collaborator Author

mho22 commented Jul 8, 2025

I tried some combinations with :

ASYNCIFY_ADD=$(awk '{ print "\"" $1 "\"" }' /root/asyncify-functions | paste -sd "," -); \

-sASYNCIFY_ADVISE -sASYNCIFY_IGNORE_INDIRECT -sASYNCIFY_ADD=[${ASYNCIFY_ADD}]

where I initially stored every functions that -sASYNCIFY_ADVISE logged me [ about 364 functions ] in a file called asyncify-functions and to my surprise the results were not significant :

Xdebug with -O3 -g : 2.5 MB
Xdebug with -O3 -g ASYNCIFY_ADD : 2.4 MB 

Xdebug with -O0 : 304 kB
Xdebug with -O0 ASYNCIFY_ADD : 303 kB
Xdebug with -O0 ASYNCIFY_ONLY : 304 kB

I guess no optimization is necessary here.

FYI, the xdebug.so file size ranges between 250 kB and 300 kB.

@mho22
Copy link
Collaborator Author

mho22 commented Jul 8, 2025

I am about to recompile php-wasm-node Asyncify, while AFK. I'll push the changes when I'm back.

@adamziel adamziel mentioned this pull request Jul 6, 2025
@mho22 mho22 marked this pull request as ready for review July 8, 2025 23:59
Copy link
Collaborator

@adamziel adamziel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have one comment, otherwise it looks good!

@mho22
Copy link
Collaborator Author

mho22 commented Jul 9, 2025

I will add an --xdebug option in Playground CLI in the next pull request as indicated in the follow-up.

@mho22 mho22 merged commit 4405353 into WordPress:trunk Jul 9, 2025
49 of 50 checks passed
@mho22 mho22 deleted the add-xdebug-support-to-php-wasm-node-asyncify branch August 8, 2025 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants