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)