From 64046f019103677f9539b303bf7be0f9be12d29f Mon Sep 17 00:00:00 2001 From: Adam Joseph Date: Mon, 7 Aug 2023 21:50:24 -0700 Subject: [PATCH] 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 --- pkgs/development/libraries/glibc/default.nix | 11 ++++++----- pkgs/top-level/all-packages.nix | 5 +++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pkgs/development/libraries/glibc/default.nix b/pkgs/development/libraries/glibc/default.nix index 0f6cad157bb7..1c822bf1ed01 100644 --- a/pkgs/development/libraries/glibc/default.nix +++ b/pkgs/development/libraries/glibc/default.nix @@ -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"; }; diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 7314a8c9f89a..44d90dd9c575 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -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 {