#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: trap.c,v 1.310 2025/06/20 17:02:18 bouyer Exp $");
#include "opt_ddb.h"
#include "opt_kgdb.h"
#include "opt_lockdebug.h"
#include "opt_multiprocessor.h"
#include "opt_xen.h"
#include "opt_dtrace.h"
#include "opt_compat_netbsd.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/acct.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/ras.h>
#include <sys/signal.h>
#include <sys/syscall.h>
#include <sys/cpu.h>
#include <sys/ucontext.h>
#include <uvm/uvm_extern.h>
#include <machine/cpufunc.h>
#include <machine/psl.h>
#include <machine/reg.h>
#include <machine/trap.h>
#include <machine/userret.h>
#include <machine/db_machdep.h>
#include <machine/pmap_private.h>
#include "mca.h"
#if NMCA > 0
#include <machine/mca_machdep.h>
#endif
#include <x86/dbregs.h>
#include <x86/nmi.h>
#include "isa.h"
#include <sys/kgdb.h>
#ifdef KDTRACE_HOOKS
#include <sys/dtrace_bsd.h>
dtrace_trap_func_t dtrace_trap_func = NULL;
dtrace_doubletrap_func_t dtrace_doubletrap_func = NULL;
#endif
void trap(struct trapframe *);
void trap_tss(struct i386tss *, int, int);
void trap_return_fault_return(struct trapframe *) __dead;
#ifndef XENPV
int ss_shadow(struct trapframe *tf);
#endif
const char * const trap_type[] = {
"privileged instruction fault",
"breakpoint trap",
"arithmetic trap",
"asynchronous system trap",
"protection fault",
"trace trap",
"page fault",
"alignment fault",
"integer divide fault",
"non-maskable interrupt",
"overflow trap",
"bounds check fault",
"FPU not available fault",
"double fault",
"FPU operand fetch fault",
"invalid TSS fault",
"segment not present fault",
"stack fault",
"machine check fault",
"SSE FP exception",
"reserved trap",
};
int trap_types = __arraycount(trap_type);
#ifdef DEBUG
int trapdebug = 0;
#endif
#define IDTVEC(name) __CONCAT(X, name)
#ifdef TRAP_SIGDEBUG
static void sigdebug(const struct trapframe *, const ksiginfo_t *, int);
#define SIGDEBUG(a, b, c) sigdebug(a, b, c)
#else
#define SIGDEBUG(a, b, c)
#endif
void
trap_tss(struct i386tss *tss, int trapno, int code)
{
struct trapframe tf;
tf.tf_gs = tss->tss_gs;
tf.tf_fs = tss->tss_fs;
tf.tf_es = tss->__tss_es;
tf.tf_ds = tss->__tss_ds;
tf.tf_edi = tss->__tss_edi;
tf.tf_esi = tss->__tss_esi;
tf.tf_ebp = tss->tss_ebp;
tf.tf_ebx = tss->__tss_ebx;
tf.tf_edx = tss->__tss_edx;
tf.tf_ecx = tss->__tss_ecx;
tf.tf_eax = tss->__tss_eax;
tf.tf_trapno = trapno;
tf.tf_err = code | TC_TSS;
tf.tf_eip = tss->__tss_eip;
tf.tf_cs = tss->__tss_cs;
tf.tf_eflags = tss->__tss_eflags;
tf.tf_esp = tss->tss_esp;
tf.tf_ss = tss->__tss_ss;
trap(&tf);
}
static void *
onfault_handler(const struct pcb *pcb, const struct trapframe *tf)
{
struct onfault_table {
uintptr_t start;
uintptr_t end;
void *handler;
};
extern const struct onfault_table onfault_table[];
const struct onfault_table *p;
uintptr_t pc;
if (pcb->pcb_onfault != NULL) {
return pcb->pcb_onfault;
}
pc = tf->tf_eip;
for (p = onfault_table; p->start; p++) {
if (p->start <= pc && pc < p->end) {
return p->handler;
}
}
return NULL;
}
static void
trap_print(const struct trapframe *frame, const lwp_t *l)
{
const int type = frame->tf_trapno;
if (frame->tf_trapno < trap_types) {
printf("fatal %s", trap_type[type]);
} else {
printf("unknown trap %d", type);
}
printf(" in %s mode\n", (type & T_USER) ? "user" : "supervisor");
printf("trap type %d code %#x eip %#x cs %#x eflags %#x cr2 %#lx "
"ilevel %#x esp %#x\n",
type, frame->tf_err, frame->tf_eip, frame->tf_cs, frame->tf_eflags,
(long)rcr2(), curcpu()->ci_ilevel, frame->tf_esp);
printf("curlwp %p pid %d lid %d lowest kstack %p\n",
l, l->l_proc->p_pid, l->l_lid, KSTACK_LOWEST_ADDR(l));
}
#ifndef XENPV
int
ss_shadow(struct trapframe *tf)
{
struct gate_descriptor *gd;
struct cpu_info *ci;
struct idt_vec *iv;
idt_descriptor_t *idt;
uintptr_t eip, func;
size_t i;
eip = tf->tf_eip;
ci = curcpu();
iv = idt_vec_ref(&ci->ci_idtvec);
idt = iv->iv_idt;
for (i = 0; i < 256; i++) {
gd = &idt[i];
func = (gd->gd_hioffset << 16) | gd->gd_looffset;
if (eip == func)
return 1;
}
return 0;
}
#endif
void
trap(struct trapframe *frame)
{
struct lwp *l = curlwp;
struct proc *p;
struct pcb *pcb;
extern char kcopy_fault[], return_address_fault[];
struct trapframe *vframe;
ksiginfo_t ksi;
void *onfault;
int type, error = 0;
uint32_t cr2;
bool pfail;
if (__predict_true(l != NULL)) {
pcb = lwp_getpcb(l);
p = l->l_proc;
} else {
pcb = NULL;
p = NULL;
}
type = frame->tf_trapno;
#ifdef DEBUG
if (trapdebug) {
trap_print(frame, l);
}
#endif
if (type != T_NMI && !KERNELMODE(frame->tf_cs)) {
type |= T_USER;
l->l_md.md_regs = frame;
pcb->pcb_cr2 = 0;
}
#ifdef KDTRACE_HOOKS
if ((type == T_PROTFLT || type == T_PAGEFLT) &&
dtrace_trap_func != NULL) {
if ((*dtrace_trap_func)(frame, type)) {
return;
}
}
#endif
switch (type) {
default:
we_re_toast:
trap_print(frame, l);
if (kdb_trap(type, 0, frame))
return;
if (kgdb_trap(type, frame))
return;
if (type == T_BPTFLT && kgdb_disconnected()) {
printf("kgdb: ignored %s\n", trap_type[type]);
return;
}
panic("trap");
case T_PROTFLT:
case T_SEGNPFLT:
case T_ALIGNFLT:
case T_STKFLT:
case T_TSSFLT:
if (p == NULL)
goto we_re_toast;
onfault = onfault_handler(pcb, frame);
if (onfault != NULL) {
copyefault:
error = EFAULT;
copyfault:
frame->tf_eip = (uintptr_t)onfault;
frame->tf_eax = error;
return;
}
kernelfault:
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = SIGSEGV;
ksi.ksi_code = SEGV_ACCERR;
ksi.ksi_trap = type;
switch (*(u_char *)frame->tf_eip) {
case 0xcf:
vframe = (void *)((int *)frame + 3);
if (KERNELMODE(vframe->tf_cs))
goto we_re_toast;
memmove(vframe, frame,
offsetof(struct trapframe, tf_eip));
ksi.ksi_addr = (void *)vframe->tf_eip;
break;
case 0x8e:
switch (*(uint32_t *)frame->tf_eip) {
case 0x8e242c8e:
case 0x0424648e:
case 0x0824448e:
case 0x0c245c8e:
break;
default:
goto we_re_toast;
}
vframe = (void *)(&frame->tf_eflags + 1);
if (KERNELMODE(vframe->tf_cs))
goto we_re_toast;
break;
default:
goto we_re_toast;
}
if (!pmap_exec_fixup(&p->p_vmspace->vm_map, vframe, pcb)) {
l->l_md.md_regs = vframe;
SIGDEBUG(vframe, &ksi, error);
(*p->p_emul->e_trapsignal)(l, &ksi);
}
trap_return_fault_return(vframe);
case T_PROTFLT|T_USER:
#if defined(COMPAT_10) || defined(COMPAT_NOMID)
{
#define LCALLSZ 7
if (frame->tf_eip < VM_MAXUSER_ADDRESS - LCALLSZ &&
x86_cpu_is_lcall((const void *)frame->tf_eip) == 0) {
frame->tf_eip += LCALLSZ;
p->p_md.md_syscall(frame);
goto out;
}
}
#endif
case T_TSSFLT|T_USER:
case T_SEGNPFLT|T_USER:
case T_STKFLT|T_USER:
case T_ALIGNFLT|T_USER:
KSI_INIT_TRAP(&ksi);
ksi.ksi_addr = (void *)rcr2();
switch (type) {
case T_SEGNPFLT|T_USER:
case T_STKFLT|T_USER:
ksi.ksi_signo = SIGBUS;
ksi.ksi_code = BUS_ADRERR;
break;
case T_TSSFLT|T_USER:
ksi.ksi_signo = SIGBUS;
ksi.ksi_code = BUS_OBJERR;
break;
case T_ALIGNFLT|T_USER:
ksi.ksi_signo = SIGBUS;
ksi.ksi_code = BUS_ADRALN;
break;
case T_PROTFLT|T_USER:
if (pmap_exec_fixup(&p->p_vmspace->vm_map, frame, pcb)){
goto out;
}
ksi.ksi_signo = SIGSEGV;
ksi.ksi_code = SEGV_ACCERR;
break;
default:
KASSERT(0);
break;
}
goto trapsignal;
case T_PRIVINFLT|T_USER:
case T_FPOPFLT|T_USER:
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = SIGILL;
ksi.ksi_addr = (void *) frame->tf_eip;
switch (type) {
case T_PRIVINFLT|T_USER:
ksi.ksi_code = ILL_PRVOPC;
break;
case T_FPOPFLT|T_USER:
ksi.ksi_code = ILL_COPROC;
break;
default:
ksi.ksi_code = 0;
break;
}
goto trapsignal;
case T_ASTFLT|T_USER:
if (l->l_pflag & LP_OWEUPC) {
l->l_pflag &= ~LP_OWEUPC;
ADDUPROF(l);
}
goto out;
case T_BOUND|T_USER:
case T_OFLOW|T_USER:
case T_DIVIDE|T_USER:
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = SIGFPE;
ksi.ksi_addr = (void *)frame->tf_eip;
switch (type) {
case T_BOUND|T_USER:
ksi.ksi_code = FPE_FLTSUB;
break;
case T_OFLOW|T_USER:
ksi.ksi_code = FPE_INTOVF;
break;
case T_DIVIDE|T_USER:
ksi.ksi_code = FPE_INTDIV;
break;
default:
ksi.ksi_code = 0;
break;
}
goto trapsignal;
case T_PAGEFLT:
if (__predict_false(l == NULL))
goto we_re_toast;
onfault = pcb->pcb_onfault;
if (onfault == return_address_fault) {
goto copyefault;
}
if (cpu_intr_p() || (l->l_pflag & LP_INTR) != 0) {
goto we_re_toast;
}
cr2 = rcr2();
if (frame->tf_err & PGEX_I) {
if (cr2 > VM_MIN_ADDRESS && cr2 <= VM_MAXUSER_ADDRESS) {
printf("prevented execution of %p (SMEP)\n",
(void *)cr2);
goto we_re_toast;
}
}
if ((frame->tf_err & PGEX_P) &&
cr2 < VM_MAXUSER_ADDRESS) {
if (onfault_handler(pcb, frame) == NULL) {
printf("prevented access to %p (SMAP)\n",
(void *)cr2);
goto we_re_toast;
}
}
goto faultcommon;
case T_PAGEFLT|T_USER: {
register vaddr_t va;
register struct vmspace *vm;
register struct vm_map *map;
vm_prot_t ftype;
extern struct vm_map *kernel_map;
cr2 = rcr2();
faultcommon:
vm = p->p_vmspace;
if (__predict_false(vm == NULL)) {
goto we_re_toast;
}
pcb->pcb_cr2 = cr2;
va = trunc_page((vaddr_t)cr2);
if (type == T_PAGEFLT && va >= VM_MIN_KERNEL_ADDRESS)
map = kernel_map;
else
map = &vm->vm_map;
if (frame->tf_err & PGEX_W)
ftype = VM_PROT_WRITE;
else if (frame->tf_err & PGEX_I)
ftype = VM_PROT_EXECUTE;
else
ftype = VM_PROT_READ;
#ifdef DIAGNOSTIC
if (map == kernel_map && va == 0) {
printf("trap: bad kernel access at %lx\n", va);
goto we_re_toast;
}
#endif
#ifdef XENPV
KASSERT(x86_read_psl() == 0);
#endif
onfault = pcb->pcb_onfault;
pcb->pcb_onfault = NULL;
error = uvm_fault(map, va, ftype);
pcb->pcb_onfault = onfault;
if (error == 0) {
if (map != kernel_map && (void *)va >= vm->vm_maxsaddr)
uvm_grow(p, va);
pfail = false;
while (type == T_PAGEFLT) {
kpreempt_disable();
if (curcpu()->ci_want_pmapload) {
onfault = onfault_handler(pcb, frame);
if (onfault != kcopy_fault) {
pmap_load();
}
}
x86_disable_intr();
l->l_nopreempt--;
if (l->l_nopreempt > 0 || !l->l_dopreempt ||
pfail) {
return;
}
x86_enable_intr();
pfail = kpreempt(0);
}
goto out;
}
if (type == T_PAGEFLT) {
onfault = onfault_handler(pcb, frame);
if (onfault != NULL)
goto copyfault;
printf("uvm_fault(%p, %#lx, %d) -> %#x\n",
map, va, ftype, error);
goto kernelfault;
}
KSI_INIT_TRAP(&ksi);
ksi.ksi_trap = type & ~T_USER;
ksi.ksi_addr = (void *)cr2;
switch (error) {
case EINVAL:
ksi.ksi_signo = SIGBUS;
ksi.ksi_code = BUS_ADRERR;
break;
case EACCES:
ksi.ksi_signo = SIGSEGV;
ksi.ksi_code = SEGV_ACCERR;
error = EFAULT;
break;
case ENOMEM:
ksi.ksi_signo = SIGKILL;
printf("UVM: pid %d.%d (%s), uid %d killed: "
"out of swap\n", p->p_pid, l->l_lid, p->p_comm,
l->l_cred ? kauth_cred_geteuid(l->l_cred) : -1);
break;
default:
ksi.ksi_signo = SIGSEGV;
ksi.ksi_code = SEGV_MAPERR;
break;
}
SIGDEBUG(frame, &ksi, error);
(*p->p_emul->e_trapsignal)(l, &ksi);
break;
}
case T_TRCTRAP:
if (x86_dbregs_user_trap())
break;
goto we_re_toast;
case T_BPTFLT|T_USER:
case T_TRCTRAP|T_USER:
if (p->p_raslist == NULL ||
(ras_lookup(p, (void *)frame->tf_eip) == (void *)-1)) {
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = SIGTRAP;
ksi.ksi_trap = type & ~T_USER;
if (x86_dbregs_user_trap()) {
x86_dbregs_store_dr6(l);
ksi.ksi_code = TRAP_DBREG;
} else if (type == (T_BPTFLT|T_USER))
ksi.ksi_code = TRAP_BRKPT;
else
ksi.ksi_code = TRAP_TRACE;
ksi.ksi_addr = (void *)frame->tf_eip;
SIGDEBUG(frame, &ksi, error);
(*p->p_emul->e_trapsignal)(l, &ksi);
}
break;
case T_NMI:
if (nmi_dispatch(frame))
return;
if (kgdb_trap(type, frame))
return;
if (kdb_trap(type, 0, frame))
return;
#if NMCA > 0
mca_nmi();
#endif
x86_nmi();
}
if ((type & T_USER) == 0)
return;
out:
userret(l);
return;
trapsignal:
ksi.ksi_trap = type & ~T_USER;
SIGDEBUG(frame, &ksi, error);
(*p->p_emul->e_trapsignal)(l, &ksi);
userret(l);
}
void
startlwp(void *arg)
{
ucontext_t *uc = arg;
lwp_t *l = curlwp;
int error __diagused;
error = cpu_setmcontext(l, &uc->uc_mcontext, uc->uc_flags);
KASSERT(error == 0);
kmem_free(uc, sizeof(ucontext_t));
userret(l);
}
#ifdef TRAP_SIGDEBUG
static void
frame_dump(const struct trapframe *tf, const struct pcb *pcb)
{
uint64_t fsd, gsd;
printf("trapframe %p\n", tf);
printf("eip 0x%08x esp 0x%08x efl 0x%08x\n",
tf->tf_eip, tf->tf_esp, tf->tf_eflags);
printf("edi 0x%08x esi 0x%08x edx 0x%08x\n",
tf->tf_edi, tf->tf_esi, tf->tf_edx);
printf("ecx 0x%08x\n",
tf->tf_ecx);
printf("ebp 0x%08x ebx 0x%08x eax 0x%08x\n",
tf->tf_ebp, tf->tf_ebx, tf->tf_eax);
printf("cs 0x%04x ds 0x%04x es 0x%04x "
"fs 0x%04x gs 0x%04x ss 0x%04x\n",
tf->tf_cs & 0xffff, tf->tf_ds & 0xffff, tf->tf_es & 0xffff,
tf->tf_fs & 0xffff, tf->tf_gs & 0xffff, tf->tf_ss & 0xffff);
memcpy(&fsd, &pcb->pcb_fsd, sizeof(fsd));
memcpy(&gsd, &pcb->pcb_gsd, sizeof(gsd));
printf("fsbase 0x%016llx gsbase 0x%016llx\n", fsd, gsd);
printf("\n");
hexdump(printf, "Stack dump", tf, 256);
}
static void
sigdebug(const struct trapframe *tf, const ksiginfo_t *ksi, int e)
{
struct lwp *l = curlwp;
struct proc *p = l->l_proc;
printf("pid %d.%d (%s): signal %d code=%d (trap %x) "
"@eip %#x addr %#x error=%d\n",
p->p_pid, l->l_lid, p->p_comm, ksi->ksi_signo, ksi->ksi_code,
tf->tf_trapno, tf->tf_eip, rcr2(), e);
frame_dump(tf, lwp_getpcb(l));
}
#endif