feat: add Rust userspace support; fix TLS layout for p_align < 16; kill whole process on sibling-thread faults
This commit is contained in:
1 parent
a81298fb24
commit
f8b8f9610f
31 files changed
+1075
-37
No files matched your search
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 302
|
#define MONTAUK_BUILD_NUMBER 306
|
||||||
@@ -221,6 +221,10 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_Kill(int pid) {
|
static int Sys_Kill(int pid) {
|
||||||
|
// A process may always kill itself: a sibling thread has no other
|
||||||
|
// way to take the whole process down (SYS_EXIT from a sibling ends
|
||||||
|
// only that thread), which abort()/panic=abort need.
|
||||||
|
if (pid == Sched::GetCurrentPid()) return Sched::KillProcess(pid);
|
||||||
if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) {
|
if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) {
|
||||||
int ancestor = pid;
|
int ancestor = pid;
|
||||||
bool descendant = false;
|
bool descendant = false;
|
||||||
|
|||||||
@@ -177,8 +177,18 @@ namespace Hal {
|
|||||||
if (i == 0x00) sig = 8; /* #DE -> SIGFPE */
|
if (i == 0x00) sig = 8; /* #DE -> SIGFPE */
|
||||||
else if (i == 0x06) sig = 4; /* #UD -> SIGILL */
|
else if (i == 0x06) sig = 4; /* #UD -> SIGILL */
|
||||||
else if (i == 0x10 || i == 0x13) sig = 8; /* x87/SIMD FP */
|
else if (i == 0x10 || i == 0x13) sig = 8; /* x87/SIMD FP */
|
||||||
|
|
||||||
|
// ExitProcess from a sibling thread ends only that thread,
|
||||||
|
// which would leave the process running with the crash
|
||||||
|
// swallowed. Flag the whole process (main thread and every
|
||||||
|
// sibling) first, carrying the crash code rather than
|
||||||
|
// KillProcess's default SIGKILL, so waiters see the fault.
|
||||||
|
if (Sched::GetCurrentThreadPtr() != proc) {
|
||||||
|
Sched::KillProcess(proc->pid, 256 + sig);
|
||||||
|
} else {
|
||||||
Sched::SetProcessExitCode(proc->pid, 256 + sig);
|
Sched::SetProcessExitCode(proc->pid, 256 + sig);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Sched::ExitProcess();
|
Sched::ExitProcess();
|
||||||
__builtin_unreachable();
|
__builtin_unreachable();
|
||||||
|
|||||||
@@ -536,16 +536,13 @@ namespace Sched {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build the main thread's TLS block (x86-64 variant II): the block
|
// Build the main thread's TLS block (x86-64 variant II): the block
|
||||||
// lives below the thread pointer, TP = base + S where
|
// lives below the thread pointer (see ComputeTlsLayout), template
|
||||||
// S = align_up(memsz, align), template data at [base, base+filesz),
|
// data first, .tbss zeroed, and [TP] holds the ABI self-pointer.
|
||||||
// .tbss zeroed, and [TP] holds the ABI-required self-pointer.
|
|
||||||
if (tlsPhdr != nullptr && outTls != nullptr) {
|
if (tlsPhdr != nullptr && outTls != nullptr) {
|
||||||
uint64_t align = tlsPhdr->p_align < 16 ? 16 : tlsPhdr->p_align;
|
TlsLayout layout = ComputeTlsLayout(tlsPhdr->p_memsz, tlsPhdr->p_align);
|
||||||
uint64_t S = (tlsPhdr->p_memsz + align - 1) & ~(align - 1);
|
|
||||||
uint64_t base = (maxVaddrEnd + 0x1FFFULL) & ~0xFFFULL; // page-aligned, 1 guard page gap
|
uint64_t base = (maxVaddrEnd + 0x1FFFULL) & ~0xFFFULL; // page-aligned, 1 guard page gap
|
||||||
uint64_t total = S + 16; // + minimal TCB
|
uint64_t numPages = (layout.totalSize + 0xFFF) / 0x1000;
|
||||||
uint64_t numPages = (total + 0xFFF) / 0x1000;
|
uint64_t tp = base + layout.tpOffset;
|
||||||
uint64_t tp = base + S;
|
|
||||||
|
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
for (uint64_t p = 0; p < numPages && ok; p++) {
|
for (uint64_t p = 0; p < numPages && ok; p++) {
|
||||||
@@ -562,7 +559,7 @@ namespace Sched {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy the .tdata template bytes that land in this page.
|
// Copy the .tdata template bytes that land in this page.
|
||||||
if (!copyPageFromFile(physAddr, virtAddr, base,
|
if (!copyPageFromFile(physAddr, virtAddr, base + layout.templateOffset,
|
||||||
tlsPhdr->p_offset, tlsPhdr->p_filesz)) {
|
tlsPhdr->p_offset, tlsPhdr->p_filesz)) {
|
||||||
ok = false;
|
ok = false;
|
||||||
break;
|
break;
|
||||||
@@ -579,7 +576,7 @@ namespace Sched {
|
|||||||
outTls->templateVaddr = tlsPhdr->p_vaddr;
|
outTls->templateVaddr = tlsPhdr->p_vaddr;
|
||||||
outTls->fileSize = tlsPhdr->p_filesz;
|
outTls->fileSize = tlsPhdr->p_filesz;
|
||||||
outTls->memSize = tlsPhdr->p_memsz;
|
outTls->memSize = tlsPhdr->p_memsz;
|
||||||
outTls->align = align;
|
outTls->align = tlsPhdr->p_align;
|
||||||
} else {
|
} else {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "ELF")
|
Kt::KernelLogStream(Kt::ERROR, "ELF")
|
||||||
<< "PT_TLS setup failed (out of pages?), "
|
<< "PT_TLS setup failed (out of pages?), "
|
||||||
|
|||||||
@@ -60,9 +60,30 @@ namespace Sched {
|
|||||||
uint64_t templateVaddr; // source of .tdata in the loaded image
|
uint64_t templateVaddr; // source of .tdata in the loaded image
|
||||||
uint64_t fileSize; // initialized bytes
|
uint64_t fileSize; // initialized bytes
|
||||||
uint64_t memSize; // total bytes (incl. .tbss)
|
uint64_t memSize; // total bytes (incl. .tbss)
|
||||||
uint64_t align;
|
uint64_t align; // PT_TLS p_align, NOT rounded up
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Geometry of one variant II TLS block, as offsets from its
|
||||||
|
// page-aligned base. The linker addresses TLS variables at
|
||||||
|
// TP - align_up(memSize, p_align) + offset, so the template must start
|
||||||
|
// exactly that far below TP; rounding p_align up first (as an older
|
||||||
|
// version did) shifts every variable away from its initial value.
|
||||||
|
// TP itself is kept at least 16-byte aligned, which the gap between
|
||||||
|
// base and the template absorbs.
|
||||||
|
struct TlsLayout {
|
||||||
|
uint64_t templateOffset; // where .tdata/.tbss start
|
||||||
|
uint64_t tpOffset; // thread pointer; [TP] holds the self-pointer
|
||||||
|
uint64_t totalSize; // bytes to map, incl. the 16-byte TCB
|
||||||
|
};
|
||||||
|
|
||||||
|
inline TlsLayout ComputeTlsLayout(uint64_t memSize, uint64_t pAlign) {
|
||||||
|
uint64_t align = pAlign ? pAlign : 1;
|
||||||
|
uint64_t block = (memSize + align - 1) & ~(align - 1);
|
||||||
|
uint64_t tpAlign = align < 16 ? 16 : align;
|
||||||
|
uint64_t tp = (block + tpAlign - 1) & ~(tpAlign - 1);
|
||||||
|
return {tp - block, tp, tp + 16};
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys, TlsInfo* outTls);
|
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys, TlsInfo* outTls);
|
||||||
|
|
||||||
// Load a shared library into a fixed address range.
|
// Load a shared library into a fixed address range.
|
||||||
|
|||||||
@@ -720,10 +720,8 @@ namespace Sched {
|
|||||||
uint64_t threadTlsPages = 0;
|
uint64_t threadTlsPages = 0;
|
||||||
Process& primary = processTable[primarySlot_];
|
Process& primary = processTable[primarySlot_];
|
||||||
if (primary.tlsMemSize > 0) {
|
if (primary.tlsMemSize > 0) {
|
||||||
uint64_t align = primary.tlsAlign < 16 ? 16 : primary.tlsAlign;
|
TlsLayout layout = ComputeTlsLayout(primary.tlsMemSize, primary.tlsAlign);
|
||||||
uint64_t S = (primary.tlsMemSize + align - 1) & ~(align - 1);
|
uint64_t numPages = (layout.totalSize + 0xFFF) / 0x1000;
|
||||||
uint64_t total = S + 16;
|
|
||||||
uint64_t numPages = (total + 0xFFF) / 0x1000;
|
|
||||||
|
|
||||||
uint64_t base = 0;
|
uint64_t base = 0;
|
||||||
if (!ReserveUserHeapRange(primarySlot_, numPages * 0x1000, base)) {
|
if (!ReserveUserHeapRange(primarySlot_, numPages * 0x1000, base)) {
|
||||||
@@ -757,9 +755,9 @@ namespace Sched {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t tp = base + S;
|
uint64_t tp = base + layout.tpOffset;
|
||||||
memcpy((void*)base, (const void*)primary.tlsTemplateVaddr,
|
memcpy((void*)(base + layout.templateOffset),
|
||||||
primary.tlsFileSize);
|
(const void*)primary.tlsTemplateVaddr, primary.tlsFileSize);
|
||||||
*(uint64_t*)tp = tp; /* ABI self-pointer */
|
*(uint64_t*)tp = tp; /* ABI self-pointer */
|
||||||
threadFsBase = tp;
|
threadFsBase = tp;
|
||||||
threadTlsBase = base;
|
threadTlsBase = base;
|
||||||
@@ -945,14 +943,13 @@ namespace Sched {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t align = primary.tlsAlign < 16 ? 16 : primary.tlsAlign;
|
TlsLayout layout = ComputeTlsLayout(primary.tlsMemSize, primary.tlsAlign);
|
||||||
uint64_t blockSize = (primary.tlsMemSize + align - 1) & ~(align - 1);
|
if (layout.tpOffset > thr.fsBase || layout.totalSize < layout.tpOffset) {
|
||||||
if (blockSize > thr.fsBase || blockSize > UINT64_MAX - 16) {
|
|
||||||
Panic("Invalid sibling TLS metadata during thread teardown", nullptr);
|
Panic("Invalid sibling TLS metadata during thread teardown", nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t base = thr.fsBase - blockSize;
|
uint64_t base = thr.fsBase - layout.tpOffset;
|
||||||
uint64_t pages = (blockSize + 16 + 0xFFF) / 0x1000;
|
uint64_t pages = (layout.totalSize + 0xFFF) / 0x1000;
|
||||||
thr.fsBase = 0;
|
thr.fsBase = 0;
|
||||||
Memory::UnmapAndFreeUserRange(primary.pml4Phys, base, pages);
|
Memory::UnmapAndFreeUserRange(primary.pml4Phys, base, pages);
|
||||||
ReleaseUserHeapRange(primarySlot_, base, pages * 0x1000ULL);
|
ReleaseUserHeapRange(primarySlot_, base, pages * 0x1000ULL);
|
||||||
@@ -1791,10 +1788,13 @@ namespace Sched {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int KillProcess(int pid) {
|
int KillProcess(int pid, int exitCode) {
|
||||||
// Refuse to kill PID 0 (init) or caller's own process
|
// Refuse to kill PID 0 (init). Killing the caller's own process is
|
||||||
|
// allowed: a faulting/aborting sibling thread shares the pid and has
|
||||||
|
// no other way to take the process down. The caller's own slot gets
|
||||||
|
// killPending below and exits at syscall return (ExitIfKilled) or
|
||||||
|
// via the ExitProcess the fault handler calls next.
|
||||||
if (pid == 0) return -1;
|
if (pid == 0) return -1;
|
||||||
if (pid == GetCurrentPid()) return -1;
|
|
||||||
|
|
||||||
schedLock.Acquire();
|
schedLock.Acquire();
|
||||||
|
|
||||||
@@ -1817,7 +1817,7 @@ namespace Sched {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
processTable[primarySlot_].exitCode = 256 + 9; /* killed (SIGKILL) */
|
processTable[primarySlot_].exitCode = exitCode;
|
||||||
|
|
||||||
// Flag the main thread so its next tick (or scheduler dispatch)
|
// Flag the main thread so its next tick (or scheduler dispatch)
|
||||||
// routes through ExitProcess, which sweeps every sibling thread
|
// routes through ExitProcess, which sweeps every sibling thread
|
||||||
|
|||||||
@@ -302,9 +302,10 @@ namespace Sched {
|
|||||||
int FutexWake(uint64_t uaddr, uint32_t count);
|
int FutexWake(uint64_t uaddr, uint32_t count);
|
||||||
|
|
||||||
// Kill a process by PID. If the process is running on another CPU,
|
// Kill a process by PID. If the process is running on another CPU,
|
||||||
// sets a kill-pending flag checked on the next timer tick.
|
// sets a kill-pending flag checked on the next timer tick. exitCode is
|
||||||
|
// what a waiter sees (256+sig; SIGKILL by default).
|
||||||
// Returns 0 on success, -1 on failure.
|
// Returns 0 on success, -1 on failure.
|
||||||
int KillProcess(int pid);
|
int KillProcess(int pid, int exitCode = 256 + 9);
|
||||||
|
|
||||||
// Start a new process session for the caller, or terminate every process
|
// Start a new process session for the caller, or terminate every process
|
||||||
// belonging to a session. KillSession returns the number of live members
|
// belonging to a session. KillSession returns the number of live members
|
||||||
|
|||||||
+23
-3
@@ -96,9 +96,9 @@ WPDIR := data/wallpapers
|
|||||||
WPSRC := $(wildcard $(WPDIR)/*.jpg)
|
WPSRC := $(wildcard $(WPDIR)/*.jpg)
|
||||||
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
|
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
|
||||||
|
|
||||||
.PHONY: all clean 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display capabilities keyboard sshserver sshd terminal syslog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs osdata bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad rtlsdr sdr check-syscalls gen-syscalls
|
.PHONY: all clean 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display capabilities keyboard sshserver sshd terminal syslog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs osdata bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad rtlsdr sdr check-syscalls gen-syscalls rust
|
||||||
|
|
||||||
all: bearssl libc libjpeg libjpegwrite tls libloader rtlsdr sdr devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display capabilities sshserver terminal syslog procmgr powermgr calculator charmap 2048 paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell sshd icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(OSDATADST) $(FWDST) $(LICDST) $(WPDST)
|
all: bearssl libc libjpeg libjpegwrite tls libloader rtlsdr sdr devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display capabilities sshserver terminal syslog procmgr powermgr calculator charmap 2048 paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell sshd icons fonts install-apps crashpad rust $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(OSDATADST) $(FWDST) $(LICDST) $(WPDST)
|
||||||
|
|
||||||
# Build BearSSL static library (cross-compiled for freestanding x86_64).
|
# Build BearSSL static library (cross-compiled for freestanding x86_64).
|
||||||
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
|
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
|
||||||
@@ -320,6 +320,26 @@ mandelbrot: libc
|
|||||||
screenshot: libc libjpegwrite libloader dialogs
|
screenshot: libc libjpegwrite libloader dialogs
|
||||||
$(MAKE) -C src/screenshot
|
$(MAKE) -C src/screenshot
|
||||||
|
|
||||||
|
# Rust programs (programs/rust, Phase 1: no_std + alloc via -Zbuild-std).
|
||||||
|
# Optional: skipped with a notice when no rustup-managed cargo with the
|
||||||
|
# pinned nightly is installed, so the default build never requires Rust.
|
||||||
|
# RUSTUP_AUTO_INSTALL=0 keeps 'make' from downloading a toolchain itself.
|
||||||
|
# Rust links the toolchain sysroot libc (like ports), not lib/libc directly:
|
||||||
|
# re-run build-montauk-toolchain.sh after libc changes.
|
||||||
|
CARGO ?= $(firstword $(wildcard $(HOME)/.cargo/bin/cargo) $(shell command -v cargo 2>/dev/null))
|
||||||
|
RUST_DIR := rust
|
||||||
|
RUST_OUT := $(RUST_DIR)/target/x86_64-unknown-montauk/release
|
||||||
|
RUST_PROGRAMS := rusthello
|
||||||
|
|
||||||
|
rust:
|
||||||
|
@if [ -z "$(CARGO)" ] || ! (cd $(RUST_DIR) && RUSTUP_AUTO_INSTALL=0 $(CARGO) --version >/dev/null 2>&1); then \
|
||||||
|
echo "rust: no nightly cargo found - skipping Rust programs (see programs/rust/README.md)"; \
|
||||||
|
else \
|
||||||
|
(cd $(RUST_DIR) && RUSTUP_AUTO_INSTALL=0 $(CARGO) build --release) || exit 1; \
|
||||||
|
mkdir -p $(BINDIR)/os; \
|
||||||
|
for p in $(RUST_PROGRAMS); do cp $(RUST_OUT)/$$p $(BINDIR)/os/$$p.elf || exit 1; done; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Verify the TCC C API header (libc/montauk.h) exposes every kernel syscall with
|
# Verify the TCC C API header (libc/montauk.h) exposes every kernel syscall with
|
||||||
# matching numbers. Fails the build on drift; 'make gen-syscalls' refreshes them.
|
# matching numbers. Fails the build on drift; 'make gen-syscalls' refreshes them.
|
||||||
check-syscalls:
|
check-syscalls:
|
||||||
@@ -486,7 +506,7 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BINDIR) obj
|
rm -rf $(BINDIR) obj $(RUST_DIR)/target
|
||||||
$(MAKE) -C lib/bearssl clean
|
$(MAKE) -C lib/bearssl clean
|
||||||
$(MAKE) -C lib/libc clean
|
$(MAKE) -C lib/libc clean
|
||||||
$(MAKE) -C lib/libjpeg clean
|
$(MAKE) -C lib/libjpeg clean
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ typedef enum {
|
|||||||
WouldBlock = 16
|
WouldBlock = 16
|
||||||
} StatusCode;
|
} StatusCode;
|
||||||
|
|
||||||
|
// Highest defined StatusCode; update when adding a code above.
|
||||||
|
enum { MaxStatusCode = WouldBlock };
|
||||||
|
|
||||||
inline const char* GetStatusText(StatusCode statusCode) {
|
inline const char* GetStatusText(StatusCode statusCode) {
|
||||||
const char* statusText[] = {
|
const char* statusText[] = {
|
||||||
"Success", // 0
|
"Success", // 0
|
||||||
@@ -53,7 +56,7 @@ inline const char* GetStatusText(StatusCode statusCode) {
|
|||||||
"Operation would block", // 16
|
"Operation would block", // 16
|
||||||
};
|
};
|
||||||
|
|
||||||
if (statusCode > 0 && statusCode <= 16) {
|
if (statusCode > 0 && statusCode <= MaxStatusCode) {
|
||||||
return statusText[statusCode];
|
return statusText[statusCode];
|
||||||
} else {
|
} else {
|
||||||
return "Unknown error";
|
return "Unknown error";
|
||||||
@@ -63,7 +66,7 @@ inline const char* GetStatusText(StatusCode statusCode) {
|
|||||||
// Map a raw syscall result to a StatusCode.
|
// Map a raw syscall result to a StatusCode.
|
||||||
inline StatusCode StatusFromResult(long long rc) {
|
inline StatusCode StatusFromResult(long long rc) {
|
||||||
if (rc >= 0) return Success;
|
if (rc >= 0) return Success;
|
||||||
if (rc < -(long long)WouldBlock) return GenericFailure;
|
if (rc < -(long long)MaxStatusCode) return GenericFailure;
|
||||||
return (StatusCode)(-rc);
|
return (StatusCode)(-rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +92,7 @@ inline const char* GetStatusExplanationText(StatusCode statusCode) {
|
|||||||
0, // 16
|
0, // 16
|
||||||
};
|
};
|
||||||
|
|
||||||
if (statusCode > 0 && statusCode <= 16) {
|
if (statusCode > 0 && statusCode <= MaxStatusCode) {
|
||||||
if (explanationText[statusCode] != 0) {
|
if (explanationText[statusCode] != 0) {
|
||||||
return explanationText[statusCode];
|
return explanationText[statusCode];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Phase 1 Rust support: no_std + alloc on a stock nightly, built with
|
||||||
|
# -Zbuild-std against the custom target spec next to this directory.
|
||||||
|
# Paths below are relative to programs/rust/ (the parent of .cargo/).
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "x86_64-unknown-montauk.json"
|
||||||
|
|
||||||
|
[unstable]
|
||||||
|
build-std = ["core", "alloc"]
|
||||||
|
json-target-spec = true
|
||||||
|
|
||||||
|
[target.x86_64-unknown-montauk]
|
||||||
|
# The montauk GCC driver supplies crt1/crti/crtbegin, -lc and libgcc, and
|
||||||
|
# links the static non-PIE ET_EXEC layout the kernel ELF loader expects.
|
||||||
|
linker = "../../toolchain/local/bin/x86_64-montauk-gcc"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
target/
|
||||||
Generated
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "montauk-rt"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusthello"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"montauk-rt",
|
||||||
|
]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = ["montauk-rt", "rusthello"]
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
debug = false
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# Rust on MontaukOS
|
||||||
|
|
||||||
|
Phase 1: `no_std` + `alloc` programs, cross-compiled from Linux with a
|
||||||
|
stock nightly toolchain and a custom target spec. No compiler fork.
|
||||||
|
|
||||||
|
## Setup (once)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --proto '=https' -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none
|
||||||
|
~/.cargo/bin/rustup toolchain install nightly --component rust-src
|
||||||
|
```
|
||||||
|
|
||||||
|
`rust-toolchain.toml` selects nightly + `rust-src` for this directory.
|
||||||
|
`make programs` builds the Rust programs when that toolchain is present
|
||||||
|
and prints a notice and skips them otherwise. It never downloads a toolchain
|
||||||
|
(`RUSTUP_AUTO_INSTALL=0`).
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make -C programs rust # cargo build --release, copies to bin/os/*.elf
|
||||||
|
cd programs/rust && cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
Output: `target/x86_64-unknown-montauk/release/<name>`, installed as
|
||||||
|
`programs/bin/os/<name>.elf`. Add new binaries to `RUST_PROGRAMS` in
|
||||||
|
`programs/GNUmakefile` and to the workspace `members`.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
| Path | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `x86_64-unknown-montauk.json` | Target spec: static non-PIE ET_EXEC, no red zone, local-exec TLS, panic=abort |
|
||||||
|
| `.cargo/config.toml` | `-Zbuild-std=core,alloc`, linker = `x86_64-montauk-gcc` |
|
||||||
|
| `montauk-rt/` | Runtime: syscalls, allocator, `print!`, argv, threads, futex `Mutex`, panic handler |
|
||||||
|
| `rusthello/` | Smoke test (`rusthello`, `rusthello panic`, `rusthello panic-thread`) |
|
||||||
|
|
||||||
|
The GCC driver links crt1/crti/crtbegin, the libc and libgcc from
|
||||||
|
`toolchain/sysroot`, the same as ports. After changing `programs/lib/libc`,
|
||||||
|
re-run `toolchain/build-montauk-toolchain.sh` so Rust programs pick it up.
|
||||||
|
|
||||||
|
Syscall numbers and status codes are generated at build time by
|
||||||
|
`montauk-rt/build.rs` from `programs/include/Api/Syscall.hpp` and
|
||||||
|
`kernel/src/Common/StatusCodes.hpp`, so there is nothing to keep in sync by hand.
|
||||||
|
|
||||||
|
## Writing a program
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
montauk_rt::entry!(main);
|
||||||
|
|
||||||
|
fn main() -> i32 {
|
||||||
|
montauk_rt::println!("hello from Rust");
|
||||||
|
0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limits (Phase 1)
|
||||||
|
|
||||||
|
- No `std`. No `std::fs`, `std::net`, `std::process` or `std::thread`; use
|
||||||
|
`montauk_rt` equivalents where they exist.
|
||||||
|
- `panic = "abort"`. A panic on any thread prints the message and kills the
|
||||||
|
whole process.
|
||||||
|
- Thread stacks are owned by the `JoinHandle`. An unjoined thread leaks its stack.
|
||||||
|
|
||||||
|
Phase 2 (a forked rustc at `~/dev/mtk/rust` with a native `target_os =
|
||||||
|
"montauk"` std) replaces the JSON spec with a built-in target.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "montauk-rt"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
description = "Minimal no_std runtime for Rust programs on MontaukOS"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/lib.rs"
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Generate syscall numbers and status codes from the C++ headers so Rust
|
||||||
|
// never becomes another hand-maintained copy of the syscall table.
|
||||||
|
//
|
||||||
|
// programs/include/Api/Syscall.hpp -> pub const SYS_*: u64
|
||||||
|
// kernel/src/Common/StatusCodes.hpp -> pub enum Status (positive values,
|
||||||
|
// matching montauk/statuscodes.h)
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||||
|
let repo = manifest.join("../../..");
|
||||||
|
let syscall_hpp = repo.join("programs/include/Api/Syscall.hpp");
|
||||||
|
let status_hpp = repo.join("kernel/src/Common/StatusCodes.hpp");
|
||||||
|
println!("cargo:rerun-if-changed={}", syscall_hpp.display());
|
||||||
|
println!("cargo:rerun-if-changed={}", status_hpp.display());
|
||||||
|
|
||||||
|
let mut out = String::from("// @generated by montauk-rt/build.rs - do not edit.\n\n");
|
||||||
|
|
||||||
|
// static constexpr uint64_t SYS_NAME = 123; (decimal literals only)
|
||||||
|
let syscalls = fs::read_to_string(&syscall_hpp).expect("read Syscall.hpp");
|
||||||
|
let mut count = 0;
|
||||||
|
for line in syscalls.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
let Some(rest) = line.strip_prefix("static constexpr uint64_t SYS_") else { continue };
|
||||||
|
let Some((name, value)) = rest.split_once('=') else { continue };
|
||||||
|
let value = value.split(';').next().unwrap_or("").trim();
|
||||||
|
if value.is_empty() || !value.bytes().all(|b| b.is_ascii_digit()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.push_str(&format!("pub const SYS_{}: u64 = {};\n", name.trim(), value));
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
assert!(count > 100, "parsed only {count} syscalls from {}", syscall_hpp.display());
|
||||||
|
|
||||||
|
// enum StatusCodes { Success = 0, GenericFailure = -1, ... };
|
||||||
|
let status = fs::read_to_string(&status_hpp).expect("read StatusCodes.hpp");
|
||||||
|
let body = status
|
||||||
|
.split_once("enum StatusCodes")
|
||||||
|
.and_then(|(_, rest)| rest.split_once('{'))
|
||||||
|
.and_then(|(_, rest)| rest.split_once('}'))
|
||||||
|
.map(|(body, _)| body)
|
||||||
|
.expect("enum StatusCodes not found");
|
||||||
|
out.push_str("\n/// Kernel status codes, stored positive like montauk/statuscodes.h.\n");
|
||||||
|
out.push_str("/// The kernel returns them negated; see [`Status::from_result`].\n");
|
||||||
|
out.push_str("#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n#[repr(i32)]\npub enum Status {\n");
|
||||||
|
let mut variants = Vec::new();
|
||||||
|
for entry in body.split(',') {
|
||||||
|
let entry = entry.split("//").next().unwrap_or("").trim();
|
||||||
|
let Some((name, value)) = entry.split_once('=') else { continue };
|
||||||
|
let value: i32 = value.trim().parse().expect("status value");
|
||||||
|
out.push_str(&format!(" {} = {},\n", name.trim(), -value));
|
||||||
|
variants.push((name.trim().to_string(), -value));
|
||||||
|
}
|
||||||
|
out.push_str("}\n\nimpl Status {\n pub const fn from_code(code: i32) -> Status {\n match code {\n");
|
||||||
|
for (name, value) in &variants {
|
||||||
|
out.push_str(&format!(" {} => Status::{},\n", value, name));
|
||||||
|
}
|
||||||
|
out.push_str(" _ => Status::GenericFailure,\n }\n }\n}\n");
|
||||||
|
|
||||||
|
let dest = PathBuf::from(env::var("OUT_DIR").unwrap()).join("abi.rs");
|
||||||
|
fs::write(dest, out).expect("write abi.rs");
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//! Command-line arguments, captured from crt1.o's argv by [`crate::entry!`].
|
||||||
|
|
||||||
|
use core::ffi::CStr;
|
||||||
|
use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
static ARGC: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
static ARGV: AtomicPtr<*const u8> = AtomicPtr::new(core::ptr::null_mut());
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// `argv` must point to `argc` NUL-terminated strings that live forever.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub unsafe fn init(argc: i32, argv: *const *const u8) {
|
||||||
|
ARGV.store(argv as *mut *const u8, Ordering::Relaxed);
|
||||||
|
ARGC.store(argc.max(0) as usize, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over the program arguments; argv[0] is the executable path.
|
||||||
|
/// Arguments that are not valid UTF-8 are yielded lossily as `"?"`.
|
||||||
|
pub fn args() -> Args {
|
||||||
|
Args { next: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Args {
|
||||||
|
next: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for Args {
|
||||||
|
type Item = &'static str;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<&'static str> {
|
||||||
|
let argc = ARGC.load(Ordering::Acquire);
|
||||||
|
if self.next >= argc {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let argv = ARGV.load(Ordering::Relaxed);
|
||||||
|
// SAFETY: init() guarantees argc valid, immortal C strings.
|
||||||
|
let s = unsafe { CStr::from_ptr(*argv.add(self.next) as *const core::ffi::c_char) };
|
||||||
|
self.next += 1;
|
||||||
|
Some(s.to_str().unwrap_or("?"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
let left = ARGC.load(Ordering::Acquire).saturating_sub(self.next);
|
||||||
|
(left, Some(left))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExactSizeIterator for Args {}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
//! Global allocator over the Montauk libc heap (process-wide locked, so
|
||||||
|
//! safe across threads). libc malloc returns 16-byte aligned blocks; larger
|
||||||
|
//! alignments over-allocate and stash the original pointer just below the
|
||||||
|
//! aligned block.
|
||||||
|
|
||||||
|
use core::alloc::{GlobalAlloc, Layout};
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
unsafe extern "C" {
|
||||||
|
fn malloc(size: usize) -> *mut u8;
|
||||||
|
fn free(ptr: *mut u8);
|
||||||
|
fn realloc(ptr: *mut u8, size: usize) -> *mut u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MALLOC_ALIGN: usize = 16;
|
||||||
|
|
||||||
|
pub struct LibcAllocator;
|
||||||
|
|
||||||
|
unsafe impl GlobalAlloc for LibcAllocator {
|
||||||
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||||
|
unsafe {
|
||||||
|
if layout.align() <= MALLOC_ALIGN {
|
||||||
|
return malloc(layout.size().max(1));
|
||||||
|
}
|
||||||
|
let Some(total) = layout.size().checked_add(layout.align() + size_of::<usize>()) else {
|
||||||
|
return ptr::null_mut();
|
||||||
|
};
|
||||||
|
let raw = malloc(total);
|
||||||
|
if raw.is_null() {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
let start = raw as usize + size_of::<usize>();
|
||||||
|
let aligned = (start + layout.align() - 1) & !(layout.align() - 1);
|
||||||
|
*((aligned - size_of::<usize>()) as *mut usize) = raw as usize;
|
||||||
|
aligned as *mut u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn dealloc(&self, p: *mut u8, layout: Layout) {
|
||||||
|
unsafe {
|
||||||
|
if layout.align() <= MALLOC_ALIGN {
|
||||||
|
free(p);
|
||||||
|
} else {
|
||||||
|
free(*((p as usize - size_of::<usize>()) as *const usize) as *mut u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn realloc(&self, p: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
|
||||||
|
unsafe {
|
||||||
|
if layout.align() <= MALLOC_ALIGN {
|
||||||
|
return realloc(p, new_size.max(1));
|
||||||
|
}
|
||||||
|
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
|
||||||
|
let q = self.alloc(new_layout);
|
||||||
|
if !q.is_null() {
|
||||||
|
ptr::copy_nonoverlapping(p, q, layout.size().min(new_size));
|
||||||
|
self.dealloc(p, layout);
|
||||||
|
}
|
||||||
|
q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOCATOR: LibcAllocator = LibcAllocator;
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
//! Terminal output. `print!`/`println!` format into a stack buffer and hand
|
||||||
|
//! it to SYS_PRINT in NUL-terminated chunks, so one call is one (or a few)
|
||||||
|
//! syscalls rather than libc write()'s one SYS_PUTCHAR per byte.
|
||||||
|
|
||||||
|
use core::fmt::{self, Write};
|
||||||
|
|
||||||
|
use crate::sys;
|
||||||
|
|
||||||
|
const CHUNK: usize = 256;
|
||||||
|
|
||||||
|
/// Buffered writer to the process's terminal (stdout).
|
||||||
|
pub struct Stdout {
|
||||||
|
buf: [u8; CHUNK],
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stdout {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Stdout { buf: [0; CHUNK], len: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_bytes(&mut self, bytes: &[u8]) {
|
||||||
|
for &b in bytes {
|
||||||
|
if b == 0 {
|
||||||
|
// SYS_PRINT stops at NUL; send it on its own.
|
||||||
|
self.flush();
|
||||||
|
unsafe { sys::syscall1(sys::SYS_PUTCHAR, 0) };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.len == CHUNK - 1 {
|
||||||
|
self.flush();
|
||||||
|
}
|
||||||
|
self.buf[self.len] = b;
|
||||||
|
self.len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush(&mut self) {
|
||||||
|
if self.len == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.buf[self.len] = 0;
|
||||||
|
unsafe { sys::syscall1(sys::SYS_PRINT, self.buf.as_ptr() as u64) };
|
||||||
|
self.len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Stdout {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for Stdout {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
self.write_bytes(s.as_bytes());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Stdout {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn _print(args: fmt::Arguments) {
|
||||||
|
let mut out = Stdout::new();
|
||||||
|
let _ = out.write_fmt(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! print {
|
||||||
|
($($arg:tt)*) => { $crate::io::_print(format_args!($($arg)*)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! println {
|
||||||
|
() => { $crate::print!("\n") };
|
||||||
|
($($arg:tt)*) => { $crate::io::_print(format_args!("{}\n", format_args!($($arg)*))) };
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//! Minimal `no_std` runtime for Rust programs on MontaukOS.
|
||||||
|
//!
|
||||||
|
//! Phase 1 of Rust support: `core` + `alloc` on a stock nightly with a
|
||||||
|
//! custom target spec. Programs link against the Montauk libc through the
|
||||||
|
//! x86_64-montauk GCC driver, so crt1.o owns `_start` (argv, environment,
|
||||||
|
//! .init_array) and calls the `main` that [`entry!`] defines.
|
||||||
|
//!
|
||||||
|
//! ```ignore
|
||||||
|
//! #![no_std]
|
||||||
|
//! #![no_main]
|
||||||
|
//! montauk_rt::entry!(main);
|
||||||
|
//! fn main() -> i32 { montauk_rt::println!("hi"); 0 }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
pub mod sys;
|
||||||
|
pub mod io;
|
||||||
|
pub mod heap;
|
||||||
|
pub mod env;
|
||||||
|
pub mod process;
|
||||||
|
pub mod sync;
|
||||||
|
pub mod thread;
|
||||||
|
pub mod time;
|
||||||
|
|
||||||
|
mod panic;
|
||||||
|
|
||||||
|
pub use sys::Status;
|
||||||
|
|
||||||
|
/// Define the C `main` that crt1.o calls, forwarding to `fn() -> i32`.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! entry {
|
||||||
|
($main:path) => {
|
||||||
|
const _: () = {
|
||||||
|
// Exported as the C symbol `main`; the Rust name stays free so
|
||||||
|
// the program can call its own entry function `main`.
|
||||||
|
#[unsafe(export_name = "main")]
|
||||||
|
extern "C" fn __montauk_rt_main(argc: i32, argv: *const *const u8) -> i32 {
|
||||||
|
// SAFETY: crt1.o passes its static argv table, which lives
|
||||||
|
// for the whole program and is never modified after startup.
|
||||||
|
unsafe { $crate::env::init(argc, argv) };
|
||||||
|
let f: fn() -> i32 = $main;
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use core::fmt::Write;
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
|
let mut out = crate::io::Stdout::new();
|
||||||
|
let _ = write!(out, "\nthread {} panicked", crate::thread::current_id());
|
||||||
|
if let Some(loc) = info.location() {
|
||||||
|
let _ = write!(out, " at {}:{}:{}", loc.file(), loc.line(), loc.column());
|
||||||
|
}
|
||||||
|
let _ = writeln!(out, ":\n{}", info.message());
|
||||||
|
out.flush();
|
||||||
|
crate::process::abort()
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
//! Process control.
|
||||||
|
|
||||||
|
use crate::sys;
|
||||||
|
|
||||||
|
unsafe extern "C" {
|
||||||
|
// libc exit(): runs atexit handlers (and .fini_array), then SYS_EXIT.
|
||||||
|
#[link_name = "exit"]
|
||||||
|
fn libc_exit(code: i32) -> !;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit the process. From a sibling thread this ends only that thread
|
||||||
|
/// (kernel semantics); use [`abort`] to take the whole process down.
|
||||||
|
pub fn exit(code: i32) -> ! {
|
||||||
|
unsafe { libc_exit(code) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id() -> u32 {
|
||||||
|
unsafe { sys::syscall0(sys::SYS_GETPID) as u32 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Terminate the whole process from any thread.
|
||||||
|
pub fn abort() -> ! {
|
||||||
|
unsafe {
|
||||||
|
if crate::thread::current_id() == id() {
|
||||||
|
sys::syscall1(sys::SYS_EXIT, 134);
|
||||||
|
} else {
|
||||||
|
// Sibling: SYS_EXIT would end only this thread. Self-kill flags
|
||||||
|
// every thread; park here until the kernel reaps us.
|
||||||
|
sys::syscall1(sys::SYS_KILL, id() as u64);
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
sys::syscall0(sys::SYS_YIELD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
//! Futex-backed synchronization (SYS_FUTEX_WAIT / SYS_FUTEX_WAKE).
|
||||||
|
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
use core::ops::{Deref, DerefMut};
|
||||||
|
use core::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
use core::time::Duration;
|
||||||
|
|
||||||
|
use crate::sys::{self, Status};
|
||||||
|
|
||||||
|
/// Block while `*word == expected`. `Ok(())` means woken by [`futex_wake`];
|
||||||
|
/// `Err(WouldBlock)` means the word had already changed; `Err(TimedOut)`
|
||||||
|
/// means the deadline passed. Always re-check your condition afterwards.
|
||||||
|
pub fn futex_wait(word: &AtomicU32, expected: u32, timeout: Option<Duration>) -> Result<(), Status> {
|
||||||
|
let ns = match timeout {
|
||||||
|
None => sys::FUTEX_WAIT_FOREVER,
|
||||||
|
Some(d) => d.as_nanos().min((sys::FUTEX_WAIT_FOREVER - 1) as u128) as u64,
|
||||||
|
};
|
||||||
|
let rc = unsafe {
|
||||||
|
sys::syscall3(sys::SYS_FUTEX_WAIT, word.as_ptr() as u64, expected as u64, ns)
|
||||||
|
};
|
||||||
|
Status::from_result(rc).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wake up to `count` waiters on `word`, longest-waiting first.
|
||||||
|
/// Returns how many were woken.
|
||||||
|
pub fn futex_wake(word: &AtomicU32, count: u32) -> u32 {
|
||||||
|
let rc = unsafe { sys::syscall2(sys::SYS_FUTEX_WAKE, word.as_ptr() as u64, count as u64) };
|
||||||
|
Status::from_result(rc).unwrap_or(0) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Mutex: Drepper's three-state futex mutex ("Futexes Are Tricky", #3).
|
||||||
|
// 0 = unlocked, 1 = locked, 2 = locked with possible waiters.
|
||||||
|
// ====================================================================
|
||||||
|
|
||||||
|
pub struct Mutex<T: ?Sized> {
|
||||||
|
state: AtomicU32,
|
||||||
|
data: UnsafeCell<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
|
||||||
|
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
|
||||||
|
|
||||||
|
impl<T> Mutex<T> {
|
||||||
|
pub const fn new(value: T) -> Self {
|
||||||
|
Mutex { state: AtomicU32::new(0), data: UnsafeCell::new(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.data.into_inner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Mutex<T> {
|
||||||
|
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||||
|
if self.state.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed).is_err() {
|
||||||
|
self.lock_contended();
|
||||||
|
}
|
||||||
|
MutexGuard { mutex: self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
|
||||||
|
self.state
|
||||||
|
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||||
|
.ok()
|
||||||
|
.map(|_| MutexGuard { mutex: self })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn lock_contended(&self) {
|
||||||
|
// Brief spin first: most critical sections are short.
|
||||||
|
for _ in 0..100 {
|
||||||
|
if self.state.load(Ordering::Relaxed) == 0
|
||||||
|
&& self.state.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed).is_ok()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
while self.state.swap(2, Ordering::Acquire) != 0 {
|
||||||
|
let _ = futex_wait(&self.state, 2, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unlock(&self) {
|
||||||
|
if self.state.swap(0, Ordering::Release) == 2 {
|
||||||
|
futex_wake(&self.state, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
|
self.data.get_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default> Default for Mutex<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Mutex::new(T::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MutexGuard<'a, T: ?Sized> {
|
||||||
|
mutex: &'a Mutex<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Deref for MutexGuard<'_, T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
unsafe { &*self.mutex.data.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
unsafe { &mut *self.mutex.data.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.mutex.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//! Raw syscall interface. Numbers and status codes are generated from
|
||||||
|
//! programs/include/Api/Syscall.hpp and kernel/src/Common/StatusCodes.hpp.
|
||||||
|
|
||||||
|
use core::arch::asm;
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/abi.rs"));
|
||||||
|
|
||||||
|
/// `SYS_FUTEX_WAIT` timeout meaning "no deadline".
|
||||||
|
pub const FUTEX_WAIT_FOREVER: u64 = !0;
|
||||||
|
/// `SYS_FUTEX_WAKE` count meaning "every waiter".
|
||||||
|
pub const FUTEX_WAKE_ALL: u32 = u32::MAX;
|
||||||
|
|
||||||
|
impl Status {
|
||||||
|
/// Convert a raw syscall result: negative values are negated status
|
||||||
|
/// codes, everything else (handles, counts, sizes) is success.
|
||||||
|
pub fn from_result(rc: i64) -> Result<u64, Status> {
|
||||||
|
if rc >= 0 {
|
||||||
|
Ok(rc as u64)
|
||||||
|
} else if rc < i32::MIN as i64 {
|
||||||
|
Err(Status::GenericFailure)
|
||||||
|
} else {
|
||||||
|
Err(Status::from_code(-(rc as i32)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The kernel's SYSCALL path clobbers rcx/r11 (return RIP/RFLAGS) and, like
|
||||||
|
// the C++ wrappers in montauk/syscall.h, is treated as clobbering every
|
||||||
|
// argument register.
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn syscall0(nr: u64) -> i64 {
|
||||||
|
let ret: i64;
|
||||||
|
unsafe {
|
||||||
|
asm!("syscall", inlateout("rax") nr as i64 => ret,
|
||||||
|
out("rcx") _, out("r11") _, out("rdi") _, out("rsi") _, out("rdx") _,
|
||||||
|
out("r8") _, out("r9") _, out("r10") _, options(nostack));
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn syscall1(nr: u64, a1: u64) -> i64 {
|
||||||
|
let ret: i64;
|
||||||
|
unsafe {
|
||||||
|
asm!("syscall", inlateout("rax") nr as i64 => ret, inlateout("rdi") a1 => _,
|
||||||
|
out("rcx") _, out("r11") _, out("rsi") _, out("rdx") _,
|
||||||
|
out("r8") _, out("r9") _, out("r10") _, options(nostack));
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn syscall2(nr: u64, a1: u64, a2: u64) -> i64 {
|
||||||
|
let ret: i64;
|
||||||
|
unsafe {
|
||||||
|
asm!("syscall", inlateout("rax") nr as i64 => ret, inlateout("rdi") a1 => _,
|
||||||
|
inlateout("rsi") a2 => _,
|
||||||
|
out("rcx") _, out("r11") _, out("rdx") _,
|
||||||
|
out("r8") _, out("r9") _, out("r10") _, options(nostack));
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn syscall3(nr: u64, a1: u64, a2: u64, a3: u64) -> i64 {
|
||||||
|
let ret: i64;
|
||||||
|
unsafe {
|
||||||
|
asm!("syscall", inlateout("rax") nr as i64 => ret, inlateout("rdi") a1 => _,
|
||||||
|
inlateout("rsi") a2 => _, inlateout("rdx") a3 => _,
|
||||||
|
out("rcx") _, out("r11") _,
|
||||||
|
out("r8") _, out("r9") _, out("r10") _, options(nostack));
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
//! Threads (SYS_THREAD_SPAWN / SYS_THREAD_JOIN).
|
||||||
|
//!
|
||||||
|
//! The kernel uses the caller's stack verbatim and delivers one argument in
|
||||||
|
//! %rdi. A thread cannot free the stack it runs on, so the [`JoinHandle`]
|
||||||
|
//! owns it and frees it after a successful join; dropping an unjoined
|
||||||
|
//! handle leaks the stack (the thread may still be running on it).
|
||||||
|
|
||||||
|
use alloc::alloc::{alloc, dealloc, Layout};
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
|
use crate::sys::{self, Status};
|
||||||
|
|
||||||
|
pub const DEFAULT_STACK_SIZE: usize = 256 * 1024;
|
||||||
|
const STACK_ALIGN: usize = 16;
|
||||||
|
|
||||||
|
pub struct JoinHandle {
|
||||||
|
tid: u32,
|
||||||
|
stack: *mut u8,
|
||||||
|
stack_layout: Layout,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for JoinHandle {}
|
||||||
|
|
||||||
|
impl JoinHandle {
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
self.tid
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for the thread to finish and return its exit code.
|
||||||
|
pub fn join(self) -> Result<i32, Status> {
|
||||||
|
let mut code: i32 = 0;
|
||||||
|
let rc = unsafe {
|
||||||
|
sys::syscall2(sys::SYS_THREAD_JOIN, self.tid as u64, &mut code as *mut i32 as u64)
|
||||||
|
};
|
||||||
|
// The kernel reports a bad tid as -1 (GenericFailure).
|
||||||
|
Status::from_result(rc)?;
|
||||||
|
unsafe { dealloc(self.stack, self.stack_layout) };
|
||||||
|
Ok(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn a thread running `f`; its return value becomes the exit code.
|
||||||
|
pub fn spawn<F>(f: F) -> Result<JoinHandle, Status>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> i32 + Send + 'static,
|
||||||
|
{
|
||||||
|
spawn_with_stack(DEFAULT_STACK_SIZE, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_with_stack<F>(stack_size: usize, f: F) -> Result<JoinHandle, Status>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> i32 + Send + 'static,
|
||||||
|
{
|
||||||
|
let stack_layout = Layout::from_size_align(stack_size.max(4096), STACK_ALIGN)
|
||||||
|
.map_err(|_| Status::InvalidArgument)?;
|
||||||
|
let stack = unsafe { alloc(stack_layout) };
|
||||||
|
if stack.is_null() {
|
||||||
|
return Err(Status::OutOfMemory);
|
||||||
|
}
|
||||||
|
let closure = Box::into_raw(Box::new(f));
|
||||||
|
let top = (stack as usize + stack_layout.size()) & !(STACK_ALIGN - 1);
|
||||||
|
|
||||||
|
let rc = unsafe {
|
||||||
|
sys::syscall3(sys::SYS_THREAD_SPAWN, trampoline::<F> as *const () as u64, closure as u64, top as u64)
|
||||||
|
};
|
||||||
|
match Status::from_result(rc) {
|
||||||
|
Ok(tid) => Ok(JoinHandle { tid: tid as u32, stack, stack_layout }),
|
||||||
|
Err(e) => unsafe {
|
||||||
|
drop(Box::from_raw(closure));
|
||||||
|
dealloc(stack, stack_layout);
|
||||||
|
Err(e)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn trampoline<F>(arg: *mut F) -> !
|
||||||
|
where
|
||||||
|
F: FnOnce() -> i32,
|
||||||
|
{
|
||||||
|
let f = unsafe { Box::from_raw(arg) };
|
||||||
|
let code = f();
|
||||||
|
unsafe { sys::syscall1(sys::SYS_THREAD_EXIT, code as u32 as u64) };
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The calling thread's id (equals the process id on the main thread).
|
||||||
|
pub fn current_id() -> u32 {
|
||||||
|
unsafe { sys::syscall0(sys::SYS_THREAD_SELF) as u32 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn yield_now() {
|
||||||
|
unsafe { sys::syscall0(sys::SYS_YIELD) };
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//! Time and sleeping.
|
||||||
|
|
||||||
|
use core::time::Duration;
|
||||||
|
|
||||||
|
use crate::sys;
|
||||||
|
|
||||||
|
/// Milliseconds since boot (monotonic).
|
||||||
|
pub fn uptime_ms() -> u64 {
|
||||||
|
unsafe { sys::syscall0(sys::SYS_GETMILLISECONDS) as u64 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sleep for at least `d`, rounded up to the 1 ms scheduler tick.
|
||||||
|
pub fn sleep(d: Duration) {
|
||||||
|
let ms = d.as_millis().min(u64::MAX as u128) as u64 + u64::from(d.subsec_nanos() % 1_000_000 != 0);
|
||||||
|
if ms > 0 {
|
||||||
|
unsafe { sys::syscall1(sys::SYS_SLEEP_MS, ms) };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
components = ["rust-src"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "rusthello"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
montauk-rt = { path = "../montauk-rt" }
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
//! rusthello - Phase 1 Rust smoke test for MontaukOS.
|
||||||
|
//!
|
||||||
|
//! Exercises alloc collections, formatting, argv, a #[thread_local]
|
||||||
|
//! (PT_TLS + per-thread %fs blocks), threads, and the futex mutex.
|
||||||
|
//! `rusthello panic` / `rusthello panic-thread` check that a panic on
|
||||||
|
//! either thread takes the whole process down; `rusthello fault-thread`
|
||||||
|
//! checks the same for a page fault on a worker thread.
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![feature(thread_local)]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use alloc::collections::BTreeMap;
|
||||||
|
use alloc::format;
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::cell::Cell;
|
||||||
|
use core::time::Duration;
|
||||||
|
|
||||||
|
use montauk_rt::sync::Mutex;
|
||||||
|
use montauk_rt::{entry, println, thread, time};
|
||||||
|
|
||||||
|
entry!(main);
|
||||||
|
|
||||||
|
static mut FAILURES: u32 = 0;
|
||||||
|
|
||||||
|
fn check(ok: bool, name: &str) {
|
||||||
|
println!("{} {}", if ok { "PASS" } else { "FAIL" }, name);
|
||||||
|
if !ok {
|
||||||
|
unsafe { FAILURES += 1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[thread_local]
|
||||||
|
static TLS_COUNTER: Cell<u64> = Cell::new(7);
|
||||||
|
|
||||||
|
const THREADS: usize = 4;
|
||||||
|
const ITERS: u64 = 20_000;
|
||||||
|
|
||||||
|
fn main() -> i32 {
|
||||||
|
println!("rusthello: Rust on MontaukOS (no_std + alloc)");
|
||||||
|
|
||||||
|
let args: Vec<&str> = montauk_rt::env::args().collect();
|
||||||
|
println!("argv = {:?}", args);
|
||||||
|
match args.get(1).copied() {
|
||||||
|
Some("panic") => panic!("requested panic on the main thread"),
|
||||||
|
Some("fault-thread") => {
|
||||||
|
// A CPU fault (not a panic) on a worker must still kill the
|
||||||
|
// whole process, with the SIGSEGV exit code (267).
|
||||||
|
let h = thread::spawn(|| {
|
||||||
|
unsafe { core::ptr::write_volatile(8 as *mut u64, 0xdead) };
|
||||||
|
0
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let _ = h.join();
|
||||||
|
thread::yield_now();
|
||||||
|
println!("FAIL process survived a worker-thread fault");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Some("panic-thread") => {
|
||||||
|
let h = thread::spawn(|| panic!("requested panic on a worker thread")).unwrap();
|
||||||
|
let _ = h.join();
|
||||||
|
println!("FAIL process survived a worker-thread panic");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- alloc ---
|
||||||
|
let mut v: Vec<u64> = (1..=100).collect();
|
||||||
|
v.retain(|x| x % 3 == 0);
|
||||||
|
check(v.len() == 33 && v.iter().sum::<u64>() == 1683, "Vec collect/retain/sum");
|
||||||
|
|
||||||
|
let mut s = String::new();
|
||||||
|
for word in ["Montauk", "speaks", "Rust"] {
|
||||||
|
if !s.is_empty() {
|
||||||
|
s.push(' ');
|
||||||
|
}
|
||||||
|
s.push_str(word);
|
||||||
|
}
|
||||||
|
check(s == "Montauk speaks Rust", "String building");
|
||||||
|
check(format!("{:>6.2}|{:#x}|{:?}", 3.14159f64, 255, Some("x")) == " 3.14|0xff|Some(\"x\")",
|
||||||
|
"format! with floats, hex, Debug");
|
||||||
|
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
for (i, w) in s.split(' ').enumerate() {
|
||||||
|
map.insert(w, i);
|
||||||
|
}
|
||||||
|
check(map.get("Rust") == Some(&2) && map.len() == 3, "BTreeMap");
|
||||||
|
|
||||||
|
let big: Vec<u8> = alloc::vec![0xA5; 1 << 20];
|
||||||
|
check(big.iter().all(|&b| b == 0xA5), "1 MiB allocation");
|
||||||
|
|
||||||
|
#[repr(align(4096))]
|
||||||
|
struct PageAligned([u8; 64]);
|
||||||
|
let page = alloc::boxed::Box::new(PageAligned([1; 64]));
|
||||||
|
check((&*page as *const PageAligned as usize) % 4096 == 0 && page.0[63] == 1, "over-aligned Box (4096)");
|
||||||
|
|
||||||
|
// --- thread-local storage ---
|
||||||
|
check(TLS_COUNTER.get() == 7, "#[thread_local] initial value on main thread");
|
||||||
|
TLS_COUNTER.set(1000);
|
||||||
|
let tls_seen = thread::spawn(|| {
|
||||||
|
let initial = TLS_COUNTER.get();
|
||||||
|
TLS_COUNTER.set(55);
|
||||||
|
initial as i32
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.join();
|
||||||
|
check(tls_seen == Ok(7), "worker thread gets a fresh TLS block");
|
||||||
|
check(TLS_COUNTER.get() == 1000, "main thread TLS untouched by worker");
|
||||||
|
|
||||||
|
// --- threads + futex mutex ---
|
||||||
|
let counter = Arc::new(Mutex::new(0u64));
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
for t in 0..THREADS {
|
||||||
|
let counter = counter.clone();
|
||||||
|
handles.push(
|
||||||
|
thread::spawn(move || {
|
||||||
|
for i in 0..ITERS {
|
||||||
|
let mut g = counter.lock();
|
||||||
|
let v = *g;
|
||||||
|
if i % 512 == 0 {
|
||||||
|
thread::yield_now(); // widen the race window under the lock
|
||||||
|
}
|
||||||
|
*g = v + 1;
|
||||||
|
}
|
||||||
|
t as i32 + 10
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let codes: Vec<_> = handles.into_iter().map(|h| h.join()).collect();
|
||||||
|
check(codes.iter().enumerate().all(|(t, c)| *c == Ok(t as i32 + 10)), "thread exit codes via join");
|
||||||
|
check(*counter.lock() == THREADS as u64 * ITERS, "futex Mutex keeps the counter exact");
|
||||||
|
|
||||||
|
// --- time ---
|
||||||
|
let t0 = time::uptime_ms();
|
||||||
|
time::sleep(Duration::from_millis(25));
|
||||||
|
let dt = time::uptime_ms() - t0;
|
||||||
|
check((25..1000).contains(&dt), "sleep(25 ms)");
|
||||||
|
|
||||||
|
let failures = unsafe { FAILURES };
|
||||||
|
println!("rusthello: {}", if failures == 0 { "all passed" } else { "FAILURES" });
|
||||||
|
if failures == 0 { 0 } else { 1 }
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"arch": "x86_64",
|
||||||
|
"cpu": "x86-64",
|
||||||
|
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
||||||
|
"llvm-target": "x86_64-unknown-none-elf",
|
||||||
|
"os": "montauk",
|
||||||
|
"vendor": "unknown",
|
||||||
|
"target-pointer-width": 64,
|
||||||
|
"max-atomic-width": 64,
|
||||||
|
"executables": true,
|
||||||
|
"linker-flavor": "gnu-cc",
|
||||||
|
"linker": "x86_64-montauk-gcc",
|
||||||
|
"no-default-libraries": false,
|
||||||
|
"crt-objects-fallback": "false",
|
||||||
|
"relocation-model": "static",
|
||||||
|
"code-model": "small",
|
||||||
|
"position-independent-executables": false,
|
||||||
|
"static-position-independent-executables": false,
|
||||||
|
"relro-level": "off",
|
||||||
|
"disable-redzone": true,
|
||||||
|
"has-thread-local": true,
|
||||||
|
"tls-model": "local-exec",
|
||||||
|
"panic-strategy": "abort",
|
||||||
|
"plt-by-default": false,
|
||||||
|
"stack-probes": { "kind": "inline" },
|
||||||
|
"metadata": {
|
||||||
|
"description": "MontaukOS userspace (static ET_EXEC, Montauk libc)",
|
||||||
|
"host_tools": false,
|
||||||
|
"std": false,
|
||||||
|
"tier": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ static void print_exit_code(const int exitCode) {
|
|||||||
montauk::print(c);
|
montauk::print(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exitCode > 0 && exitCode <= 14) { // 0-14 - current range of status codes within StatusCodes enum
|
if (exitCode > 0 && exitCode <= montauk::MaxStatusCode) {
|
||||||
const char* statusText = montauk::GetStatusText((montauk::StatusCode)exitCode);
|
const char* statusText = montauk::GetStatusText((montauk::StatusCode)exitCode);
|
||||||
|
|
||||||
if (statusText != nullptr) {
|
if (statusText != nullptr) {
|
||||||
|
|||||||
Reference in new issue
Block a user