#include "types.h" #include "defs.h" #include "irq.h" #include "param.h" #include "memlayout.h" #include "mmu.h" #include "proc.h" #include "x86.h" #include "traps.h" #include "spinlock.h" #include "posix.h" uint *idt; extern addr_t vectors[]; struct spinlock tickslock; uint ticks; static void mkgate(uint *idt, uint n, addr_t kva, uint pl) { uint64 addr = (uint64) kva; n *= 4; idt[n+0] = (addr & 0xFFFF) | (KERNEL_CS << 16); idt[n+1] = (addr & 0xFFFF0000) | 0x8E00 | ((pl & 3) << 13); idt[n+2] = addr >> 32; idt[n+3] = 0; } void idtinit(void) { struct { ushort limit; uint64 base; } __attribute__((packed)) idtdesc; idtdesc.limit = PGSIZE - 1; idtdesc.base = (uint64)idt; asm volatile("lidt %0" : : "m" (idtdesc)); } void tvinit(void) { int n; idt = (uint*) kalloc(); memset(idt, 0, PGSIZE); for (n = 0; n < 256; n++) mkgate(idt, n, vectors[n], 0); mkgate(idt, T_SYSCALL, vectors[T_SYSCALL], 3); } uint uptime(void) { uint xticks; acquire(&tickslock); xticks = ticks; release(&tickslock); return xticks; } // Which signal a CPU fault corresponds to, so a process killed by a fault is // reported to wait() as died-by-signal with a sensible cause rather than a // blanket SIGKILL. static int trapsig(uint64 trapno) { switch(trapno){ case T_DIVIDE: return SIGFPE; case T_ILLOP: return SIGILL; case T_GPFLT: return SIGSEGV; case T_PGFLT: return SIGSEGV; case T_BRKPT: return SIGTRAP; case T_DEBUG: return SIGTRAP; default: return SIGSEGV; } } void trap(struct trapframe *tf) { kstack_check_canary("trap entry"); switch(tf->trapno){ case T_IRQ0 + IRQ_TIMER: if(cpunum() == 0){ acquire(&tickslock); ticks++; wakeup(&ticks); release(&tickslock); } // Charge the tick to whoever it interrupted. Every CPU does this, not just // CPU 0 -- the global clock advances once, but the *work* happened on // whichever processor was running that process. // // The split is by the interrupted privilege level, because that is the // only thing the tick knows about what it interrupted. It is sampling, not // measurement, which is what Linux does too and why these are ticks rather // than microseconds. // // **stime is therefore almost always zero here, and that is structural.** // MSR_SFMASK includes FL_IF (see syscallinit), so SYSCALL clears interrupts // and nothing re-enables them for the duration -- a timer tick cannot land // while a process is executing a syscall. The only ticks that ever reach // the `else` are ones that arrive in a nested trap. Charging real system // time needs TSC deltas closed and reopened around every sched(), which is // scheduler accounting rather than sampling, and is not written. if(myproc()){ if((tf->cs & 3) == DPL_USER) myproc()->utime++; else myproc()->stime++; } // Debug registers are per-CPU, and there is no general IPI here, so this // is how a watchpoint armed on one processor reaches the others: each // notices the table's generation has moved and reloads. One comparison // per tick when nothing has changed. See kernel/arch/x86_64/watch.c. watch_sync(); lapiceoi(); break; case T_DEBUG: // A hardware watchpoint fired. It is a *trap*, not a fault -- the store // has already happened -- so reporting and returning carries on with the // program none the wiser, which is what lets this be used on a machine // that is still running. if(watch_trap(tf)) break; // Not ours: a single-step, or a #DB from somewhere else. Falls through to // the unexpected-trap path rather than being swallowed. goto unexpected; case T_IRQ0 + IRQ_SPURIOUS: cprintf("cpu%d: spurious interrupt at %p:%p\n", cpunum(), tf->cs, tf->rip); lapiceoi(); break; default: // Every other IOAPIC pin goes through the registration table. The device // interrupts used to be listed here one case at a time, which cannot work // for anything found on the PCI bus: a driver does not know its line // until it reads the device's config space at probe time, so there is no // constant to write a case label for. See include/irq.h. if(tf->trapno >= T_IRQ0 && tf->trapno < T_IRQ0 + NIRQ){ irq_dispatch(tf->trapno - T_IRQ0); lapiceoi(); break; } unexpected: if(myproc() == 0 || (tf->cs&3) == 0){ // The rip is named where it can be. This is a kernel address, so // ksym_lookup() has something to say about it -- and the whole reason // docs/architecture/debugging.md puts D0 first is that "rip ffff8000001294c2" // costs an objdump session and "rip in dma_alloc+0x8c" does not. char where[72]; ksym_fmt(where, sizeof(where), tf->rip); cprintf("unexpected trap %d from cpu %d rip %p <%s> (cr2=0x%p) err=0x%p\n", tf->trapno, cpunum(), tf->rip, where, rcr2(), tf->err); if (myproc()) cprintf("proc id: %d\n", myproc()->pid); if (tf->trapno == T_GPFLT) { uint64 *iretframe = (uint64*)tf->rsp; cprintf("iretq frame @ %p: rip=%p cs=%p rflags=%p rsp=%p ss=%p\n", iretframe, iretframe[0], iretframe[1], iretframe[2], iretframe[3], iretframe[4]); if (myproc()) cprintf("this tf=%p myproc()->tf=%p myproc()->kstack=%p (tf-kstack=%p)\n", tf, myproc()->tf, myproc()->kstack, (uint64)tf - (uint64)myproc()->kstack); } panic("trap"); } cprintf("pid %d %s: trap %d err %d on cpu %d " "rip 0x%p addr 0x%p--kill proc\n", myproc()->pid, myproc()->name, tf->trapno, tf->err, cpunum(), tf->rip, rcr2()); // General-purpose register dump. This used to be followed by ~25 lines // that walked musl's mallocng group/meta structures out of rax/rdi and // dumped the user stack -- scaffolding for the boot-crash investigation // recorded in docs/history/kernel.md section 1. That // investigation is closed and the underlying kernel bugs are fixed, so // the allocator-specific part is gone; the registers stay, since they are // useful for any fault. // The full picture, for the two faults that mean something went wrong // *elsewhere* rather than here: a general protection fault, and a jump to // an address no code is ever linked at. // // The second case earns its own dump because of what it catches. A process // executing at rip 0 did not branch there; it was handed a zero and // followed it, and the registers say which one. The same dump on a // *faulting address* below a page caught `addr 0xffffffffffffffea` in // schinitd -- which is -22, which is -EINVAL: a call returned an error and // the caller used it as a pointer. Neither of those is visible from the // one-line report. if (tf->trapno == T_GPFLT || tf->rip < PGSIZE || rcr2() + PGSIZE < PGSIZE * 2) { cprintf(" rax=%p rbx=%p rcx=%p rdx=%p rsi=%p rdi=%p rbp=%p rsp=%p\n", tf->rax, tf->rbx, tf->rcx, tf->rdx, tf->rsi, tf->rdi, tf->rbp, tf->rsp); cprintf(" cs=%p ss=%p rflags=%p r11=%p tf=%p kstack=%p sz=%p\n", tf->cs, tf->ss, tf->rflags, tf->r11, tf, myproc()->kstack, myproc()->sz); // The top of the user stack -- whatever a `ret` was about to use. cprintf(" ustack:"); for (int i = -2; i < 4; i++) { addr_t v; if (fetchaddr(tf->rsp + i * 8, &v) == 0) cprintf(" %p", v); else cprintf(" ????"); } cprintf("\n"); } myproc()->killed = 1; if(myproc()->killsig == 0) myproc()->killsig = trapsig(tf->trapno); } if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER) exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL)); if(myproc() && myproc()->state == RUNNING && tf->trapno == T_IRQ0+IRQ_TIMER) yield(); if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER) exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL)); kstack_check_canary("trap exit"); }