#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: trap.c,v 1.74 2022/05/28 21:14:56 andvar Exp $");
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/userret.h>
#include <sys/kauth.h>
#include <sys/errno.h>
#include <uvm/uvm_extern.h>
#include <machine/cpu.h>
#include <machine/pcb.h>
#include <machine/pmap.h>
#include <machine/machdep.h>
#include <machine/intr.h>
#include <machine/thunk.h>
#include "opt_kgdb.h"
#ifdef KGDB
#include <sys/kgdb.h>
#endif
#ifndef NSIG
#define NSIG 64
#endif
void setup_signal_handlers(void);
void stop_all_signal_handlers(void);
static sigfunc_t pagefault;
static sigfunc_t illegal_instruction;
static sigfunc_t alarm;
static sigfunc_t sigio;
static sigfunc_t pass_on;
void kgdb_kernel_trap(int signo, vaddr_t pc, vaddr_t va, ucontext_t *ctx);
static char sig_stack[SIGSTKSZ];
static stack_t sigstk;
ucontext_t jump_ucp;
sigfunc_t *sig_funcs[NSIG];
extern bool pmap_fault(pmap_t pmap, vaddr_t va, vm_prot_t *atype);
void setup_clock_intr(void);
extern void clock_intr(void *priv);
extern int clock_running;
void *alrm_ih;
struct intr_handler {
int (*func)(void *);
void *arg;
};
#define SIGIO_MAX_HANDLERS 8
static struct intr_handler sigio_intr_handler[SIGIO_MAX_HANDLERS];
int astpending = 0;
void
startlwp(void *arg)
{
}
void
setup_signal_handlers(void)
{
int i;
sigstk.ss_sp = sig_stack;
sigstk.ss_size = SIGSTKSZ;
sigstk.ss_flags = 0;
if (thunk_sigaltstack(&sigstk, 0) < 0)
panic("can't set alternate stacksize: %d",
thunk_geterrno());
for (i = 0; i < NSIG; i++)
sig_funcs[i] = NULL;
signal_intr_establish(SIGILL, illegal_instruction);
signal_intr_establish(SIGTRAP, pass_on);
signal_intr_establish(SIGFPE, pass_on);
signal_intr_establish(SIGBUS, pagefault);
signal_intr_establish(SIGSEGV, pagefault);
signal_intr_establish(SIGALRM, alarm);
signal_intr_establish(SIGIO, sigio);
}
void
stop_all_signal_handlers(void)
{
int i;
for (i = 0; i < NSIG; i++)
if (sig_funcs[i])
thunk_sigblock(i);
}
void
setup_clock_intr(void)
{
alrm_ih = softint_establish(SOFTINT_CLOCK,
clock_intr, NULL);
}
static void
ast(struct lwp *l)
{
struct pcb *pcb;
curcpu()->ci_data.cpu_ntrap++;
do {
astpending = 0;
mi_userret(l);
} while (astpending);
#if 0
if (l->l_pflag & LP_OWEUPC) {
l->l_pflag &= ~LP_OWEUPC;
ADDUPROF(l);
}
#endif
KASSERT(l == curlwp); KASSERT(l);
pcb = lwp_getpcb(l); KASSERT(pcb);
}
void
userret(struct lwp *l)
{
mi_userret(l);
ast(l);
}
#ifdef DEBUG
static void
print_mem_access_siginfo(int sig, siginfo_t *info, void *ctx,
vaddr_t pc, vaddr_t va, vaddr_t sp)
{
#if 0
thunk_printf_debug("SIGSEGV or SIGBUS!\n");
thunk_printf_debug("\tsi_signo = %d\n", info->si_signo);
thunk_printf_debug("\tsi_errno = %d\n", info->si_errno);
thunk_printf_debug("\tsi_code = %d\n", info->si_code);
if (info->si_code == SEGV_MAPERR)
thunk_printf_debug("\t\tSEGV_MAPERR\n");
if (info->si_code == SEGV_ACCERR)
thunk_printf_debug("\t\tSEGV_ACCERR\n");
if (info->si_code == BUS_ADRALN)
thunk_printf_debug("\t\tBUS_ADRALN\n");
if (info->si_code == BUS_ADRERR)
thunk_printf_debug("\t\tBUS_ADRERR\n");
if (info->si_code == BUS_OBJERR)
thunk_printf_debug("\t\tBUS_OBJERR\n");
thunk_printf_debug("\tsi_addr = %p\n", info->si_addr);
thunk_printf_debug("\tsi_trap = %d\n", info->si_trap);
#endif
#if 0
thunk_printf("memaccess error, pc %p, va %p, sp %p\n",
(void *) pc, (void *) va, (void *) sp);
#endif
}
static void
print_illegal_instruction_siginfo(int sig, siginfo_t *info, void *ctx,
vaddr_t pc, vaddr_t va, vaddr_t sp)
{
#if 0
thunk_printf("SIGILL!\n");
thunk_printf("\tsi_signo = %d\n", info->si_signo);
thunk_printf("\tsi_errno = %d\n", info->si_errno);
thunk_printf("\tsi_code = %d\n", info->si_code);
if (info->si_code == ILL_ILLOPC)
thunk_printf("\t\tIllegal opcode");
if (info->si_code == ILL_ILLOPN)
thunk_printf("\t\tIllegal operand");
if (info->si_code == ILL_ILLADR)
thunk_printf("\t\tIllegal addressing mode");
if (info->si_code == ILL_ILLTRP)
thunk_printf("\t\tIllegal trap");
if (info->si_code == ILL_PRVOPC)
thunk_printf("\t\tPrivileged opcode");
if (info->si_code == ILL_PRVREG)
thunk_printf("\t\tPrivileged register");
if (info->si_code == ILL_COPROC)
thunk_printf("\t\tCoprocessor error");
if (info->si_code == ILL_BADSTK)
thunk_printf("\t\tInternal stack error");
thunk_printf("\tsi_addr = %p\n", info->si_addr);
thunk_printf("\tsi_trap = %d\n", info->si_trap);
thunk_printf("%p : ", info->si_addr);
for (int i = 0; i < 10; i++)
thunk_printf("%02x ", *((uint8_t *) info->si_addr + i));
thunk_printf("\n");
#endif
#if 0
thunk_printf("sigill\n");
#endif
}
#else
#define print_mem_access_siginfo(s, i, c, p, v, sp) {}
#define print_illegal_instruction_siginfo(s, i, c, p, v, sp) {}
#endif
static void
handle_signal(int sig, siginfo_t *info, void *ctx)
{
sigfunc_t *f;
ucontext_t *ucp = ctx;
struct lwp *l;
struct pcb *pcb;
vaddr_t va, sp, pc, fp;
long from_userland;
if (sig == SIGBUS || sig == SIGSEGV || sig == SIGILL) {
if (info->si_code == SI_NOINFO)
panic("received signal %d with no info",
info->si_signo);
}
f = sig_funcs[sig];
KASSERT(f);
va = (vaddr_t) info->si_addr;
va = trunc_page(va);
pc = md_get_pc(ctx);
#ifdef KGDB
if (sig == SIGTRAP) {
from_userland = 0;
if (pc < kmem_user_end)
from_userland = 1;
if (!from_userland) {
kgdb_kernel_trap(sig, pc, va, ucp);
return;
}
}
#endif
sp = md_get_sp(ctx);
if (sig == SIGBUS || sig == SIGSEGV)
print_mem_access_siginfo(sig, info, ctx, pc, va, sp);
if (sig == SIGILL)
print_illegal_instruction_siginfo(sig, info, ctx, pc, va, sp);
l = curlwp; KASSERT(l);
pcb = lwp_getpcb(l); KASSERT(pcb);
from_userland = 0;
if ((sp < (vaddr_t) pcb->sys_stack) ||
(sp > (vaddr_t) pcb->sys_stack_top)) {
sp = (vaddr_t) pcb->sys_stack_top - sizeof(register_t);
fp = (vaddr_t) &pcb->pcb_userret_ucp;
if (pc < kmem_user_end)
from_userland = 1;
} else {
fp = sp - sizeof(ucontext_t) - sizeof(register_t);
sp = fp - sizeof(register_t);
if (fp - 4*PAGE_SIZE < (vaddr_t) pcb->sys_stack)
panic("%s: out of system stack", __func__);
}
memcpy((void *) fp, ucp, sizeof(ucontext_t));
memcpy(&jump_ucp, ucp, sizeof(ucontext_t));
jump_ucp.uc_stack.ss_sp = (void *) pcb->sys_stack;
jump_ucp.uc_stack.ss_size = sp - (vaddr_t) pcb->sys_stack;
jump_ucp.uc_link = (void *) fp;
if (sig == SIGIO)
thunk_sigfillset(&jump_ucp.uc_sigmask);
else
thunk_sigemptyset(&jump_ucp.uc_sigmask);
jump_ucp.uc_flags = _UC_STACK | _UC_CPU | _UC_SIGMASK;
thunk_makecontext(&jump_ucp,
(void (*)(void)) f,
4, info, (void *) from_userland, (void *) pc, (void *) va);
thunk_setcontext(&jump_ucp);
}
void
signal_intr_establish(int sig, sigfunc_t f)
{
static struct sigaction sa;
sig_funcs[sig] = f;
sa.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
sa.sa_sigaction = (void *) handle_signal;
thunk_sigfillset(&sa.sa_mask);
if (thunk_sigaction(sig, &sa, NULL) == -1)
panic("couldn't register SIG%d handler: %d", sig,
thunk_geterrno());
}
static void
pagefault(siginfo_t *info, vaddr_t from_userland, vaddr_t pc, vaddr_t va)
{
struct proc *p;
struct lwp *l;
struct pcb *pcb;
struct vmspace *vm;
struct vm_map *vm_map;
vm_prot_t atype;
void *onfault;
int from_kernel, lwp_errno, error;
ksiginfo_t ksi;
l = curlwp; KASSERT(l);
pcb = lwp_getpcb(l);
p = l->l_proc;
vm = p->p_vmspace;
lwp_errno = thunk_geterrno();
vm_map = &vm->vm_map;
from_kernel = (pc >= kmem_k_start) && (!from_userland);
if (from_kernel && (va >= VM_MIN_KERNEL_ADDRESS))
vm_map = kernel_map;
#if 0
thunk_printf("%s: l %p, pcb %p\n", __func__, l, pcb);
thunk_printf("\tpc %p, va %p\n", (void *) pc, (void *) va);
#endif
if (pmap_fault(vm_map->pmap, va, &atype)) {
goto out_quick;
}
#if 0
thunk_printf("%s: l %p, pcb %p, ", __func__, l, pcb);
thunk_printf("pc %p, va %p ", (void *) pc, (void *) va);
thunk_printf("derived atype %d\n", atype);
#endif
thunk_printf_debug("pmap fault couldn't handle it! : "
"derived atype %d\n", atype);
onfault = pcb->pcb_onfault;
pcb->pcb_onfault = NULL;
error = uvm_fault(vm_map, va, atype);
pcb->pcb_onfault = onfault;
if (vm_map != kernel_map) {
if (error == 0)
uvm_grow(l->l_proc, va);
}
if (error == EACCES)
error = EFAULT;
if (error == 0) {
goto out;
}
if (onfault) {
panic("%s: can't call onfault yet\n", __func__);
goto out;
}
if (from_kernel) {
thunk_printf("%s: uvm fault %d, pc %p, va %p, from_kernel %d\n",
__func__, error, (void *) pc, (void *) va, from_kernel);
panic("Unhandled page fault in kernel mode");
}
thunk_printf_debug("%s: uvm fault %d, pc %p, va %p, from_kernel %d\n",
__func__, error, (void *) pc, (void *) va, from_kernel);
thunk_printf_debug("giving signal to userland\n");
KASSERT(from_userland);
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = info->si_signo;
ksi.ksi_trap = 0;
ksi.ksi_code = (error == EPERM) ? SEGV_ACCERR : SEGV_MAPERR;
ksi.ksi_addr = (void *) va;
if (error == ENOMEM) {
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);
ksi.ksi_signo = SIGKILL;
}
#if 0
p->p_emul->e_trapsignal(l, &ksi);
#else
trapsignal(l, &ksi);
#endif
out:
if (from_userland)
userret(l);
out_quick:
thunk_seterrno(lwp_errno);
pcb->pcb_errno = lwp_errno;
}
static void
illegal_instruction(siginfo_t *info, vaddr_t from_userland, vaddr_t pc, vaddr_t va)
{
struct lwp *l = curlwp;
struct pcb *pcb = lwp_getpcb(l);
ucontext_t *ucp = &pcb->pcb_userret_ucp;
ksiginfo_t ksi;
KASSERT(from_userland);
if (md_syscall_check_opcode(ucp)) {
syscall();
userret(l);
return;
}
thunk_printf("%s: giving SIGILL (TRAP)\n", __func__);
KASSERT(from_userland);
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = SIGILL;
ksi.ksi_trap = 0;
ksi.ksi_errno = 0;
ksi.ksi_code = 0;
ksi.ksi_addr = (void *) md_get_pc(ucp);
#if 0
p->p_emul->e_trapsignal(l, &ksi);
#else
trapsignal(l, &ksi);
#endif
userret(l);
}
static void
pass_on(siginfo_t *info, vaddr_t from_userland, vaddr_t pc, vaddr_t va)
{
struct lwp *l = curlwp;
struct pcb *pcb = lwp_getpcb(l);
ucontext_t *ucp = &pcb->pcb_userret_ucp;
ksiginfo_t ksi;
KASSERT(from_userland);
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = info->si_signo;
ksi.ksi_trap = 0;
ksi.ksi_errno = info->si_errno;
ksi.ksi_code = info->si_code;
ksi.ksi_addr = (void *) md_get_pc(ucp);
trapsignal(l, &ksi);
userret(l);
}
static void
alarm(siginfo_t *info, vaddr_t from_userland, vaddr_t pc, vaddr_t va)
{
struct lwp *l = curlwp;
struct pcb *pcb = lwp_getpcb(l); KASSERT(pcb);
if (!clock_running)
return;
softint_schedule(alrm_ih);
KASSERT(l == curlwp);
if (from_userland)
userret(l);
}
static void
sigio(siginfo_t *info, vaddr_t from_userland, vaddr_t pc, vaddr_t va)
{
struct lwp *l = curlwp;
struct pcb *pcb = lwp_getpcb(l); KASSERT(pcb);
struct intr_handler *sih;
unsigned int n, pass;
for (pass = 0; pass < 2; pass++) {
for (n = 0; n < SIGIO_MAX_HANDLERS; n++) {
sih = &sigio_intr_handler[n];
if (sih->func)
sih->func(sih->arg);
}
}
KASSERT(l == curlwp);
if (from_userland)
userret(l);
}
void *
sigio_intr_establish(int (*func)(void *), void *arg)
{
struct intr_handler *sih;
unsigned int n;
for (n = 0; n < SIGIO_MAX_HANDLERS; n++) {
sih = &sigio_intr_handler[n];
if (sih->func == NULL) {
sih->func = func;
sih->arg = arg;
return sih;
}
}
panic("increase SIGIO_MAX_HANDLERS");
}