Creating a binutils/gcc toolchain for your OS

I wanted to update the binutils/gcc-based compiler toolchain of my Dogfood operating system. This entry describes some concepts and the changes that needed to be made in order to add this target to binutils 2.39 and gcc 12.2.0.

Configuration targets

Most of the GNU autotools work with configuration targets, which have the form <cpu>-<vendor>-<os>, where <os> can be <system> or <kernel>-<system>. For example, my Debian installation is x86_64-pc-linux-gnu, which decodes to a x86_64 cpu, vendor pc with a linux kernel and a gnu system.

These tend to be auto-detected, yet have to be specified if you want to do cross-compilation. Most often, --host=… is used to specify on which system the resulting binaries will run.

Since my OS is called Dogfood, runs on x86_64 and uses ELF binaries, the resulting configuration target will be x86_64-elf-dogfood.

More information regarding configuration targets can be found in the GNU autoconf manual.


One of the programs involved is config.sub, which is a script to validate and canonicalize a configuration target. The idea is that you give it a configuration target (or system alias) and it will validate and yield a configuration target. Usually this means it will just output whatever the input was.

             | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
             | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
             | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \
             | fiwix* | dogfood* )
        # This one is extra strict with allowed versions
        sco3.2v2 | sco3.2v[4-9]* | sco5v6*)

Depending on the actual version of autotools used, config.sub may differ a bit from what is started above but the idea is that you add your OS without any specifics.

GNU binutils

Dogfood-specific patches to binutils

GNU binutils contains an assembler (as), a linker (ld) and various other tools. It needs to know which executable and object file format to use for the target platform. Like most *NIX-like systems and homebrew OS-es, I’ll go with ELF.


We need to tell Binutils to create ELF x86-64 executables by default. However, even though x86_64 is a 64-bit platform, it can run 32-bit code as well. Let’s enable 32-bit ELF support as it may be useful later on.



  i386-*-dogfood*)          fmt=elf ;;


x86_64-*-dogfood*)     targ_emul=elf_x86_64

GCC dependencies

Dogfood-specific patches to GMP, MPC and MPFR

GCC depends on three libraries: gmp (I used 6.2.1), mpc (I used 1.2.1) and mpfr (I used 4.1.0). mpc has a config.sub in the build-aux directory, whereas gmp supplies a wrapper which calls configfsf.sub. Finally, mpfr has config.sub in the root directory. All of these need to be patched to recognize the Dogfood OS.


Dogfood-specific patches to gcc


GCC contains a script to fix system header files. Our header files do not need such fixing, and to prevent strange issues we just add our OS to the list which does not need any work:

    *-musl* | *-dogfood*)


We need to specify which assembler, linker and some other flags are to be used for gcc. We’ll be using the standard GNU tools.


Furthermore, for x86_64, we need to include some extra settings for the ELF support, i386 and x86_64 support and others.

    tm_file="${tm_file} i386/unix.h i386/att.h dbxelf.h elfos.h newlib-stdint.h i386/i386elf.h i386/x86-64.h dogfood.h"

This refers to a file dogfood.h which we need to create.


This file contains all the definitions for our OS: which libc to link against by default, which startfile/endfile objects are to be used, which #define‘s are to be enabled, etc.

/* Useful if you wish to make target-specific GCC changes. */

#undef LIB_SPEC
#define LIB_SPEC "-lc"

/* Files that are linked before user code.
   The %s tells GCC to look for these files in the library directory. */
#define STARTFILE_SPEC "crt0.o%s crti.o%s crtbegin.o%s"

/* Files that are linked after user code. */
#define ENDFILE_SPEC "crtend.o%s crtn.o%s"

/* Additional predefined macros. */
#define TARGET_OS_CPP_BUILTINS()       \
  do {                                 \
    builtin_define_std ("unix");       \
    builtin_define ("__dogfood__");    \
    builtin_assert ("system=unix");    \
    builtin_assert ("system=dogfood"); \
  } while(0);


libgcc is a low-level library provided by GCC, for which “GCC generates calls to routines in this library automatically, whenever it needs to perform some operation that is too complicated to emit inline code for.”. More information in the gcc internals documentation

We need to instruct the build infrastructure how to build the library for our purposes, and which parts (crt etc) need to be build.


For the Dogfood OS itself, we want libgcc to provide the crt files as they are fine and it avoids having to maintain them ourselves.

  extra_parts="$extra_parts crti.o crtbegin.o crtend.o crtn.o"

We also need some CPU-specific settings:

    tmake_file="$tmake_file i386/t-crtstuff t-crtstuff-pic t-libgcc-pic"

libstdc++ (bundled with gcc)

libstdc++ is the C++ standard library implementation that is shipped with GCC.


Libstdc++ does not know anything about our OS, so we must add it to the configuration.


Afterwards, you must run autoreconf in the libstdc++-v3 directory to update the configure script with the changes made to the crossconfig.m4 file.


All done now – we just need to build everything at this point. Both binutils and gcc use out of tree builds, which means the build directory resides outside of the source code. This ensures the source files will not be modified, which in turn helps to ensure builds are reproducible.

We start by building binutils:

$ mkdir binutils-build
$ cd binutils-build
$ ../binutils-2.39/configure --target=x86_64-elf-dogfood --disable-nls --disable-werror --prefix=/tmp/dogfood-toolchain --with-sysroot=/tmp/dogfood-sysroot
$ make
$ make install

And gcc:

$ mkdir gcc-build
$ cd gcc-build
$ ../gcc-12.2.0/configure --target=x86_64-elf-dogfood --disable-nls --with-newlib --without-headers --enable-languages='c,c++' --prefix=/tmp/dogfood-toolchain --disable-libstdcxx --disable-build-with-cxx --disable-libssp --disable-libquadmath --with-sysroot=/tmp/dogfood-sysroot --with-gxx-include-dir=/tmp/dogfood-sysroot/usr/include/c++/12.2.0

And we’re done! You should end up with something like the following:

$ x86_64-elf-dogfood-ld -v
GNU ld (GNU Binutils) 2.39
$ x86_64-elf-dogfood-gcc -v
Using built-in specs.
Target: x86_64-elf-dogfood
Configured with: ../../userland/gcc-12.2.0/configure --target=x86_64-elf-dogfood --disable-nls --with-newlib --without-headers --en
able-languages=c,c++ --prefix=/opt/dogfood-toolchain --disable-libstdcxx --disable-build-with-cxx --disable-libssp --disable-libqua
dmath --with-sysroot=/tmp/dogfood-sysroot --with-gxx-include-dir=/tmp/dogfood-sysroot/usr/include/c++/12.2.0
Thread model: single
Supported LTO compression algorithms: zlib
gcc version 12.2.0 (GCC)

This entry was posted in Operating System Development, Toolchain and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *