Beyond x86: A Practical Guide to Cross-Compiling LFS for ARM, RISC-V, and Embedded Targets
Photo by Photo by Vishnu Mohanan on Unsplash on Unsplash
For most of its history, Linux From Scratch has been associated with the x86 and x86_64 architectures. The official book targets those platforms, and the majority of community documentation assumes an Intel or AMD host machine. Yet the underlying methodology—building an entire Linux system from source, package by package—applies equally well to a Raspberry Pi 5, a HiFive Unmatched RISC-V board, or a custom embedded device destined for an industrial application.
Cross-compiling LFS for a foreign architecture is a genuinely different challenge from building natively, but it is a challenge that rewards careful preparation. This guide covers the conceptual framework, the practical toolchain setup, and the architecture-specific considerations that matter most when you are targeting ARM, RISC-V, or other non-x86 platforms.
Understanding the Cross-Compilation Model
Before writing a single configure flag, it is worth establishing precise terminology. In any cross-compilation scenario, three machines are relevant: the build machine, which runs the compiler; the host machine, which will run the resulting binaries; and the target machine, which is relevant primarily when building a compiler itself.
For a typical LFS cross-compilation workflow, the build machine is your existing x86_64 workstation or server. The host is your ARM or RISC-V device. When you construct the cross-toolchain in stage one of the LFS process, all three designations come into play—you are building a compiler on x86_64, which will run on x86_64, but which generates code for AArch64 or riscv64gc.
The LFS book's existing cross-compilation chapters provide the conceptual scaffolding for this workflow. The cross-tools built in early chapters of the book are, in fact, already a cross-compiler from the perspective of the target system. Extending that logic to a genuinely foreign architecture requires adjusting the target triplet and accounting for architecture-specific quirks throughout the build.
Setting Up the Cross-Toolchain
The cross-toolchain is the foundation upon which everything else rests. For AArch64 targets—which cover most modern ARM hardware, including the Raspberry Pi 4 and 5 running 64-bit operating systems—the target triplet is typically aarch64-linux-gnu or aarch64-linux-musl, depending on whether you are targeting glibc or musl libc.
For RISC-V 64-bit targets, the standard triplet is riscv64-linux-gnu, with the ABI specification lp64d being the most common choice for hardware with floating-point support.
Building binutils and GCC with the appropriate --target flag is the first major step. Pay careful attention to the --with-arch and --with-abi options when configuring GCC for ARM targets. For Cortex-A series processors, --with-arch=armv8-a and --with-cpu=cortex-a72 (or the appropriate variant for your hardware) will produce better-optimized code than a generic AArch64 build.
For RISC-V, GCC's multilib support and the ISA extension string require explicit attention. A board with a RISC-V core that supports the G extension set (which bundles IMAFD) should be targeted with --with-arch=rv64gc --with-abi=lp64d. Mismatching the ABI between your toolchain and your libc build is one of the most common sources of subtle failures during cross-compilation, so verifying consistency across all three components—binutils, GCC, and glibc—is essential before proceeding.
Architecture-Specific Gotchas
Every architecture has its peculiarities, and cross-compiling LFS surfaces them quickly.
On ARM, endianness is occasionally a source of confusion. Modern AArch64 hardware is almost universally little-endian in practice, but the architecture supports both modes. If you are targeting a 32-bit ARMv7 platform, confirm the endianness of your target hardware before configuring your toolchain.
Linux kernel header installation for cross-compilation requires passing ARCH and CROSS_COMPILE variables to make explicitly. For AArch64, this looks like make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- headers_install. Omitting these variables will install headers for the build machine's architecture rather than the target, which will silently corrupt subsequent builds in difficult-to-diagnose ways.
On RISC-V platforms, the OpenSBI firmware layer and the U-Boot bootloader introduce a build dependency that x86 LFS builders rarely encounter. Constructing a bootable RISC-V system requires understanding the privilege architecture and ensuring that your kernel configuration includes the correct SBI driver. The RISC-V community maintains excellent documentation on this topic, and the riscv-gnu-toolchain project on GitHub is a valuable reference for toolchain configuration specifics.
Testing Foreign Binaries Without Target Hardware
One of the most practical advantages available to cross-compilation builders is the ability to test foreign binaries on the build machine using QEMU's user-mode emulation. By installing qemu-user and registering the appropriate binfmt handler, your x86_64 system can transparently execute AArch64 or RISC-V ELF binaries.
This capability is particularly useful during the chroot phase of an LFS build. With binfmt_misc configured and the appropriate QEMU static binary available, you can chroot into your LFS target directory and run the foreign binaries interactively, exactly as the standard LFS book describes for native builds. The performance overhead is meaningful but entirely acceptable for build and test purposes.
For more thorough testing, QEMU's full-system emulation mode allows you to boot a complete virtual machine representing your target architecture. Running your LFS image in a QEMU AArch64 or RISC-V virtual machine before writing it to physical media catches a significant proportion of configuration errors and missing dependencies.
Real-World Use Cases Where LFS Shines on Alternative Hardware
The effort of cross-compiling LFS for a non-x86 target is justified most clearly in contexts where control over the software stack has direct practical consequences.
Embedded industrial systems are a prime example. A device running custom firmware in a manufacturing environment benefits enormously from a minimal, audited software stack with no unnecessary services, no package manager overhead, and a build process that can be reproduced exactly from a known set of source tarballs. LFS provides precisely this kind of deterministic foundation.
Single-board computer enthusiasts building custom network appliances, home automation hubs, or media servers on Raspberry Pi hardware frequently find that distribution-provided images include far more than they need. A custom LFS build for AArch64 can be tuned to include exactly the services required and nothing else, resulting in faster boot times and a smaller attack surface.
The emerging RISC-V ecosystem presents perhaps the most compelling frontier for LFS builders. As RISC-V hardware matures and becomes more accessible to individual developers, the ability to build a complete, understood Linux system from source on an open-architecture processor represents a convergence of two philosophies—open hardware and open software—that aligns closely with the values that motivate LFS builders in the first place.
Getting Started
The CLFS (Cross Linux From Scratch) project, a community-maintained extension of the LFS methodology, provides architecture-specific guidance for several non-x86 platforms and is worth consulting alongside the main LFS book. The LFS community forums and the #lfs IRC channel on Libera.Chat are active resources for builders encountering architecture-specific issues.
Cross-compiling LFS for a new architecture is not a weekend project for the uninitiated. However, for builders who have already worked through the standard LFS book and are comfortable with the build process, it represents a natural and deeply satisfying extension of the craft.