Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

glibcCross: use a libgcc built separately from gcc #247900

Merged
15 commits merged into from Aug 15, 2023
Merged

glibcCross: use a libgcc built separately from gcc #247900

15 commits merged into from Aug 15, 2023

Conversation

ghost
Copy link

@ghost ghost commented Aug 8, 2023

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

  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 #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 #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". 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!)

@ghost
Copy link
Author

ghost commented Aug 8, 2023

@ofborg build tests.cross.mbuffer

@ghost
Copy link
Author

ghost commented Aug 8, 2023

tests.cross.mbuffer on x86_64-linux - Success

🎉

@ghost ghost marked this pull request as ready for review August 8, 2023 13:17
@ghost ghost requested a review from matthewbauer as a code owner August 8, 2023 13:17
@ghost
Copy link
Author

ghost commented Aug 8, 2023

@ofborg eval

@ghost
Copy link
Author

ghost commented Aug 14, 2023

Figured out how to do this without a rebuild of native stdenv; base has been switched to master.

@ghost ghost marked this pull request as ready for review August 14, 2023 09:19
@Majiir
Copy link
Contributor

Majiir commented Aug 14, 2023

Building pkgsCross.armv7l-hf-multiplatform.glibc fails (full build log):

       > In file included from ../../.././gcc/options.h:8,
       >                  from ../../.././gcc/tm.h:23,
       >                  from /build/gcc-12.3.0/libgcc/libgcc2.c:29:
       > /build/gcc-12.3.0/libgcc/../gcc/config/arm/arm-opts.h:29:10: fatal error: arm-isa.h: No such file or directory
       >    29 | #include "arm-isa.h"
       >       |          ^~~~~~~~~~~
       > compilation terminated.
       > make: *** [Makefile:501: _muldi3.o] Error 1

Bisect results:

@ghost ghost marked this pull request as draft August 14, 2023 20:19
@ghost
Copy link
Author

ghost commented Aug 14, 2023

Building pkgsCross.armv7l-hf-multiplatform.glibc fails (full build log):

Thanks for catching this, @Majiir.

  • Fix is in 322acc7
  • Regression test for armv7hf is in ac86ffc (so I don't miss this again)

@ghost
Copy link
Author

ghost commented Aug 14, 2023

36909cf and 1fea173 fail with infinite recursion encountered

I've squashed these two commits into the commit after them.

I originally separated them in order to try to change only one package per commit, but unfortunately that creates non-evaluable commits in the history which -- as you noticed -- is a pain when git bisecting.

@ghost ghost marked this pull request as ready for review August 14, 2023 22:05
Adam Joseph added 15 commits August 14, 2023 15:08
This commit restores the pkgs/development/libraries/gcc/libgcc
package, which was deleted by commit 9818d12.

We need to be able to build libgcc separately from gcc in order to
avoid a circular dependency.  Nixpkgs is unusual -- unlike any other
distribution, it cannot tolerate circular dependencies between
dynamically linked libraries.  Because of this, upstream is
extremely unsympathetic to the trouble that the glibc<->gcc circular
dependency causes for us; if we don't solve it ourselves it will not
be solved.
This is necessary in order to prevent gcc from switching on
`inhibit_libc`, which cripples the `libgcc_s.so` unwinder.
This duplicates (by reference) the two-line adjustment to libgcc's
Makefile needed in order to get crtstuff to build without a full
build of gcc.
A lot of these flags were unnecessary.
This commit minimizes libgcc's gccConfigureFlags, and -- importantly
-- includes the three flags needed in order to prevent
`inhibit_libc` from becoming active.
 ### 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 #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 #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]: #213453
[238154]: #238154
[hardwired]: https://github.com/NixOS/nixpkgs/blob/7553d0fe29801938bcb280bb324b579ef9016aea/pkgs/development/libraries/glibc/default.nix#L69-L88
This test passes now.  Also fixes a minor oversight in the bug --
the test case needs to `touch $out` on success.
Copy link
Contributor

@Majiir Majiir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build successful, and it fixes the mbuffer issue. 🎉 I tested it on my armv7l hardware (cross-compiled from x86_64) to be sure.

I'm also trying to cross-compile my whole armv7l system on this branch, but currently running into build failures on latest master (unrelated to this PR). I'll update if I get a successful build or run into any problems.

I spent some time reading through your commits. I'm not qualified to give these changes a critical review, but it generally looks good to me.

Thank you for your efforts on this and for looking after the core bits of nixpkgs!

@ghost
Copy link
Author

ghost commented Aug 15, 2023

I'm also trying to cross-compile my whole armv7l system on this branch

Let me know how it goes! If you run into any problems that manifest for cross but not for native let me know; there's a good chance I've seen them before. I mostly hang out in #tvl on hackint.org.

@ghost ghost merged commit 0dfed0d into NixOS:master Aug 15, 2023
8 checks passed
@ghost ghost deleted the pr/stdenv/libgcc-no-more-cycles branch August 15, 2023 04:09
@lopsided98
Copy link
Contributor

I get the arm-isa.h: No such file or directory error on armv6l and armv5tel; 322acc7 probably needs to be expanded to cover all 32-bit ARM architectures.

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

mbuffer: cross-compiled libgcc_s.so.1 must be built with glibc headers for pthread_cleanup_push() to work
2 participants