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