- Anonymous memory can be reserved without being backed: large mappings, MAP_NORESERVE and PROT_NONE are recorded and each page appears, zeroed, on first touch -- from trap() for the program, from uva2ka() for the kernel on its behalf. WebKit reserves 4 GB at start and touches a sliver; backing it all failed with ENOMEM and Epiphany aborted. - r8169: the 8168G generation and later (the laptop's 8168H, XID 541) are taken from the firmware's out-of-band mode, get their PLL powered, the receive gate opened, and their PHY reached through the OCP window. The PHY's power-down bit is cleared; the old code kept it while restarting autonegotiation. Untested on the hardware so far. - The network thread follows each device's link into lwIP. The link was read once at attach, 0.02 s into boot; real autonegotiation takes seconds, so on hardware the interface stayed down for good. - Twelve terminals, switched with Ctrl+Alt+F1..F12 by default: the keys X uses, so the console and the desktop agree. AltGr+F-key is gone from the defaults (chvt -c altgr+F brings it back). - SO_PASSCRED on Unix sockets, with SCM_CREDENTIALS delivered by recvmsg. WebKit sets it on its IPC sockets and aborted when refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
262 lines
10 KiB
C
262 lines
10 KiB
C
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/irq.h"
|
|
#include "core/param.h"
|
|
#include "arch/memlayout.h"
|
|
#include "arch/mmu.h"
|
|
#include "core/proc.h"
|
|
#include "arch/x86.h"
|
|
#include "arch/traps.h"
|
|
#include "core/spinlock.h"
|
|
#include "uapi/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);
|
|
// NMI on its own stack, IST slot 1; see nmistack in ioperm.c.
|
|
idt[T_NMI * 4 + 1] |= 1;
|
|
}
|
|
|
|
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)
|
|
{
|
|
// Another processor has panicked and wants this one stopped: a processor
|
|
// left running carries on with a kernel that is already dead, and on a
|
|
// laptop that showed as the report flashing up and being drawn over, and
|
|
// as fans that stayed loud until somebody held the power button. First, and
|
|
// before the canary check, because this stack may be what is broken.
|
|
if(tf->trapno == T_NMI && panic_stop)
|
|
for(;;){
|
|
cli();
|
|
hlt();
|
|
}
|
|
|
|
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/core/irq.h.
|
|
if(tf->trapno >= T_IRQ0 && tf->trapno < T_IRQ0 + NIRQ){
|
|
irq_dispatch(tf->trapno - T_IRQ0);
|
|
lapiceoi();
|
|
break;
|
|
}
|
|
|
|
unexpected:
|
|
// A user page fault on anonymous memory that is reserved but not backed
|
|
// yet: the page is made and the instruction runs again. See vm_fault().
|
|
if(tf->trapno == T_PGFLT && myproc() && (tf->cs&3) == DPL_USER &&
|
|
!(tf->err & 1) && vm_fault(rcr2()) == 0)
|
|
break;
|
|
if(myproc() == 0 || (tf->cs&3) == 0){
|
|
// A kernel-mode trap ends in panic() a dozen lines down, so the console
|
|
// can be put into its panic mode *now* -- and it has to be, because the
|
|
// fault may have happened inside the console itself.
|
|
//
|
|
// cprintf() takes cons.lock. If the faulting code was already holding
|
|
// it -- anything under console output, which on a verbose boot is most
|
|
// of the kernel -- then this report deadlocks on a lock its own call
|
|
// chain owns, acquire() panics with "acquire", and the trap that
|
|
// actually happened is never printed. That is a bad trade at the best
|
|
// of times and a worse one here: the whole point of the report is to
|
|
// name the fault.
|
|
cons_panic_mode();
|
|
// 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));
|
|
|
|
// The timer tick is when preemption is *considered*, not when it happens.
|
|
//
|
|
// This used to yield unconditionally, so a process alone on a CPU paid a
|
|
// full context switch -- switchuvm, a %cr3 reload, a flushed TLB -- a
|
|
// hundred times a second only to be handed straight back to itself.
|
|
// sched_slice_expired() asks whether it has actually had its share; see the
|
|
// scheduler in kernel/proc/proc.c.
|
|
if(myproc() && myproc()->state == RUNNING && tf->trapno == T_IRQ0+IRQ_TIMER
|
|
&& sched_slice_expired())
|
|
yield();
|
|
|
|
if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER)
|
|
exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL));
|
|
|
|
kstack_check_canary("trap exit");
|
|
}
|