| | {{infobox |
| | | title = LFS: The Cross-Toolchain |
| | | chapter = Linux Handbook, chapter 3 — binutils → gcc → glibc → libstdc++ |
| | | stage | Chapter 5 of the LFS book — the compiler that builds for the future |
| | | output = $LFS/tools — a toolchain that targets x86_64-lfs-linux-gnu |
| | | hardest_idea = Bootstrapping: building a compiler that builds compilers |
| | | needs = Chapter 2 complete — partition, sources, lfs user, sterile env |
| | | next = LFS II — the temporary system and the chroot |
| | }} |
| | |
| | **The cross-toolchain** is where Linux From Scratch becomes real: you compile a *new* compiler — one that runs on your host but builds for a system that does not yet exist. Binutils, then GCC, then glibc, then GCC's C++ library — four packages that together sever the build's dependence on the host's toolchain. When this chapter ends, `$LFS/tools` holds a working compiler whose binaries know nothing of the host's libraries — the foundation every remaining package is built upon. |
| | |
| | ## What cross-compilation actually is |
| | |
| | A **native** compiler builds binaries for the machine it runs on; a **cross** compiler builds for a different *target* — here, the not-yet-existing LFS system, named by the triplet **`x86_64-lfs-linux-gnu`**. The mechanism is GNU's three-machine model: |
| | |
| | - **build** — where the package compiles (your host, `x86_64-pc-linux-gnu`) |
| | - **host** — where the built program will *run* (the future LFS system) |
| | - **target** — what a *compiler* produces code for (only meaningful for compilers/binutils) |
| | |
| | For LFS, host = target = `x86_64-lfs-linux-gnu` while build = the host. The configure flags `--build`, `--host`, `--target` encode this; `config.guess` inside every tarball sniffs the build machine automatically. The trick: once gcc-for-LFS exists, every later package compiles *with* it — the host toolchain retires after this chapter. |
| | |
| | ## The build ritual — every package, always |
| | |
| | Every LFS package follows the same liturgy, worth internalizing now: |
| | |
| | ```sh |
| | cd $LFS/sources |
| | tar -xf package-version.tar.xz |
| | cd package-version |
| | # (any required patches / build-dir mkdir / configure) |
| | make |
| | make install # or DESTDIR=$LFS make install |
| | cd .. && rm -rf package-version |
| | ``` |
| | |
| | **Never build inside the source tree unless the book says to** — several packages (gcc especially) require a separate `build/` directory; stray artifacts from one configure poison the next. And always delete the tree after installing — the same tarball is re-extracted fresh for later passes. This ritual repeats ~90 times; memorizing it is the job of this chapter. |
| | |
| | ## Package 1 — Binutils pass 1 |
| | |
| | **Binutils** — `ld`, `as`, `ar`, `nm`, `objdump`, `strip` — is first because nothing builds without a linker: |
| | |
| | ```sh |
| | cd binutils-*/ |
| | mkdir -v build && cd build |
| | ../configure --prefix=$LFS/tools \ |
| | --with-sysroot=$LFS \ |
| | --target=$LFS_TGT \ |
| | --disable-nls \ |
| | --enable-gprofng=no \ |
| | --disable-werror \ |
| | --enable-new-dtags \ |
| | --enable-default-hash-style=gnu |
| | make |
| | make install |
| | ``` |
| | |
| | Each flag earns its keep: `--with-sysroot=$LFS` tells the linker where the future system's headers/libraries will live; `--target=$LFS_TGT` makes it a cross-assembler/linker; `--disable-nls` skips translations (useless in a temp toolchain); `--disable-werror` stops host gcc warnings from killing the build; `--enable-new-dtags` uses RUNPATH over RPATH for better runtime behavior. **Time this build — it's your SBU.** Every other package's "x SBU" is a multiple of this number. |
| | |
| | ## Package 2 — GCC pass 1 |
| | |
| | GCC pass 1 is a *limited* compiler — C only, no C++ library yet, statically linked where possible because it must not depend on host glibc internals: |
| | |
| | ```sh |
| | cd gcc-*/ |
| | # gcc needs gmp/mpfr/mpc sources unpacked inside it |
| | tar -xf ../mpfr-*.tar.xz && mv -v mpfr-* mpfr |
| | tar -xf ../gmp-*.tar.xz && mv -v gmp-* gmp |
| | tar -xf ../mpc-*.tar.gz && mv -v mpc-* mpc |
| | |
| | # adjust gcc's default dynamic-linker path to the FUTURE system's |
| | sed -e '/m64=/s/lib64/lib/' -i.orig gcc/config/i386/t-linux64 |
| | |
| | mkdir -v build && cd build |
| | ../configure --target=$LFS_TGT \ |
| | --prefix=$LFS/tools \ |
| | --with-glibc-version=2.41 \ |
| | --with-sysroot=$LFS \ |
| | --with-newlib \ |
| | --without-headers \ |
| | --enable-default-pie \ |
| | --enable-default-ssp \ |
| | --disable-nls \ |
| | --disable-shared \ |
| | --disable-multilib \ |
| | --disable-threads \ |
| | --disable-libatomic \ |
| | --disable-libgomp \ |
| | --disable-libquadmath \ |
| | --disable-libssp \ |
| | --disable-libvtv \ |
| | --disable-libstdcxx \ |
| | --enable-languages=c,c++ |
| | make |
| | make install |
| | # create the 'full limits' header gcc needs |
| | cd .. && cat gcc/limitx.h gcc/glimits.h gcc/limity.h > \ |
| | `dirname $($LFS_TGT-gcc -print-libgcc-file-name)`/include-fixed/limits.h |
| | ``` |
| | |
| | The wall of `--disable-*` flags is the bootstrap bargain: a compiler built without threads, shared libs, or its own runtime can be built *without already having them*. `--with-newlib --without-headers` tells gcc there's no libc yet — because there isn't; glibc is next. |
| | |
| | ## Package 3 — Linux API headers |
| | |
| | Before glibc can compile, it needs the kernel's **API headers** — the stable interface every userspace program codes against: |
| | |
| | ```sh |
| | cd linux-*/ |
| | make mrproper # pristine tree |
| | make headers # generate the sanitized headers |
| | find usr/include -type f ! -name '*.h' -delete |
| | cp -rv usr/include $LFS/usr |
| | ``` |
| | |
| | The kernel's raw headers are unusable — this step produces the *sanitized* subset userspace links against. |
| | |
| | ## Package 4 — glibc |
| | |
| | The C library — the border between userspace and kernel — builds against those fresh headers: |
| | |
| | ```sh |
| | cd glibc-*/ |
| | case $(uname -m) in |
| | i?86) ln -sfv ld-linux.so.2 $LFS/lib/ld-lsb.so.3 ;; |
| | x86_64) ln -sfv ../lib/ld-linux-x86-64.so.2 $LFS/lib64 ;; |
| | esac |
| | # (apply the book's FHS-compliance patch) |
| | mkdir -v build && cd build |
| | echo "rootsbindir=/usr/sbin" > configparms |
| | ../configure --prefix=/usr \ |
| | --host=$LFS_TGT \ |
| | --build=$(../scripts/config.guess) \ |
| | --enable-kernel=5.4 \ |
| | --with-headers=$LFS/usr/include \ |
| | --disable-nscd \ |
| | libc_cv_slibdir=/usr/lib |
| | make |
| | make DESTDIR=$LFS install |
| | # fix the ldd hardcode (it bakes the build path) |
| | sed '/RTLDLIST=/s@/usr@@g' -i $LFS/usr/bin/ldd |
| | ``` |
| | |
| | `DESTDIR=$LFS` — glibc *believes* it's installing to `/usr`, but lands in `$LFS/usr`; inside the chroot, `/usr` is exactly where it wanted to be. **The sanity check after glibc is the most important command in the book:** |
| | |
| | ```sh |
| | echo 'int main(){}' | $LFS_TGT-gcc -xc - |
| | readelf -l a.out | grep ': /lib' |
| | # must print: [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] |
| | # WRONG answer = stop everything — the toolchain targets the host, not LFS |
| | rm -v a.out |
| | ``` |
| | |
| | If the interpreter path is the host's (`/lib64/ld-linux-x86-64.so.2` exists on the host too — check it's the *LFS* one being baked via `--with-sysroot`), the toolchain is wrong and everything after fails. This echo-and-readelf is the build's first real proof of life. |
| | |
| | ## Package 5 — libstdc++ |
| | |
| | GCC's C++ runtime library, built against the fresh glibc — the last piece of the cross-toolchain: |
| | |
| | ```sh |
| | cd gcc-*/build # REUSE the pass-1 source tree is wrong — re-extract fresh |
| | # (book: re-enter a fresh gcc tree, build only libstdc++-v3) |
| | cd libstdc++-v3 |
| | mkdir -v build && cd build |
| | ../configure --prefix=$LFS/tools \ |
| | --host=$LFS_TGT \ |
| | --build=$(../config.guess) \ |
| | --disable-multilib \ |
| | --disable-nls \ |
| | --disable-libstdcxx-pch \ |
| | --with-gxx-include-dir=/tools/$LFS_TGT/include/c++/<gcc-version> |
| | make |
| | make DESTDIR=$LFS install |
| | rm -v $LFS/usr/lib/lib{stdc++{,exp,fs},supc++}.la # libtool files break the cross-build |
| | ``` |
| | |
| | ## What's in $LFS/tools now |
| | |
| | A complete cross-environment: `x86_64-lfs-linux-gnu-gcc`, `ld`, `as`, headers, and a glibc-installed `$LFS` that knows its own name. Every binary there runs on the host but produces code for `x86_64-lfs-linux-gnu`. The host toolchain's job is done. |
| | |
| | ## Where this chapter goes wrong |
| | |
| | | Symptom | Cause | |
| | | --- | --- | |
| | | The readelf sanity check shows the host's interpreter | sed step skipped or `$LFS_TGT` wrong — STOP and redo gcc pass 1 | |
| | | `cannot find -lc` / missing headers | glibc's `--with-headers` or DESTDIR wrong | |
| | | gcc builds host code, not target | `--target=$LFS_TGT` missing or unset — check `echo $LFS_TGT` | |
| | | "limits.h" missing errors in later builds | The limits.h concatenation step skipped | |
| | | Build ran as root | Go back — files must be lfs-owned or the host is at risk | |
| | | `make` fails only in parallel | Retry serial — a racy Makefile, not a broken system | |
| | | Old toolchain binaries used instead of new | `set +h` was missing — bash hashed the old path | |
| | | configure says "C compiler cannot create executables" | glibc/headers step skipped, or `$LFS` unset in this shell | |
| | |
| | ## The bootstrap — why it takes two passes |
| | |
| | The two-pass build is the part that confuses everyone; it's worth understanding fully. A compiler is itself a program — it needs to be *compiled*. Whose compiler builds the new gcc? The host's. But the host's gcc links binaries against the host's glibc — the very thing we're severing from. So: |
| | |
| | - **Pass 1** — the host's gcc builds a *target-prefixed* gcc (`x86_64-lfs-linux-gnu-gcc`) with `--with-newlib --without-headers` — a compiler that believes no libc exists yet. It can compile C/C++ but produces freestanding binaries only — perfect for building glibc. |
| | - **glibc** — built by pass-1 gcc against the kernel headers — now `x86_64-lfs-linux-gnu` has a libc. |
| | - **Pass 2** — the host's gcc (or pass-1 gcc) builds a *full* gcc — threads, shared libs, libstdc++ — this time *linked against the new glibc*. This is the compiler the rest of the build uses. |
| | - **Chapter 5's native gcc** — the final compiler built *by* pass-2 gcc — the system re-compiling its own compiler, closing the bootstrap. |
| | |
| | The chain: `host-gcc → cross-gcc → new-glibc → full-cross-gcc → native-gcc`. Each generation is built by the previous; each is freer of the host. By the end, every binary on the system descends from the toolchain you built — nothing remains of the host's except history. |
| | |
| | ## Reading the flags — the configure deep-read |
| | |
| | The flag walls in the builds aren't arbitrary — each answers a bootstrap question: |
| | |
| | | Flag | Answers | |
| | | --- | --- | |
| | | `--target=$LFS_TGT` | "Build a toolchain that produces code for the new system" | |
| | | `--host=$LFS_TGT` | "This program will *run on* the new system" (pass 2) | |
| | | `--build=$(config.guess)` | "I'm compiling on the host" | |
| | | `--with-sysroot=$LFS` | "The new system's headers/libs live under $LFS" | |
| | | `--prefix=$LFS/tools` | "Install into the toolchain staging area" | |
| | | `--disable-nls` | "Skip translations — a temp toolchain doesn't need i18n" | |
| | | `--disable-werror` | "Host gcc warnings aren't fatal" | |
| | | `--enable-new-dtags` | "Use RUNPATH, not RPATH — the correct modern behavior" | |
| | | `--disable-shared` | "Pass-1 can't link shared — it would need a libc" | |
| | | `--disable-multilib` | "One ABI only — no 32-bit variants" | |
| | | `--without-headers --with-newlib` | "No libc exists yet — build freestanding" | |
| | | `--with-glibc-version=X` | "Tell gcc the future glibc's version — it configures its headers for it" | |
| | |
| | The configure `--help` output of binutils/gcc is a document worth reading — hundreds of these flags, each a fork in the toolchain's shape. |
| | |
| | ## The sanity checks — the proofs of life |
| | |
| | The book's two verification points are the build's most important commands — learn to read them: |
| | |
| | ```sh |
| | # after glibc — the cross-toolchain check |
| | echo 'int main(){}' | $LFS_TGT-gcc -xc - |
| | readelf -l a.out | grep ': /lib' |
| | # MUST show the LFS interpreter: /lib64/ld-linux-x86-64.so.2 (64-bit) |
| | # — the dynamic linker baked into every binary the toolchain makes. |
| | # If it shows the host's, the --with-sysroot/--target was wrong — STOP. |
| | |
| | # and the deeper check — what crt files the linker finds |
| | $LFS_TGT-gcc -print-sysroot # should print $LFS |
| | $LFS_TGT-gcc -print-file-name=crt1.o # should resolve inside $LFS/usr/lib |
| | ``` |
| | |
| | The interpreter path is the whole story: every dynamically-linked binary records which `ld-linux` will run it. Cross = the *LFS* path baked in — the binaries literally know which system they belong to before it exists. |
| | |
| | ## The packages in detail — the other nine |
| | |
| | Beyond the big four, the cross-toolchain chapter quietly includes several more that make the temp system possible — worth knowing their roles: |
| | |
| | - **gmp / mpfr / mpc** — gcc's own math dependencies (arbitrary precision) — extracted *inside* the gcc tree so its build finds them |
| | - **isl / zstd** — further gcc/lib deps in some editions |
| | - **m4 / ncurses / bash / coreutils / diffutils / file / findutils / gawk / grep / gzip / make / patch / sed / tar / xz** — mostly deferred to the temp system proper (chapter 4), but the toolchain chapter's ordering matters: tools needed *before* chroot land here, tools needed *inside* land next |
| | |
| | The full list lives in the book's chapter 5 — the point is the *mechanism*: each package cross-built with the same `--build/--host/--target` grammar, each landing in `/tools`, each severed from the host. |
| | |
| | ## Common cross-build failures — the forensic table |
| | |
| | | Symptom | Read | |
| | | --- | --- | |
| | | `checking for $LFS_TGT-gcc... no` | Pass-1 gcc didn't install — check `--prefix` and `$LFS_TGT` | |
| | | `crt1.o: no such file` | glibc's DESTDIR wrong — it landed in the wrong root | |
| | | Binaries link host's `/lib` | `--with-sysroot` missing — the toolchain doesn't know $LFS | |
| | | `configure: error: cannot compute suffix` | The triplet's wrong — `x86_64-lfs-linux-gnu` spelled wrong or unset | |
| | | gcc says "no input files" on the sanity test | `echo | gcc -xc -` syntax — the `-` is stdin | |
| | | Limits.h errors in later packages | The `limitx.h`/`glimits.h`/`limity.h` concatenation step was skipped | |
| | |
| | ## Exercises |
| | |
| | 1. Explain `--build`, `--host`, `--target` for a package that *isn't* a compiler. |
| | 2. Run `config.guess` and `$LFS_TGT-gcc -dumpmachine`; explain the difference. |
| | 3. Corrupt the readelf sanity check deliberately (unset `$LFS_TGT`) and observe the failure — learn what it looks like. |
| | 4. Read `$LFS/tools/bin`'s contents; identify which are target-prefixed. |
| | 5. Time your binutils build — record your SBU. |
| | |
| | ## Timeline |
| | |
| | | Step | Result | |
| | | --- | --- | |
| | | binutils pass 1 | cross linker/assembler in $LFS/tools (your SBU) | |
| | | gcc pass 1 | bootstrap C/C++ compiler, target-prefixed | |
| | | Linux headers | the API surface for glibc | |
| | | glibc | the C library, installed under $LFS | |
| | | libstdc++ | C++ runtime completes the toolchain | |
| | | sanity check | readelf proves the interpreter path | |
| | | next | LFS II — the temporary system + the chroot | |
| | |
| | ## References |
| | |
| | - linuxfromscratch.org — LFS book chapter 5: constructing the cross-toolchain |
| | - GNU Autoconf manual — the build/host/target model |
| | - gcc configure documentation — bootstrap and cross builds |
| | |
| | ## See also |
| | |
| | - [[LFS Preparation]] — the staging ground this builds on |
| | - [[Linux]] — chapter 1 |
| | - [[C toolchain]] — the compiler's own anatomy |
| | |
| | [[Category:Linux]] |
| | [[Category:LFS]] |
| | [[Category:Handbook]] |
| | |