ForestOS Cross-Toolchain
The ForestOS cross-toolchain is a self-contained source package that builds the GCC + Binutils cross-compilers targeting the ForestOS operating system. The output is a set of freestanding ELF cross-compilers used to build the Fern kernel and, eventually, ForestOS userspace.
Vocabulary: the OS is ForestOS, the kernel is Fern, the bootloader is foreboots.
Why a Custom Cross-Compiler is Needed
The Fern kernel targets bare-metal x86 in freestanding mode. It does not link against a hosted libc in the traditional sense — it uses its own linker script, its own startup code, and its own syscall interface. A stock x86_64-linux-gnu-gcc would pull in Linux-specific assumptions (dynamic linker paths, glibc startfiles, SSP flags) that don't apply.
A custom cross-compiler gives ForestOS clean triple isolation, freestanding defaults, correct predefines, and sysroot control.
GCC and Binutils Versions
The toolchain pins two versions (see checksums.txt):
| Component | Version | Tarball |
|---|---|---|
| Binutils | 2.43 | binutils-2.43.tar.xz |
| GCC | 13.2.0 | gcc-13.2.0.tar.xz |
Both are downloaded from the official GNU mirrors and verified against SHA-256 checksums before extraction. The checksums file is compatible with sha256sum -c.
Target Triples
| Architecture | Target Triple | Tool Prefix | GCC Arch Flag |
|---|---|---|---|
| 32-bit x86 | i686-forestos |
i686-forestos- |
--with-arch=i686 |
| 64-bit x86 | x86_64-forestos |
x86_64-forestos- |
--with-arch=x86-64 |
Both targets use ELF output format and are configured as freestanding (no libc assumed present).
Building the Toolchain from Source
Host Dependencies
You need a Linux host with build tools and GCC prerequisite libraries:
Debian/Ubuntu:
sudo apt install -y build-essential flex bison gawk texinfo curl xz-utils \
libgmp-dev libmpfr-dev libmpc-dev libisl-dev zlib1g-dev
Fedora/RHEL:
sudo dnf install -y gcc gcc-c++ make flex bison gawk texinfo curl xz \
gmp-devel mpfr-devel libmpc-devel isl-devel zlib-devel
Arch:
sudo pacman -S --needed base-devel flex bison gawk texinfo curl xz gmp mpfr libmpc
The required tools are: gcc g++ make makeinfo flex bison gawk tar xz plus either curl or wget.
Build Commands
Set your repo root:
export FOREST="$(git rev-parse --show-toplevel)"
Build both architectures (default):
cd $FOREST/forestos-toolchain
./build-toolchain.sh
Build a single architecture with parallelism:
./build-toolchain.sh --arch 32 # i686-forestos only
./build-toolchain.sh --arch 64 -j8 # x86_64-forestos only, 8 jobs
Other useful options:
./build-toolchain.sh --help # full option list
./build-toolchain.sh --skip-deps # skip dependency probe
./build-toolchain.sh --clean # force full rebuild
./build-toolchain.sh --no-download # use tarballs already in src/
What the Build Does
The script (build-toolchain.sh) is idempotent and resumable:
- Checks host dependencies (tools + GMP/MPFR/MPC headers).
- Downloads binutils and GCC tarballs into
src/, verifying SHA-256. - Patches both source trees so the real
*-forestostriples are
recognised (not the old i686-elf + symlink hack).
- Populates the sysroot from
sysroot-skeleton/, Fern kernel headers,
and consolidated libc headers.
- Builds binutils for each target (configure + make + make install).
- Builds GCC for each target (
all-gcc+all-target-libgcc). - Verifies each toolchain with a freestanding compile probe.
- Fixes include-fixed — copies missing headers into GCC's
include-fixed/ directory so the include chain resolves.
A full build takes tens of minutes and needs several GB of scratch space.
Build Artifacts
Everything generated is .gitignored. Only the source is committed:
forestos-toolchain/
build-toolchain.sh # the canonical build script
patches/ # triple-support patches + forestos.h
checksums.txt # pinned SHA-256 sums
sysroot-skeleton/ # tracked header island
README.md
# all generated, gitignored:
src/ # downloaded + extracted sources
build/ # out-of-tree build directories
install/ # finished toolchain (install/bin/<prefix>-gcc, ...)
sysroot/ # assembled sysroot (headers + libc objects)
The Sysroot Skeleton
The sysroot skeleton (sysroot-skeleton/) is a small, tracked header tree that gets installed into sysroot/usr/include/. It exists because the Fern kernel compiles freestanding and passes no --sysroot or -isystem flags — the sysroot is reached only because the cross-GCC is configured with --with-sysroot=.../sysroot.
Skeleton Contents
sysroot-skeleton/
usr/include/stdio.h # libc island — kernel pulls <stdio.h>
usr/include/forestos/syscalls.h # REQUIRED; the only source of this header
usr/include/sys/types.h # thin wrapper -> ../libc/sys/types.h
usr/include/libc/sys/types.h # real POSIX type definitions
lib/README.md # marks lib/ as a generated output dir
Why It Exists
The kernel pulls , which transitively requires and . The forestos/syscalls.h header has no clean source anywhere else in the repo, so it must be carried here. The sys/types.h wrapper delegates to the consolidated libc, which only depends on GCC freestanding builtins (, ).
The skeleton is installed verbatim into sysroot/usr/include/. During build, build-toolchain.sh overlays three sources into the sysroot:
sysroot-skeleton/(tracked) — the critical header island.- Fern kernel headers from
fern/src/include/— the wider set of
userspace/wrapper headers.
- Consolidated libc headers from
libs/libc/— the single source of
truth for all libc headers.
CRT and libc objects (crt0.o, crti.o, crtn.o, libc.a) are build outputs, not part of the skeleton. They're arch-specific and regenerated on every build.
Kernel Headers: forestos/syscalls.h
The file sysroot-skeleton/usr/include/forestos/syscalls.h is the most critical header in the sysroot. It defines:
- Syscall numbers (
SYS_READ,SYS_WRITE,SYS_OPEN, etc.)
that must match src/include/syscall.h in the Fern kernel.
- Error codes (
EPERM,ENOENT,ENOMEM, etc.) that must match
src/include/libc/errno.h.
- Inline syscall wrappers (
syscall0throughsyscall6) using
int $0x80 — these are inline assembly stubs for the Fern kernel's interrupt-based syscall interface.
- Framebuffer extension syscalls (
SYS_MMAP_FB,SYS_FB_FLUSH,
SYS_SOUND_PLAY, etc.) for the graphics subsystem.
This header must be kept in sync with the kernel's own definitions. If you add or change a syscall in the kernel, update it here too.
Patches Applied to GCC/Binutils
Stock GNU binutils and GCC don't know about the *-forestos triple. The toolchain applies several patches to make them work.
Patches Directory
patches/
config.sub.forestos.patch # reference diff for config.sub
config.gcc.forestos.patch # reference diff for gcc/config.gcc
gcc/config/forestos.h # GCC OS-config header (copied into gcc source)
README.md # documents the patch strategy
What Gets Patched
| File | Purpose |
|---|---|
config.sub (both binutils & gcc) |
Adds forestos* to the OS validation list so config.sub i686-forestos canonicalizes correctly. |
gas/configure.tgt |
Adds i[3-7]86--forestos and x86_64--forestos with fmt=elf. |
ld/configure.tgt |
Adds forestos targets with elf_i386 / elf_x86_64 emulations. |
bfd/config.bfd |
Adds forestos BFD vectors (i386_elf32_vec / x86_64_elf64_vec). |
gcc/config.gcc |
Adds i[34567]86--forestos and x86_64--forestos target cases. |
gcc/config/forestos.h |
Defines __forestos__, ELF startfile/endfile specs, dynamic linker path. |
libgcc/config.host |
Adds forestos tmake_file entries for CRT and libgcc-pic. |
libcody/configure |
Patches C++11 check to accept >= 201103 (for GCC >= 14 host compilers). |
The forestos.h GCC Config Header
This header (patches/gcc/config/forestos.h) is copied verbatim into the GCC source tree. Key definitions:
// Predefined macros for target code detection
builtin_define("__forestos__");
builtin_define("__ForestOS__");
builtin_assert("system=forestos");
// ELF startup/shutdown files
STARTFILE_SPEC "%{!shared:crt0.o%s} crti.o%s crtbegin.o%s"
ENDFILE_SPEC "crtend.o%s crtn.o%s"
// Dynamic linker (for future shared userspace)
DYNAMIC_LINKER "/lib/ld-forestos.so.1"
Patch Application Strategy
Patches are applied via guarded, idempotent sed insertions in build-toolchain.sh, not via patch -p1. This is because anchor lines drift between binutils/gcc versions. The .patch files in patches/ are reference diffs documenting exactly what the script produces.
Toolchain Directory Layout
After a successful build:
forestos-toolchain/install/
bin/
i686-forestos-ld # 32-bit linker
i686-forestos-as # 32-bit assembler
i686-forestos-gcc # 32-bit C/C++ compiler
i686-forestos-g++ # 32-bit C++ compiler
i686-forestos-objcopy # binary manipulation
i686-forestos-strip # symbol stripping
i686-forestos-nm # symbol listing
x86_64-forestos-* # (same set for 64-bit)
lib/gcc/
i686-forestos/13.2.0/ # 32-bit GCC support files
include-fixed/ # auto-processed headers
libgcc.a # compiler runtime library
x86_64-forestos/13.2.0/ # 64-bit GCC support files
forestos-toolchain/sysroot/
usr/include/
stdio.h # libc island
forestos/syscalls.h # kernel syscall interface
sys/types.h # POSIX types wrapper
libc/sys/types.h # real type definitions
(Fern kernel headers) # from fern/src/include/
usr/lib/ # (placeholder)
lib/ # CRT objects (build outputs)
Troubleshooting Toolchain Builds
Missing Dependencies
Symptom: ERROR Missing host build dependencies: tools: ... libs: ...
Fix: Install the missing packages (see Host Dependencies above). Or use --skip-deps if you know they're present on your system.
Config.sub Doesn't Recognize ForestOS
Symptom: Invalid configuration ... OS 'forestos' not recognized
Fix: This means the config.sub patch wasn't applied. Ensure you're running build-toolchain.sh (not building manually). The script patches config.sub before configuring. If you need to patch manually:
cd $FOREST/forestos-toolchain/src/binutils-2.43
patch -p1 < ../patches/config.sub.forestos.patch
Checksum Mismatch
Symptom: Checksum MISMATCH for gcc-13.2.0.tar.xz
Fix: Delete the corrupted tarball and re-run:
rm src/gcc-13.2.0.tar.xz
./build-toolchain.sh
GCC Build Fails with C++ Errors
Symptom: Compile errors in GCC's own code during all-gcc.
Fix: The script forces CXXFLAGS=-std=gnu++17 for the host compiler. If your host GCC is very old (< 7), this may not be enough. Upgrade your host compiler or use --skip-deps with a known-good host.
Broken Include Chain
Symptom: fatal error: forestos/syscalls.h: No such file or directory
Fix: This means the sysroot wasn't populated correctly. Re-run the build — the setup_sysroot and patch_include_fixed functions should copy the missing headers. If the issue persists, check that sysroot-skeleton/usr/include/forestos/syscalls.h exists.
Include-Fixed Headers Stale
Symptom: GCC compiles but can't find forestos/syscalls.h via the sysroot.
Fix: GCC's fixincludes may have created broken copies in include-fixed/. The build script patches this automatically, but if you've modified headers after the build, re-run:
cp -f sysroot/usr/include/forestos/syscalls.h \
install/lib/gcc/<target>/13.2.0/include-fixed/forestos/syscalls.h
Kernel Can't Find Toolchain
Symptom: make: * [validate-toolchain] Architecture toolchain not found
Fix: The Fern kernel expects the toolchain at a specific path. Either set the environment variable:
export FORESTOS_TOOLCHAIN_DIR=$FOREST/forestos-toolchain
Or create a symlink:
ln -s ../forestos-toolchain $FOREST/fern/forestos-toolchain
Using the Toolchain
Basic Compilation
export TOOLCHAIN=$FOREST/forestos-toolchain/install
export PATH=$TOOLCHAIN/bin:$PATH
# Compile a freestanding C file (32-bit)
i686-forestos-gcc -ffreestanding -nostdlib -c -o boot.o boot.c
# Compile a freestanding C file (64-bit)
x86_64-forestos-gcc -ffreestanding -nostdlib -c -o boot.o boot.c
# Assemble
i686-forestos-as -o boot.o boot.S
# Link (with your own linker script)
i686_64-forestos-ld -T link.ld -o kernel.elf boot.o main.o
Checking Toolchain Info
# Verify the target triple
i686-forestos-gcc -dumpmachine
# Output: i686-forestos
# Check libgcc location
x86_64-forestos-gcc -print-libgcc-file-name
# Output: .../lib/gcc/x86_64-forestos/13.2.0/libgcc.a
# Version info
i686-forestos-gcc --version
x86_64-forestos-ld --version
Fern Kernel Build Integration
The kernel's fern/build/toolchain.mk resolves tools as:
$(FORESTOS_TOOLCHAIN_DIR)/install/bin/<prefix>-{gcc,g++,ld,as,objcopy,strip,nm}
With the sysroot at $(FORESTOS_TOOLCHAIN_DIR)/sysroot.
Build the kernel:
export FORESTOS_TOOLCHAIN_DIR=$FOREST/forestos-toolchain
make -C $FOREST/fern all
Or build for a specific architecture:
make -C $FOREST/fern all ARCH=32 # 32-bit
make -C $FOREST/fern all ARCH=64 # 64-bit
Self-Hosting Considerations
The same build-toolchain.sh is host-agnostic. When build == host == x86_64-forestos, it:
- Skips the Debian-centric dependency probe — the script detects a
ForestOS host via uname -s or by checking if cc -dumpmachine reports a forestos triple.
- Uses the Forest-hosted GCC/binutils to rebuild the toolchain natively.
- Requires the sysroot-skeleton headers to already be installed so
that → resolves during the build.
Self-hosting is the long-term goal. The initial toolchain must be built on a Linux host (or other supported system), but once ForestOS can run GCC, subsequent toolchain rebuilds can happen natively.
The key prerequisite for self-hosting is that the native ForestOS GCC must be able to compile freestanding code — which is exactly what the cross-toolchain produces.
Further Reading
$FOREST/fern/build/toolchain.mk— how the kernel consumes the toolchain$FOREST/fern/build/flags.mk— kernel compiler flags (freestanding mode)$FOREST/MAKE_AN_OS.md— full build walkthrough (toolchain → kernel →
foreboots → bootable image)
$FOREST/forestos-toolchain/README.md— the original toolchain README