Articles

How to make a cross compiler (gcc) for freebsd under linux. A small tutorial.


Introduction

I manage a Hudson CI Server that runs on a linux system where I work, and one of our projects is written in plain C code that should be able to run on linux and also freebsd 7. Up to now we were using a freebsd (hudson) slave node in order to build the freebsd binaries. So I decided to get rid of it and try to build the code in the main hudson server, for both linux and freebsd. The solution: to build a full cross toolchain that can generate freebsd 7 binaries in a linux box.

You can find the source for this article here.

First things, first

What exactly is a toolchain? Generically speaking, a toolchain is a set of tools (other software) that is run to generate other software, in a certain specific sequence. In this case, we are talking about a C/C++ preprocessor, a C/C++ compiler, an assembler, a linker, etc.

Understanding the task

We are going to use our current toolchain (installed in a linux box) to generate a new toolchain that builds freebsd 7 binaries for either 32 or 64 bits cpu's. This new toolchain will be installed in parallel to our current toolchain, so we can still generate binaries for linux also. This is a halfway to the infamous canadian cross.

The canadian cross

The canadian cross can be used to generate a full working application (or operating system) for a certain architecture from scratch. Suppose you have 3 machines, named A, B, and C. The idea is to use machine A to build a cross compiler that runs on machine B, to create executables for machine C. It envolves the following steps:

  • The native compiler (1) of a machine (A) is used to generate the native GCC (2) for machine A.
  • The compiler 2, is used to generate the GCC cross compiler (3) that runs on A, but generates binaries for a machine B.
  • The compiler 3 is used in B to generate a GCC cross compiler (4) that can generate binaries for machine C.

In our particular case, we already have compilers 1 and 2 because linux systems will mostly have a preshipped version of GCC fully functional. In order to simplify things a little, we're gonna stop at making the compiler 3, that is the cross compiler from machine A (the linux box) to the machine B (the freebsd box).

About GNU targets

When compiling software, the GNU toolchain needs to know 2 targets:

  • Host target, which describes where the software is being built (hardware architecture and operating system).
  • Build target, which is the hardware architecture and operating system where the executable is intended to be run.

In my own case, I had a host target x86_64-pc-linux-gnu and I wanted to build software for a x86_64-pc-freebsd7 target.

A list of available targets is here.

By convention, the GNU toolchain will store all its data in ${prefix}/${target} where the prefix is the installation prefix for the toolchain (i.e: /usr) and target is the name of the target for which this toolchain generates binaries (i.e: /usr/x86_64-pc-freebsd7, /usr/x86_64-pc-linux-gnu, etc). Also, the names for the linker, compiler, etc, will be named in the form ${target}-gcc, ${target}-ar, ${target}-ld, etc. So you can have multiple toolchains for multiple targets residing in the same prefix installation directory. Inside each target directory, you would find /include, /lib, and /bin which helps to keep things clear, since each target has its own specific libraries, includes, stubs for executables, etc.

The requirements

  • The linux box, of course, with a full and working GNU toolchain (or any other propietary toolchain) to build the cross toolchain that will generate the freebsd binaries.
  • FreeBSD 7 include files (the whole /usr/include)
  • FreeBSD 7 stubs: (/usr/lib/crt1.o, /usr/lib/crti.o, /usr/lib/crtn.o)
  • Freebsd 7 libc: (/usr/lib/libc.a, /usr/lib/libc.so, /usr/lib/libm.a, /usr/lib/libm.so)

Software involved

  • GNU binutils (I used 2.21)
  • GCC (I used 4.5.2)

If you decide to use GCC >= 4.1.x (like I did), you will also need:

  • MPC (I used 0.8.1)
  • MPFR (I used 2.4.2)
  • GMP (I used 4.3.2)

I'm going to use /usr/cross-freebsd as the prefix for the installation.

Step 1: Build binutils

GNU Binutils has some low level utilities that can manipulate executables. You can get it directly from GNU

Uncompress the distribution: tar jxf binutils-2.21.tar.bz2
Configure the software:

cd binutils-2.21
./configure --enable-libssp --enable-gold --enable-ld --target=x86_64-pc-freebsd7 --prefix=/usr/cross-freebsd

Build and install:

gmake
gmake install

Step 2: Install freebsd specific files

Remember that I told you to go get some files from a freebsd distribution? It's time to use them.

Following the GNU toolchain convention, create the following directories:

mkdir -p /usr/cross-freebsd/x86_64-pc-freebsd7/include
mkdir -p /usr/cross-freebsd/x86_64-pc-freebsd7/lib

Deploy the contents of /usr/include in /usr/cross-freebsd/x86_64-pc-freebsd7/include
The files you got from /usr/lib in /usr/cross-freebsd/x86_64-pc-freebsd7/lib.

Step 3: Build the tools needed by GCC

Skip this if you're using a gcc < 4.1.

GMP

Uncompress the distribution:

tar jxf gmp-4.3.2.tar.bz2
Configure the software:

cd gmp-4.3.2
./configure --prefix=/usr/cross-freebsd --enable-shared --enable-static --enable-mpbsd --enable-fft --enable-cxx --host=x86_64-pc-freebsd7

Build and install:

gmake
gmake install

MPFR

Uncompress the distribution:

tar jxf mpfr-2.4.2.tar.bz2

Configure the software:

cd mpfr-2.4.2
./configure --prefix=/usr/cross-freebsd --with-gnu-ld --with-gmp=/usr/cross-freebsd --enable-static --enable-shared --host=x86_64-pc-freebsd7

Build and install:

gmake
gmake install

MPC

Uncompress the distribution:

tar jxf mpc-0.8.1.tar.bz2

Configure the software:

cd mpc-0.8.1
./configure --prefix=/usr/cross-freebsd --with-gnu-ld --with-gmp=/usr/cross-freebsd --with-mpfr=/usr/cross-freebsd --enable-static --enable-shared --host=x86_64-pc-freebsd7

Build and install:

gmake
gmake install

Step 4: Build the cross compiler

NOTE: GCC requires to be built in a directory that is not the source dir, that's why we are creating an objdir. See below:

Uncompress the distribution:

tar jxf gcc-4.5.2.tar.bz2

Configure the software:

cd gcc-4.5.2
mkdir objdir
cd objdir
../configure --without-headers --with-gnu-as --with-gnu-ld --enable-languages=c,c++ --disable-nls --enable-libssp --enable-gold --enable-ld --target=x86_64-pc-freebsd7 --prefix=/usr/cross-freebsd --with-gmp=/usr/cross-freebsd --with-mpc=/usr/cross-freebsd --with-mpfr=/usr/cross-freebsd --disable-libgomp

Build and install:

LD_LIBRARY_PATH=/usr/cross-freebsd/lib gmake
gmake install

Changing the LD_LIBRARY_PATH is necessary so the gcc build wont fail when trying to use mpc and mpfr.

Step 5: Test the installation with a simple program

Write a simple "hello world" code in C to test the native and cross compilers:

#include<stdio.h>
int
main(int argc, char *argv[])
{
    fprintf(stdout, "Hello world\n");
    return 0;
}

$ gcc helloworld.c -o helloworld
$ file helloworld
helloworld: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), for GNU/Linux 2.6.9, dynamically linked (uses shared libs), not stripped

$ LD_LIBRARY_PATH=/usr/cross-freebsd/lib
$ /usr/cross-freebsd/bin/x86_64-pc-freebsd7-gcc helloworld.c -o helloworld
$ file helloworld
helloworld: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), for FreeBSD 7.2, dynamically linked (uses shared libs), FreeBSD-style, not stripped

Step 6: Test the installation with a gnu software

We will now try to build a full gnu program with our brand new cross compiler. i.e: libxml2

Get and unpack libxml2. Enter the source directory:

export PATH=${PATH}:/usr/cross-freebsd/bin
export LD_LIBRARY_PATH=/usr/cross-freebsd/lib
./configure --prefix=/usr/cross-freebsd --host=x86_64-pc-freebsd7
gmake
gmake install

Now, if you copy (for instance.. ) xmllint to a freebsd box, you should be able to run it ;) You're all set.

Conclusions

As you can see there is some complexity in effectively building a cross compiler, however it IS a pretty straightforward process when you know the steps involved. It's also very easy to break things when you start playing with different options for binutils and gcc. Anyway, following these steps will (generically speaking) help you setup a cross compiler for whatever target you need (the details may vary, but the steps should be almost the same).
You may also try to compile and install other stuff, like m4, autoconf, autoheader, libtool, libiconv, libxml2, etc to a directory and the move the whole directory to the target box, it should work like a charm ;)