This is Part 1 of the Embedded Linux series. This post sets up the cross-compiling toolchain that every subsequent lab depends on.
Time: ~2 hours (mostly waiting for the build) Target: AArch64 (ARM 64-bit), musl C library, GCC 14.3.0
What you will learn
- What a cross-compiling toolchain is and why you need one
- How to install and configure Crosstool-NG
- How to build a custom AArch64 toolchain from scratch
- How to test your toolchain with QEMU user emulation
Prerequisites
- Ubuntu 24.04 (or any recent Debian-based distro)
- At least 4 GB of RAM
- At least 15 GB of free disk space
- Basic familiarity with the Linux command line
Background: what is a cross-compiling toolchain?
When you develop software on your laptop, you compile and run code on the same machine. Both the compiler and the binary target the same architecture — x86_64.
In embedded Linux development, this changes. You write code on your x86_64 laptop, but the code runs on an ARM64 processor — a Raspberry Pi 4, an STM32MP2, a custom board. Your native gcc has no idea how to produce AArch64 binaries. You need a cross-compiler.
A cross-compiling toolchain is a set of tools that runs on your host (x86_64) but produces binaries for a different target (AArch64). It includes:
- GCC — the cross-compiler (
aarch64-linux-gcc) - binutils — linker, assembler, objcopy (
aarch64-linux-ld,aarch64-linux-as) - C library — musl, glibc, or uClibc-ng (compiled for AArch64)
- Kernel headers — the interface between userspace and the Linux kernel
Building this from scratch by hand is notoriously painful. You have to compile each component in the right order, with the right version combinations. Crosstool-NG automates the entire process.
Part A — Installing dependencies
A.1 System packages
sudo apt install build-essential git autoconf bison flex texinfo \
help2man gawk libtool-bin libncurses5-dev unzip gettext python3 rsync
Part B — Downloading and building Crosstool-NG
B.1 Getting the sources
Clone the repository and check out a stable release:
git clone https://github.com/crosstool-ng/crosstool-ng
cd crosstool-ng/
git checkout crosstool-ng-1.28.0
B.2 Building Crosstool-NG
Since we are building from git rather than a release archive, the build system files need to be generated first:
./bootstrap
Configure it for local use — this installs Crosstool-NG inside the current directory instead of system-wide:
./configure --enable-local
make
Verify the installation:
./ct-ng help
Part C — Configuring the toolchain
C.1 Loading the AArch64 sample
Crosstool-NG ships with ready-made configuration templates called samples. List them with:
./ct-ng list-samples
Load the AArch64 sample as a starting point:
./ct-ng aarch64-unknown-linux-gnu
Open the configuration menu:
./ct-ng menuconfig
C.2 Path and misc options
Enable Try features marked as EXPERIMENTAL.
C.3 Toolchain options
- Set Tuple's vendor string (
TARGET_VENDOR) totraining - Set Tuple's alias (
TARGET_ALIAS) toaarch64-linux
This allows calling the compiler as
aarch64-linux-gccinstead of the full tuple nameaarch64-training-linux-musl-gcc.
C.4 Operating System
Set Version of linux to the closest version older than or equal to 6.1.
This is important. The kernel headers used in the toolchain must not be more recent than the kernel running on the target board. If they are, binaries may reference syscalls or data structures that the running kernel does not know about.
C.5 C library
- Set C library to
musl - Keep the default version
musl is a lightweight alternative to glibc, commonly used in embedded systems. It produces smaller binaries and has a simpler, more auditable codebase.
C.6 C compiler
- Set Version of gcc to
14.3.0 - Make sure C++ (
CC_LANG_CXX) is enabled
C.7 Debug facilities
Remove all options.
Save and exit the menu.
Part D — Building the toolchain
D.1 Running the build
./ct-ng build
This will take between 20 and 60 minutes depending on your machine. Crosstool-NG will download, configure, and compile all components automatically.
The toolchain will be installed in:
$HOME/x-tools/aarch64-training-linux-musl/
D.2 Adding the toolchain to your PATH
export PATH=$HOME/x-tools/aarch64-training-linux-musl/bin:$PATH
Add this line to your ~/.bashrc to make it permanent.
Verify the compiler is accessible:
aarch64-linux-gcc --version
Expected output:
aarch64-linux-gcc (crosstool-NG 1.28.0) 14.3.0
Part E — Testing the toolchain
E.1 Compiling a test program
Create a simple hello.c:
#include <stdio.h>
int main(void) {
printf("Hello from AArch64!\n");
return 0;
}
Cross-compile it:
aarch64-linux-gcc -o hello hello.c
Verify the binary targets AArch64:
file hello
Expected output:
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked,
interpreter /lib/ld-musl-aarch64.so.1, not stripped
E.2 Running the binary with QEMU user emulation
You do not need physical ARM hardware to test the binary. QEMU's user emulation mode lets you run ARM binaries directly on your x86 machine.
Install QEMU:
sudo apt install qemu-user
Try running the binary:
qemu-aarch64 hello
You will get an error:
qemu-aarch64: Could not open '/lib/ld-musl-aarch64.so.1': No such file or directory
This is expected. The binary is dynamically linked and needs ld-musl-aarch64.so.1 — the AArch64 shared library loader — which does not exist on the x86 system.
Find it inside the toolchain sysroot:
find ~/x-tools -name "ld-musl-aarch64.so.1"
Output:
/home/user/x-tools/aarch64-training-linux-musl/aarch64-training-linux-musl/sysroot/lib/ld-musl-aarch64.so.1
Tell QEMU where to find AArch64 shared libraries using the -L flag:
qemu-aarch64 -L ~/x-tools/aarch64-training-linux-musl/aarch64-training-linux-musl/sysroot hello
Output:
Hello from AArch64!
The cross-compiled AArch64 binary is running on the x86 machine.
Understanding the toolchain tuple
The toolchain is named aarch64-training-linux-musl. Each part has a meaning:
| Part | Meaning |
|---|---|
aarch64 |
Target CPU architecture (ARM 64-bit) |
training |
Vendor string (set in menuconfig) |
linux |
Target OS |
musl |
C library |
Cleaning up (optional)
If disk space is tight, remove the downloaded source archives and build artifacts. This saves around 9 GB but means re-downloading everything on a rebuild:
./ct-ng clean
Only do this once you are satisfied with the toolchain configuration.
Key takeaways
- A cross-compiling toolchain runs on x86 but produces binaries for AArch64
- Crosstool-NG automates the complex process of building one from source
- Always use kernel headers older than or equal to the kernel on the target board
- musl is a lightweight alternative to glibc, well-suited for embedded systems
- QEMU user emulation lets you test AArch64 binaries without physical hardware
Credits
This article is based on Bootlin's Embedded Linux training materials, authored by Bootlin engineers and available under the Creative Commons Attribution-ShareAlike 3.0 license.
This article is also published under the Creative Commons Attribution-ShareAlike 3.0 license.