diff --git a/.gitignore b/.gitignore index 4187e92..20ac073 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ qemu.log toolchain/local/ toolchain/build/ toolchain/src/ +toolchain/sysroot/ programs/bin/ programs/obj/ programs/src/*/obj/ diff --git a/programs/include/libc/sys/types.h b/programs/include/libc/sys/types.h index 350e0fb..7d14c01 100644 --- a/programs/include/libc/sys/types.h +++ b/programs/include/libc/sys/types.h @@ -3,6 +3,12 @@ #pragma once +/* Fixed-width and pointer-sized integer types. Freestanding builds get + this from the kernel freestanding headers; the cross toolchain from + the GCC-provided stdint.h. Hosted code (e.g. libgcov) expects + intptr_t to be visible via the stdio/stdlib include chain. */ +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/toolchain/README.md b/toolchain/README.md new file mode 100644 index 0000000..f5149bd --- /dev/null +++ b/toolchain/README.md @@ -0,0 +1,63 @@ +# MontaukOS Toolchains + +Two cross toolchains live here, both installed to `toolchain/local/`: + +| Script | Triple | Purpose | +|--------|--------|---------| +| `build-toolchain.sh` | `x86_64-elf` | Bare-metal compiler for the kernel | +| `build-montauk-toolchain.sh` | `x86_64-montauk` | OS-aware compiler for userspace programs | + +Both use Binutils 2.43.1 + GCC 14.2.0 and share the source trees in +`src/`. The Montauk target is added by the patches in `patches/` plus +the target header `files/montauk.h` (installed as +`gcc/config/montauk.h` in the GCC tree). + +## The x86_64-montauk target + +`x86_64-montauk-gcc` knows the Montauk userspace ABI, so a hosted +program builds with no special flags: + +```bash +toolchain/local/bin/x86_64-montauk-gcc hello.c -o hello.elf +``` + +What the target does by default: + +- Links static, non-PIE `ET_EXEC` binaries (what the kernel ELF loader + in `kernel/src/Sched/ElfLoader.cpp` accepts), text at 0x400000. +- `-z max-page-size=0x1000` — the loader maps 4 KiB pages; the x86-64 + default of 2 MiB segment alignment would bloat every binary. +- Startup/link line: `crt1.o crti.o crtbegin.o ... -lc crtend.o crtn.o` + taken from the sysroot (`toolchain/sysroot/usr/lib`), which is + assembled from `programs/lib/libc` by the build script. +- Headers resolve against the sysroot (`toolchain/sysroot/usr/include`), + assembled from `programs/include/libc` plus the `montauk/` SDK + headers. `#include ` just works; no `-nostdinc` needed. +- `-mno-red-zone` (the project-wide userspace convention). Standard + hard-float SysV ABI otherwise; pass `-mno-sse` etc. per program if + wanted. +- Defines `__montauk__` / `__MONTAUK__`. + +C++ works with `-fno-exceptions -fno-rtti` (the project convention). +There is no libstdc++ yet — only libgcc is built. A hosted libstdc++ +needs a more complete libc and is the next step toward self-hosting. + +## Refreshing the sysroot + +The sysroot is rebuilt every time `build-montauk-toolchain.sh` runs +(the compiler build steps are skipped once installed). After changing +libc headers or the libc itself, re-run the script to refresh it. + +## Layout + +``` +toolchain/ + build-toolchain.sh # bare-metal x86_64-elf (kernel) + build-montauk-toolchain.sh # x86_64-montauk (userspace) + patches/ # Montauk target patches (checked in) + files/montauk.h # GCC target header (checked in) + src/ # downloaded + patched sources (ignored) + build/ # build trees (ignored) + local/ # install prefix (ignored) + sysroot/ # generated target sysroot (ignored) +``` diff --git a/toolchain/build-montauk-toolchain.sh b/toolchain/build-montauk-toolchain.sh new file mode 100755 index 0000000..12e2581 --- /dev/null +++ b/toolchain/build-montauk-toolchain.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +# +# build-montauk-toolchain.sh — Build an x86_64-montauk cross compiler +# (Binutils + GCC) for MontaukOS userspace programs. +# +# Usage: ./toolchain/build-montauk-toolchain.sh +# +# Unlike the bare-metal x86_64-elf toolchain (build-toolchain.sh), this +# target knows the Montauk userspace ABI: it links static ET_EXEC +# binaries against the Montauk libc (crt1/crti/crtn + libc.a from +# programs/lib/libc), defaults to -mno-red-zone, forces 4 KiB segment +# alignment for the kernel ELF loader, and defines __montauk__. +# +# The toolchain is installed to toolchain/local/ and provides: +# x86_64-montauk-gcc, x86_64-montauk-g++, x86_64-montauk-ld, etc. +# +# The target sysroot (libc headers + libs) is assembled into +# toolchain/sysroot/ from programs/include/libc and programs/lib/libc. +# Re-run this script after changing libc headers or rebuilding libc to +# refresh the sysroot (the sysroot step always runs; compiler builds +# are skipped once installed). +# +# This script is idempotent — it skips steps whose output already exists. + +set -euo pipefail + +# ── Versions (kept in lockstep with build-toolchain.sh) ─────────────────────── +BINUTILS_VERSION="2.43.1" +GCC_VERSION="14.2.0" +TARGET="x86_64-montauk" + +# ── Directories ─────────────────────────────────────────────────────────────── +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(dirname "${SCRIPT_DIR}")" +PREFIX="${SCRIPT_DIR}/local" +SRC_DIR="${SCRIPT_DIR}/src" +BUILD_DIR="${SCRIPT_DIR}/build" +SYSROOT="${SCRIPT_DIR}/sysroot" +PATCH_DIR="${SCRIPT_DIR}/patches" +FILES_DIR="${SCRIPT_DIR}/files" + +BINUTILS_TARBALL="binutils-${BINUTILS_VERSION}.tar.xz" +GCC_TARBALL="gcc-${GCC_VERSION}.tar.xz" +BINUTILS_URL="https://ftp.gnu.org/gnu/binutils/${BINUTILS_TARBALL}" +GCC_URL="https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VERSION}/${GCC_TARBALL}" + +JOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)" + +# ── Color helpers ───────────────────────────────────────────────────────────── +bold() { printf '\033[1m%s\033[0m\n' "$*"; } +green() { printf '\033[1;32m%s\033[0m\n' "$*"; } +red() { printf '\033[1;31m%s\033[0m\n' "$*"; } + +bold "=== Building ${TARGET} cross compiler ===" +bold " Binutils ${BINUTILS_VERSION} | GCC ${GCC_VERSION}" +bold " Install prefix: ${PREFIX}" +bold " Sysroot: ${SYSROOT}" +bold " Parallel jobs: ${JOBS}" +echo + +export PATH="${PREFIX}/bin:${PATH}" + +mkdir -p "${SRC_DIR}" "${BUILD_DIR}" "${PREFIX}" + +# ── Download + extract sources (shared with build-toolchain.sh) ─────────────── +download() { + local url="$1" dest="$2" + if [[ -f "${dest}" ]]; then + bold "Already downloaded: $(basename "${dest}")" + else + bold "Downloading $(basename "${dest}")..." + wget -q --show-progress -O "${dest}" "${url}" + fi +} + +extract() { + local tarball="$1" name="$2" + if [[ -d "${SRC_DIR}/${name}" ]]; then + bold "Already extracted: ${name}" + else + bold "Extracting ${name}..." + tar xf "${tarball}" -C "${SRC_DIR}" + fi +} + +download "${BINUTILS_URL}" "${SRC_DIR}/${BINUTILS_TARBALL}" +download "${GCC_URL}" "${SRC_DIR}/${GCC_TARBALL}" +extract "${SRC_DIR}/${BINUTILS_TARBALL}" "binutils-${BINUTILS_VERSION}" +extract "${SRC_DIR}/${GCC_TARBALL}" "gcc-${GCC_VERSION}" + +if [[ ! -d "${SRC_DIR}/gcc-${GCC_VERSION}/gmp" ]]; then + bold "Downloading GCC prerequisites..." + (cd "${SRC_DIR}/gcc-${GCC_VERSION}" && ./contrib/download_prerequisites) +fi + +# ── Apply Montauk target patches ────────────────────────────────────────────── +apply_patch() { + local dir="$1" patch="$2" + if grep -q "montauk" "${dir}/config.sub"; then + bold "Already patched: $(basename "${dir}")" + else + bold "Patching $(basename "${dir}")..." + patch -d "${dir}" -p1 < "${patch}" + fi +} + +apply_patch "${SRC_DIR}/binutils-${BINUTILS_VERSION}" "${PATCH_DIR}/binutils-${BINUTILS_VERSION}-montauk.patch" +apply_patch "${SRC_DIR}/gcc-${GCC_VERSION}" "${PATCH_DIR}/gcc-${GCC_VERSION}-montauk.patch" +cp "${FILES_DIR}/montauk.h" "${SRC_DIR}/gcc-${GCC_VERSION}/gcc/config/montauk.h" + +# ── Assemble the target sysroot ─────────────────────────────────────────────── +# usr/include — the Montauk libc headers (self-contained C; stddef/stdint/ +# stdarg come from GCC itself) plus the montauk/ SDK headers. +# usr/lib — crt startup objects and the libc static library. +bold "Assembling sysroot from programs/lib/libc..." +make -C "${REPO_DIR}/programs" libc >/dev/null + +rm -rf "${SYSROOT}" +mkdir -p "${SYSROOT}/usr/include" "${SYSROOT}/usr/lib" +cp -r "${REPO_DIR}/programs/include/libc/." "${SYSROOT}/usr/include/" +cp -r "${REPO_DIR}/programs/include/montauk" "${SYSROOT}/usr/include/montauk" +cp -r "${REPO_DIR}/programs/include/Api" "${SYSROOT}/usr/include/Api" +# Freestanding C++ headers (, , , ...) — +# there is no hosted libstdc++ yet, so C++ programs get the same +# freestanding subset the rest of the tree builds against. +cp -r "${REPO_DIR}/kernel/freestnd-cxx-hdrs/x86_64/include/." "${SYSROOT}/usr/include/" +cp "${REPO_DIR}/programs/lib/libc/crt1.o" \ + "${REPO_DIR}/programs/lib/libc/crti.o" \ + "${REPO_DIR}/programs/lib/libc/crtn.o" "${SYSROOT}/usr/lib/" +cp "${REPO_DIR}/programs/lib/libc/liblibc.a" "${SYSROOT}/usr/lib/libc.a" +# Empty stand-ins: math functions live inside libc.a, and there is no +# libstdc++ yet, but the g++ driver links -lstdc++ -lm unconditionally. +ar rcs "${SYSROOT}/usr/lib/libm.a" +ar rcs "${SYSROOT}/usr/lib/libstdc++.a" +green "Sysroot ready." + +# ── Build Binutils ──────────────────────────────────────────────────────────── +if [[ -x "${PREFIX}/bin/${TARGET}-ld" ]]; then + bold "Binutils already installed — skipping." +else + bold "Building Binutils ${BINUTILS_VERSION} for ${TARGET}..." + rm -rf "${BUILD_DIR}/binutils-montauk" + mkdir -p "${BUILD_DIR}/binutils-montauk" + ( + cd "${BUILD_DIR}/binutils-montauk" + "${SRC_DIR}/binutils-${BINUTILS_VERSION}/configure" \ + --target="${TARGET}" \ + --prefix="${PREFIX}" \ + --with-sysroot="${SYSROOT}" \ + --disable-nls \ + --disable-werror + make -j"${JOBS}" + make install + ) + green "Binutils installed." +fi + +# ── Build GCC ───────────────────────────────────────────────────────────────── +# Hosted against the Montauk libc headers in the sysroot. Only the +# compiler and libgcc are built; libstdc++ needs a more complete libc +# (that is the self-hosting step, not the cross-toolchain step). +if [[ -x "${PREFIX}/bin/${TARGET}-gcc" ]]; then + bold "GCC already installed — skipping." +else + bold "Building GCC ${GCC_VERSION} for ${TARGET}..." + rm -rf "${BUILD_DIR}/gcc-montauk" + mkdir -p "${BUILD_DIR}/gcc-montauk" + ( + cd "${BUILD_DIR}/gcc-montauk" + "${SRC_DIR}/gcc-${GCC_VERSION}/configure" \ + --target="${TARGET}" \ + --prefix="${PREFIX}" \ + --with-sysroot="${SYSROOT}" \ + --enable-languages=c,c++ \ + --disable-nls \ + --disable-shared \ + --disable-multilib \ + --disable-gcov \ + --with-gnu-as \ + --with-gnu-ld + make -j"${JOBS}" all-gcc + make -j"${JOBS}" all-target-libgcc + make install-gcc + make install-target-libgcc + ) + green "GCC installed." +fi + +# ── Done ────────────────────────────────────────────────────────────────────── +echo +green "=== ${TARGET} cross compiler ready ===" +"${PREFIX}/bin/${TARGET}-gcc" --version | head -1 +"${PREFIX}/bin/${TARGET}-ld" --version | head -1 +echo +bold "Try it:" +bold " echo 'int main(){return 0;}' > /tmp/t.c" +bold " ${PREFIX}/bin/${TARGET}-gcc /tmp/t.c -o /tmp/t.elf" diff --git a/toolchain/files/montauk.h b/toolchain/files/montauk.h new file mode 100644 index 0000000..900b5e6 --- /dev/null +++ b/toolchain/files/montauk.h @@ -0,0 +1,41 @@ +/* + * montauk.h + * GCC target definitions for x86_64-montauk userspace + * Copyright (c) 2026 Daniel Hammer + * + * Programs are static, non-PIE ET_EXEC images loaded by the kernel + * ELF loader (kernel/src/Sched/ElfLoader.cpp) at their linked + * address. The loader maps 4 KiB pages, so segments must not be + * aligned to the 2 MiB x86-64 default. +*/ + +#undef TARGET_OS_CPP_BUILTINS +#define TARGET_OS_CPP_BUILTINS() \ + do { \ + builtin_define("__montauk__"); \ + builtin_define("__MONTAUK__"); \ + builtin_assert("system=montauk"); \ + } while (0) + +/* The userspace build convention is -mno-red-zone everywhere; keep it + as the target default. A later -mred-zone on the command line still + overrides this. */ +#undef CC1_SPEC +#define CC1_SPEC "-mno-red-zone" + +#undef LINK_SPEC +#define LINK_SPEC \ + "-m elf_x86_64 -z max-page-size=0x1000 %{shared:-shared} %{!shared:-static}" + +/* crt1/crti/crtn come from the Montauk libc (sysroot usr/lib); + crtbegin/crtend are GCC's. crti/crtn are empty today (the runtime + does not run global constructors yet) but are kept in the link so + that enabling them later needs no toolchain change. */ +#undef STARTFILE_SPEC +#define STARTFILE_SPEC "crt1.o%s crti.o%s crtbegin.o%s" + +#undef ENDFILE_SPEC +#define ENDFILE_SPEC "crtend.o%s crtn.o%s" + +#undef LIB_SPEC +#define LIB_SPEC "-lc" diff --git a/toolchain/patches/binutils-2.43.1-montauk.patch b/toolchain/patches/binutils-2.43.1-montauk.patch new file mode 100644 index 0000000..31bf02d --- /dev/null +++ b/toolchain/patches/binutils-2.43.1-montauk.patch @@ -0,0 +1,47 @@ +--- a/config.sub ++++ b/config.sub +@@ -1768,7 +1768,7 @@ + | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ + | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \ + | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \ +- | fiwix* | mlibc* | cos* | mbr* | ironclad* ) ++ | fiwix* | mlibc* | cos* | mbr* | ironclad* | montauk* ) + ;; + # This one is extra strict with allowed versions + sco3.2v2 | sco3.2v[4-9]* | sco5v6*) +--- a/bfd/config.bfd ++++ b/bfd/config.bfd +@@ -681,6 +681,11 @@ + targ_selvecs="i386_elf32_vec iamcu_elf32_vec" + want64=true + ;; ++ x86_64-*-montauk*) ++ targ_defvec=x86_64_elf64_vec ++ targ_selvecs=i386_elf32_vec ++ want64=true ++ ;; + x86_64-*-elf* | x86_64-*-rtems* | x86_64-*-fuchsia | x86_64-*-genode*) + targ_defvec=x86_64_elf64_vec + targ_selvecs="i386_elf32_vec iamcu_elf32_vec x86_64_elf32_vec" +--- a/gas/configure.tgt ++++ b/gas/configure.tgt +@@ -229,6 +229,7 @@ + i386-*-beos*) fmt=elf ;; + i386-*-elfiamcu) fmt=elf arch=iamcu ;; + i386-*-elf*) fmt=elf ;; ++ i386-*-montauk*) fmt=elf ;; + i386-*-fuchsia*) fmt=elf ;; + i386-*-haiku*) fmt=elf em=haiku ;; + i386-*-genode*) fmt=elf ;; +--- a/ld/configure.tgt ++++ b/ld/configure.tgt +@@ -1036,6 +1036,9 @@ + tdir_elf_i386=`echo ${targ_alias} | \ + sed -e 's/x86_64/i386/' -e 's/aout//'` + ;; ++x86_64-*-montauk*) targ_emul=elf_x86_64 ++ targ_extra_emuls=elf_i386 ++ ;; + x86_64-*-elf* | x86_64-*-rtems* | x86_64-*-fuchsia* | x86_64-*-genode*) + targ_emul=elf_x86_64 + targ_extra_emuls="elf_i386 elf_iamcu elf32_x86_64" diff --git a/toolchain/patches/gcc-14.2.0-montauk.patch b/toolchain/patches/gcc-14.2.0-montauk.patch new file mode 100644 index 0000000..cb6a0f0 --- /dev/null +++ b/toolchain/patches/gcc-14.2.0-montauk.patch @@ -0,0 +1,49 @@ +--- a/config.sub ++++ b/config.sub +@@ -1749,7 +1749,7 @@ + | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ + | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \ + | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \ +- | fiwix* ) ++ | fiwix* | montauk* ) + ;; + # This one is extra strict with allowed versions + sco3.2v2 | sco3.2v[4-9]* | sco5v6*) +--- a/gcc/config.gcc ++++ b/gcc/config.gcc +@@ -1935,6 +1935,12 @@ + x86_64-*-elf*) + tm_file="${tm_file} i386/unix.h i386/att.h elfos.h newlib-stdint.h i386/i386elf.h i386/x86-64.h" + ;; ++x86_64-*-montauk*) ++ tm_file="${tm_file} i386/unix.h i386/att.h elfos.h newlib-stdint.h i386/i386elf.h i386/x86-64.h montauk.h" ++ # The Montauk libc has no stdint.h of its own (programs normally use ++ # the freestanding kernel headers), so GCC must provide one. ++ use_gcc_stdint=provide ++ ;; + x86_64-*-rtems*) + tm_file="${tm_file} i386/unix.h i386/att.h elfos.h newlib-stdint.h i386/i386elf.h i386/x86-64.h i386/rtemself.h rtems.h" + ;; +--- a/libgcc/config.host ++++ b/libgcc/config.host +@@ -741,6 +741,10 @@ + i[34567]86-*-elf*) + tmake_file="$tmake_file i386/t-crtstuff t-crtstuff-pic t-libgcc-pic" + ;; ++x86_64-*-montauk*) ++ tmake_file="$tmake_file i386/t-crtstuff t-crtstuff-pic t-libgcc-pic" ++ extra_parts="crtbegin.o crtend.o" ++ ;; + x86_64-*-elf* | x86_64-*-rtems*) + tmake_file="$tmake_file i386/t-crtstuff t-crtstuff-pic t-libgcc-pic" + case ${host} in +--- a/fixincludes/mkfixinc.sh ++++ b/fixincludes/mkfixinc.sh +@@ -20,6 +20,7 @@ + powerpcle-*-eabisim* | \ + powerpcle-*-eabi* | \ + *-*-vxworks7* | \ ++ *-montauk* | \ + *-musl* ) + # IF there is no include fixing, + # THEN create a no-op fixer and exit