Skip to content

fix(build): stage libstdc++.a before the guards; fail the job when the container aborts (#49) - #50

Merged
luthermonson merged 1 commit into
mainfrom
fix/stage-libstdcxx-before-guards
Aug 23, 2026
Merged

fix(build): stage libstdc++.a before the guards; fail the job when the container aborts (#49)#50
luthermonson merged 1 commit into
mainfrom
fix/stage-libstdcxx-before-guards

Conversation

@luthermonson

Copy link
Copy Markdown
Contributor

Fixes #49. lib/libstdc++.a has been missing from a growing set of Linux x86_64 SDK tarballs since 2026-07-09. ICU is C++, so anything static-linking those SDKs dies on operator new, __cxa_begin_catch, __gxx_personality_v0, and __cxxabiv1 vtables.

Root cause

The container staging script runs under set -eux and ordered its work:

copy *.a  ->  copy headers  ->  ZTS guard  ->  x86_64 intrinsics guard  ->  stage libstdc++.a

libstdc++.a was staged last, after the guards. The x86_64-only intrinsics guard (added in 43267a7, 2026-07-09) aborts the script under set -e, so the staging step never ran. Two independent abort paths, both confirmed against shipped artifacts:

  • grep -q "define HAVE_FUNC_ATTRIBUTE_TARGET 1" fails on every Alpine x86_64 build — it is /* #undef */ in the shipped php_config.h of 8.3.31, 8.3.33 and 8.5.9. The CFLAGS override from 43267a7 works on almalinux and has never worked on Alpine.
  • nm libphp.a | grep -q SHA256_Transform_shani fails on PHP 8.3 — that symbol entered ext/hash/hash_sha.c in PHP 8.4 and is absent from 8.3's libphp.a.

aarch64 skips the guard entirely (if [ "$(uname -m)" = "x86_64" ]), which is exactly why every aarch64 tarball is intact.

The regression therefore begins at 43267a7, not at the 8.3.33 build.

Why CI never noticed

A container-side abort does not fail the job. docker run exiting non-zero does not stop the step. mkdir -p /output runs long before the guards, so once /output exists an aborted script still produces a green job and a short tarball. (Observed behaviour; the reason the exit status doesn't propagate on this fleet was not determined, and the fix does not depend on it.)

That is the real defect — the missing file is one symptom of it.

Blast radius

Verified by listing archive members of the published tarballs.

Missing lib/libstdc++.a:

Variant Affected
linux-x86_64-gnu 8.3.32, 8.3.33
linux-x86_64 (musl) 8.3.32, 8.3.33, 8.4.23, 8.4.24, 8.5.8, 8.5.9

Intact: every aarch64 tarball (incl. 8.3.33), linux-x86_64-gnu on 8.4/8.5, 8.4.24-linux-x86_64-gnu-nts, and everything built before 2026-07-09.

Secondary damage in the affected tarballs: bin/php-config --libs enumerates lib/*.a, so it also omits -lstdc++; and THIRD-PARTY-NOTICES.txt advertises a bundled libstdc++ that isn't present.

The change

  • Stage libstdc++.a immediately after the archive/header copy, before any guard runs.
  • Container writes /output/.build-complete as its final statement; Package SDK fails if it is absent. This closes the class rather than this one instance — any future early abort now reds the build instead of shipping a truncated tarball.
  • Package SDK fails when lib/ contains libicuuc.a without libstdc++.a — the exact inconsistent pair that shipped.
  • Skip the SHA-NI symbol assertion below PHP 8.4, where the symbol does not exist.
  • HAVE_FUNC_ATTRIBUTE_TARGET undefined is now a hard error on glibc and a ::warning:: on musl (see below).

Validation

Two builds dispatched with release_tag_suffix=-libstdcxxfix, so production tags were never touched.

  • run 32586295645 (linux-x86_64-gnu): logs show Staged libstdc++.a, HAVE_FUNC_ATTRIBUTE_TARGET is defined, skipping SHA-NI symbol check, PHP 8.3.33 predates it, and touch /output/.build-complete. Both new Package SDK guards passed.
  • run 32586300008 (linux-x86_64, musl): same, with the intrinsics regression now surfacing as a visible warning instead of silently truncating the tarball.

Resulting tarballs carry 38 archives each, and the only delta versus the shipped broken ones is + ./lib/libstdc++.a. No sentinel file leaked into the payload. The staged archive provides every symbol the downstream link was missing: _Znwm, __cxa_begin_catch, __gxx_personality_v0, _ZNSt18condition_variableC1Ev, _ZTVN10__cxxabiv120__si_class_type_infoE.

Known gap, filed separately

The musl x86_64 SDKs have shipped without SHA-NI/PCLMUL/AVX2 resolvers continuously since 8.3.31 — the CFLAGS override never applied on Alpine. That is an independent bug; it is a warning here rather than an error so it doesn't block musl builds, and it needs its own fix.

After merge

Affected releases need re-dispatching without a suffix to replace the published assets. v8.3.33 linux-x86_64-gnu is the urgent one: it blocks #47 (Windows PHP 8.3 shipping with OPcache silently disabled) via ephpm/ephpm#373.

…pletion

The x86_64-only intrinsics guard aborts the container script under set -e,
and libstdc++.a was staged after it, so it never got copied. A container
abort does not fail the job (docker run returning non-zero does not stop
the step), so the tarball shipped short and green.

Broken artifacts: every x86_64 musl tarball since the guard landed
(8.3.32, 8.3.33, 8.4.24, 8.5.9 verified) and x86_64-gnu on PHP 8.3
(8.3.32, 8.3.33). aarch64 skips the guard and is unaffected.

- stage libstdc++.a right after the archives, before any guard
- container writes /output/.build-complete last; Package SDK fails if it
  is absent
- Package SDK fails when lib/ carries libicuuc.a without libstdc++.a
- skip the SHA-NI symbol assertion below PHP 8.4: SHA256_Transform_shani
  was added in 8.4, so the check could never pass on 8.3
- musl x86_64 HAVE_FUNC_ATTRIBUTE_TARGET warns instead of failing; it has
  been undefined there since before the guard existed and is its own bug
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8.3.33 linux-x86_64-gnu tarball is missing lib/libstdc++.a — static links fail on ICU's C++ symbols

1 participant