#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); } // 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/plan/debugger.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. if (tf->trapno == T_GPFLT) { 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); } 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"); }