Cross-compilation is an essential skill for developers working on embedded systems. Unlike standard Linux development, where code is compiled and run on the same architecture, embedded systems often use different CPU architectures (e.g., ARM, RISC-V) than your development machine (typically x86_64). This guide provides a thorough overview of how cross-compilation works, why it matters, and how to set up your own environment for building Linux for embedded systems.

What is Cross-Compilation?

Cross-compilation is the process of building executable code for a target system that is different from the host system. For embedded systems, this usually means compiling code on an x86_64 PC that will run on an ARM or RISC-V device. This method is used because embedded systems often lack the resources to compile large projects locally.

Key Concepts:

  • Host: The system where the compilation is performed (e.g., Ubuntu x86_64)
  • Target: The system where the compiled code will run (e.g., ARM Cortex-A SBC)
  • Toolchain: A set of programs (compiler, linker, etc.) configured for a specific target

Why Cross-Compile for Embedded Linux?

There are several reasons why cross-compilation is necessary:

  1. Resource Constraints: Embedded boards typically have limited CPU and RAM, making native compilation slow or impossible.
  2. Custom Kernels: You’ll often need to build custom Linux kernels or U-Boot versions specific to your board.
  3. Optimized Binaries: Cross-compilation allows building for performance-optimized targets using tailored compiler flags.

Choosing the Right Toolchain

Toolchains are central to cross-compilation. You can either:

  • Download pre-built toolchains (e.g., Linaro, GCC from Arm)
  • Build your own toolchain using crosstool-NG
  • Use BSP-provided toolchains that come with vendor SDKs (e.g., Rockchip, Allwinner)

A popular prebuilt toolchain for ARM 32-bit:

sudo apt install gcc-arm-linux-gnueabihf

And for 64-bit ARM:

sudo apt install gcc-aarch64-linux-gnu

Anatomy of a Toolchain

A complete toolchain contains:

  • gcc – The cross-compiler
  • ld – The linker
  • as – The assembler
  • libc – C standard library (often glibc or musl)
  • binutils – Binary utilities like objdump, strip, etc.

Build Systems that Support Cross-Compilation

There are many ways to manage a cross-compilation workflow. Here are a few popular tools:

1. Makefiles

Basic Makefiles allow cross-compiling by setting the compiler:

CC=arm-linux-gnueabihf-gcc
CFLAGS+=-march=armv7-a -mfpu=neon

2. CMake

CMake supports toolchain files:

cmake -DCMAKE_TOOLCHAIN_FILE=toolchain-arm.cmake .

A basic toolchain-arm.cmake might look like:

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR arm)
SET(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)

3. Buildroot

Buildroot is a simple and efficient embedded Linux build system. It handles everything: toolchains, root filesystems, kernel, and bootloader.

git clone https://github.com/buildroot/buildroot.git
cd buildroot
make qemu_arm_defconfig
make menuconfig
make

4. Yocto Project

Yocto is an advanced framework for building embedded Linux distributions. It’s more complex than Buildroot but extremely flexible.

git clone https://git.yoctoproject.org/poky
source poky/oe-init-build-env
bitbake core-image-minimal

Targeting ARM-based SBCs

Many modern embedded Single Board Computers (SBCs), such as those using the Rockchip RK3566 or Allwinner A64, require specific BSPs (Board Support Packages). These usually include:

  • Cross-toolchain
  • U-Boot bootloader
  • Linux kernel source
  • Device Tree files

For example, if you’re working with a Rockchip RK3566 SBC, you may want to explore:

👉 RK3566 Embedded Android/Linux SBC

Or, if you’re compiling generic code for your own board:

export CROSS_COMPILE=aarch64-linux-gnu-
make ARCH=arm64 your_target_defconfig
make -j$(nproc)

Handling Libraries and Dependencies

Cross-compiling projects that depend on shared libraries (like OpenSSL or GTK) requires special attention. You’ll often need:

  • Precompiled libraries for your target architecture
  • Proper PKG_CONFIG_PATH and sysroot paths
  • Environment variables like LD_LIBRARY_PATH or CMAKE_FIND_ROOT_PATH

For example:

export PKG_CONFIG_SYSROOT_DIR=/path/to/sysroot
export PKG_CONFIG_PATH=$PKG_CONFIG_SYSROOT_DIR/usr/lib/pkgconfig

Debugging Cross-Compiled Applications

Using QEMU

You can use QEMU to emulate ARM binaries on your host PC:

qemu-aarch64 ./mybinary

Remote GDB Debugging

If you’re testing on the real hardware:

# On the target:
gdbserver :1234 ./mybinary

# On the host:
aarch64-linux-gnu-gdb ./mybinary
target remote <target-ip>:1234

Common Pitfalls and Tips

  • Make sure your target and host use the same endianness and ABI.
  • Use file and readelf -h to verify binaries.
  • Always test cross-compiled binaries on the actual hardware when possible.
  • Keep your toolchains version-controlled and documented.
  • Use containerized build environments for repeatability (e.g., Docker).

Summary

Cross-compiling Linux for embedded systems may seem complex at first, but with the right toolchain and workflow, it becomes highly efficient. Whether you’re working with custom hardware, SBCs like Rockchip RK3566, or prototyping with Buildroot and Yocto, mastering cross-compilation unlocks the full power of embedded Linux development.

This article was prepared for developers using embedded-sbc.com to learn about embedded Linux topics. For advanced SBCs like RK3566 or PX30, refer to your vendor’s SDK and prebuilt environments to save time.

Want to dive deeper? Follow more tutorials at embedded-sbc.com or explore real-world SBC examples on our blog.