Commit 64046f01 authored by Adam Joseph's avatar Adam Joseph
Browse files

glibcCross: use a libgcc built separately from gcc

 ### Summary

This PR completely and finally solves the gcc<->glibc circular
`buildInputs` problem, for cross compilation.  The same technique
can be applied to native builds in the future.

Closes #213453

 ### Motivation

Prior to this PR, we had the following circular `buildInputs` problem:

1. gcc has glibc in its `buildInputs`

   - a compiled copy of glibc must be present before building gcc;
     if it isn't, gcc cripples itself (`inhibit_libc`) and refuses
     to build libgcc_s.so

2. glibc has libgcc_s.so in its `buildInputs`

   - glibc `dlopen()`s libgcc_s.so in order to implement POSIX
     thread cancellation.  For security reasons `glibc` requires
     that the path to `libgcc_s.so` is [hardwired] into `glibc` at
     compile time, so it's technically not a true dynamic link -- it
     just pretends to be one.

3. libgcc_s.so is built in the same derivation as gcc

   - libgcc_s.so is built as part of the gcc build process

We must cut one of these three links in the loop.

 ### Previous Attempts

Previously https://github.com/NixOS/nixpkgs/pull/238154 had
attempted to cut link (1) by building `gcc` without `glibc`, and
using the `libgcc_s` which emerges from that build.  Unfortunately
this just doesn't work.  GCC's configure script extracts quite a lot
of information from the glibc headers (which are a build artifact --
you can't just copy them out of the source tarball) and various
`./configure`-driven linking attempts.  If `glibc` isn't around at
build time you wind up with a `libgcc_s.so` that is missing various
unwinder features (see https://github.com/NixOS/nixpkgs/issues/213453
for the most problematic one).

Musl "cuts" link (2), or rather never creates it in the first place.
["Cancellation cleanup handling in musl has no relationship to C++
exceptions and unwinding... glibc implements cancellation as an
exception"](https://wiki.musl-libc.org/functional-differences-from-glibc.html#Thread-cancellation).
IMHO Musl made the smarter decision here.  It is incredibly rare to
find a codebase that uses both POSIX thread cancellation *and* C++
exceptions.  I have never seen a codebase that uses both *and*
expects them to be aware of each other, and I would be astonished if
one existed.  Glibc paid an immense cost in complexity for something
nobody has ever used.

 ### Changes Made

This PR cuts link (3): instead of building libgcc_s.so as part of
gcc, we build it separately from gcc.  Now there is a strict acyclic
graph of `buildInputs`:

```
 gccWithoutTargetLibc
 |
 +--->glibc-nolibgcc
 |    |
 |    v
 +--->libgcc
 |    |
 |    v
 +--->glibc
 |    |
 |    v
 +--->gcc
```

In other words, there's a simple linear `buildInputs` chain
`glibc-nolibgcc` `->` `libgcc` `->` `glibc` `->` `gcc` where all
four packages are compiled by (and therefore have as a
`(native)BuildInput`) `gccWithoutTargetLibc`.

`gccWithoutTargetLibc` and `glibc-nolibgcc` are strictly
bootstrapping artifacts; nothing else has them as a `buildInput` and
they shouldn't appear in the closure of any final deployment
packages.  `glibc-nolibgcc` lacks `libgcc_s.so`, so it will segfault
if you try to use it with POSIX thread cancellation.  Fortunately
all we need from it is (a) its headers (`lib.getDev`) and (b) to use
it in the `./configure` script for `libgcc`.

When translated over to the native bootstrap, `xgcc` takes the place
of `gccWithoutTargetLibc`, and the "first `glibc`" (we build two of
them) takes the place of `glibc-nolibgcc`.  At that point our native
and cross bootstrap have the same overall architecture, and it
becomes possible to merge them (at last!)

[213453]: https://github.com/NixOS/nixpkgs/issues/213453
[238154]: https://github.com/NixOS/nixpkgs/pull/238154
[hardwired]: https://github.com/NixOS/nixpkgs/blob/7553d0fe29801938bcb280bb324b579ef9016aea/pkgs/development/libraries/glibc/default.nix#L69-L88
parent 18c52d09
Loading
Loading
Loading
Loading
+6 −5
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
, withGd ? false
, withLibcrypt? false
, buildPackages
, libgcc
}:

let
@@ -16,7 +17,7 @@ in

(callPackage ./common.nix { inherit stdenv; } {
  inherit withLinuxHeaders withGd profilingLibraries withLibcrypt;
  pname = "glibc" + lib.optionalString withGd "-gd";
  pname = "glibc" + lib.optionalString withGd "-gd" + lib.optionalString (stdenv.cc.isGNU && libgcc==null) "-nolibgcc";
}).overrideAttrs(previousAttrs: {

    # Note:
@@ -90,8 +91,8 @@ in
    #
    makeFlags =
      (previousAttrs.makeFlags or [])
      ++ lib.optionals (stdenv.cc.cc?libgcc) [
        "user-defined-trusted-dirs=${stdenv.cc.cc.libgcc}/lib"
      ++ lib.optionals (libgcc != null) [
        "user-defined-trusted-dirs=${libgcc}/lib"
      ];

    postInstall = previousAttrs.postInstall + (if stdenv.hostPlatform == stdenv.buildPlatform then ''
@@ -166,8 +167,8 @@ in

    passthru =
      (previousAttrs.passthru or {})
      // lib.optionalAttrs (stdenv.cc.cc?libgcc) {
        inherit (stdenv.cc.cc) libgcc;
      // lib.optionalAttrs (libgcc != null) {
        inherit libgcc;
      };

  meta = (previousAttrs.meta or {}) // { description = "The GNU C Library"; };
+5 −0
Original line number Diff line number Diff line
@@ -21439,6 +21439,11 @@ with pkgs;
  # Being redundant to avoid cycles on boot. TODO: find a better way
  glibcCross = callPackage ../development/libraries/glibc {
    stdenv = gccCrossLibcStdenv; # doesn't compile without gcc
    libgcc = callPackage ../development/libraries/gcc/libgcc {
      gcc = gccCrossLibcStdenv.cc;
      glibc = glibcCross.override { libgcc = null; };
      stdenvNoLibs = gccCrossLibcStdenv;
    };
  };
  muslCross = musl.override {