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.

config.sub

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.

bfd/config.bfd

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.

  x86_64-*-dogfood*)
    targ_defvec=x86_64_elf64_vec
    targ_selvecs="i386_elf32_vec"
    want64=true
    ;;

gas/configure.tgt

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

ld/configure.tgt

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.

GCC

Dogfood-specific patches to gcc

fixincludes/mkfixinc.sh

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

gcc/config.gcc

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.

*-*-dogfood*)
  gas=yes
  gnu_ld=yes
  default_use_cxa_atexit=yes
  use_gcc_stdint=wrap
  ;;

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

x86_64-*-dogfood*)
    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.

gcc/config/dogfood.h

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 TARGET_DOGFOOD
#define TARGET_DOGFOOD 1

#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. */
#undef STARTFILE_SPEC
#define STARTFILE_SPEC "crt0.o%s crti.o%s crtbegin.o%s"

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

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

libgcc

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.

libgcc/config.host

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.

*-*-dogfood*)
  extra_parts="$extra_parts crti.o crtbegin.o crtend.o crtn.o"
  ;;

We also need some CPU-specific settings:

x86_64-*-dogfood*)
    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++-v3/crossconfig.m4

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

  *-dogfood*)
    GLIBCXX_CHECK_COMPILER_FEATURES
    GLIBCXX_CHECK_LINKER_FEATURES
    GLIBCXX_CHECK_MATH_SUPPORT
    GLIBCXX_CHECK_STDLIB_SUPPORT
    ;;

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

Building

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.
COLLECT_GCC=x86_64-elf-dogfood-gcc
COLLECT_LTO_WRAPPER=/opt/dogfood-toolchain/libexec/gcc/x86_64-elf-dogfood/12.2.0/lto-wrapper
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 *