Cross-Compilation
Cross-Development is the process of developing code on one machine - the host, to run on anonther machine - the target. The host will be a powerful machine which supports development better on the target machine which normally has limited resource or unsupported softwares.
Last update: 2022-06-04
Table of Content
Cross-compiler#
A cross-compiler is a compiler capable of creating executable code for a platform other than the one on which the compiler is running. For example, a compiler that runs on a PC but generates code that runs on an Android smartphone is a cross-compiler.
A cross-compiler is necessary to compile code for multiple platforms from one development host. Direct compilation on the target platform might be infeasible, for example on embedded systems with limited computing resources.
Why use cross-compiler?
-
Speed - Target platforms are usually much slower than hosts, by an order of magnitude or more. Most special-purpose embedded hardware is designed for low cost and low power consumption, not high performance. Modern emulators (like
qemu
) are actually faster than a lot of the real world hardware they emulate, by virtue of running on high-powered desktop hardware. -
Capability - Compiling is very resource-intensive. The target platform usually doesn’t have gigabytes of memory and hundreds of gigabytes of disk space the way a desktop does; it may not even have the resources to build “hello world”, let alone large and complicated packages.
-
Availability - Bringing Linux up on a hardware platform it has never run on before requires a cross-compiler. Even on long-established platforms like ARM or MIPS, finding an up-to-date full-featured prebuilt native environment for a given target can be hard. However, you can easily set up a host machine to build a new package for your target machine.
-
Flexibility - A fully capable Linux distribution consists of hundreds of packages, but most packages are not used on the target machine. Providing a big system with full-loaded packages clearly is not a good idea on the target system with limited resource. Cross-compilation helps developer to deploy only necessary packages with a small customized system.
-
Convenience - The user interface of headless boxes tends to be a bit cramped. On a powerful host machine, you can easily edit, test, and do more work.
How it works#
Let talk about a case:
- Host machine: PC running on x64 Intel hardware (laptop/ desktop)
- Target machine: Embedded device running on ARM X64 hardware (smartphone/ Raspberry Pi)
We have to do 2 big steps:
- Build a cross-compiler from a native-compiler
- Cross-compile program for target machine
The diagram on the right represents a sample program, a.out
, running on the target OS, built using the cross-compiler and linked with the target system’s standard C and C++ libraries. The standard C++ library makes calls to the standard C library, and the C library makes direct system calls to the AArch64 Linux kernel.
BinUtils
BinUtils are a collection of binary tools. In Compilation process, you see that you only invoke gcc
with different options. However, gcc
is just a driving program which call other programs in a defined order with corresponding inputs, options and outputs.
Let run the example in the Compilation process page, but with -v
option to see what are actually run behind the scenes.
gcc -v source.c mylib.c
You will find some interesting that gcc
invoked, detailed comparison in below section.
- Configuration for default settings
- Compiler call:
/usr/lib/gcc/x86_64-linux-gnu/7/cc1
- Assembler call:
/usr/bin/as
- Linker call:
/usr/lib/gcc/x86_64-linux-gnu/7/collect2
When you build a cross-compiler, you actually build a compiler and a set of binary utility tools. That is the reason, when you install a prebuilt cross-compiler, you will see the package manager will also install binutils
, and lib
cross-compiled for the target.
For example:
sudo apt install gcc-aarch64-linux-gnu
binutils-aarch64-linux-gnu 2.30-21ubuntu1~18.04.7 2.8MiB
cpp-7-aarch64-linux-gnu 7.5.0-3ubuntu1~18.04cross1 5.5MiB
cpp-aarch64-linux-gnu 4:7.4.0-1ubuntu2.3 3.5KiB
gcc-7-aarch64-linux-gnu 7.5.0-3ubuntu1~18.04cross1 6.2MiB
gcc-7-aarch64-linux-gnu-base 7.5.0-3ubuntu1~18.04cross1 19KiB
gcc-7-cross-base 7.5.0-3ubuntu1~18.04cross1 13KiB
gcc-8-cross-base 8.4.0-1ubuntu1~18.04cross2 14KiB
gcc-aarch64-linux-gnu 4:7.4.0-1ubuntu2.3 1.4KiB
libc6-arm64-cross 2.27-3ubuntu1cross1.1 1.1MiB
libc6-dev-arm64-cross 2.27-3ubuntu1cross1.1 2.0MiB
libgcc1-arm64-cross 1:8.4.0-1ubuntu1~18.04cross2 34KiB
libgcc-7-dev-arm64-cross 7.5.0-3ubuntu1~18.04cross1 815KiB
libgomp1-arm64-cross 8.4.0-1ubuntu1~18.04cross2 67KiB
libitm1-arm64-cross 8.4.0-1ubuntu1~18.04cross2 24KiB
libstdc++6-arm64-cross 8.4.0-1ubuntu1~18.04cross2 328KiB
linux-libc-dev-arm64-cross 4.15.0-35.38cross1.1 840KiB
Example#
In this example, we will install a pre-compiled ARM64 cross-compiler.
Install the target cross-compiler
sudo apt install \
build-essential \
gcc-aarch64-linux-gnu \
Hello from machine
This simple program will show the machine type it is running on.
#include <sys/utsname.h>
#include <stdio.h>
int main() {
struct utsname name;
uname(&name);
printf("Hello from %s\n",name.machine);
return 0;
}
Compile using native compiler
gcc hello.c -o hello_native
This command builds a program for current host machine, run it, and you should see current host machine type is x86_64
:
./hello_native
Hello from x86_64
Compile using ARM64 compiler
aarch64-linux-gnu-gcc hello.c -o hello_arm
when you run it, it can not execute:
./hello_arm
-bash: ./hello_arm: cannot execute binary file: Exec format error
Check the differences with native compiler
Cross-Compilation:
aarch64-linux-gnu-gcc -v hello.c
Target:
aarch64-linux-gnu
Configuration:
--host=x86_64-linux-gnu
--target=aarch64-linux-gnu
--includedir=/usr/aarch64-linux-gnu/include
Options:
-mlittle-endian -mabi=lp64
Compiler:
/usr/lib/gcc-cross/aarch64-linux-gnu/7/cc1
Assembler:
/usr/aarch64-linux-gnu/bin/as
Linker:
/usr/lib/gcc-cross/aarch64-linux-gnu/7/collect2
Native compilation:
gcc -v hello.c
Target:
x86_64-linux-gnu
Configuration:
--host=x86_64-linux-gnu
--target=x86_64-linux-gnu
Options:
-mtune=generic -march=x86-64
Compiler:
/usr/lib/gcc/x86_64-linux-gnu/7/cc1
Assembler:
/usr/bin/as
Linker:
/usr/lib/gcc/x86_64-linux-gnu/7/collect2
Run ARM64 Binary with QEMU
You can run hello_cross
on ARM64 machine. However, for testing, you can run an ARM64 emulator on qemu
.
Install qemu-user
package which provides qemu-aarch64
emulator:
sudo apt install \
qemu-user \
Then you can run hello_arm
by providing the dynamic libraries of ARM64 machine using -L <dir>
option:
qemu-aarch64 -L /usr/aarch64-linux-gnu/ ./hello_arm
Hello from aarch64
That’s it! You have cross-compiled hello.c
using aarch64-linux-gnu-gcc
cross-compiler from a host x86_64
PC machine to run on a target aarch64
ARM machine.
Static Libraries
If you want to run hello_arm
directly without sending it to qemu-aarch64
, you can do below steps:
- Install
qemu-user-static
package - Build
hello_arm
with-static
flag - Run directly
./hello_arm