Anyone working with ARM microcontrollers will inevitably run into the
problem of acquiring a working compiler suite for their device. Many resort to
prebuilt “freebie” suites from vendors, and an unlucky few try to roll their
own and give up. This quick tutorial will hopefully save some heartache for
those who like to know where their bits came from.
The goal will be to build a complete toolchain with working gcc, binutils,
and uClibc, sufficient to compile both OS-less microcontroller firmware and
high-level linux software.
Step 1: Setup
This bootstrap will be done in a simple directory tree that keeps clean
source trees separate from build directories. This makes it trivial to nuke a
dir and start over if you make a mistake. First, you will need a top-level
directory. Mine is ~/armcross2. Then, you will need these
subdirectories:
- source – Original source trees
- build – Scratch space for builds
- sysroot – Install root for target binaries
- tools – Install prefix for build binaries
Once those are created, download and unpack your sources into the
source directory. I will be using the following, which were the latest
at the time:
Next, create a script in the top directory called env.sh . This
will hold environment variables for convenience, and must be executed first
every time you open a terminal to be sure that they are set. In it, place the
following:
top=~/armcross2 # Change me
target=arm-linux-uclibceabi
arch=arm
tools=$top/tools
sysroot=$top/sysroot
export PATH=$tools/bin:/usr/bin:/bin
[ -z "$oldps1" ] && oldps1="$PS1"
PS1="(toolchain) $oldps1"
Don’t change the target unless you like pain. I could explain what it means,
but it really boils down to how different tools interpret it, and most of the
target-related problems tend to come from gcc which is no fun at all. Now load
your environment with the “.” command (that’s a single period):
. env.sh
The PS1 line causes the terminal prompt to change so that you know
your environment is loaded. Now you’re ready to compile.
Step 2: binutils
binutils, as its name implies, is a suite of tools for manipulating and
inspecting program binaries. It includes the linker, assembler, and tools for
converting object code to various formats that one might need.
To compile and install it, execute these commands:
cd $top/build
mkdir binutils; cd binutils
../../source/binutils-*/configure \
--target=$target \
--prefix=$tools \
--with-sysroot=$sysroot
make -j4
make install
Step 3: linux headers
There are two important sets of headers on a linux system, one comes from
libc and the other comes from the kernel. Neither are terribly important when
you have no operating system but gcc won’t compile without them. First comes
the kernel headers.
cd $top/build
mkdir linux; cd linux
make -C ../../source/linux-*/ O=`pwd` headers_install \
ARCH=$arch CROSS_COMPILE=$target- \
INSTALL_HDR_PATH=$sysroot/usr/
Step 4: gcc bootstrap
A full toolchain bootstrap requires building GCC 3 separate times. In
theory this first build is sufficient to produce microcontroller firmware,
however in my experience it is too difficult to use since it seems to not have
enough information about where headers are located.
cd $top/build
mkdir gcc-1; cd gcc-1
../../source/gcc-*/configure \
--target=$target \
--prefix=$tools \
--without-headers --with-newlib --disable-shared \
--disable-threads --disable-libssp --disable-libgomp \
--disable-libmudflap --disable-libquadmath \
--enable-languages=c
make -j4 all-gcc
make install-gcc
Step 5: libc headers
Next the libc headers need to be installed. This will involve first
configuring uClibc, which has a menu-driven system identical to that used by
the kernel. Use the arrow keys to navigate, enter to select, and press escape
twice to go back to the previous menu.
cd $top/build
mkdir uclibc; cd uclibc
make -C ../../source/uClibc-*/ O=`pwd` menuconfig
Now configure as follows: First, set the “Target Architecture” to arm.
Then enter the “features and options” submenu and set “Target Processor Endianness”
to “Little Endian”. Go up a level and enter the “General Library Settings”
menu, and set “Thread support” to “older (stable) version of linuxthreads”. You
don’t have to have a libc with threading support, but if it is disabled you
will need to adjust the configure options to gcc parts 2 and 3.
Finally, back out and save your configuration, then complete the installation:
make -C ../../source/uClibc-*/ O=`pwd` install_headers install_startfiles \
DESTDIR=$sysroot CROSS=$target- \
RUNTIME_PREFIX=/ DEVEL_PREFIX=/usr/ STRIPTOOL=true \
KERNEL_HEADERS=$sysroot/usr/include
Step 6: gcc, Part 2
First, a dirty trick. In order to compile all the components of gcc, a libc
is required. But libc hasn’t been built yet, and can’t be until gcc #2 is done.
What to do? Fake it. A libc.so with no contents will be created first,
which will placate the linker well enough to get gcc built, and then the real
libc will be built in the next step.
$target-gcc -nostdlib -nostartfiles -shared -x c /dev/null -o $sysroot/usr/lib/libc.so
cd $top/build
mkdir gcc-2; cd gcc-2
../../source/gcc-*/configure \
--target=$target \
--prefix=$tools \
--with-sysroot=$sysroot \
--disable-libssp --disable-libgomp --disable-libmudflap \
--disable-libquadmath --enable-languages=c
make -j4
make install
Step 7: libc
Next, the final libc will be built.
cd $top/build/uclibc
make -C ../../source/uClibc-*/ O=`pwd` all install -j4 \
DESTDIR=$sysroot CROSS=$target- \
RUNTIME_PREFIX=/ DEVEL_PREFIX=/usr/ STRIPTOOL=true \
KERNEL_HEADERS=$sysroot/usr/include
Step 8: gcc, Part 3
Now the third and final gcc will be built against the new libc and with all
features enabled. One more hack is required; as documented here and here a configure
script must be modified to not run a test it is unable to execute.
cd $top/build
mkdir gcc-3; cd gcc-3
../../source/gcc-*/configure \
--target=$target \
--prefix=$tools \
--with-sysroot=$sysroot \
--enable-__cxa_atexit \
--disable-libssp --disable-libgomp --disable-libmudflap \
--enable-languages=c,c++
make -j4
make install
Step 9: Profit!!
If you made it this far, you have gcc compiled and installed into your tools
directory. Congratulations! All you have to do to use it, is add your
tools/bin directory to PATH and prefix toolchain commands with your
target setting (in this case, arm-linux-uclibceabi-). You can check
out a makefile I used for a Cortex-M3 device
here