Creating a Cross-compiling Toolchain For Beaglebone Black With Crosstool-NG

Embedded Linux Development Assignment 1 (part 1)

This semester, I am doing an independent study course on embedded Linux. This tutorial is the writeup for part 1 of my first assignment

This blog is based on Bootlin's Embedded Linux Course as well as Frank Vasquez and Chris Simmonds Mastering Embedded Linux Programming, so you may have some success reading their materials alongside this blog.

The Toolchain Generator

Crosstool-ng is a cross-compiling toolchain generator.

For those that don't know, a cross-compiling toolchain is not much different from a general toolchain you might use in regular C/C++ development (for example gcc or g++). However, the cross-compiler generates binaries for other architectures, in this case, ARM.

Our toolchain needs to have some special setup though. To write code to interface with the kernel, we need the Linux kernel headers. Therefore, our toolchain needs to insert these headers, as well as a specific C library.

We're going to use uClibc for our project. uClibc is a very small C library designed for embedded applications. It has a focus on small size over performance.

Compiling crosstool-NG

I am semi-following the instructions from the crosstool-NG documentation

First, clone the git repository.

git clone https://github.com/crosstool-ng/crosstool-ng
cd crosstool-ng/

Next, we need to configure and build the source code. There are two ways to do this, however, I'll be following the hacker's way which allows us to run this locally, rather than exporting it to the PATH.

./bootstrap
./configure --enable-local

Likely you will need to run this configure script multiple times, installing missing packages along the way. If you're not on Ubuntu like me, you might need to do some tactical googling along the way to discover the alternate package names.

after fiddling, you can compile.

make

Creating the toolchain

you can see the help menu by entering:

./ct-ng help

we want to use the cortex a8 sample since that will remove much of the configuring we might need to do. you can see all the samples with:

./ct-ng list-samples

we like the a8 one, so:

./ct-ng arm-cortex_a8-linux-gnueabi

ok, now let's enter the menu and configure our toolchain

./ct-ng menuconfig

the menu should look something like this:

crosstool menu

Configure the toolchain as follows (excerpt from embedded-linux-bbb-labs.pdf):

In Path and misc options:

• Change Maximum log level to see to DEBUG (look for LOG_DEBUG in the interface, using the / key) so that we can have more details on what happened during the build in case something went wrong.

In Target options:

• Set Use specific FPU (ARCH_FPU) to vfpv3.

• Set Floating point to hardware (FPU).

In Toolchain options:

• Set Tuple's vendor string (TARGET_VENDOR) to training.

• Set Tuple's alias (TARGET_ALIAS) to arm-linux. This way, we will be able to use the compiler as arm-linux-gcc instead of arm-training-linux-uclibcgnueabihf-gcc, which is much longer to type.

In Operating System:

• Set Version of linux to the 5.15.x version that is proposed. We choose this version because this matches the version of the kernel we will run on the board. At least, the version of the kernel headers are not more recent.

In C-library:

• If not set yet, set C library to uClibc-ng (LIBC_UCLIBC_NG)

• Keep the default version that is proposed

• If needed, enable Add support for IPv6 (LIBC_UCLIBC_IPV6)2 , Add support for WCHAR (LIBC_UCLIBC_WCHAR) and Support stack smashing protection (SSP) (LIBC_UCLIBC_HAS_ SSP)

In C compiler:

• Set Version of gcc to 11.3.0. We need to stick to gcc 11.x, as Buildroot 2022.02 which we are going to use later doesn’t support gcc 12.x toolchains yet: gcc 12.x was released after Buildroot 2022.02.

• Make sure that C++ (CC_LANG_CXX) is enabled

In Debug facilities:

• Disable duma, gdb, ltrace. They will not be needed for the labs that will use this toolchain.

• Keep only strace (DEBUG_STRACE) enabled

now, we are ready to build the toolchain:

./ct-ng build

this may take a while, as we're building an entire arm compiler, linker, debugger, etc.

Now that it's built, you should add $HOME/x-tools/arm-training-linux-uclibcgnueabihf/bin to your PATH. My perfered way of doing this is modifying your ~/.profile file which should define your PATH.

Confirm that the toolchain works.

take this ultra-simple C file:

#include <stdlib.h>
#include <stdio.h>

int main (void)
{
    printf( "Hello world!\n" );
    return EXIT_SUCCESS;
}

Create a hello.c file, and put this as the contents.

try compiling with arm-linux-gcc -o hello hello.c

if you receive bash: arm-linux-gcc: command not found... you haven't added the compiler to your PATH properly.

otherwise, you should now see your compiled C file. Typically, you would run this file with ./hello, but you will receive bash: ./hello: cannot execute binary file: Exec format error. This is because we've now compiled this binary to ARM rather than X86, which is what we wanted!! :)

In order to test this, we'll need an arm VM. qemu is the obvious choice here. Go ahead and install it with sudo dnf install qemu-user if you are a fellow RHEL/Fedora user like yours truly. sudo apt install qemu-user will work if you use Ubuntu or a Debian-based distro.

Try running the binary with qemu-arm hello.

You will receive the error /lib/ld-uClibc.so.0: No such file or directory. This is because qemu is missing our C library, uClibc. This was compiled into an object file by our configuration with crosstool-NG. Tell qemu where to find it with qemu-arm -L ~/x-tools/arm-training-linux-uclibcgnueabihf/arm-training-linux-uclibcgnueabihf/sysroot hello

You should now see the classic: Hello world!

Congratulations! You've compiled your first ARM program with a toolchain you've created yourself!