feat: port gcc to MontaukOS
This commit is contained in:
@@ -156,6 +156,13 @@ run-hdd-bios: $(IMAGE_NAME).hdd
|
|||||||
toolchain:
|
toolchain:
|
||||||
./toolchain/build-toolchain.sh
|
./toolchain/build-toolchain.sh
|
||||||
|
|
||||||
|
# Build the Montauk SDK: native binutils + GCC that run on MontaukOS,
|
||||||
|
# shipped into the image at 0:/sdk by the programs/ devkit target.
|
||||||
|
# One-time (long) build; idempotent afterwards.
|
||||||
|
.PHONY: sdk
|
||||||
|
sdk:
|
||||||
|
./toolchain/build-native-sdk.sh
|
||||||
|
|
||||||
limine/limine:
|
limine/limine:
|
||||||
rm -rf limine
|
rm -rf limine
|
||||||
git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1
|
git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1
|
||||||
|
|||||||
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 26
|
#define MONTAUK_BUILD_NUMBER 29
|
||||||
|
|||||||
+10
-1
@@ -52,7 +52,16 @@ namespace montauk::abi {
|
|||||||
if (userVa + size < userVa || userVa + size > USER_SPACE_END) return 0;
|
if (userVa + size < userVa || userVa + size > USER_SPACE_END) return 0;
|
||||||
|
|
||||||
uint64_t numPages = size / 0x1000;
|
uint64_t numPages = size / 0x1000;
|
||||||
if (g_heapAllocCount[slot] >= MaxHeapAllocs) return 0;
|
if (g_heapAllocCount[slot] >= MaxHeapAllocs) {
|
||||||
|
// Out of allocation records, not out of memory. Log it: a
|
||||||
|
// silent 0 here surfaced as bogus downstream errors (BFD
|
||||||
|
// turned NULL mallocs into "file format not recognized").
|
||||||
|
Kt::KernelLogStream(Kt::ERROR, "Heap")
|
||||||
|
<< "pid " << proc->pid << " (" << proc->name
|
||||||
|
<< ") hit MaxHeapAllocs (" << (uint64_t)MaxHeapAllocs
|
||||||
|
<< "), SYS_ALLOC refused";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Allocate physical pages and map them into the process
|
// Allocate physical pages and map them into the process
|
||||||
uint64_t mappedPages = 0;
|
uint64_t mappedPages = 0;
|
||||||
|
|||||||
+43
-3
@@ -78,8 +78,10 @@ namespace Hal {
|
|||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<size_t i>
|
// Shared fatal-exception path: kill the faulting user process (with a
|
||||||
__attribute__((interrupt)) void ExceptionHandler(System::PanicFrame* frame)
|
// crash report) or panic the kernel. `frame` is the RAW interrupt frame
|
||||||
|
// (error code at offset 0 for vectors that push one). Never returns.
|
||||||
|
static void HandleFatalException(uint8_t i, System::PanicFrame* frame)
|
||||||
{
|
{
|
||||||
uint64_t cs = GetExceptionCS(i, frame);
|
uint64_t cs = GetExceptionCS(i, frame);
|
||||||
bool fromUser = (cs & 3) == 3;
|
bool fromUser = (cs & 3) == 3;
|
||||||
@@ -165,6 +167,41 @@ namespace Hal {
|
|||||||
if (fromUser) asm volatile("swapgs");
|
if (fromUser) asm volatile("swapgs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<size_t i>
|
||||||
|
__attribute__((interrupt)) void ExceptionHandler(System::PanicFrame* frame)
|
||||||
|
{
|
||||||
|
HandleFatalException(i, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page faults get a dedicated handler with the proper error-code
|
||||||
|
// signature (so GCC pops the error code before IRET) because, unlike
|
||||||
|
// the generic handler, this one can RETURN: a non-present fault in the
|
||||||
|
// user stack growth region maps a fresh zeroed page and retries the
|
||||||
|
// faulting instruction. With the two-argument form, `frame` points past
|
||||||
|
// the error code, directly at the saved IP.
|
||||||
|
__attribute__((interrupt)) void PageFaultHandler(System::PanicFrame* frame, uint64_t errorCode)
|
||||||
|
{
|
||||||
|
bool fromUser = (frame->CS & 3) == 3;
|
||||||
|
if (fromUser) asm volatile("swapgs");
|
||||||
|
|
||||||
|
uint64_t cr2;
|
||||||
|
asm volatile("mov %%cr2, %0" : "=r"(cr2));
|
||||||
|
|
||||||
|
// Bit 0 of the error code: 0 = non-present page. Covers both user
|
||||||
|
// pushes past the mapped stack and kernel accesses to not-yet-grown
|
||||||
|
// user stack buffers passed into syscalls.
|
||||||
|
if ((errorCode & 1) == 0 && Sched::GetCurrentPid() >= 0
|
||||||
|
&& Sched::TryGrowUserStack(cr2)) {
|
||||||
|
if (fromUser) asm volatile("swapgs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a growable fault. Hand the RAW frame (error code at offset 0)
|
||||||
|
// to the fatal path, which re-derives fromUser and swaps GS itself.
|
||||||
|
if (fromUser) asm volatile("swapgs");
|
||||||
|
HandleFatalException(0x0E, (System::PanicFrame*)((uint8_t*)frame - 8));
|
||||||
|
}
|
||||||
|
|
||||||
void LoadIDT(IDTRStruct& idtr) {
|
void LoadIDT(IDTRStruct& idtr) {
|
||||||
asm("lidt %0" : : "m"(idtr));
|
asm("lidt %0" : : "m"(idtr));
|
||||||
}
|
}
|
||||||
@@ -208,7 +245,10 @@ namespace Hal {
|
|||||||
// Use IST1 for NMI (2) and Double Fault (8) so they get a
|
// Use IST1 for NMI (2) and Double Fault (8) so they get a
|
||||||
// known-good stack even if the kernel stack has overflowed.
|
// known-good stack even if the kernel stack has overflowed.
|
||||||
uint8_t ist = (I == 2 || I == 8) ? 1 : 0;
|
uint8_t ist = (I == 2 || I == 8) ? 1 : 0;
|
||||||
IDTEncodeInterrupt(I, (void*)ExceptionHandler<I>, InterruptGate, ist);
|
// Vector 14 uses the dedicated page fault handler (stack growth).
|
||||||
|
void* handler = (I == 14) ? (void*)PageFaultHandler
|
||||||
|
: (void*)ExceptionHandler<I>;
|
||||||
|
IDTEncodeInterrupt(I, handler, InterruptGate, ist);
|
||||||
SetHandler<I+1,N>::run();
|
SetHandler<I+1,N>::run();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1103,6 +1103,48 @@ namespace Sched {
|
|||||||
return &processTable[slot];
|
return &processTable[slot];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serializes stack growth so two threads faulting on the same page do not
|
||||||
|
// both map it (the loser's page would leak: FreeUserHalf only frees pages
|
||||||
|
// still referenced by the page tables).
|
||||||
|
static kcp::Spinlock stackGrowLock;
|
||||||
|
|
||||||
|
bool TryGrowUserStack(uint64_t faultAddr) {
|
||||||
|
constexpr uint64_t growBase = UserStackTop - UserStackMax;
|
||||||
|
constexpr uint64_t eagerBase = UserStackTop - UserStackSize;
|
||||||
|
if (faultAddr < growBase || faultAddr >= eagerBase) return false;
|
||||||
|
|
||||||
|
Process* proc = GetCurrentProcessPtr();
|
||||||
|
if (proc == nullptr) return false;
|
||||||
|
|
||||||
|
uint64_t pageVa = faultAddr & ~0xFFFULL;
|
||||||
|
|
||||||
|
stackGrowLock.Acquire();
|
||||||
|
|
||||||
|
// Another thread may have mapped this page between the fault and here.
|
||||||
|
if (Memory::VMM::Paging::IsUserRangeAccessible(proc->pml4Phys, pageVa, 0x1000, true)) {
|
||||||
|
stackGrowLock.Release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* page = Memory::g_pfa->AllocateZeroed();
|
||||||
|
if (page == nullptr) {
|
||||||
|
stackGrowLock.Release();
|
||||||
|
Kt::KernelLogStream(Kt::ERROR, "Sched")
|
||||||
|
<< "Out of memory growing user stack for pid " << proc->pid;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||||
|
if (!Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, pageVa)) {
|
||||||
|
stackGrowLock.Release();
|
||||||
|
Memory::g_pfa->Free(page);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stackGrowLock.Release();
|
||||||
|
|
||||||
|
asm volatile("invlpg (%0)" :: "r"(pageVa) : "memory");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void ExitProcess() {
|
void ExitProcess() {
|
||||||
auto* cpu = Smp::GetCurrentCpuData();
|
auto* cpu = Smp::GetCurrentCpuData();
|
||||||
int slot = cpu->currentSlot;
|
int slot = cpu->currentSlot;
|
||||||
|
|||||||
@@ -19,9 +19,14 @@ namespace Sched {
|
|||||||
static constexpr int MaxProcesses = 256;
|
static constexpr int MaxProcesses = 256;
|
||||||
static constexpr uint64_t StackPages = 4; // 16 KiB kernel stack per thread
|
static constexpr uint64_t StackPages = 4; // 16 KiB kernel stack per thread
|
||||||
static constexpr uint64_t StackSize = StackPages * 0x1000;
|
static constexpr uint64_t StackSize = StackPages * 0x1000;
|
||||||
static constexpr uint64_t UserStackPages = 8; // 32 KiB user stack (main thread)
|
static constexpr uint64_t UserStackPages = 8; // 32 KiB user stack (main thread, eagerly mapped)
|
||||||
static constexpr uint64_t UserStackSize = UserStackPages * 0x1000;
|
static constexpr uint64_t UserStackSize = UserStackPages * 0x1000;
|
||||||
static constexpr uint64_t UserStackTop = 0x7FFFFFF000ULL; // Main-thread user stack top VA
|
static constexpr uint64_t UserStackTop = 0x7FFFFFF000ULL; // Main-thread user stack top VA
|
||||||
|
// The main-thread stack demand-grows below the eager region up to this
|
||||||
|
// limit: page faults in [UserStackTop - UserStackMax, UserStackTop -
|
||||||
|
// UserStackSize) map fresh zeroed pages instead of killing the process.
|
||||||
|
// Heavy recursive programs (GCC's cc1plus) need megabytes of stack.
|
||||||
|
static constexpr uint64_t UserStackMax = 64 * 1024 * 1024; // 64 MiB
|
||||||
static constexpr uint64_t UserHeapBase = 0x40000000ULL; // User heap start VA
|
static constexpr uint64_t UserHeapBase = 0x40000000ULL; // User heap start VA
|
||||||
static constexpr uint32_t UserReadDirSlots = 64; // rotating scratch pages for SYS_READDIR
|
static constexpr uint32_t UserReadDirSlots = 64; // rotating scratch pages for SYS_READDIR
|
||||||
static constexpr uint64_t UserReadDirBase =
|
static constexpr uint64_t UserReadDirBase =
|
||||||
@@ -139,6 +144,12 @@ namespace Sched {
|
|||||||
// Get a pointer to the currently running thread's slot (may be a sibling).
|
// Get a pointer to the currently running thread's slot (may be a sibling).
|
||||||
Process* GetCurrentThreadPtr();
|
Process* GetCurrentThreadPtr();
|
||||||
|
|
||||||
|
// Demand-grow the current process's main-thread user stack. Called from
|
||||||
|
// the page fault handler when a non-present fault lands in the stack
|
||||||
|
// growth region. Maps one zeroed page at the faulting address and returns
|
||||||
|
// true if the faulting instruction should be retried.
|
||||||
|
bool TryGrowUserStack(uint64_t faultAddr);
|
||||||
|
|
||||||
// Called by terminated processes to mark themselves done.
|
// Called by terminated processes to mark themselves done.
|
||||||
// Tears down the entire process (kills all sibling threads, frees address space).
|
// Tears down the entire process (kills all sibling threads, frees address space).
|
||||||
void ExitProcess();
|
void ExitProcess();
|
||||||
|
|||||||
+10
-2
@@ -424,10 +424,18 @@ ifneq ($(wildcard ../toolchain/native-gcc/sdk/bin/gcc),)
|
|||||||
mkdir -p $(BINDIR)/tmp
|
mkdir -p $(BINDIR)/tmp
|
||||||
printf 'scratch space for compiler intermediates\n' > $(BINDIR)/tmp/readme.txt
|
printf 'scratch space for compiler intermediates\n' > $(BINDIR)/tmp/readme.txt
|
||||||
else
|
else
|
||||||
@echo "devkit: toolchain/native-gcc/ not built; skipping native GCC"
|
@echo "======================================================================"
|
||||||
|
@echo "devkit: WARNING: toolchain/native-gcc/ not built."
|
||||||
|
@echo " The image will ship WITHOUT the native GCC (g++, cc1plus, ...)."
|
||||||
|
@echo " Run 'make sdk' from the repo root to build the full Montauk SDK."
|
||||||
|
@echo "======================================================================"
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
@echo "devkit: toolchain/native/ not built; skipping native dev tools"
|
@echo "======================================================================"
|
||||||
|
@echo "devkit: WARNING: toolchain/native/ not built."
|
||||||
|
@echo " The image will ship WITHOUT the Montauk SDK (0:/sdk dev tools)."
|
||||||
|
@echo " Run 'make sdk' from the repo root to build it (one-time, long)."
|
||||||
|
@echo "======================================================================"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -123,13 +123,28 @@ namespace heap_detail {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool grow(uint64_t bytes) {
|
// Next slab size for heap growth. The kernel tracks a finite number
|
||||||
uint64_t pages = (bytes + 0xFFF) / 0x1000;
|
// of SYS_ALLOC records per process (MaxHeapAllocs), so growing once
|
||||||
if (pages < 4) pages = 4;
|
// per large allocation exhausts them under allocation-heavy loads
|
||||||
|
// (the native ld ran out mid-link). Doubling slabs keep the syscall
|
||||||
|
// count logarithmic in total heap size.
|
||||||
|
inline uint64_t g_grow_slab = 16 * 0x1000;
|
||||||
|
|
||||||
void* mem = montauk::alloc(pages * 0x1000);
|
static inline bool grow(uint64_t bytes) {
|
||||||
|
uint64_t want = (bytes + 0xFFF) & ~0xFFFULL;
|
||||||
|
if (want < 0x4000) want = 0x4000;
|
||||||
|
|
||||||
|
uint64_t slab = (want > g_grow_slab) ? want : g_grow_slab;
|
||||||
|
if (g_grow_slab < 4 * 1024 * 1024) g_grow_slab *= 2;
|
||||||
|
|
||||||
|
void* mem = montauk::alloc(slab);
|
||||||
|
if (mem == nullptr && slab > want) {
|
||||||
|
// Big slab refused (low memory): retry with the exact need.
|
||||||
|
slab = want;
|
||||||
|
mem = montauk::alloc(slab);
|
||||||
|
}
|
||||||
if (mem == nullptr) return false;
|
if (mem == nullptr) return false;
|
||||||
insert_overflow(mem, pages * 0x1000);
|
insert_overflow(mem, slab);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -687,12 +687,28 @@ static void *heap_take_overflow(uint64_t needed) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Next slab size for heap growth. The kernel tracks a finite number of
|
||||||
|
SYS_ALLOC records per process (MaxHeapAllocs), so growing once per
|
||||||
|
large allocation exhausts them: ld ran out mid-link and BFD reported
|
||||||
|
the resulting NULL mallocs as "file format not recognized". Doubling
|
||||||
|
slabs keep the syscall count logarithmic in total heap size. */
|
||||||
|
static uint64_t g_heap_slab = 16 * 0x1000;
|
||||||
|
|
||||||
static void heap_grow(uint64_t bytes) {
|
static void heap_grow(uint64_t bytes) {
|
||||||
uint64_t pages = (bytes + 0xFFF) / 0x1000;
|
uint64_t want = (bytes + 0xFFF) & ~0xFFFULL;
|
||||||
if (pages < 4) pages = 4;
|
if (want < 0x4000) want = 0x4000;
|
||||||
void *mem = (void *)_zos_syscall1(SYS_ALLOC, (long)(pages * 0x1000));
|
|
||||||
|
uint64_t slab = (want > g_heap_slab) ? want : g_heap_slab;
|
||||||
|
if (g_heap_slab < 4 * 1024 * 1024) g_heap_slab *= 2;
|
||||||
|
|
||||||
|
void *mem = (void *)_zos_syscall1(SYS_ALLOC, (long)slab);
|
||||||
|
if (mem == NULL && slab > want) {
|
||||||
|
/* Big slab refused (low memory): retry with the exact need. */
|
||||||
|
slab = want;
|
||||||
|
mem = (void *)_zos_syscall1(SYS_ALLOC, (long)slab);
|
||||||
|
}
|
||||||
if (mem != NULL)
|
if (mem != NULL)
|
||||||
heap_insert_overflow(mem, pages * 0x1000);
|
heap_insert_overflow(mem, slab);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Refill a small-block bucket from overflow */
|
/* Refill a small-block bucket from overflow */
|
||||||
@@ -1504,6 +1520,25 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'o': {
|
||||||
|
unsigned long val;
|
||||||
|
if (is_long >= 1) val = va_arg(ap, unsigned long);
|
||||||
|
else val = va_arg(ap, unsigned int);
|
||||||
|
/* %#o: force a leading zero digit */
|
||||||
|
if (alt && val != 0 && precision < 0) {
|
||||||
|
_pf_putc(&st, '0');
|
||||||
|
if (width > 0) width--;
|
||||||
|
}
|
||||||
|
if (left_align) {
|
||||||
|
size_t before = st.pos;
|
||||||
|
_pf_putnum(&st, val, 8, 0, 0, pad, 0, precision);
|
||||||
|
size_t len = st.pos - before;
|
||||||
|
for (size_t w = len; (int)w < width; w++) _pf_putc(&st, ' ');
|
||||||
|
} else {
|
||||||
|
_pf_putnum(&st, val, 8, 0, width, pad, 0, precision);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'p': {
|
case 'p': {
|
||||||
void *val = va_arg(ap, void *);
|
void *val = va_arg(ap, void *);
|
||||||
_pf_puts(&st, "0x");
|
_pf_puts(&st, "0x");
|
||||||
|
|||||||
Binary file not shown.
@@ -64,6 +64,20 @@ toolchain/
|
|||||||
|
|
||||||
## Native binutils (runs on MontaukOS)
|
## 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
|
Binutils can be cross-compiled to run *on* MontaukOS itself
|
||||||
(`--host=x86_64-montauk`), the first step toward a self-hosted GCC:
|
(`--host=x86_64-montauk`), the first step toward a self-hosted GCC:
|
||||||
|
|
||||||
|
|||||||
Executable
+170
@@ -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)."
|
||||||
@@ -1,17 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* sdk-diag.c
|
* sdk-diag.c
|
||||||
* Probes the exact libc/kernel layers the GCC driver depends on:
|
* Probes the exact libc/kernel layers the GCC driver depends on:
|
||||||
* access/stat on the compiler backends, and a direct posix_spawn
|
* access/stat on the compiler backends, a direct posix_spawn of
|
||||||
* of cc1plus. Prints one line per probe.
|
* 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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <spawn.h>
|
#include <spawn.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#define CC1PLUS "/sdk/libexec/gcc/x86_64-montauk/14.2.0/cc1plus"
|
#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) {
|
static void probe(const char *p) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@@ -24,12 +29,101 @@ static void probe(const char *p) {
|
|||||||
printf("\n");
|
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) {
|
int main(void) {
|
||||||
probe(CC1PLUS);
|
probe(CC1PLUS);
|
||||||
probe("/sdk/libexec/gcc/x86_64-montauk/14.2.0");
|
probe("/sdk/libexec/gcc/x86_64-montauk/14.2.0");
|
||||||
probe("/sdk/libexec/gcc");
|
probe("/sdk/libexec/gcc");
|
||||||
probe("/sdk/bin/as.elf");
|
probe("/sdk/bin/as.elf");
|
||||||
probe("/tmp");
|
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;
|
pid_t pid = -1;
|
||||||
char *argv[] = { (char *)"cc1plus", (char *)"--version", 0 };
|
char *argv[] = { (char *)"cc1plus", (char *)"--version", 0 };
|
||||||
|
|||||||
@@ -59,3 +59,32 @@
|
|||||||
$as_echo "#define HAVE_HYPOT 1" >>confdefs.h
|
$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). */
|
||||||
|
|||||||
Reference in New Issue
Block a user