feat: port gcc to MontaukOS

This commit is contained in:
2026-07-17 15:47:45 +02:00
parent 324b18edda
commit 8e6b619b02
14 changed files with 493 additions and 19 deletions
+14
View File
@@ -64,6 +64,20 @@ toolchain/
## Native binutils (runs on MontaukOS)
The whole native SDK (binutils + GCC below, staged for the OS image) is
built by one idempotent script — this is what a fresh clone should run:
```bash
make sdk # from the repo root; wraps toolchain/build-native-sdk.sh
```
It invokes build-montauk-toolchain.sh first (cross compiler + sysroot),
applies the host-build fix-ups listed under "Host-build gotchas", and
strips the staged binaries. To force a rebuild after libc changes:
`rm -rf toolchain/native toolchain/native-gcc toolchain/build/binutils-native
toolchain/build/gcc-native` and re-run. The manual steps below are kept
as a reference for what the script does.
Binutils can be cross-compiled to run *on* MontaukOS itself
(`--host=x86_64-montauk`), the first step toward a self-hosted GCC:
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env bash
#
# build-native-sdk.sh — Build the Montauk SDK: binutils + GCC compiled
# to run *on* MontaukOS (--host=x86_64-montauk), staged for the OS image.
#
# Usage: ./toolchain/build-native-sdk.sh (or: make sdk)
#
# Produces:
# toolchain/native/sdk/ — native as/ld/ar/nm/... (stripped)
# toolchain/native-gcc/sdk/ — native gcc/g++ driver + cc1/cc1plus/collect2
#
# The `devkit` target in programs/GNUmakefile ships these into the OS
# image at 0:/sdk. Without them the ISO builds WITHOUT the SDK.
#
# Requires the x86_64-montauk cross toolchain (build-montauk-toolchain.sh
# is invoked automatically; it also downloads/patches the shared sources
# and refreshes toolchain/sysroot from the current libc).
#
# This script is idempotent — each stage is skipped once its output
# exists. To force a rebuild (e.g. after libc changes, so the native
# tools relink against the fixed libc):
# rm -rf toolchain/native toolchain/native-gcc \
# toolchain/build/binutils-native toolchain/build/gcc-native
set -euo pipefail
BINUTILS_VERSION="2.43.1"
GCC_VERSION="14.2.0"
HOST="x86_64-montauk"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PREFIX="${SCRIPT_DIR}/local"
SRC_DIR="${SCRIPT_DIR}/src"
BUILD_DIR="${SCRIPT_DIR}/build"
SYSROOT="${SCRIPT_DIR}/sysroot"
NATIVE_BINUTILS="${SCRIPT_DIR}/native"
NATIVE_GCC="${SCRIPT_DIR}/native-gcc"
JOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
bold() { printf '\033[1m%s\033[0m\n' "$*"; }
green() { printf '\033[1;32m%s\033[0m\n' "$*"; }
bold "=== Building the Montauk SDK (native ${HOST} binutils + GCC) ==="
# ── Cross toolchain + sources + sysroot ──────────────────────────────────────
# build-montauk-toolchain.sh is idempotent: it downloads and patches the
# shared binutils/GCC sources, always refreshes the sysroot from the
# current libc, and skips compiler builds once installed.
"${SCRIPT_DIR}/build-montauk-toolchain.sh"
export PATH="${PREFIX}/bin:${PATH}"
# ── Host-build fix-ups in the GCC source tree ────────────────────────────────
# The bundled gmp/mpfr/mpc/isl/gettext carry their own config.sub copies
# that predate the montauk target; overwrite each with the patched
# top-level one so they configure for --host=x86_64-montauk.
GCC_SRC="${SRC_DIR}/gcc-${GCC_VERSION}"
for sub in gmp mpfr mpc isl gettext; do
for cs in "${GCC_SRC}/${sub}/config.sub" "${GCC_SRC}/${sub}/build-aux/config.sub"; do
if [[ -f "${cs}" ]] && ! grep -q montauk "${cs}"; then
bold "Patching config.sub: ${cs#"${SRC_DIR}/"}"
cp "${GCC_SRC}/config.sub" "${cs}"
fi
done
done
# --disable-nls does not skip the bundled gettext, and its gnulib needs
# more locale surface than the Montauk libc has. Drop the symlink so the
# toplevel treats it as absent.
if [[ -L "${GCC_SRC}/gettext" ]]; then
bold "Removing gettext symlink (not buildable against the Montauk libc)"
rm "${GCC_SRC}/gettext"
fi
# The gcc subdir resolves --with-native-system-header-dir=/sdk/include
# inside the build sysroot at build time; give the sysroot an sdk -> usr
# alias so that path exists.
if [[ ! -e "${SYSROOT}/sdk" ]]; then
ln -s usr "${SYSROOT}/sdk"
fi
# ── Native binutils ──────────────────────────────────────────────────────────
if [[ -x "${NATIVE_BINUTILS}/sdk/bin/ld" ]]; then
bold "Native binutils already staged — skipping."
else
bold "Building native binutils ${BINUTILS_VERSION} (host ${HOST})..."
rm -rf "${BUILD_DIR}/binutils-native"
mkdir -p "${BUILD_DIR}/binutils-native"
(
cd "${BUILD_DIR}/binutils-native"
"${SRC_DIR}/binutils-${BINUTILS_VERSION}/configure" \
--build=x86_64-pc-linux-gnu \
--host="${HOST}" \
--target="${HOST}" \
--prefix=/sdk \
--disable-nls \
--disable-werror \
--disable-gprofng \
--disable-gold \
--disable-plugins \
--disable-shared \
--enable-static
# Only gas/ld/binutils: gprof needs fscanf %[] scansets the
# Montauk libc lacks; gold and gprofng are disabled outright.
make -j"${JOBS}" all-gas all-ld all-binutils
make install-strip-gas install-strip-ld install-strip-binutils \
DESTDIR="${NATIVE_BINUTILS}"
)
green "Native binutils staged into toolchain/native/sdk."
fi
# ── Native GCC ───────────────────────────────────────────────────────────────
if [[ -x "${NATIVE_GCC}/sdk/bin/gcc" ]]; then
bold "Native GCC already staged — skipping."
else
bold "Building native GCC ${GCC_VERSION} (host ${HOST})..."
rm -rf "${BUILD_DIR}/gcc-native"
mkdir -p "${BUILD_DIR}/gcc-native"
(
cd "${BUILD_DIR}/gcc-native"
"${SRC_DIR}/gcc-${GCC_VERSION}/configure" \
--build=x86_64-pc-linux-gnu \
--host="${HOST}" \
--target="${HOST}" \
--prefix=/sdk \
--with-native-system-header-dir=/sdk/include \
--with-build-sysroot="${SYSROOT}" \
--enable-languages=c,c++ \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-gcov \
--disable-lto \
--disable-plugin \
--disable-bootstrap \
--disable-fixincludes \
--with-newlib \
--enable-initfini-array \
--disable-wchar_t \
--disable-libstdcxx-pch \
--with-gnu-as \
--with-gnu-ld
# The gcc subdir configure cannot run host (montauk) binaries to
# probe endianness; preset it.
ac_cv_c_bigendian=no make -j"${JOBS}" all-gcc
make install-gcc DESTDIR="${NATIVE_GCC}"
)
# install-gcc does not strip, and cc1/cc1plus carry ~700 MB of debug
# info between them (the ISO balloons to 1.7 GB if this is skipped).
bold "Stripping native GCC binaries..."
GCC_LIBEXEC="${NATIVE_GCC}/sdk/libexec/gcc/${HOST}/${GCC_VERSION}"
for b in "${GCC_LIBEXEC}/cc1" "${GCC_LIBEXEC}/cc1plus" "${GCC_LIBEXEC}/collect2" \
"${NATIVE_GCC}/sdk/bin/gcc" "${NATIVE_GCC}/sdk/bin/g++" \
"${NATIVE_GCC}/sdk/bin/cpp"; do
if [[ -f "${b}" ]]; then
"${PREFIX}/bin/${HOST}-strip" "${b}"
fi
done
green "Native GCC staged into toolchain/native-gcc/sdk."
fi
# ── Done ─────────────────────────────────────────────────────────────────────
echo
green "=== Montauk SDK ready ==="
bold "Staged trees:"
bold " ${NATIVE_BINUTILS}/sdk (as, ld, ar, nm, objdump, ...)"
bold " ${NATIVE_GCC}/sdk (gcc, g++, cpp, cc1, cc1plus, collect2)"
bold "The next 'make' ships them into the OS image at 0:/sdk (devkit target)."
+96 -2
View File
@@ -1,17 +1,22 @@
/*
* sdk-diag.c
* Probes the exact libc/kernel layers the GCC driver depends on:
* access/stat on the compiler backends, and a direct posix_spawn
* of cc1plus. Prints one line per probe.
* access/stat on the compiler backends, a direct posix_spawn of
* cc1plus, and BFD-style ar archive reads on /sdk/lib/libc.a
* (armap, member header at an armap offset, nested second fopen
* of the same file, member ELF magic). Prints one line per probe.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <spawn.h>
#include <sys/wait.h>
#define CC1PLUS "/sdk/libexec/gcc/x86_64-montauk/14.2.0/cc1plus"
#define LIBC_A_DOTS "/sdk/bin/../lib/gcc/x86_64-montauk/14.2.0/../../../libc.a"
static void probe(const char *p) {
struct stat st;
@@ -24,12 +29,101 @@ static void probe(const char *p) {
printf("\n");
}
/* Replay the sequence BFD/ld performs on an ar archive. Each step
prints ok/FAIL so the first broken layer is visible directly. */
static void ar_probe(const char *path) {
unsigned char buf[64];
char hdr[61];
long armap_size, member_off, member_size, i;
printf("ar_probe %s\n", path);
FILE *f = fopen(path, "rb");
if (f == NULL) { printf(" FAIL fopen\n"); return; }
/* Global magic */
if (fread(buf, 1, 8, f) != 8) { printf(" FAIL read magic\n"); fclose(f); return; }
printf(" magic %s: %.7s\n", memcmp(buf, "!<arch>\n", 8) == 0 ? "ok" : "FAIL", (char *)buf);
/* Armap ("/") member header at offset 8; size at header offset 48 */
if (fseek(f, 8, SEEK_SET) != 0 || fread(hdr, 1, 60, f) != 60) {
printf(" FAIL read armap header\n"); fclose(f); return;
}
hdr[60] = '\0';
armap_size = strtol(hdr + 48, NULL, 10);
printf(" armap hdr %s: name='%.4s' size=%ld fmag=%s\n",
hdr[0] == '/' ? "ok" : "FAIL", hdr, armap_size,
(hdr[58] == 0x60 && hdr[59] == '\n') ? "ok" : "FAIL");
/* First armap entry: 4-byte BE count, then BE member offsets */
if (fread(buf, 1, 8, f) != 8) { printf(" FAIL read armap data\n"); fclose(f); return; }
member_off = ((long)buf[4] << 24) | ((long)buf[5] << 16) | ((long)buf[6] << 8) | (long)buf[7];
printf(" armap syms=%ld first member offset=%ld\n",
((long)buf[0] << 24) | ((long)buf[1] << 16) | ((long)buf[2] << 8) | (long)buf[3],
member_off);
/* Member header at the armap offset, via the SAME stream */
if (fseek(f, member_off, SEEK_SET) != 0 || fread(hdr, 1, 60, f) != 60) {
printf(" FAIL read member header\n"); fclose(f); return;
}
hdr[60] = '\0';
member_size = strtol(hdr + 48, NULL, 10);
printf(" member hdr: name='%.16s' size=%ld fmag=%s\n",
hdr, member_size, (hdr[58] == 0x60 && hdr[59] == '\n') ? "ok" : "FAIL");
/* Nested second fopen of the same archive (ld does this per member),
ELF magic read at the member data offset while `f` stays open. */
FILE *g = fopen(path, "rb");
if (g == NULL) { printf(" FAIL nested fopen\n"); fclose(f); return; }
if (fseek(g, member_off + 60, SEEK_SET) != 0 || fread(buf, 1, 16, g) != 16) {
printf(" FAIL nested read\n"); fclose(g); fclose(f); return;
}
printf(" member ELF magic %s:", memcmp(buf, "\177ELF", 4) == 0 ? "ok" : "FAIL");
for (i = 0; i < 8; i++) printf(" %02x", buf[i]);
printf("\n");
/* Whole-member read through the nested stream + checksum */
{
unsigned long sum = 0;
long total = 0;
if (fseek(g, member_off + 60, SEEK_SET) != 0) { printf(" FAIL re-seek\n"); }
while (total < member_size) {
unsigned char chunk[4096];
long want = member_size - total;
size_t got;
if (want > (long)sizeof(chunk)) want = sizeof(chunk);
got = fread(chunk, 1, (size_t)want, g);
if (got == 0) break;
for (i = 0; i < (long)got; i++) sum = sum * 31 + chunk[i];
total += (long)got;
}
printf(" member read %ld/%ld bytes %s (sum=%08lx)\n",
total, member_size, total == member_size ? "ok" : "FAIL", sum);
}
/* Interleave: original stream must still read correctly after the
nested stream was used (independent positions). */
if (fseek(f, 0, SEEK_SET) == 0 && fread(buf, 1, 8, f) == 8) {
printf(" interleaved re-read %s\n", memcmp(buf, "!<arch>\n", 8) == 0 ? "ok" : "FAIL");
} else {
printf(" FAIL interleaved re-read\n");
}
fclose(g);
fclose(f);
}
int main(void) {
probe(CC1PLUS);
probe("/sdk/libexec/gcc/x86_64-montauk/14.2.0");
probe("/sdk/libexec/gcc");
probe("/sdk/bin/as.elf");
probe("/tmp");
probe("/sdk/lib/libc.a");
probe(LIBC_A_DOTS);
ar_probe("/sdk/lib/libc.a");
ar_probe(LIBC_A_DOTS);
pid_t pid = -1;
char *argv[] = { (char *)"cc1plus", (char *)"--version", 0 };
@@ -59,3 +59,32 @@
$as_echo "#define HAVE_HYPOT 1" >>confdefs.h
--- a/gcc/collect2.cc
+++ b/gcc/collect2.cc
@@ -1049,6 +1049,26 @@
prefix_from_env ("COMPILER_PATH", &cpath);
prefix_from_env ("PATH", &path);
+#ifdef __montauk__
+ /* MontaukOS does not pass environment variables across spawn yet, so
+ COMPILER_PATH and PATH above are always empty. Derive the compiler
+ search path from argv[0] instead: the driver invokes collect2 by its
+ full path (the kernel provides the real exec path in argv[0]), and
+ `ld` lives in the same libexec directory. Also seed PATH with the
+ SDK bin directories so the nm/strip/tooldir fallbacks work. */
+ if (cpath.plist == NULL && argv[0] != NULL)
+ {
+ const char *slash = strrchr (argv[0], '/');
+ if (slash != NULL)
+ add_prefix (&cpath, xstrndup (argv[0], slash - argv[0] + 1));
+ }
+ if (path.plist == NULL)
+ {
+ add_prefix (&path, "/sdk/x86_64-montauk/bin/");
+ add_prefix (&path, "/sdk/bin/");
+ }
+#endif
+
/* Try to discover a valid linker/nm/strip to use. */
/* Maybe we know the right file to use (if not cross). */