Articles

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


Why a C/C++ Cross Compiler is useful

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.

What is a Toolchain and why it's needed to build software

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.

One operating system: Multiple toolchains

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 foundation stone of cross compiling

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: Choosing what to build and how

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.

Requirements for cross compiling in Linux for FreeBSD

  • 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)

GNU Software needed

  • 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 GNU binutils

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

Uncompress the distribution, configure the software, build and 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:

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 GNU tools needed by GCC

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

GMP

Uncompress the distribution, configure the software, build and install:

MPFR

Uncompress the distribution, configure the software, build and install:

MPC

Uncompress the distribution, configure the software, build and 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, configure the software, build and 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:

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:

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

Cross compiler aftermath

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 ;)