I wanted to build a cross-compiler for MacOS X that would allow me to compile the ELF kernel image of my tiny operating system I’m developing without having to always use my Arch Linux distribution instead of just using OS X. Creating a cross-compiler is not that hard, but it requires following some instructions and it takes some time. The good thing is that once you build it, you can leave it in a folder installed and you won’t ever have to build it again. In this article I’ll describe how I compiled myself the i386-elf-gcc compiler on MacOS X El Capitan.

What is a cross-compiler anyway?

A cross compiler is a compiler that generates code for a different platform than the one is running in. As an example, in this article I’ll be creating a compiler for my Mac (x86_64-apple-darwin15.0.0) 1, that is able to create binaries for the i386-elf platform. By default the clang compiler that comes with OS X knows how to build binaries for OS X (Mach-O binaries), but if you ask it to build an ELF binary, it will just not know what to do!

Setting up a cross-compiler is a recommended task anyway when you do operating systems development. Yes, you can build i386-elf binaries using the i686-linux or x86_64-linux GCC that comes with your Linux distribution, but it will make a lot of assumptions about your target platform that you’ll need to override using flags. As an example, my Arch Linux box uses the x86-64 Linux GCC Compiler and I have to use a flag for making it generate 32 bit code instead of 64 bit code. By using a cross-compiler that targets just i*86-elf, all these assumptions are gone.

If you are using a different operating system or using a different processor architecture it might not even be possible to do that trick and setting up a cross compiler might be your only choice.

Setting up the environment

GCC has the following dependencies:

  • The GCC and the G++ compiler. Since a few OS X releases, the default compiler for OS X is actually clang 2, and running gcc actually spawns clang. It is compatible as I have had no problems building this on my machine. You might want to install the real GNU GCC from Homebrew or MacPorts.
  • The libraries GNU GMP, GNU MPFR, GNU MPC. ISL and CLOOG are optional dependencies. You can choose whether to install this now (via Homebrew, MacPorts or similar) or just wait, since the GCC buildscript is able to download and compile them on the fly as I’ll show you later.
  • A few other GNU dependencies such as Make, Bison, Flex and Texinfo. They probably either come preinstalled with the OS or are installed as part of the Xcode Command Line Tools, as I don’t remember installing them and Homebrew says it didn’t install them.

We will also create the folder where our compiler will live in. You probably won’t want to install the compiler to /usr/bin or /usr/local/bin as it might conflict with other files for your host compiler. I use /opt/local for these kind of things, although you can use any other folder such as $HOME/opt/ in case you don’t have enough rights on your system. We’ll be making this folder as the $PREFIX variable to use it later and we’ll add $PREFIX/bin to our PATH.

$ export PREFIX=/opt/local
$ sudo mkdir -p $PREFIX
$ sudo chown danirod $PREFIX
$ export PATH="$PREFIX/bin:$PATH"

Download GNU Binutils and GNU GCC. I’ll be using Binutils 2.25 and GCC 5.2.0. The trickiest part of this is that not every version of Binutils is compatible with every version of GCC. Therefore, care must be taken to use a correct version. Get the packages from the GNU FTP site and extract them in a folder such as ~/src.

$ mkdir -p $HOME/src
$ cd $HOME/src
$ wget ftp://ftp.gnu.org/gnu/binutils/binutils-2.25.tar.gz
$ wget ftp://ftp.gnu.org/gnu/gcc/gcc-5.2.0/gcc-5.2.0.tar.gz
$ for pkg in *.tar.gz; do tar zxf $pkg; done

Compiling GNU Binutils

Our first stop will be building binutils. It has to be done in a different folder. Make sure you provide the configure script these arguments.

$ mkdir build-binutils
$ cd build-binutils
$ ../binutils-2.25/configure --prefix=$PREFIX \
   --target=i386-elf --disable-multilib \
   --disable-nls --disable-werror
$ make
$ make install

Some of the arguments we provided to the configure script will make the software be compiled without some unrequired features, such as multiple target support (--disable-multilib) or internationalization (--disable-nls).

You can test that the package has been successfully installed by issuing the command i386-elf-as --version and checking that you get something that makes sense.

If you are done, let’s get to building the GCC Compiler.

Compiling GNU GCC

GCC has a few dependencies. You need the gmp, mpc and mpfr libraries to compile GCC. You can get them using Homebrew or MacPorts if you wish. Alternatively, there is an script in the GCC distribution that will automatically download them for you. It has to be executed right inside the GCC folder.

$ cd $HOME/src/gcc-5.2.0
$ ./contrib/download_prerequisites

It will take a few moments as it has to download a few packages and then extract them. The buildscript for GCC will detect these folders when compiling GCC and therefore will compile them on the fly so that they can be used to “compile the compiler”. Note that if you don’t do this step, you’ll have to install the libraries by yourself. Otherwise, you’ll get errors.

GCC has to be built from a different directory as well. Go ahead and compile it using the flags described in configure. You don’t want to actually make everything so we are just making two targets: all-gcc and all-target-libgcc. The first one is our compiler. The second one is the shared library that GCC uses:

$ mkdir build-gcc
$ cd build-gcc
$ ../gcc-5.2.0/configure --prefix=$PREFIX --target=i386-elf \
   --disable-multilib --disable-nls --disable-werror \
   --without-headers --enable-languages=c,c++
$ make all-gcc install-gcc
$ make all-target-libgcc install-target-libgcc

This is going to take some time, so let’s talk about the flags while it is compiling. --disable-multilib, --disable-nls and --disable-werror were already present in the GNU Binutils build.

The most important new flag here is --enable-languages=c,c++. GCC is actually a compiler collection that comes with support for many programming languages: C, C++, FORTRAN, Java, Go… we have to tell the configure script only to create a C compiler and a C++ compiler. We don’t need any other compiler at this point.

Once this is installed, you can test that it works as well by checking that the i386-elf-gcc command gives you something useful and not just an error about command not found.

$ i386-elf-gcc
~ i386-elf-gcc: fatal error: no input files
~ compilation terminated.

Testing the setup

If I tried to compile my kernel image using the standard x86_64-darwin clang compiler, I would get some errors when linking the image:

$ make
~ [Lots of output from the compiling process...]
~ ld -melf_i386 -nostdlib -T linker.ld [...]
~ ld: unknown option: -melf_i386

However, if I modify the buildscript so that it uses i386-elf-gcc as the linker (gcc also links), and I remove all the undesired flags, such as -m32 on the compiler or -melf_i386 on the linker (it’s an i386-elf linker, I don’t need to tell it that), it will just work.

Does this work on other OS?

I have successfully done this on Linux as well. On Linux one has to double check that the required dependencies are installed. The building process is similar.

I want to test this both on FreeBSD and on Windows. On FreeBSD might not be hard since is a UNIX environment as well. Probably setting this on Windows requires manually installing some UNIX environment such as MSYS or Cygwin. I don’t know, I’m not that interested on Windows at the moment.

  1. You can get this information by running gcc -dumpmachine. Depending on the operating system or processor you are using you might get a different output.

  2. Assuming you have the Xcode Command Line tools installed, which being a developer you probably have. Although you can always run on your terminal gcc: if you don’t, you’ll be prompted with a dialog asking whether you want to install them or not.

↩ Back home