Building an ARM GCC Toolchain from Source

This article will explain how to build a cross compiling toolchain for the ARM platform and how to cross compile various programs and libraries.

Before I go ahead and build the toolchain for arm from scratch, I just wanted to introduce some of the precompiled GNU GCC toolchains available for ARM such as CodeSourcery G++ Lite or Yagarto, using a precompiled toolchain leaves you at the mercy of someone else’s whims and fancies about what features to include, what libraries to use, etc.  Depending on precompiled toolchains also means that you’re depending on their authors to regularly update them when new releases of GCC become available.

Building cross-compiling toolchain

Required sources

Linux kernel 2.6
ARM Kernel patch
binutils 2.21
gcc
glibc
glibc linuxthreads add-on

binutils

Unpack the binutils tarball into a temporary directory, change to the unpacked binutils directory and run the following commands:
# ./configure –target=arm-linux
# make
# make install
You have now some arm-linux-* binaries in /usr/local/bin. These are the binutils used by the cross-compiling toolchain. And you’ll find the new directory /usr/local/arm-linux/. This is where the cross-compiling toolchain will be installed.
You can check if the binutils are compiled correctly by calling arm-linux-ar. This tool outputs the supported targets in its command line help. You should find targets like elf32-littlearm there.

Linux Kernel header files

To compile gcc we need some header files from the linux kernel source. Unpack the kernel source code in a temporary directory and change to the unpacked source directory. You’ll need to patch the kernel with the ARM kernel patch. You do this by running this command:

# zcat path-to-arm-patch/patch-2.4.17-rmk4.gz | patch -p1
Now you need to configure the kernel by calling this command:

# make menuconfig ARCH=arm
Notice that you need to specify ARCH=arm otherwise you are going to configure the kernel for your host architecture which maybe a x86 machine.

You don’t need to do a complete configuration unless you want to compile the kernel now. Up to now you don’t have a cross compiler so you can’t compile it anyway. All you need to do is to select the correct processor type. Now save the configuration and call the following command to finish the kernel configuration:

# make dep
Now copy the include files from the kernel source to the toolchain directory:

# mkdir /usr/local/arm-linux/include
# cp -dR include/asm-arm /usr/local/arm-linux/include/asm
# cp -dR include/linux /usr/local/arm-linux/include/linux
Finally change to the toolchain directory and create a symbolic link from include to sys-include:

# cd /usr/local/arm-linux/
# ln -s include sys-linux
gcc, which we will compile now, is searching for the include files in sys-linux by default. You can use the –with-headers configure-option to specify an other directory but this results in copying the specifed directory to sys-linux. So I think it’s better to create a symbolic link to avoid redundant files.

gcc

Unpack the gcc source code and change to the unpacked source directory. We currently don’t have a running glibc so we can’t compile the whole compiler suite. But for now it is enough to compile only the C compiler. Later we can compile the glibc with this cross compiler and after that we can compile the whole compiler suite.
It may be necessary to modify the gcc source a little bit. I have done this because otherwise I was not able to compile, I got these error messages:

./libgcc2.c:41: stdlib.h: No such file or directory
./libgcc2.c:42: unistd.h: No such file or directory
.make[3]: *** [libgcc2.a] Error 1
There are rumors that it is not always needed. If you think (or know, or learn) that you need it, edit the file gcc/config/arm/t-linux, search this line:

TARGET_LIBGCC2_CFLAGS = -fomit-frame-pointer -fPIC

And change it to this:

TARGET_LIBGCC2_CFLAGS = -fomit-frame-pointer -fPIC -Dinhibit_libc -D__gthr_posix_h

Now configure the source code, compile and install:

# ./configure –target=arm-linux –disable-threads –enable-languages=c
# make
# make install
You have now a running cross compiler (/usr/local/bin/arm-linux-gcc) but without glibc it is not really useful. So let’s cross-compile that beast.

glibc

Unpack the glibc tarball in a temporary directory as usual. Then switch to the unpacked source directory and unpack the linuxthreads add-on into it:

# tar xvfz glibc-2.2.4.tar.gz
# cd glibc-2.2.4
# tar xvfz ../glibc-linuxthreads-2.2.4.tar.gz
Now set the environment variable CC to arm-linux-gcc because we want the glibc to be cross-compiled for the ARM platform. Then configure, compile and install the beast:

# export CC=arm-linux-gcc
# ./configure arm-linux –target=arm-linux –prefix=/usr/local/arm-linux –enable-add-ons
# make
# make install
Be sure you use the –prefix parameter correctly, otherwise you mess up your hosts glibc installation.

You’ll now find a lot of new files and directories in /usr/local/arm-linux. These are the glibc headers, libraries and utitilies.

Notice that you can’t use this compiled glibc on the target machine because of the specified prefix. If you want to compile a glibc which you can copy to your target machine, use an empty prefix (–prefix=) instead and use the install_root parameter to specify the installation directory:

# make install install_root=/path/to/target/root

Finally, make sure you unset the CC environment variable (with unset CC), because in the next step we are going to recompile the cross compiler and we don’t want to cross-compile the cross-compiler.

gcc (Second try)

Now we have a cross compiled glibc so we can now go on and compile the whole gcc compiler suite.

You can use the already unpacked source code of gcc but you have to remove the changes you have made and you should call make distclean to clean up the source. To be sure to do it right I suggest you delete the old source directory and unpack the gcc sources again. Whatever, after you have a clean gcc source directory, change into it, configure the source, compile and install it:

# ./configure –target=arm-linux
# make
# make install
If compilation fails because PATH_MAX is undeclared in basicio.c then add the following line to the file libchill/basicio.c somewhere between all the other includes at the top of the file:

#include <linux/limits.h>
Call make again and it should compile fine now.

That’s it. You should now have a working cross-compile toolchain for the ARM platform. If you want to cross-compile a program just set the CC environment-variable to arm-linux-gcc and compile the program as usual.

Cross-compiling various libraries

Cross-compiling ncurses ( download )

After you have unpacked the sources change to the source directory:

# cd ncurses-5.9
Now configure the source:

# CC=arm-linux-gcc \
./configure arm-linux –target=arm-linux –with-shared –prefix=/usr
Note: If you are going to use this library in your cross-compiling toolchain and not on the target machine you have to specify the prefix /usr/local/arm-linux or wherever your toolchain is installed.

To compile the sources type this:

# make HOSTCC=gcc CXX=arm-linux-c++

HOSTCC is needed because some tools needs to be compiled for the host system. The documentation says this variable has the name BUILD_CC but this is wrong. Never trust documenations

Now install the files:

# make install DESTDIR=<root-directory>

If you are going to use this library in your cross-compiling toolchain you don’t need to specify prefix because the configured one is already the right one.

Cross-compiling zlib ( download )

After you have unpacked the sources change to the source directory:

# cd zlib-1.1.4
Now the source code needs to be configured. If you want to compile a static version of this library use this configure command:

# CC=arm-linux-gcc \
./configure –prefix=/usr
Building a shared version is a little bit more complicated:

# CC=arm-linux-gcc \
LDSHARED=”arm-linux-gcc -shared -Wl,-soname,libz.so.1″ \
./configure –shared –prefix=/usr
Note: If you are going to use this library in your cross-compiling toolchain and not on the target machine you have to specify the prefix /usr/local/arm-linux or wherever your toolchain is installed.

To compile the sources, just type:

# make

Now install the binaries:

# make install prefix=<root-directory>

If you are going to use this library in your cross-compiling toolchain you don’t need to specify prefix because the configured one is already the right one.

Note that the binaries are not stripped automatically so you have to do this manually if needed.

Cross-compiling various programs

Cross-compiling bash ( download )

After you have unpacked the sources change to the source directory:

# cd bash-4.2
There is a small bug in the Makefile template that prevents cross-compiling. To fix it edit the file Makefile.in and search for this part:

bashversion$(EXEEXT): patchlevel.h conftypes.h version.h version.o $(SUPPORT_SRC)bashversion.c
$(CC_FOR_BUILD) $(CCFLAGS_FOR_BUILD) $(CPPFLAGS) -o $@ $(SUPPORT_SRC)bashversion.c version.o
Now replace the two occurences of version.o with version.c so it looks like this:

bashversion$(EXEEXT): patchlevel.h conftypes.h version.h version.c $(SUPPORT_SRC)bashversion.c
$(CC_FOR_BUILD) $(CCFLAGS_FOR_BUILD) $(CPPFLAGS) -o $@ $(SUPPORT_SRC)bashversion.c version.c
The reason behind this is: The binary bashversion needs to be compiled for the build platform. But version.o is been compiled for the target platform so it can’t be linked. So we simply replace the object file with the source file. In this case the source file is compiled again but this time for the correct platform.

Now the source code needs to be configured. Bash uses autoconf and uses compiled programs to test various things. This won’t work during cross compile so these checks has to be overridden. That’s what all the environment variables in the following command are doing:

# ac_cv_sys_restartable_syscalls=yes \
ac_cv_func_setvbuf_reversed=yes \
./configure –build=i386-linux –host=arm-linux –enable-readline –prefix=/
This configures the sources to cross-compile for the ARM platform and to use / as install prefix. You’ll see some warnings, that’s because some checks fail and default values are used. I think these default values are ok so I don’t override them like the other two ac-options above.

To compile the sources, just type:
# make
Now install the files:
# make install prefix=<root-directory>
Note that the root-directory must be an absolute path. Relative paths will not work.

If you get the error message unknown option –dir-file your texinfo installation is pretty old. This may happen if you are running Debian GNU/Linux. If you can’t (or don’t want to) update your texinfo installation you can do this: Edit the file doc/Makefile.in and search for this part:

install-info –dir-file=$(DESTDIR)$(infodir)/dir $(DESTDIR)$(infodir)/bash.info; \
And modify it so it looks like this:

install-info –info-dir=$(DESTDIR)$(infodir) $(DESTDIR)$(infodir)/bash.info; \
Now you need to add a dummy dir-file file if it’s not already present:

# echo 1 > <root-directory>/info/dir

Now run the above configure command again so the Makefile is regenerated. Now the call to make install should work like a charm.

Note that the installed binaries are not stripped automatically so you have to do it manually by using arm-linux-strip.

Cross-compiling BusyBox ( download )

After you have unpacked the sources change to the source directory:

# cd busybox-1.18
To compile the sources type this:
# make CROSS=arm-linux-
Now install the files:
# make install PREFIX=<root-directory>

Advertisements