This post is part of the series about my personal BeagleBone Black project. The C part of the HAL is compiled on an x86 development machine — but the binary has to run on the ARM Cortex-A8 of the BeagleBone Black. That’s cross-compilation, and CMake makes it manageable.
Prerequisites
Installing the toolchain
On Ubuntu/Debian:
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihfThat installs the essential tools:
arm-linux-gnueabihf-gcc— C compiler for ARMv7 with the hard-float ABI`arm-linux-gnueabihf-g` — C compiler
arm-linux-gnueabihf-ld— linkerarm-linux-gnueabihf-strip— symbol stripper
Check the version:
arm-linux-gnueabihf-gcc --version
# arm-linux-gnueabihf-gcc (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0Sysroot (optional but recommended)
A sysroot is a copy of the target filesystem — with the correct libraries and headers for the target platform. Without a sysroot, the compiler links against the host libraries, which causes ABI mismatches.
Copy the sysroot from the BeagleBone Black:
rsync -avz \
--exclude="/proc" --exclude="/sys" --exclude="/dev" \
pi@beaglebone:/ \
~/bbb-sysroot/For simple projects without system-specific libraries, the toolchain alone is enough.
As soon as you link against |
CMake toolchain file
The centerpiece is the toolchain file — a CMake file that tells the build system which compiler, which linker, and which target system to use.
File cmake/armv7-toolchain.cmake:
# Target system
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR armv7)
# Toolchain prefix
set(TOOLCHAIN_PREFIX arm-linux-gnueabihf)
# Compiler
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++)
# Sysroot (comment out if no sysroot is available)
# set(CMAKE_SYSROOT /home/user/bbb-sysroot)
# Only search the sysroot for libraries, not the host
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# ARMv7 Cortex-A8 specific flags
set(CMAKE_C_FLAGS "-march=armv7-a -mfpu=neon -mfloat-abi=hard" CACHE STRING "")
set(CMAKE_CXX_FLAGS "-march=armv7-a -mfpu=neon -mfloat-abi=hard" CACHE STRING "")The most important variables explained:
CMAKE_SYSTEM_NAMETells CMake that the build targets a different operating system. Disables many host checks.
CMAKE_SYSTEM_PROCESSORProcessor architecture of the target system — influences compiler defaults.
CMAKE_C_COMPILERAbsolute path or name of the cross-compiler.
CMAKE_SYSROOTWhere CMake looks for headers and libraries.
CMAKE_FIND_ROOT_PATH_MODE_*Prevents CMake from accidentally finding host libraries.
CMakeLists.txt specifics
The CMakeLists.txt itself barely differs from a normal configuration.
A few points need attention when cross-compiling:
cmake_minimum_required(VERSION 3.20)
project(bbb_hal C)
# Disable the compiler test if no emulator is available
# (otherwise CMake tries to run the compiled binary)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
# Target library
add_library(bbb_drivers STATIC
src/gpio_driver.c
src/i2c_driver.c
src/uart_driver.c
)
target_include_directories(bbb_drivers PUBLIC include)
# Install path for the deploy script
install(TARGETS bbb_drivers DESTINATION lib)
install(DIRECTORY include/ DESTINATION include)
|
Running the build
# Create and configure the build directory
cmake \
-DCMAKE_TOOLCHAIN_FILE=cmake/armv7-toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release \
-B build-arm
# Compile
cmake --build build-arm
# Check: is it really an ARM binary?
file build-arm/libbbb_drivers.a
# build-arm/libbbb_drivers.a: current ar archive
arm-linux-gnueabihf-readelf -h build-arm/libbbb_drivers.a | grep Machine
# Machine: ARMIntegration in Drone CI
The toolchain needs to be installed in the CI container.
I use plain apt-get in the pipeline to avoid maintaining my own images:
steps:
- name: build-c
image: ubuntu:22.04
commands:
- apt-get update -q
- apt-get install -y cmake gcc-arm-linux-gnueabihf
- cmake
-DCMAKE_TOOLCHAIN_FILE=cmake/armv7-toolchain.cmake
-DCMAKE_BUILD_TYPE=Release
-B build-arm
- cmake --build build-arm --parallel $(nproc)Deployment to the board
After the build, the binary has to get onto the BeagleBone Black:
# Transfer the library and headers
rsync -avz build-arm/libbbb_drivers.a pi@beaglebone:/opt/bbb-hal/lib/
rsync -avz include/ pi@beaglebone:/opt/bbb-hal/include/
# Shared library (if built)
rsync -avz build-arm/libbbb_drivers.so pi@beaglebone:/usr/local/lib/
ssh pi@beaglebone "ldconfig"In the CI deploy step:
- name: deploy
image: alpine
environment:
SSH_KEY:
from_secret: bbb_ssh_key
commands:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo "$SSH_KEY" | ssh-add -
- rsync -avz build-arm/ pi@beaglebone:/opt/bbb-hal/
- ssh pi@beaglebone "systemctl restart bbb-hal"
when:
branch: [main]Common errors
Wrong ABI: hard vs softfp
/usr/bin/ld: skipping incompatible /usr/lib/libm.so when searching for -lmThe linker finds a library with the wrong float ABI.
Fix: set the sysroot correctly, or compile the libraries explicitly with -mfloat-abi=hard.
Missing sysroot headers
fatal error: linux/i2c-dev.h: No such file or directoryThe header doesn’t exist in the toolchain itself, only in the board sysroot. Fix: configure the sysroot correctly or include the header manually.
CMake finds host libraries
-- Found OpenSSL: /usr/lib/x86_64-linux-gnu/libssl.so (found version "3.0.2")CMake found the host library instead of the ARM version.
Fix: check CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY in the toolchain file.
Binary doesn’t run on the board
pi@beaglebone:~$ ./myapp
bash: ./myapp: cannot execute binary file: Exec format errorThe binary was compiled for x86, not for ARM.
Cause: the toolchain file wasn’t passed.
Check with file myapp — it should show ARM.
Rust cross-compilation
As a bonus: here’s what cross-compilation looks like for the Rust part of the project.
# Install the target
rustup target add armv7-unknown-linux-gnueabihfFile .cargo/config.toml:
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"Build:
cargo build \
--release \
--target armv7-unknown-linux-gnueabihfRust and CMake use the same toolchain ( |
Next post in the series: Rust in the HAL — why and how