/* * crt1.c * Minimal C runtime startup for MontaukOS userspace programs * Copyright (c) 2026 Daniel Hammer * * This startup shim is intentionally small: it fetches the raw * command-line buffer from the kernel, tokenizes it into argc/argv, * calls main(), then exits with main()'s return code. */ static inline long _sys1(long nr, long a1) { long ret; __asm__ volatile( "mov %[a1], %%rdi\n\t" "syscall" : "=a"(ret) : "a"(nr), [a1] "r"(a1) : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); return ret; } static inline long _sys2(long nr, long a1, long a2) { long ret; __asm__ volatile( "mov %[a1], %%rdi\n\t" "mov %[a2], %%rsi\n\t" "syscall" : "=a"(ret) : "a"(nr), [a1] "r"(a1), [a2] "r"(a2) : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); return ret; } #define SYS_EXIT 0 #define SYS_GETARGS 25 #define SYS_GETEXECPATH 151 extern int main(int argc, char** argv); /* Global constructor/destructor tables. The linker provides these boundary symbols whenever .preinit_array/.init_array/.fini_array sections exist; the weak references make them all optional so plain C programs keep working unchanged. Frame registration for C++ exceptions (crtbegin's frame_dummy) also arrives via .init_array. */ extern void (*__preinit_array_start[])(void) __attribute__((weak)); extern void (*__preinit_array_end[])(void) __attribute__((weak)); extern void (*__init_array_start[])(void) __attribute__((weak)); extern void (*__init_array_end[])(void) __attribute__((weak)); extern void (*__fini_array_start[])(void) __attribute__((weak)); extern void (*__fini_array_end[])(void) __attribute__((weak)); extern int atexit(void (*fn)(void)); extern void exit(int status); static void _run_init_arrays(void) { for (void (**f)(void) = __preinit_array_start; f < __preinit_array_end; f++) { (*f)(); } for (void (**f)(void) = __init_array_start; f < __init_array_end; f++) { (*f)(); } } /* Destructors run in reverse registration order, hooked through atexit so they also run when the program calls exit() directly. */ static void _run_fini_array(void) { for (void (**f)(void) = __fini_array_end; f > __fini_array_start; f--) { (*(f - 1))(); } } void _start(void) { /* Static: 4 KiB args + 256 argv slots would crowd a 32 KiB stack. */ static char argbuf[4096]; static char pathbuf[256]; static char* argv[256]; int len = (int)_sys2(SYS_GETARGS, (long)argbuf, (long)sizeof(argbuf)); int argc = 0; /* Real argv[0]: tools like the GCC driver relocate their install prefix from it, so "prog" placeholders send them to garbage paths. Fall back to "prog" on kernels without the syscall. */ int plen = (int)_sys2(SYS_GETEXECPATH, (long)pathbuf, (long)sizeof(pathbuf)); argv[argc++] = (plen > 0) ? pathbuf : (char*)"prog"; if (len > 0) { if (len > (int)sizeof(argbuf) - 1) { len = (int)sizeof(argbuf) - 1; } argbuf[len] = '\0'; /* Split on spaces, honouring '...' and "..." so an argument can contain them: `git commit -m "two words"` has to reach main() as two arguments, not three. The quotes are removed, and the token is rewritten in place -- the unquoted form is never longer than the quoted one, so it always fits. Quotes are the only metacharacter: there is no escaping and no substitution, which is deliberate. The kernel hands over one flat string (SYS_GETARGS) and this is a tokenizer, not a shell. */ char* p = argbuf; while (*p != '\0' && argc < 255) { while (*p == ' ') { p++; } if (*p == '\0') { break; } char* out = p; argv[argc++] = out; char quote = 0; while (*p != '\0' && (quote != 0 || *p != ' ')) { if (quote == 0 && (*p == '"' || *p == '\'')) { quote = *p++; } else if (quote != 0 && *p == quote) { quote = 0; p++; } else { *out++ = *p++; } } /* Step over the separator before terminating: the write below lands on the byte p is about to leave behind. */ if (*p != '\0') { p++; } *out = '\0'; } } argv[argc] = 0; if (__fini_array_start != __fini_array_end) { atexit(_run_fini_array); } _run_init_arrays(); /* exit() (not raw SYS_EXIT) so the atexit chain - including the fini-array walker registered above - runs on return from main. */ exit(main(argc, argv)); __builtin_unreachable(); }