Linux Cross Development

Introduction

Developing software for embedded devices has many challenges. One of them is to be able to build software for different platforms than the ones used by the developers. It is necessary to have a clear set of dependencies and reduce the overhead of optional libraries.

In this post, I will present briefly how Ubuntu 20.04 can cross-compile a C++ executable or use Docker to have a container running Arm64 on an Amd64 machine to build a package for Ubuntu 20.04.

blocks

Photo by Ketut Subiyanto from Pexels

Cross-compilation

Cross-compile allows a developer to create an application for a different computer architecture than the one used for development and to have enough resources not only to run, but also to benchmark and debug the software faster.

“To cross-compile is to build on one platform a binary that will run on another platform”[1]

Alternatives

Today there are different tools that help with cross-compilation, to name a few:

  • The Yocto project that can generate not only binaries but, complete Linux distributions[2]
  • Crosstool-NG that help’s to build only toolchains[3]
  • Docker together with Qemu could help build the binaries for arm64 and amd64 machines.
  • Cross-tool packages from a Linux distribution.

On this post we focus only on the last two that works for us developing for Raspberry Pi’s.

Run a Docker Arm64 container on a Amd64 machine

This alternative is quite simple to run and is very well supported by debian distributions.

  • Using ubuntu 20.04 try the following command

    docker run arm64v8/ubuntu
    

    See that we have to prepend the architecture arm64v8:

  • You should see the message:

    WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64) and no specific platform was requested
    
  • Install the library on ubuntu sudo apt install qemu-user-static and run the command:

    docker run --platform linux/arm64 arm64v8/ubuntu uname -m
    
  • You should see the output:

    aarch64
    

Congratulations, now you are running ubuntu on an amd64 system, you can also build images with docker build. It can be not as efficient as runing directly on the machine, but will allow you to build and run packages for Arm64 or 32 without much more effort.

Using the cross-tool packages for GCC

Another option is to build using GCC, this will work for a program without dependencies but will make it very difficult to have all the dependencies for the right architecture.

  • Dependencies

    sudo apt install build-essential autoconf libtool cmake pkg-config git
    
  • Install the libraries for cross-compilation

    sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu g++-arm-linux-gnueabihf
    
  • Prepare the CMAKE file with the following dependencies

    cmake_minimum_required(VERSION 3.1)
    project(test)
    
    set(CMAKE_SYSTEM_NAME Linux)
    set(CMAKE_SYSTEM_PROCESSOR aarch64)
    set(CMAKE_CXX_STANDARD 17)
    
    SET(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
    SET(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
    
    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)
    
    add_executable(${PROJECT_NAME} main.cpp)
    
  • The C++ code that will be executed

    //main.cpp
    #include <iostream>
    
    int main() {
        std::cout << "Cross compiled" << std::endl;
        return 0;
    }
    
  • Build

    mkdir build && cd build && cmake .. && make
    
  • Check the architecture for the binary with file test the output will be test: ELF 64-bit LSB shared object, ARM aarch64 ...

  • Run the binary on an arm64 machine

    docker run -it --rm --platform linux/arm64 -v $PWD:/test arm64v8/ubuntu /test/test
    

Congrats you should see: Cross compiled on the standard out.

Having a program today that runs on different processor architectures is a given. For example, Java or Python allows developers to run applications almost transparently on different computer architectures. Still, the capability of doing so on systems with low resources is higly beneficial to be able to test first and then cross-compile on the target device.