[FIX] gcc: two defects only a chroot could find

Our gcc package was built a dozen times and never once used to compile
anything -- stage 1 compiles with Ubuntu's gcc. The first program it was ever
asked to link was in the stage-2 chroot, and it failed twice.

  collect2: fatal error: cannot find 'ld'

/usr/bin/ld was present, executable, on PATH, and ran as the same user. -B,
COMPILER_PATH and three symlinks changed nothing. strace settled it in one
line: it was looking for s390x-linux-gnu-ld, the DEBIAN triplet, and printing
the generic name. gcc's configure had recorded Ubuntu's copy, a file no Arch
system will ever have. --with-ld and --with-as now pin absolute paths, correct
on the host, in the chroot and on the target alike.

  /usr/bin/ld: cannot find -lgcc_s

package_gcc() moves libgcc_s{,_asneeded}.so three directories deeper. The
asneeded one is a linker script and survives; libgcc_s.so is a symlink naming
its target relative to /usr/lib, so after the move it points at nothing. The
only libgcc_s.so on the system was dangling, so -lgcc_s could not resolve and
nothing linked at all -- not even int main(void){return 0;}.

Verified: the chroot now compiles and runs a program.

--- FR ---

Notre paquet gcc a été bâti une douzaine de fois sans jamais servir à
compiler : l'étage 1 compile avec le gcc d'Ubuntu. Le premier programme qu'on
lui a demandé de lier l'a été dans le chroot d'étage 2, et il a échoué deux
fois.

  collect2: fatal error: cannot find 'ld'

/usr/bin/ld était présent, exécutable, dans le PATH, et répondait pour le même
utilisateur. -B, COMPILER_PATH et trois liens symboliques n'ont rien changé.
strace a tranché en une ligne : il cherchait s390x-linux-gnu-ld, le triplet
DEBIAN, en affichant le nom générique. La configuration de gcc avait
enregistré la copie d'Ubuntu, un fichier qu'aucun système Arch n'aura jamais.
--with-ld et --with-as épinglent désormais des chemins absolus, corrects sur
l'hôte, dans le chroot et sur la cible.

  /usr/bin/ld: cannot find -lgcc_s

package_gcc() déplace libgcc_s{,_asneeded}.so trois répertoires plus bas.
Le second est un script ld et survit ; libgcc_s.so est un symlink dont la
cible est relative à /usr/lib, si bien qu'après le déplacement il ne pointe
plus sur rien. Le seul libgcc_s.so du système pendait dans le vide : -lgcc_s
ne pouvait se résoudre et plus rien ne se liait — pas même
int main(void){return 0;}.

Vérifié : le chroot compile et exécute désormais un programme.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-19 08:30:49 -04:00
parent 7ec3d14851
commit 22b101dc47

View file

@ -311,3 +311,93 @@ for m in re.finditer(r"depends=\(([^)]*)\)", s):
assert not left, "gcc: dropped names still depended on: %s" % sorted(set(left))
print("gcc: depends pruned of %d dropped components" % len(drop))
PY
# --with-ld / --with-as: our gcc looked for a Debian-named linker.
#
# Found only by building a chroot from stage 1 and trying to compile in it:
#
# collect2: fatal error: cannot find 'ld'
#
# THE MESSAGE NAMES THE WRONG FILE. /usr/bin/ld was present and executable,
# ld --version ran as the same user, and PATH contained /usr/bin. strace
# settled it in one line:
#
# newfstatat("/usr/bin/s390x-linux-gnu-ld") = ENOENT
#
# collect2 was not looking for `ld`. It was looking for
# `s390x-linux-gnu-ld` -- the DEBIAN triplet -- and printing the generic name
# in its error. gcc -dumpmachine says s390x-ibm-linux-gnu, so the prefix is
# not the target either; it is what gcc's configure recorded after finding
# Ubuntu's /usr/bin/s390x-linux-gnu-ld, which that host ships and no Arch
# system ever will.
#
# THE REASON STAGE 1 NEVER NOTICED is that every stage-1 build ran on the
# host, where that file exists. The defect was in the shipped compiler all
# along and only a chroot could see it -- the same lesson as the missing
# dynamic linker, one layer up: sixty-eight successful builds proved nothing
# about whether the compiler works anywhere else.
#
# Absolute paths rather than the canonical triplet, because they are correct
# in BOTH places: /usr/bin/ld is Ubuntu's during stage 1 and ours in the
# chroot and on the target. A triplet-prefixed symlink would have papered
# over it and shipped a gcc that still needs a Debian-named tool.
python3 - <<'PY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
assert "--with-ld=" not in s, "gcc: linker already pinned, hook ran twice"
# _confflags is an ARRAY, shared by both configure passes -- the compiler and
# libgccjit -- so one insertion covers both, and no line-continuation
# backslash belongs on the new entries.
anchor = " --with-linker-hash-style=gnu\n"
n = s.count(anchor)
assert n == 1, "gcc: expected one --with-linker-hash-style in _confflags, found %d" % n
s = s.replace(anchor, anchor + " --with-ld=/usr/bin/ld\n --with-as=/usr/bin/as\n", 1)
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
print("gcc: ld and as pinned to absolute paths")
PY
grep -q -- '--with-ld=/usr/bin/ld' PKGBUILD || {
echo "gcc: --with-ld not inserted" >&2; exit 1; }
# libgcc_s.so: the PKGBUILD moves a RELATIVE symlink into a deeper directory.
#
# /usr/bin/ld: cannot find -lgcc_s: No such file or directory
#
# package_gcc() does, at the end:
#
# mv -v usr/lib/libgcc_s{,_asneeded}.so $_libdir/
#
# where $_libdir is /usr/lib/gcc/<triplet>/16. The two files are not alike.
# libgcc_s_asneeded.so is a linker SCRIPT -- `INPUT ( AS_NEEDED ( -lgcc_s ) )`
# -- so it survives being moved anywhere. libgcc_s.so is a SYMLINK whose
# target is the bare name `libgcc_s.so.1`, i.e. the same directory. That was
# true in /usr/lib, where libgcc_s.so.1 lives; three directories deeper it
# points at nothing.
#
# So -lgcc_s cannot resolve: the only libgcc_s.so on the system is a dangling
# link, and the asneeded script that ld reads instead asks for -lgcc_s again.
# Nothing links, including the trivial `int main(void){return 0;}`.
#
# WHY NOTHING CAUGHT THIS EARLIER, and it is the same reason as the linker
# lookup above: stage 1 compiles with UBUNTU's gcc. Our gcc package was built
# a dozen times and never once used to compile anything. The first program it
# was ever asked to link was in the stage-2 chroot.
#
# The link is repointed rather than the mv removed, because $_libdir is where
# gcc's own search path expects it -- that part of the PKGBUILD is right.
# Three levels up from /usr/lib/gcc/<triplet>/16 is /usr/lib.
python3 - <<'PY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
old = ' mv -v usr/lib/libgcc_s{,_asneeded}.so $_libdir/\n'
assert s.count(old) == 1, "gcc: libgcc_s move not in the expected form"
fix = old + (
' # The moved symlink named its target relative to /usr/lib; from\n'
' # $_libdir that resolves to nothing. Repoint it, or -lgcc_s cannot\n'
' # be satisfied and the compiler links nothing at all.\n'
' ln -sfv ../../../libgcc_s.so.1 $_libdir/libgcc_s.so\n')
s = s.replace(old, fix, 1)
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
PY
grep -qF 'ln -sfv ../../../libgcc_s.so.1' PKGBUILD || {
echo "gcc: libgcc_s.so link not repointed" >&2; exit 1; }
echo "gcc: libgcc_s.so repointed to ../../../libgcc_s.so.1"