The toolchain is the first thing you need before building any other component of an embedded Linux system. Everything — the bootloader, the kernel, the root filesystem — depends on it. Without a properly configured toolchain, you cannot produce any code for your target hardware.
What is a Toolchain?
A toolchain is the set of tools that transforms C/C++ source code into executables that can run on the target device.
The core components are:
- Compiler (GCC or Clang) — translates source code to object code
- Linker — combines object files into executables or shared libraries
- Run-time libraries — the C library (glibc, musl, uClibc)
- Binutils — assembler, linker support, and binary inspection tools
Source Code (.c)
|
v
Compiler (gcc)
|
v
Object File (.o)
|
v
Linker (ld)
|
v
Executable (ELF binary for target)
GCC Toolchain Components
The GCC-based toolchain used in embedded Linux consists of three main packages:
Binutils
A collection of binary utilities including:
- Assembler (
as) — assembles assembly source to object code - Linker (
ld) — links object files into executables - objdump — disassembles object files
- readelf — displays ELF file structure and metadata
- strip — removes debug symbols to reduce binary size
- addr2line — converts addresses to source file and line numbers
GCC (GNU Compiler Collection)
The compiler itself. In embedded Linux development this primarily means the C compiler, though GCC also supports C++, Ada, and Fortran.
C Library
The C library provides the POSIX API used by applications and system programs. Common options in embedded Linux:
| Library | Description |
|---|---|
| glibc | Full-featured, standard choice for most systems |
| musl | Lightweight, standards-compliant |
| uClibc | Very small footprint, for highly constrained devices |
Setting Up a Toolchain
There are two ways to obtain a toolchain:
- Build it manually — download and build each component from source (complex, rarely done)
- Use a build system — Buildroot and Yocto both generate toolchains automatically as part of the build process
Native vs Cross Toolchain
Native Toolchain
A native toolchain runs on the same machine and architecture as the target. For example, compiling on an x86 host to produce x86 executables.
Cross Toolchain
A cross toolchain runs on the host machine but produces binaries for a different target architecture. This is the standard approach in embedded Linux.
Host Machine (x86_64)
+-------------------------------+
| Cross-Compiler |
| aarch64-none-linux-gnu-gcc |
+-------------------------------+
|
| Produces ARM64 binary
v
Target Device (ARM Cortex-A)
+-------------------------------+
| Executable runs here |
+-------------------------------+
Why use a cross toolchain?
- The host machine is faster and more powerful, so builds complete quicker
- You do not want development tools installed on the target image (increases size and attack surface)
GNU Toolchain Target Triplet
GNU toolchains are named with a target triplet that describes what they produce:
aarch64 - none - linux - gnu
| | | |
CPU Vendor Kernel OS/ABI
Example: aarch64-none-linux-gnu-gcc
- CPU:
aarch64— 64-bit ARM - Vendor:
none— supports a generic set of ARM CPUs - Kernel:
linux - OS/ABI:
gnu— GNU C Library (glibc)
Using the Cross Compiler
aarch64-none-linux-gnu-gcc -g -Wall -c -o writer.o writer.c
All toolchain tools share the same prefix:
aarch64-none-linux-gnu-gcc # C compiler
aarch64-none-linux-gnu-g++ # C++ compiler
aarch64-none-linux-gnu-gdb # Debugger
aarch64-none-linux-gnu-ld # Linker
aarch64-none-linux-gnu-objdump # Object file inspector
aarch64-none-linux-gnu-strip # Symbol stripper
aarch64-none-linux-gnu-readelf # ELF file analyser
Sysroot
The sysroot is the root filesystem of your cross toolchain. It mirrors the directory structure of the target device, containing the libraries and headers needed to compile programs that will run on the target.
aarch64-none-linux-gnu-gcc -print-sysroot
/usr/local/arm-cross-compiler/install/gcc-arm-10.2.2020.11-x86_64-aarch64-none-linux-gnu/bin/../aarch64-none-linux-gnu/libc
Sysroot Directory Structure
| Directory | Contents |
|---|---|
lib/ | Shared objects for the C library (copied to target at runtime) |
usr/lib/ | Static library archive files |
usr/include/ | Header files (e.g. <stdio.h>) |
usr/(s)bin/ | Utility programs for the cross toolchain |
Static vs Dynamic Linking
When building programs, you must choose how libraries are linked.
Static Linking
All library functions are copied from archives into the executable at build time.
Executable: myapp
|-- libc code (embedded inside)
|-- libm code (embedded inside)
(self-contained, larger binary)
Use static linking when:
- You have a single application (e.g. BusyBox)
- The application must run before the root filesystem is fully mounted (e.g. early boot drivers)
Dynamic Linking
Linking is deferred to runtime. The shared libraries must already exist on the target.
Executable: myapp
|-- depends on: libc.so
|-- depends on: libm.so
(smaller binary, libraries loaded at runtime)
Use dynamic linking when:
- The library is already present on the target (e.g. libc)
- Multiple applications share the same library
The dynamic linker searches for shared libraries in:
/liband/lib64/usr/liband/usr/lib64- Paths defined in
LD_LIBRARY_PATH
Logging and Debugging
Embedded Linux applications can use syslog for structured logging.
syslogd is a daemon that reads the /etc/rsyslog.d/*.conf configuration and writes log entries to /var/log/.
Log messages are categorised by:
Facilities:
LOG_USER— user-level messagesLOG_DAEMON— system daemonsLOG_LOCAL0throughLOG_LOCAL7— custom application use
Priorities:
LOG_ERR— error conditionsLOG_WARNING— warning conditionsLOG_INFO— informational messagesLOG_DEBUG— debug-level messages
Using syslog in C
#include <syslog.h>
// Open syslog with a facility
openlog(NULL, 0, LOG_USER);
// Log a message
syslog(LOG_ERR, "Invalid number of arguments: %d", argc);
Log output is written to /var/log/syslog.
Final Thoughts
The toolchain is the invisible foundation beneath every other component of an embedded Linux system. Getting the toolchain right — the correct architecture, the correct C library, and the correct sysroot — means everything else can be built correctly.
Toolchain Setup Flow:
Select target architecture
|
v
Choose C library (glibc / musl / uClibc)
|
v
Install or build cross toolchain
|
v
Verify: aarch64-none-linux-gnu-gcc --version
|
v
Build bootloader, kernel, root filesystem
All embedded Linux development flows through the toolchain — it is the first and most fundamental piece of the puzzle.