SuperAPI AI 工具集

debug-php-wasm-side-modules

测试与质量

查看项目

更新于 10-16 06:44

技能介绍

该技能用于诊断和修复以 WASM side module 形式构建、通过 dlopen 加载的动态 PHP 扩展问题。

用途:定位 zend_extension 加载崩溃、dlopen 失败、JSPI 挂起异常、Asyncify 重写越界、弱符号 stub 干扰以及扩展实际功能失效等场景。

主要能力与要点:

  1. _dlopen_js 必须同步。Emscripten 默认将其标记为 isAsync=true,JSPI 会用 WebAssembly.Suspending 包装,即使返回普通值也会扰乱 WASM 调用栈并在后续触发 zend_mm_panic。修复:在 phpwasm-emscripten-library-dynamic-linking.js 中设 _dlopen_js__async: false。症状包括 php.ini 中 zend_extension= 后任何代码都崩溃,而运行时 dl() 正常。

  2. side module 的 JSPI 挂起模式。side module 调用阻塞 C 函数(如 recv)时解析到主模块 WASM 实现而非 JS import,无法被 Suspending 包装,会立即返回 EAGAIN。通用修复有三部分:在 phpwasm-emscripten-library.js 增加 JS 包装及 _async: true;在 Dockerfile 中把该函数加入 JSPI_IMPORTS;编译 side module 时用 -Drecv=wasm_recv 重定向。适用于 recv、select、sleep、read、connect 等阻塞调用。

  3. 扩展加载生命周期。需在 new PHP(runtime) 之后写入 .so 与 ini,再 php.run()。loadNodeRuntime 会在展开用户选项后覆盖 onRuntimeInitialized,用户回调中写的文件会丢失;preRun 早于 FS 初始化,写入会崩溃。

  4. Asyncify 与 side module。使用自定义重命名 import 时必须传 -sASYNCIFY_IMPORTS=<自定义名>,否则 Binaryen 不插桩,重写阶段出现 table index out of bounds。可用 wasm-opt --print module.so | grep -c '__asyncify_state' 对比验证;不要为导入函数添加 ASYNCIFY_EXPORTS。

  5. C++ 弱符号与 JSPI。弱符号 import 经 JS 闭包 stub 惰性解析,阻塞 JSPI 挂起并报 SuspendError。可选两遍实例化(先取导出、加入 wasmImports、再以增强 import 实例化),或在 Dockerfile 中打补丁去除非必要 syscall。Asyncify 不受影响。

  6. Web 平台。需通过 HTTP fetch 获取 .so 并用 FS.writeFile 写入虚拟文件系统;Vite 用 assetsInclude: ['**/*.so'];需 MAIN_MODULE 与 ENVIRONMENT=web,worker。

  7. 验证与版本。extension_loaded() 仅代表 MINIT 成功,需实测 Xdebug 断点、intl 排序、GD 输出等。每个 PHP 版本单独构建 side module,Zend API 或 Emscripten 版本不一致会导致加载错误或函数表损坏。

限制:以上内容针对 Emscripten、JSPI、Asyncify 与 @php-wasm 工具链,其他运行时或构建方式暂未提供说明。