Cross Compiler Toolchain – how to build from scratch

What is a Cross Compiler

Compilation is the process of generating machine code to be executed on a processor. Typically, we compile and execute code on the same machine. However, this is not the case in some instances, especially for embedded systems where the target system (e.g. Arduino) is not powerful enough to generate its own code. Cross compilation generates machine code targeted to run on a system different than the one generating it. This post will outline the steps for building a cross compiler.

cross-compiler

There are a number of reasons and possible scenarios where cross compilation is recommended, or even absolutely compulsory:

  • Support – Bare-metal embedded systems do not have an operating system running on them. Hence, it is not virtually possible to teach them to generate code for themselves.
  • Speed – Powerful build machines are much more suited and quicker to generate code rather than slower machines which will execute the generated executables.
  • Different OS – Cross compilers save the hassle of setting up the build process on all supported OS. A single versatile OS of choice can be used to cross compile for all supported platforms.

Cross compiling a toolchain is the process of generating the set of required and optional tools used to generate machine code for a target machine different from the development machine.

How to build a cross compiler toolchain

We will use GCC as the compiler for the purpose of this post. Binutils, GCC and newlib have to be configured and compiled from their sources, to enable cross compilation for a target machine of our choice.

We are going to make a cross compiler toolchain which can generate code for powerpc-eabi architecture. PowerPC is a renowned and widely adapted processor architecture. Furthermore, EABI means that no operating system will be running on the target (bare metal).

cross compiler gcc build process

binutils is a set of binary tools for reading, analyzing and generating executable (from assembly code) for a specified target architecture. It is the first of the cross toolchain components to be compiled. Next stage involves GCC compilation. GCC build process will use the binaries installed by binutils in the installation directory. However, some portions of GCC require a libc implementation to compile successfully; whereas a libc implementation (newlib in our case) requires GCC for cross compilation. This chicken and egg problem is solved by building a minimal portion of GCC which does not require a libc implementation. Subsequently, generated minimal GCC is used to build newlib. Finally, a complete build of GCC is carried out as the last step in cross compiler toolchain build process.

Following is a step by step guide of the whole process, annotated with comments, suggestions and reasons behind some of the choices. Advanced users can skip this guide to the end and directly use the build-cross-toolchain.sh script.

Tip

Different versions of required components and libraries may not be compatible with each other. I have mentioned a set of compatible component/library versions next to each entry.

Let’s get started!

Prerequisites

Cross compiler build process has certain prerequisites before the process can start. Firstly, a set of libraries required by GCC and binutils need to be installed or built from their respective sources. The required libraries are:

  • isl – 0.15
  • gmp – 4.3.2
  • mpc – 0.8.1
  • mpfr – 2.4.2

All these libraries are required by GCC. Binutils needs isl and gmp only. They can be downloaded here: ftp://gcc.gnu.org/pub/gcc/infrastructure/

The simplest method is to download sources for all these libraries and place them in GCC and binutils source tree.

Tip

To avoid the hassle of making copies of these libraries’ sources, the script downloads these libraries once and generates symlinks to their directories in GCC and binutils source trees.

Similarly, sources for GCC, binutils and newlib have to be downloaded from the following locations:

With all the required sources downloaded to your local machine, decide the installation directory and the target for which the cross compiler has to be built. For the following snippets, $target expands to the chosen target and $install_dir expands to cross compiler toolchain installation directory.

Step by step guide

I would recommend following the build-cross-toolchain.sh script along with this step by step guide.

Tip

make command emits a plethora of messages to stdout, making errors and warnings difficult to spot. Generally, stderr is used for all messages of significance. Redirect the stdout to a log file for more meaningful console output.

  1. binutils

Configure binutils build tree, providing the installation directory and target information. These two pieces of information will be provided for all invocations of configure in this tutorial. make binutils and install it to your installation directory.

$binutils_src_dir/configure --prefix=$install_dir --target=$target \
  > log-configure-$target.log
 make -j8 > log-make-$target.log
 make install > log-install-$target.log

Click here for list of files in installation directory after binutils install.

  1. gcc-static

As discussed earlier, GCC compilation is a two stage process. In the first stage, GCC is built as much as possible. Notice that compiler will be built for C language only. This is an attempt to keep this GCC build as simple as possible.

$gcc_src_dir/configure --prefix=$install_dir --target=$target \
 --enable-languages=c --with-newlib > log-configure-$target.log
make -j8 all-gcc > log-make-$target.log
make install-gcc > log-install-$target.log

Click here for list of files in installation directory after gcc-static install.

Tip

The following GCC make command with all-target-libgcc is essential for the cross compiler to work.

Later on,  GCC attempts to compile libgcc object files for target. Realizing that a libc is not present, many steps in the process will fail. However, some of the installed object files from this step are essential to move forward. -k flag instructs make to build as much as possible where as the -i flag tells make to ignore errors.

make -j8 -k all-target-libgcc
make -i install-target-libgcc

Click here for list of files in installation directory after gcc-static-libgcc install.

Initially, I was missing the all-target-libgcc step mentioned above, resulting in the following target binary compile-time errors:


talha@talha-workstation-1:~/scratch/toolchain/proj$ powerpc-none-eabi-gcc ./main.c -o main.out
/home/talha/scratch/toolchain/install/lib/gcc/powerpc-none-eabi/6.1.0/../../../../powerpc-none-eabi/bin/ld: cannot find ecrti.o: No such file or directory
/home/talha/scratch/toolchain/install/lib/gcc/powerpc-none-eabi/6.1.0/../../../../powerpc-none-eabi/bin/ld: cannot find crtbegin.o: No such file or directory
/home/talha/scratch/toolchain/install/lib/gcc/powerpc-none-eabi/6.1.0/../../../../powerpc-none-eabi/bin/ld: cannot find -lgcc
/home/talha/scratch/toolchain/install/lib/gcc/powerpc-none-eabi/6.1.0/../../../../powerpc-none-eabi/bin/ld: cannot find -lgcc
/home/talha/scratch/toolchain/install/lib/gcc/powerpc-none-eabi/6.1.0/../../../../powerpc-none-eabi/bin/ld: cannot find crtend.o: No such file or directory
/home/talha/scratch/toolchain/install/lib/gcc/powerpc-none-eabi/6.1.0/../../../../powerpc-none-eabi/bin/ld: cannot find ecrtn.o: No such file or directory
collect2: error: ld returned 1 exit status

  1. newlib

Successful compilation of GCC requires a libc;  which the portion of GCC built so far can cross compile successfully. We will use newlib as the implementation of libc (read more about this here).

$newlib_src_dir/configure --prefix=$install_dir --target=$target \
  > log-configure-$target.log
make -j8 all > log-make-$target.log
make install > log-install-$target.log

Click here for list of files in installation directory after newlib install.

  1. gcc

With a libc implementation in place in the installation directory, we can execute a complete build of GCC. Notice that we are building compiler for both C and C++ now.

$gcc_src_dir/configure --prefix=$install_dir --target=$target \
  --enable-languages=c,c++ --with-newlib > log-configure-$target.log
make -j8 all > log-make-$target.log
make install > log-install-$target.log

Click here for list of files in installation directory after GCC install.

  1. test cross toolchain

We can now compile a sample program to test drive the built cross compiler toolchain.

# Write a sample program.
echo "int main() { return 0; }" > ./main.c

# Add the cross toolchain installation directory to PATH
PATH=${PATH}:${install_dir}/bin

# Cross compile
powerpc-none-eabi-gcc ./main.c -o main.out

Snippets from assembly code generated for the above program are copied below:

talha@talha-workstation-1:~/scratch/toolchain/proj$ powerpc-none-eabi-objdump -d ./main.out

./main.out: file format elf32-powerpc

Disassembly of section .init:

01800074 <__init>:
 1800074: 94 21 ff f0 stwu r1,-16(r1)
 1800078: 7c 08 02 a6 mflr r0
 180007c: 90 01 00 14 stw r0,20(r1)
 1800080: 48 00 01 7d bl 18001fc <frame_dummy>
 1800084: 48 00 02 89 bl 180030c <__do_global_ctors_aux>
 1800088: 80 01 00 14 lwz r0,20(r1)

...

0180028c <main>:
 180028c: 94 21 ff f0 stwu r1,-16(r1)
 1800290: 7c 08 02 a6 mflr r0
 1800294: 90 01 00 14 stw r0,20(r1)
 1800298: 93 e1 00 0c stw r31,12(r1)
 180029c: 7c 3f 0b 78 mr r31,r1
 18002a0: 48 00 00 25 bl 18002c4 <__eabi>
 18002a4: 39 20 00 00 li r9,0
 18002a8: 7d 23 4b 78 mr r3,r9
 18002ac: 39 7f 00 10 addi r11,r31,16
 18002b0: 80 0b 00 04 lwz r0,4(r11)
 18002b4: 7c 08 03 a6 mtlr r0
 18002b8: 83 eb ff fc lwz r31,-4(r11)
 18002bc: 7d 61 5b 78 mr r1,r11
 18002c0: 4e 80 00 20 blr

...

 1800388: 7c 08 03 a6 mtlr r0
 180038c: 38 21 00 10 addi r1,r1,16
 1800390: 4e 80 00 20 blr

Voila! We have successfully built a cross compiler toolchain to generate code, from the host machine, which the target machine can execute.

Cross Compile toolchain shell script

As promised, the following script performs all the steps discussed above on a Ubuntu 14.04 machine successfully. You might want to tweak some of the paths and settings at the top of the script.

build-cross-toolchain.sh

References

2 comments

Leave a Reply

Your email address will not be published. Required fields are marked *