Skip to content

v0.3.1 — the INSTALL crash on Linux

Choose a tag to compare

@s2x s2x released this 17 Aug 16:21
· 9 commits to main since this release

Recommended if you run on Linux. Without this, INSTALL segfaults in any process that has a PHP extension linking libstdc++ loaded — intl is the common one — taking the whole process with it, with no exception to catch.

Supports liblbug 0.19.x. No API changes.

What was happening

liblbug 0.19.1's prebuilt Linux .so statically links libstdc++ and exports its symbols, 130 of them with STB_GNU_UNIQUE binding — std::locale facet ids and their init guards:

nm -D --defined-only liblbug.so | grep -c '^[0-9a-f]* u '     # 130

glibc binds STB_GNU_UNIQUE symbols process-wide and ignores RTLD_DEEPBIND for them, while ordinary globals honour it. Zend's DL_LOAD uses RTLD_LAZY|RTLD_GLOBAL|RTLD_DEEPBIND for every extension and for ext/ffi's dlopen. So with a system libstdc++ also in the process, liblbug's locale registry is split across two C++ runtimes, and the first std::regex compiled — which INSTALL does, building its HTTP client — dies inside std::codecvt.

tools/repro-install-crash-dlopen.c shows it in C with no PHP at all: DEEPBIND on liblbug plus a libstdc++ loaded first gives 139, dropping either gives 0.

What changed here

The FFI connector fixes itself. It now dlopens liblbug before ext/ffi can, with plain RTLD_LAZY — an already-loaded object keeps its original binding, so DEEPBIND never applies. Linux only, only when a libstdc++ is already mapped, and LADYBUG_NO_PRELOAD=1 opts out. Nothing to configure.

The native extension needs --enable-ladybug-static. There liblbug is a link-time dependency, so PHP's flags apply before any of our code runs. The static build avoids the problem because liblbug.a carries no libstdc++ of its own: the .so links the system one dynamically, leaving a single runtime. This is now the documented linkage for distributing on Linux.

One thing to know: loading a dynamically linked ladybug.so re-exposes the FFI connector too, because liblbug is bound at PHP startup. Don't mix a shared-linked extension with the FFI connector.

Failing both, LD_PRELOAD=/path/to/liblbug.so php … works for either connector. maxThreads: 1 does not help.

The real fix belongs upstream — -Wl,--exclude-libs,ALL and -fno-gnu-unique on the Linux release. The macOS dylib exports none of these symbols, so it is a packaging difference rather than a design choice. Nothing has been filed yet.

Also

  • make docker-test runs the whole suite, both connectors, on Linux from a macOS workstation — which is how this was diagnosed after a first attempt through a throwaway CI branch.
  • The Linux image installs PHPUnit as a phar; pulling the full dev set hit codeload's rate limit often enough to break builds for no reason. CI now authenticates Composer downloads for the same reason.

Full notes in CHANGELOG.md.