Previously: Into the Chroot — we built a minimal chroot environment and entered it. Now we build the real system.
The Big Ones
Chapter 8 of LFS is where the real work happens. ~80 packages. This post covers the first half — the foundation packages that everything else depends on. GCC, Glibc, Binutils, compression libraries, the test infrastructure, and the final versions of critical tools.
This is where compile times stop being trivial and start being coffee breaks.
Binutils — The Binary Toolbox
ld, as, objdump, readelf, ar, nm, strip. Every time you compile C code, Binutils does the actual assembling and linking. GCC is the front-end; Binutils is the back-end.
This is the final Binutils build. The cross-compiler versions from earlier get replaced. The linker now targets the LFS system natively.
GMP, MPFR, MPC — Math for the Compiler
Three libraries that exist almost exclusively for GCC:
- GMP — GNU Multiple Precision Arithmetic. Arbitrary-precision integers and rationals.
- MPFR — Multiple Precision Floating-Point. Built on GMP. Correct rounding.
- MPC — Multiple Precision Complex. Built on MPFR. Complex number arithmetic.
GCC needs these to evaluate constant expressions at compile time. If your code says const double x = 3.14159 * 2.0, GCC computes that during compilation using MPFR. Three libraries so one compiler can do math properly.
GCC — The Longest Compile
GCC is a beast. ~30-45 minutes with -j8. Here's why.
When you build GCC, you're bootstrapping: the compiler compiles itself. Three times. Stage 1 uses whatever compiler you have to build a new GCC. Stage 2 uses that new GCC to build itself again. Stage 3 builds itself one more time and compares the output with Stage 2 — if they match, the compiler is reproducible.
Three full compilations of a massive C++ codebase. That's why it takes forever.
The final GCC supports C, C++, and optionally Fortran. It links against the GMP/MPFR/MPC libraries we just built. This is THE compiler for the system. Every package after this gets compiled by this GCC.
Glibc — The Most Critical Package
Glibc is the GNU C Library. printf, malloc, open, read, write, fork, exec, pthread_create — every C program on the system calls into Glibc. It's the bridge between your code and the kernel.
Get Glibc wrong and nothing works. Not "some things break" — literally nothing works. Every dynamically linked binary on the system depends on libc.so.6 and the dynamic linker ld-linux-x86-64.so.2.
Glibc also installs locale data, timezone handling, and the NSS (Name Service Switch) framework. When your program calls getaddrinfo() to resolve a hostname, that's Glibc calling into NSS plugins.
The test suite is extensive (~200 tests). Some failures are expected in LFS. The ones that matter are the core functionality tests — math, string operations, threading.
Zlib, Bzip2, Xz, Lz4, Zstd — Five Compression Formats
Why does Linux need five compression libraries?
- Zlib (gzip/deflate) — The universal default. HTTP compression, PNG images,
.gzfiles. Fast, decent ratio. Everything supports it. - Bzip2 — Better compression than gzip, slower. Used by older tarballs (
.tar.bz2). Falling out of favor but still everywhere in legacy packages. - Xz (LZMA2) — Best compression ratio of the traditional formats. Kernel source tarballs use
.tar.xz. Slower to compress, fast to decompress. - Lz4 — Blazing fast, lower ratio. Used for real-time compression: kernel's initramfs, ZFS, journald. When speed matters more than size.
- Zstd (Zstandard) — Facebook's modern format. Fast like Lz4, ratios approaching Xz. The new default for many things: kernel modules, Arch Linux packages,
btrfsfilesystem compression.
Each exists because compression is a speed-vs-ratio tradeoff, and different use cases land at different points on that curve. The kernel alone uses three of these.
File — What Is This Thing?
The file command. Identifies file types by reading magic bytes — the first few bytes of a file that identify its format. ELF for executables, PK for zip archives, %PDF for PDFs.
Simple tool. Used constantly by build systems, package managers, and humans who forgot what they named things.
Readline — Line Editing
The library behind Bash's line editing, tab completion, and history. When you press up-arrow to recall a command, that's Readline. When you press Ctrl+R for reverse search, Readline. Bash, Python's REPL, and dozens of other interactive programs link against it.
M4 — The Macro Processor
A text macro processor from the 1970s. You'd think it's obsolete. It's not. Autoconf is written entirely in M4 macros. Every configure script on the system was generated by M4 processing Autoconf macros. Kill M4 and the entire Autotools build system dies.
Bc — The Calculator That Broke
Bc is an arbitrary-precision calculator language. Sounds minor, but the Linux kernel build system uses bc to compute certain values during compilation.
This one broke. First error: c99: command not found. LFS doesn't install a c99 symlink by default — fixed with ln -s gcc /usr/bin/c99.
Second error: GCC 15 changed how true/false macros interact with UINTMAX_C in the standard headers. Bc's source used boolean constants in a context that clashed with the new macro definitions. The fix was a sed command patching the source before compilation.
Real-world lesson: newer compilers break older code. This is why distributions have patch sets.
Flex — The Lexer Generator
Generates lexical analyzers (tokenizers) from pattern descriptions. If you write a language parser, Flex handles the "break input into tokens" step. Several LFS packages need Flex-generated scanners during their build.
Tcl, Expect, DejaGNU — The Test Trinity
These three packages exist for one purpose: testing other packages.
- Tcl — Tool Command Language. A scripting language. Nobody writes new Tcl code in 2025, but the test infrastructure depends on it.
- Expect — Built on Tcl. Automates interactive programs. "When you see this prompt, type this response." Used to test programs that expect user input.
- DejaGNU — Built on Expect. A testing framework. GCC and Binutils use DejaGNU for their test suites.
Three packages, installed solely so that make check works on other packages. Meta-packages for quality assurance. After the system is built, they sit idle unless you're running test suites.
Pkgconf — Package Discovery
A replacement for pkg-config. When a build system needs to find a library — "where is zlib? what flags do I need?" — it calls pkg-config --cflags --libs zlib. Pkgconf reads .pc files installed by libraries and returns the right compiler/linker flags.
Small tool. Absolutely essential for any non-trivial build.
Attr, Acl, Libcap — Extended Permissions
Linux file permissions go beyond the classic rwxr-xr-x:
- Attr — Extended attributes. Arbitrary key-value metadata on files. The foundation for the other two.
- Acl — Access Control Lists. Fine-grained permissions beyond owner/group/other. "Give user bob read access to this specific file."
- Libcap — POSIX capabilities. Instead of running a program as root (all privileges), give it specific capabilities.
CAP_NET_BIND_SERVICElets a program bind to port 80 without being root.
Modern Linux security depends on capabilities. Docker, Kubernetes, systemd all use them heavily.
Libxcrypt — Password Hashing
The crypt() function. Hashes passwords. Used by login, passwd, sudo, and anything that verifies passwords against /etc/shadow. Supports modern algorithms like SHA-512 and yescrypt.
Previously part of Glibc, now a separate library. When you set your root password later, Libxcrypt does the hashing.
Shadow — User Management
passwd, useradd, usermod, groupadd, login, su. The entire user and password management system. Shadow also manages /etc/shadow — the file where hashed passwords actually live (not /etc/passwd, despite the name).
After installing Shadow, you run passwd root to set the root password. This is the first time the system has real authentication.
Ncurses — Terminal UI
The library behind every terminal UI application. top, htop, vim, less, dialog — anything that draws boxes, colors, or moves the cursor around in a terminal uses Ncurses.
This one gave us trouble earlier. During the temporary tools phase (Chapter 6), Ncurses failed three times due to host header contamination. The cross-compile environment was picking up /usr/include/ncurses.h from the host instead of the LFS version. Cross-compilation is fragile — one wrong include path and you're silently building against the wrong headers.
The final Ncurses build in Chapter 8 is clean because we're in the chroot now. No host headers to contaminate anything.
The Pattern
By now you see the rhythm:
tar -xf package-version.tar.xz
cd package-version
./configure --prefix=/usr [options]
make
make check # sometimes
make install
cd ..
rm -rf package-version
Extract, configure, build, test, install, clean up. Repeat ~80 times. Some packages use cmake or meson instead of configure, but the pattern is the same.
The first half of Chapter 8 took the longest — GCC and Glibc dominate the build time. Everything after this builds faster because these are the biggest packages in LFS.
What We Have Now
After this batch, the system has:
- A native GCC compiler (no more cross-compilation)
- Glibc (the C library everything links against)
- Five compression libraries
- Terminal UI (Ncurses)
- User management (Shadow)
- A testing framework (Tcl/Expect/DejaGNU)
- Extended permissions (capabilities, ACLs)
The foundation is solid. Next, we install the system plumbing — shells, scripting languages, build systems, and security.
Next up: System Plumbing — From Shells to Security — Bash, Perl, Python, OpenSSL, and the two competing build ecosystems.
Compiled by AI. Proofread by caffeine. ☕