Daniel Nechtan

Daniel Nechtan

daniel@nechtan.io | WhatsApp | Signal

ArticlesProjectsLinkedInGithubTwitterMastodonBlueskyAbout


Cross-compiling for OpenBSD/arm64

Following on from OpenBSD/arm64 on QEMU, it’s not always practical to compile userland software or a new kernel on some systems, particularly small SoCs with limited space and memory - or indeed QEMU, in fear of melting your CPU.

There are two scenarios here - the first, if you are looking for a standard cross-compiler for Aarch64, and the second if you want an OpenBSD-specific environment.

Scenario 1: Linaro ARM/AArch64 toolchain

Available in ports, this is the go-to GCC toolchain for cross-compilation to ARM targets. aarch64-none-elf-gcc-linaro is relatively new and there doesn’t exist a port for gdb nor newlib as yet.

This will pull in binutils and gcc, which will be installed to /usr/local/aarch64-none-elf-*:

doas pkg_add aarch64-none-elf-gcc-linaro

The 32-bit ARM toolchain is also available, which includes GDB and newlib for the ARM target. 32-bit ARM binaries will run on Aarch64, which is why you see devices such as the Raspi3 having 32-bit or mixed-arch operating systems (64-bit kernel, 32-bit userland for example).

doas pkg_add arm-none-eabi-gcc-linaro 
doas pkg_add arm-none-eabi-gdb
doas pkg_add arm-none-eabi-newlib

This will give us a traditional cross-compilation environment with gdb built to use your host, in my case x86_64-unknown-openbsd6.4 and a target of arm-none-eabi. Newlib is an implementation of the standard C library which was intended to be a free library for embedded devices and is popular with OS development hobbyists at stages where they have not written their own implementations. This will allow us to write and compile C code that uses the std C library.

Scenario 2: OpenBSD aarch64 development

Cross-building is unsupported on OpenBSD - if you’ve ever dabbled in OS development or LinuxFromScratch, you can probably guess why; it can be unpredictable. Moreover, the OpenBSD platform lifecycle focuses on making OpenBSD self-hosting and only cross-compiles to the target platform for initial bootstrapping.

There, you have been duly warned; now let’s get down to building our AArch64 toolchain!

I must admit, I have never looked at /usr/src/Makefile.cross before - so instead of jumping in I had a look to see if there was any existing resources on the process.

As the process is unsupported anyway, I’m not using /usr/src. We can’t even build a release without hacking to death the Makefiles and wrappers so that may be in a future article. Grab the source somewhere in your home dir:

mkdir arm64
cd arm64
cvs -qd anoncvs@anoncvs.fr.openbsd.org:/cvs checkout -rOPENBSD_6_4 -P src

Set some environment variables for our build tree:

target=arm64
topdir=${HOME}/arm64
srcdir=${topdir}/src
destdir=${topdir}/dest.${target}
objdir=${topdir}/obj.${target}
toolsdir=${topdir}/tools.${target}
cd ${srcdir}

As root, we create the directories for our toolchain, set the environment, then build the toolchain itself. Aarch64 is strictly an llvm/lld platform on OpenBSD (thanks, brynet!) - but for some reason when I tried to build the toolchain it complained about ld.bfd being missing from somewhere in ${destdir}… quick solution: touch the file and run make again as below!

doas make -f Makefile.cross TARGET=${target} CROSSDIR=${destdir} cross-env
doas make -f Makefile.cross TARGET=${target} CROSSDIR=${destdir} cross-dirs
doas make -f Makefile.cross TARGET=${target} CROSSDIR=${destdir} cross-tools

Build and install Aarch64 userland to our destination:

doas make -f Makefile.cross TARGET=${target} CROSSDIR=${destdir} cross-distrib

Create some convient links

ln -sf ${destdir}/usr/obj ${objdir}
ln -sf ${destdir}/usr/${target}-unknown-openbsd6.4 ${toolsdir}

chown everything back to your user and group (modify as required):

doas chown -R ${USER}:${USER} ${objdir}/*

Finally, test our environment out by compiling a C file with cc (clang):

$ eval export $( make -f Makefile.cross TARGET=${target} CROSSDIR=${destdir} cross-env )
$ cd ..
$ cat <<EOF >>hello.c
#include <stdio.h>

int
main(void) {
    printf("Hello Aarch64\n");
}
EOF

$ ${CC} -o hello hello.c
$ file hello
hello: ELF 64-bit LSB shared object, AArch64, version 1

$ ${destdir}/usr/aarch64-unknown-openbsd6.4/bin/readelf -h hello

ELF Header:
Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
Class:                             ELF64
Data:                              2's complement, little endian
Version:                           1 (current)
OS/ABI:                            UNIX - System V
ABI Version:                       0
Type:                              DYN (Shared object file)
Machine:                           AArch64
<--CUT-->

All working as expected!

${HOME}