feat: port gcc to MontaukOS
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#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;
|
||||
|
||||
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
|
||||
uint64_t mappedPages = 0;
|
||||
|
||||
+43
-3
@@ -78,8 +78,10 @@ namespace Hal {
|
||||
return frame;
|
||||
}
|
||||
|
||||
template<size_t i>
|
||||
__attribute__((interrupt)) void ExceptionHandler(System::PanicFrame* frame)
|
||||
// Shared fatal-exception path: kill the faulting user process (with a
|
||||
// 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);
|
||||
bool fromUser = (cs & 3) == 3;
|
||||
@@ -165,6 +167,41 @@ namespace Hal {
|
||||
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) {
|
||||
asm("lidt %0" : : "m"(idtr));
|
||||
}
|
||||
@@ -208,7 +245,10 @@ namespace Hal {
|
||||
// Use IST1 for NMI (2) and Double Fault (8) so they get a
|
||||
// known-good stack even if the kernel stack has overflowed.
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1103,6 +1103,48 @@ namespace Sched {
|
||||
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() {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
int slot = cpu->currentSlot;
|
||||
|
||||
@@ -19,9 +19,14 @@ namespace Sched {
|
||||
static constexpr int MaxProcesses = 256;
|
||||
static constexpr uint64_t StackPages = 4; // 16 KiB kernel stack per thread
|
||||
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 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 uint32_t UserReadDirSlots = 64; // rotating scratch pages for SYS_READDIR
|
||||
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).
|
||||
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.
|
||||
// Tears down the entire process (kills all sibling threads, frees address space).
|
||||
void ExitProcess();
|
||||
|
||||
Reference in New Issue
Block a user