190 lines
7.5 KiB
C
190 lines
7.5 KiB
C
#include "isr.h"
|
|
#include "console.h"
|
|
#include "pic.h"
|
|
#include "pit.h"
|
|
#include "keyboard.h"
|
|
#include "sched.h"
|
|
#include "ports.h"
|
|
#include "signal.h"
|
|
#include "mmap.h"
|
|
|
|
static const char* exceptionNames[32] = {
|
|
"divide by zero",
|
|
"debug",
|
|
"non maskable interrupt",
|
|
"breakpoint",
|
|
"overflow",
|
|
"bound range exceeded",
|
|
"invalid opcode",
|
|
"device not available",
|
|
"double fault",
|
|
"coprocessor segment overrun",
|
|
"invalid tss",
|
|
"segment not present",
|
|
"stack segment fault",
|
|
"general protection fault",
|
|
"page fault",
|
|
"reserved",
|
|
"x87 floating point",
|
|
"alignment check",
|
|
"machine check",
|
|
"simd floating point",
|
|
"virtualization",
|
|
"control protection",
|
|
"reserved", "reserved", "reserved", "reserved",
|
|
"reserved", "reserved", "reserved", "reserved",
|
|
"hypervisor injection", "vmm communication"
|
|
};
|
|
|
|
static void panicOnException(Registers* regs)
|
|
{
|
|
uint64 faultAddress = 0;
|
|
if (regs->intNo == 14)
|
|
__asm__ volatile("mov %%cr2, %0" : "=r"(faultAddress));
|
|
|
|
kprintf("\n[PANIC] exception %lu (%s)\n", regs->intNo, exceptionNames[regs->intNo]);
|
|
kprintf(" errCode=%lx rip=%lx cs=%lx rflags=%lx\n",
|
|
regs->errCode, regs->rip, regs->cs, regs->rflags);
|
|
if (regs->intNo == 14)
|
|
kprintf(" cr2=%lx (faulting address)\n", faultAddress);
|
|
kprintf(" rax=%lx rbx=%lx rcx=%lx rdx=%lx\n", regs->rax, regs->rbx, regs->rcx, regs->rdx);
|
|
kprintf(" system halted\n");
|
|
|
|
for (;;)
|
|
__asm__ volatile("cli; hlt");
|
|
}
|
|
|
|
/* Runs at every single return-to-ring3 point this kernel has (a completed
|
|
syscall, a timer reschedule, any other IRQ resuming whoever it
|
|
interrupted) -- the one choke point isrDispatch funnels its result
|
|
through below, regardless of which of those three paths produced it.
|
|
`rsp` is whatever Registers* the caller is about to resume; if that
|
|
context is not ring 3 (a kernel thread, e.g. workerA/shell -- see
|
|
sched.h's userCodeSelector|3 vs kernelCodeSelector), signals are not a
|
|
concept that applies to it at all and this is a no-op. Otherwise
|
|
currentThread() is guaranteed to be exactly the owner of `rsp` (schedule()
|
|
already updated currentIndex before returning it; the syscall/other-IRQ
|
|
paths never changed currentIndex away from whoever trapped in), so
|
|
sigConsumePending always inspects the right thread's pending/blocked
|
|
state. A default-disposition Terminate (or job-control Stop, sigTstp's
|
|
default -- see signal.h) signal cannot be handled here directly -- it
|
|
needs a NEW thread's rsp, which only schedule() can produce -- so the
|
|
loop below reuses the exact same "flip state, then reschedule" pattern
|
|
sysExit/sysSleep already use (threadExit for Terminate, threadStop for
|
|
Stop), and loops again in case the freshly chosen thread also has a
|
|
signal of its own waiting (e.g. two threads both made Runnable by the
|
|
same timer tick and both signaled). */
|
|
static uint64 deliverPendingSignals(uint64 rsp)
|
|
{
|
|
for (;;) {
|
|
Registers* regs = (Registers*)rsp;
|
|
if ((regs->cs & 3) != 3)
|
|
return rsp;
|
|
|
|
Thread* thread = currentThread();
|
|
int signum = 0;
|
|
SigOutcome outcome = sigConsumePending(thread, regs, &signum);
|
|
|
|
if (outcome == sigOutcomeNone)
|
|
return rsp;
|
|
|
|
if (outcome == sigOutcomeHandler)
|
|
return (uint64)regs;
|
|
|
|
if (outcome == sigOutcomeStop) {
|
|
kprintf("[sig] pid=%u '%s' stopped by signal %d\n",
|
|
thread->id, thread->name, signum);
|
|
threadStop(thread->id);
|
|
rsp = schedule(rsp);
|
|
continue;
|
|
}
|
|
|
|
kprintf("[sig] pid=%u '%s' terminated by uncaught signal %d\n",
|
|
thread->id, thread->name, signum);
|
|
threadExit(128u + (uint64)signum);
|
|
rsp = schedule(rsp);
|
|
}
|
|
}
|
|
|
|
/* Attempts to resolve a ring-3 #PF through mmap.c's own demand-paging
|
|
machinery: cr2 is the real faulting address (same register the panic
|
|
path above already reads), errCode bit 1 (0x2) is the CPU's own
|
|
"this was a write access" flag -- the exact distinction
|
|
mmapFaultHandler needs to refuse a write against a read-only mapping
|
|
instead of silently allowing it. Returns whatever mmapFaultHandler
|
|
itself returns: true means a real frame is now mapped and the faulting
|
|
instruction can simply be retried (the CPU never advanced rip past a
|
|
#PF to begin with); false means this address is not a mapping this
|
|
kernel's mmap.c even knows about, or the access violates its own prot
|
|
bits -- the caller (isrDispatch below) turns that into a real SIGSEGV
|
|
rather than the kernel-wide panic every OTHER exception still gets. */
|
|
static bool tryResolveUserPageFault(Registers* regs)
|
|
{
|
|
/* errCode bit 4 (I/D) is the CPU's own "this fault was an instruction
|
|
fetch" flag -- only ever set once paging.h's pageNoExecute is real
|
|
hardware enforcement (IA32_EFER.NXE, see paging.c's
|
|
pagingNxSupported/boot.S), meaning a PRESENT page's own NX bit
|
|
refused execution. This can never be something mmapFaultHandler's
|
|
demand-paging logic fixes: there is no lazy version of "make this
|
|
page executable" the way there is for a not-yet-faulted-in page, and
|
|
mmapFaultHandler's own "pagingResolve != 0 -> already resolved, just
|
|
retry" shortcut (mmap.c) would otherwise see the very same present
|
|
page, report success, and send the CPU straight back to retry the
|
|
identical faulting instruction fetch -- an infinite fault loop that
|
|
hangs the whole kernel rather than the one offending thread. Refuse
|
|
outright instead; isrDispatch below turns this into a real SIGSEGV
|
|
exactly like any other refusal, whether the address belongs to an
|
|
mmap Vma, or (mmapFind would never even find it) a stack/brk page
|
|
mapped NX by kmain.c/syscall.c/jobctl.c/sched.c. */
|
|
if (regs->errCode & 0x10)
|
|
return false;
|
|
|
|
uint64 faultAddress;
|
|
__asm__ volatile("mov %%cr2, %0" : "=r"(faultAddress));
|
|
bool forWrite = (regs->errCode & 0x2) != 0;
|
|
return mmapFaultHandler(currentThread(), faultAddress, forWrite);
|
|
}
|
|
|
|
uint64 isrDispatch(Registers* regs)
|
|
{
|
|
/* A ring-3 (user-mode) page fault gets one real chance at recovery
|
|
before falling through to the unconditional panic below: an
|
|
instruction inside SOME OTHER thread's buggy user program should
|
|
never be able to take the whole machine down with it, exactly like
|
|
a real kernel's own per-process fault isolation -- this kernel's
|
|
flat, single-shared-address-space design (see paging.c's own file
|
|
comments) cannot isolate a bad ACCESS the way separate page tables
|
|
would, but it can still isolate the CONSEQUENCE, by killing only the
|
|
one thread that caused it. A kernel-mode fault (ring 0, cs&3==0 --
|
|
this kernel's own bug, not a user program's) still panics
|
|
unconditionally below, exactly as before this existed. */
|
|
if (regs->intNo == 14 && (regs->cs & 3) == 3) {
|
|
if (!tryResolveUserPageFault(regs))
|
|
sigRaiseInfo(currentThreadId(), sigSegv, siCodeKernel, 0, 0);
|
|
return deliverPendingSignals((uint64)regs);
|
|
}
|
|
|
|
if (regs->intNo < 32)
|
|
panicOnException(regs);
|
|
|
|
uint64 nextRsp;
|
|
|
|
if (regs->intNo == 128) {
|
|
nextRsp = syscallDispatch(regs);
|
|
} else {
|
|
uint8 irq = (uint8)(regs->intNo - 32);
|
|
nextRsp = (uint64)regs;
|
|
|
|
if (irq == 0) {
|
|
pitTick();
|
|
nextRsp = schedule((uint64)regs);
|
|
} else if (irq == 1) {
|
|
keyboardHandler();
|
|
}
|
|
|
|
picSendEoi(irq);
|
|
}
|
|
|
|
return deliverPendingSignals(nextRsp);
|
|
}
|