diff --git a/MAINTAINERS b/MAINTAINERS index b43344fa98..9814580975 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3186,6 +3186,7 @@ R: Kyle Evans S: Maintained F: bsd-user/ F: configs/targets/*-bsd-user.mak +F: tests/vm/*bsd T: git https://github.com/qemu-bsd-user/qemu-bsd-user bsd-user-rebase-3.1 Linux user diff --git a/bsd-user/arm/signal.c b/bsd-user/arm/signal.c index 1478f008d1..2b1dd745d1 100644 --- a/bsd-user/arm/signal.c +++ b/bsd-user/arm/signal.c @@ -59,19 +59,31 @@ abi_long set_sigtramp_args(CPUARMState *env, int sig, return 0; } +static abi_long get_vfpcontext(CPUARMState *env, abi_ulong frame_addr, + struct target_sigframe *frame) +{ + /* see sendsig and get_vfpcontext in sys/arm/arm/exec_machdep.c */ + target_mcontext_vfp_t *vfp = &frame->sf_vfp; + target_mcontext_t *mcp = &frame->sf_uc.uc_mcontext; + + /* Assumes that mcp and vfp are locked */ + for (int i = 0; i < 32; i++) { + vfp->mcv_reg[i] = tswap64(*aa32_vfp_dreg(env, i)); + } + vfp->mcv_fpscr = tswap32(vfp_get_fpscr(env)); + mcp->mc_vfp_size = tswap32(sizeof(*vfp)); + mcp->mc_vfp_ptr = tswap32(frame_addr + ((uintptr_t)vfp - (uintptr_t)frame)); + return 0; +} + /* - * Compare to arm/arm/machdep.c get_mcontext() + * Compare to arm/arm/exec_machdep.c get_mcontext() * Assumes that the memory is locked if mcp points to user memory. */ abi_long get_mcontext(CPUARMState *env, target_mcontext_t *mcp, int flags) { - int err = 0; uint32_t *gr = mcp->__gregs; - if (mcp->mc_vfp_size != 0 && mcp->mc_vfp_size != sizeof(target_mcontext_vfp_t)) { - return -TARGET_EINVAL; - } - gr[TARGET_REG_CPSR] = tswap32(cpsr_read(env)); if (flags & TARGET_MC_GET_CLEAR_RET) { gr[TARGET_REG_R0] = 0; @@ -97,17 +109,30 @@ abi_long get_mcontext(CPUARMState *env, target_mcontext_t *mcp, int flags) gr[TARGET_REG_LR] = tswap32(env->regs[14]); gr[TARGET_REG_PC] = tswap32(env->regs[15]); - if (mcp->mc_vfp_size != 0 && mcp->mc_vfp_ptr != 0) { - /* see get_vfpcontext in sys/arm/arm/exec_machdep.c */ - target_mcontext_vfp_t *vfp; - vfp = lock_user(VERIFY_WRITE, mcp->mc_vfp_ptr, sizeof(*vfp), 0); - for (int i = 0; i < 32; i++) { - vfp->mcv_reg[i] = tswap64(*aa32_vfp_dreg(env, i)); - } - vfp->mcv_fpscr = tswap32(vfp_get_fpscr(env)); - unlock_user(vfp, mcp->mc_vfp_ptr, sizeof(*vfp)); - } - return err; + /* + * FreeBSD's get_mcontext doesn't save VFP info, but sets the pointer and + * size to zero. Applications that need the VFP state use + * sysarch(ARM_GET_VFPSTATE) and are expected to adjust mcontext after that. + */ + mcp->mc_vfp_size = 0; + mcp->mc_vfp_ptr = 0; + memset(&mcp->mc_spare, 0, sizeof(mcp->mc_spare)); + + return 0; +} + +/* + * Compare to arm/arm/exec_machdep.c sendsig() + * Assumes that the memory is locked if frame points to user memory. + */ +abi_long setup_sigframe_arch(CPUARMState *env, abi_ulong frame_addr, + struct target_sigframe *frame, int flags) +{ + target_mcontext_t *mcp = &frame->sf_uc.uc_mcontext; + + get_mcontext(env, mcp, flags); + get_vfpcontext(env, frame_addr, frame); + return 0; } /* Compare to arm/arm/exec_machdep.c set_mcontext() */ diff --git a/bsd-user/arm/target_arch_cpu.h b/bsd-user/arm/target_arch_cpu.h index c675419c30..b087db48fa 100644 --- a/bsd-user/arm/target_arch_cpu.h +++ b/bsd-user/arm/target_arch_cpu.h @@ -21,6 +21,7 @@ #define _TARGET_ARCH_CPU_H_ #include "target_arch.h" +#include "signal-common.h" #define TARGET_DEFAULT_CPU_MODEL "any" @@ -38,8 +39,7 @@ static inline void target_cpu_init(CPUARMState *env, static inline void target_cpu_loop(CPUARMState *env) { - int trapnr; - target_siginfo_t info; + int trapnr, si_signo, si_code; unsigned int n; CPUState *cs = env_cpu(env); @@ -50,33 +50,22 @@ static inline void target_cpu_loop(CPUARMState *env) process_queued_cpu_work(cs); switch (trapnr) { case EXCP_UDEF: - { - /* See arm/arm/undefined.c undefinedinstruction(); */ - info.si_addr = env->regs[15]; - - /* illegal instruction */ - info.si_signo = TARGET_SIGILL; - info.si_errno = 0; - info.si_code = TARGET_ILL_ILLOPC; - queue_signal(env, info.si_signo, &info); - - /* TODO: What about instruction emulation? */ - } + case EXCP_NOCP: + case EXCP_INVSTATE: + /* + * See arm/arm/undefined.c undefinedinstruction(); + * + * A number of details aren't emulated (they likely don't matter): + * o Misaligned PC generates ILL_ILLADR (these can't come from qemu) + * o Thumb-2 instructions generate ILLADR + * o Both modes implement coprocessor instructions, which we don't + * do here. FreeBSD just implements them for the VFP coprocessor + * and special kernel breakpoints, trace points, dtrace, etc. + */ + force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC, env->regs[15]); break; case EXCP_SWI: - case EXCP_BKPT: { - /* - * system call - * See arm/arm/trap.c cpu_fetch_syscall_args() - */ - if (trapnr == EXCP_BKPT) { - if (env->thumb) { - env->regs[15] += 2; - } else { - env->regs[15] += 4; - } - } n = env->regs[7]; if (bsd_type == target_freebsd) { int ret; @@ -84,7 +73,7 @@ static inline void target_cpu_loop(CPUARMState *env) int32_t syscall_nr = n; int32_t arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8; - /* See arm/arm/trap.c cpu_fetch_syscall_args() */ + /* See arm/arm/syscall.c cpu_fetch_syscall_args() */ if (syscall_nr == TARGET_FREEBSD_NR_syscall) { syscall_nr = env->regs[0]; arg1 = env->regs[1]; @@ -160,32 +149,52 @@ static inline void target_cpu_loop(CPUARMState *env) /* just indicate that signals should be handled asap */ break; case EXCP_PREFETCH_ABORT: - /* See arm/arm/trap.c prefetch_abort_handler() */ case EXCP_DATA_ABORT: - /* See arm/arm/trap.c data_abort_handler() */ - info.si_signo = TARGET_SIGSEGV; - info.si_errno = 0; - /* XXX: check env->error_code */ - info.si_code = 0; - info.si_addr = env->exception.vaddress; - queue_signal(env, info.si_signo, &info); + /* + * See arm/arm/trap-v6.c prefetch_abort_handler() and + * data_abort_handler() + * + * However, FreeBSD maps these to a generic value and then uses that + * to maybe fault in pages in vm/vm_fault.c:vm_fault_trap(). I + * believe that the indirection maps the same as Linux, but haven't + * chased down every single possible indirection. + */ + + /* For user-only we don't set TTBCR_EAE, so look at the FSR. */ + switch (env->exception.fsr & 0x1f) { + case 0x1: /* Alignment */ + si_signo = TARGET_SIGBUS; + si_code = TARGET_BUS_ADRALN; + break; + case 0x3: /* Access flag fault, level 1 */ + case 0x6: /* Access flag fault, level 2 */ + case 0x9: /* Domain fault, level 1 */ + case 0xb: /* Domain fault, level 2 */ + case 0xd: /* Permission fault, level 1 */ + case 0xf: /* Permission fault, level 2 */ + si_signo = TARGET_SIGSEGV; + si_code = TARGET_SEGV_ACCERR; + break; + case 0x5: /* Translation fault, level 1 */ + case 0x7: /* Translation fault, level 2 */ + si_signo = TARGET_SIGSEGV; + si_code = TARGET_SEGV_MAPERR; + break; + default: + g_assert_not_reached(); + } + force_sig_fault(si_signo, si_code, env->exception.vaddress); break; case EXCP_DEBUG: - { - - info.si_signo = TARGET_SIGTRAP; - info.si_errno = 0; - info.si_code = TARGET_TRAP_BRKPT; - info.si_addr = env->exception.vaddress; - queue_signal(env, info.si_signo, &info); - } - break; - case EXCP_ATOMIC: - cpu_exec_step_atomic(cs); + case EXCP_BKPT: + force_sig_fault(TARGET_SIGTRAP, TARGET_TRAP_BRKPT, env->regs[15]); break; case EXCP_YIELD: /* nothing to do here for user-mode, just resume guest code */ break; + case EXCP_ATOMIC: + cpu_exec_step_atomic(cs); + break; default: fprintf(stderr, "qemu: unhandled CPU exception 0x%x - aborting\n", trapnr); @@ -204,7 +213,7 @@ static inline void target_cpu_clone_regs(CPUARMState *env, target_ulong newsp) env->regs[0] = 0; } -static inline void target_cpu_reset(CPUArchState *cpu) +static inline void target_cpu_reset(CPUArchState *env) { } diff --git a/bsd-user/freebsd/target_os_siginfo.h b/bsd-user/freebsd/target_os_siginfo.h index 84944faa4d..d50a3034a8 100644 --- a/bsd-user/freebsd/target_os_siginfo.h +++ b/bsd-user/freebsd/target_os_siginfo.h @@ -71,11 +71,24 @@ typedef struct target_siginfo { int32_t _mqd; } _mesgp; - /* SIGPOLL */ + /* SIGPOLL -- Not really genreated in FreeBSD ??? */ struct { int _band; /* POLL_IN, POLL_OUT, POLL_MSG */ } _poll; + struct { + int _mqd; + } _mesgq; + + struct { + /* + * Syscall number for signals delivered as a result of system calls + * denied by Capsicum. + */ + int _syscall; + } _capsicum; + + /* Spare for future growth */ struct { abi_long __spare1__; int32_t __spare2_[7]; diff --git a/bsd-user/freebsd/target_os_signal.h b/bsd-user/freebsd/target_os_signal.h index 3ed454e086..43700d08f7 100644 --- a/bsd-user/freebsd/target_os_signal.h +++ b/bsd-user/freebsd/target_os_signal.h @@ -4,6 +4,9 @@ #include "target_os_siginfo.h" #include "target_arch_signal.h" +abi_long setup_sigframe_arch(CPUArchState *env, abi_ulong frame_addr, + struct target_sigframe *frame, int flags); + /* Compare to sys/signal.h */ #define TARGET_SIGHUP 1 /* hangup */ #define TARGET_SIGINT 2 /* interrupt */ diff --git a/bsd-user/freebsd/target_os_ucontext.h b/bsd-user/freebsd/target_os_ucontext.h index 41b28b2c15..b196b1c629 100644 --- a/bsd-user/freebsd/target_os_ucontext.h +++ b/bsd-user/freebsd/target_os_ucontext.h @@ -36,9 +36,9 @@ abi_long set_sigtramp_args(CPUArchState *env, int sig, struct target_sigframe *frame, abi_ulong frame_addr, struct target_sigaction *ka); -abi_long get_mcontext(CPUArchState *regs, target_mcontext_t *mcp, int flags); -abi_long set_mcontext(CPUArchState *regs, target_mcontext_t *mcp, int srflag); -abi_long get_ucontext_sigreturn(CPUArchState *regs, abi_ulong target_sf, +abi_long get_mcontext(CPUArchState *env, target_mcontext_t *mcp, int flags); +abi_long set_mcontext(CPUArchState *env, target_mcontext_t *mcp, int srflag); +abi_long get_ucontext_sigreturn(CPUArchState *env, abi_ulong target_sf, abi_ulong *target_uc); #endif /* TARGET_OS_UCONTEXT_H */ diff --git a/bsd-user/host/arm/host-signal.h b/bsd-user/host/arm/host-signal.h new file mode 100644 index 0000000000..56679bd699 --- /dev/null +++ b/bsd-user/host/arm/host-signal.h @@ -0,0 +1,35 @@ +/* + * host-signal.h: signal info dependent on the host architecture + * + * Copyright (c) 2021 Warner Losh + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ARM_HOST_SIGNAL_H +#define ARM_HOST_SIGNAL_H + +#include + +static inline uintptr_t host_signal_pc(ucontext_t *uc) +{ + return uc->uc_mcontext.__gregs[_REG_PC]; +} + +static inline void host_signal_set_pc(ucontext_t *uc, uintptr_t pc) +{ + uc->uc_mcontext.__gregs[_REG_PC] = pc; +} + +static inline bool host_signal_write(siginfo_t *info, ucontext_t *uc) +{ + /* + * In the FSR, bit 11 is WnR. FreeBSD returns this as part of the + * si_info.si_trapno. + */ + uint32_t fsr = info->si_trapno; + + return extract32(fsr, 11, 1); +} + +#endif diff --git a/bsd-user/host/i386/host-signal.h b/bsd-user/host/i386/host-signal.h new file mode 100644 index 0000000000..169e61b154 --- /dev/null +++ b/bsd-user/host/i386/host-signal.h @@ -0,0 +1,37 @@ +/* + * host-signal.h: signal info dependent on the host architecture + * + * Copyright (c) 2021 Warner Losh + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef I386_HOST_SIGNAL_H +#define I386_HOST_SIGNAL_H + +#include +#include +#include +#include + +static inline uintptr_t host_signal_pc(ucontext_t *uc) +{ + return uc->uc_mcontext.mc_eip; +} + +static inline void host_signal_set_pc(ucontext_t *uc, uintptr_t pc) +{ + uc->uc_mcontext.mc_eip = pc; +} + +static inline bool host_signal_write(siginfo_t *info, ucontext_t *uc) +{ + /* + * Look in sys/i386/i386/trap.c. NOTE: mc_err == tr_err due to type punning + * between a trapframe and mcontext on FreeBSD/i386. + */ + return uc->uc_mcontext.mc_trapno == T_PAGEFLT && + uc->uc_mcontext.mc_err & PGEX_W; +} + +#endif diff --git a/bsd-user/host/x86_64/host-signal.h b/bsd-user/host/x86_64/host-signal.h new file mode 100644 index 0000000000..47ca19f881 --- /dev/null +++ b/bsd-user/host/x86_64/host-signal.h @@ -0,0 +1,37 @@ +/* + * host-signal.h: signal info dependent on the host architecture + * + * Copyright (c) 2021 Warner Losh + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef X86_64_HOST_SIGNAL_H +#define X86_64_HOST_SIGNAL_H + +#include +#include +#include +#include + +static inline uintptr_t host_signal_pc(ucontext_t *uc) +{ + return uc->uc_mcontext.mc_rip; +} + +static inline void host_signal_set_pc(ucontext_t *uc, uintptr_t pc) +{ + uc->uc_mcontext.mc_rip = pc; +} + +static inline bool host_signal_write(siginfo_t *info, ucontext_t *uc) +{ + /* + * Look in sys/amd64/amd64/trap.c. NOTE: mc_err == tr_err due to type + * punning between a trapframe and mcontext on FreeBSD/amd64. + */ + return uc->uc_mcontext.mc_trapno == T_PAGEFLT && + uc->uc_mcontext.mc_err & PGEX_W; +} + +#endif diff --git a/bsd-user/i386/signal.c b/bsd-user/i386/signal.c index 2939d32400..5dd975ce56 100644 --- a/bsd-user/i386/signal.c +++ b/bsd-user/i386/signal.c @@ -32,6 +32,19 @@ abi_long set_sigtramp_args(CPUX86State *env, int sig, return 0; } +/* + * Compare to i386/i386/exec_machdep.c sendsig() + * Assumes that the memory is locked if frame points to user memory. + */ +abi_long setup_sigframe_arch(CPUX86State *env, abi_ulong frame_addr, + struct target_sigframe *frame, int flags) +{ + target_mcontext_t *mcp = &frame->sf_uc.uc_mcontext; + + get_mcontext(env, mcp, flags); + return 0; +} + /* Compare to i386/i386/machdep.c get_mcontext() */ abi_long get_mcontext(CPUX86State *regs, target_mcontext_t *mcp, int flags) { diff --git a/bsd-user/i386/target_arch_cpu.h b/bsd-user/i386/target_arch_cpu.h index b28602adbb..3cbf69d8af 100644 --- a/bsd-user/i386/target_arch_cpu.h +++ b/bsd-user/i386/target_arch_cpu.h @@ -20,6 +20,7 @@ #define _TARGET_ARCH_CPU_H_ #include "target_arch.h" +#include "signal-common.h" #define TARGET_DEFAULT_CPU_MODEL "qemu32" @@ -199,9 +200,9 @@ static inline void target_cpu_clone_regs(CPUX86State *env, target_ulong newsp) env->regs[R_EAX] = 0; } -static inline void target_cpu_reset(CPUArchState *cpu) +static inline void target_cpu_reset(CPUArchState *env) { - cpu_reset(env_cpu(cpu)); + cpu_reset(env_cpu(env)); } #endif /* ! _TARGET_ARCH_CPU_H_ */ diff --git a/bsd-user/main.c b/bsd-user/main.c index cb5ea40236..f1d58e905e 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -215,15 +215,13 @@ void qemu_cpu_kick(CPUState *cpu) } /* Assumes contents are already zeroed. */ -void init_task_state(TaskState *ts) +static void init_task_state(TaskState *ts) { - int i; - - ts->first_free = ts->sigqueue_table; - for (i = 0; i < MAX_SIGQUEUE_SIZE - 1; i++) { - ts->sigqueue_table[i].next = &ts->sigqueue_table[i + 1]; - } - ts->sigqueue_table[i].next = NULL; + ts->sigaltstack_used = (struct target_sigaltstack) { + .ss_sp = 0, + .ss_size = 0, + .ss_flags = TARGET_SS_DISABLE, + }; } void gemu_log(const char *fmt, ...) diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h index 1b3b974afe..02921ac8b3 100644 --- a/bsd-user/qemu.h +++ b/bsd-user/qemu.h @@ -70,17 +70,9 @@ struct image_info { uint32_t elf_flags; }; -#define MAX_SIGQUEUE_SIZE 1024 - -struct qemu_sigqueue { - struct qemu_sigqueue *next; - target_siginfo_t info; -}; - struct emulated_sigtable { int pending; /* true if signal is pending */ - struct qemu_sigqueue *first; - struct qemu_sigqueue info; /* Put first signal info here */ + target_siginfo_t info; }; /* @@ -93,15 +85,39 @@ typedef struct TaskState { struct bsd_binprm *bprm; struct image_info *info; + struct emulated_sigtable sync_signal; + /* + * TODO: Since we block all signals while returning to the main CPU + * loop, this needn't be an array + */ struct emulated_sigtable sigtab[TARGET_NSIG]; - struct qemu_sigqueue sigqueue_table[MAX_SIGQUEUE_SIZE]; /* siginfo queue */ - struct qemu_sigqueue *first_free; /* first free siginfo queue entry */ - int signal_pending; /* non zero if a signal may be pending */ + /* + * Nonzero if process_pending_signals() needs to do something (either + * handle a pending signal or unblock signals). + * This flag is written from a signal handler so should be accessed via + * the qatomic_read() and qatomic_set() functions. (It is not accessed + * from multiple threads.) + */ + int signal_pending; + /* True if we're leaving a sigsuspend and sigsuspend_mask is valid. */ + bool in_sigsuspend; + /* + * This thread's signal mask, as requested by the guest program. + * The actual signal mask of this thread may differ: + * + we don't let SIGSEGV and SIGBUS be blocked while running guest code + * + sometimes we block all signals to avoid races + */ + sigset_t signal_mask; + /* + * The signal mask imposed by a guest sigsuspend syscall, if we are + * currently in the middle of such a syscall + */ + sigset_t sigsuspend_mask; - uint8_t stack[]; + /* This thread's sigaltstack, if it has one */ + struct target_sigaltstack sigaltstack_used; } __attribute__((aligned(16))) TaskState; -void init_task_state(TaskState *ts); void stop_all_tasks(void); extern const char *qemu_uname_release; @@ -201,16 +217,18 @@ print_openbsd_syscall(int num, abi_long arg1, abi_long arg2, abi_long arg3, abi_long arg4, abi_long arg5, abi_long arg6); void print_openbsd_syscall_ret(int num, abi_long ret); +/** + * print_taken_signal: + * @target_signum: target signal being taken + * @tinfo: target_siginfo_t which will be passed to the guest for the signal + * + * Print strace output indicating that this signal is being taken by the guest, + * in a format similar to: + * --- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=0} --- + */ +void print_taken_signal(int target_signum, const target_siginfo_t *tinfo); extern int do_strace; -/* signal.c */ -void process_pending_signals(CPUArchState *cpu_env); -void signal_init(void); -long do_sigreturn(CPUArchState *env); -long do_rt_sigreturn(CPUArchState *env); -void queue_signal(CPUArchState *env, int sig, target_siginfo_t *info); -abi_long do_sigaltstack(abi_ulong uss_addr, abi_ulong uoss_addr, abi_ulong sp); - /* mmap.c */ int target_mprotect(abi_ulong start, abi_ulong len, int prot); abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, @@ -451,4 +469,6 @@ static inline void *lock_user_string(abi_ulong guest_addr) #include +#include "user/safe-syscall.h" + #endif /* QEMU_H */ diff --git a/bsd-user/signal-common.h b/bsd-user/signal-common.h new file mode 100644 index 0000000000..7ff8e8f2e4 --- /dev/null +++ b/bsd-user/signal-common.h @@ -0,0 +1,70 @@ +/* + * Emulation of BSD signals + * + * Copyright (c) 2013 Stacey Son + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SIGNAL_COMMON_H +#define SIGNAL_COMMON_H + +/** + * block_signals: block all signals while handling this guest syscall + * + * Block all signals, and arrange that the signal mask is returned to + * its correct value for the guest before we resume execution of guest code. + * If this function returns non-zero, then the caller should immediately + * return -TARGET_ERESTARTSYS to the main loop, which will take the pending + * signal and restart execution of the syscall. + * If block_signals() returns zero, then the caller can continue with + * emulation of the system call knowing that no signals can be taken + * (and therefore that no race conditions will result). + * This should only be called once, because if it is called a second time + * it will always return non-zero. (Think of it like a mutex that can't + * be recursively locked.) + * Signals will be unblocked again by process_pending_signals(). + * + * Return value: non-zero if there was a pending signal, zero if not. + */ +int block_signals(void); /* Returns non zero if signal pending */ + +long do_rt_sigreturn(CPUArchState *env); +int do_sigaction(int sig, const struct target_sigaction *act, + struct target_sigaction *oact); +abi_long do_sigaltstack(abi_ulong uss_addr, abi_ulong uoss_addr, abi_ulong sp); +long do_sigreturn(CPUArchState *env, abi_ulong addr); +void force_sig_fault(int sig, int code, abi_ulong addr); +int host_to_target_signal(int sig); +void host_to_target_sigset(target_sigset_t *d, const sigset_t *s); +void process_pending_signals(CPUArchState *env); +void queue_signal(CPUArchState *env, int sig, int si_type, + target_siginfo_t *info); +void signal_init(void); +int target_to_host_signal(int sig); +void target_to_host_sigset(sigset_t *d, const target_sigset_t *s); + +/* + * Within QEMU the top 8 bits of si_code indicate which of the parts of the + * union in target_siginfo is valid. This only applies between + * host_to_target_siginfo_noswap() and tswap_siginfo(); it does not appear + * either within host siginfo_t or in target_siginfo structures which we get + * from the guest userspace program. Linux kenrels use this internally, but BSD + * kernels don't do this, but its a useful abstraction. + * + * The linux-user version of this uses the top 16 bits, but FreeBSD's SI_USER + * and other signal indepenent SI_ codes have bit 16 set, so we only use the top + * byte instead. + * + * For FreeBSD, we have si_pid, si_uid, si_status, and si_addr always. Linux and + * {Open,Net}BSD have a different approach (where their reason field is larger, + * but whose siginfo has fewer fields always). + */ +#define QEMU_SI_NOINFO 0 /* nothing other than si_signo valid */ +#define QEMU_SI_FAULT 1 /* _fault is valid in _reason */ +#define QEMU_SI_TIMER 2 /* _timer is valid in _reason */ +#define QEMU_SI_MESGQ 3 /* _mesgq is valid in _reason */ +#define QEMU_SI_POLL 4 /* _poll is valid in _reason */ +#define QEMU_SI_CAPSICUM 5 /* _capsicum is valid in _reason */ + +#endif diff --git a/bsd-user/signal.c b/bsd-user/signal.c index 05b277c642..ad22ba9d90 100644 --- a/bsd-user/signal.c +++ b/bsd-user/signal.c @@ -2,6 +2,7 @@ * Emulation of BSD signals * * Copyright (c) 2003 - 2008 Fabrice Bellard + * Copyright (c) 2013 Stacey Son * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,41 +20,1026 @@ #include "qemu/osdep.h" #include "qemu.h" +#include "signal-common.h" +#include "trace.h" +#include "hw/core/tcg-cpu-ops.h" +#include "host-signal.h" + +static struct target_sigaction sigact_table[TARGET_NSIG]; +static void host_signal_handler(int host_sig, siginfo_t *info, void *puc); +static void target_to_host_sigset_internal(sigset_t *d, + const target_sigset_t *s); + +static inline int on_sig_stack(TaskState *ts, unsigned long sp) +{ + return sp - ts->sigaltstack_used.ss_sp < ts->sigaltstack_used.ss_size; +} + +static inline int sas_ss_flags(TaskState *ts, unsigned long sp) +{ + return ts->sigaltstack_used.ss_size == 0 ? SS_DISABLE : + on_sig_stack(ts, sp) ? SS_ONSTACK : 0; +} /* - * Stubbed out routines until we merge signal support from bsd-user - * fork. + * The BSD ABIs use the same singal numbers across all the CPU architectures, so + * (unlike Linux) these functions are just the identity mapping. This might not + * be true for XyzBSD running on AbcBSD, which doesn't currently work. */ +int host_to_target_signal(int sig) +{ + return sig; +} + +int target_to_host_signal(int sig) +{ + return sig; +} + +static inline void target_sigemptyset(target_sigset_t *set) +{ + memset(set, 0, sizeof(*set)); +} + +static inline void target_sigaddset(target_sigset_t *set, int signum) +{ + signum--; + uint32_t mask = (uint32_t)1 << (signum % TARGET_NSIG_BPW); + set->__bits[signum / TARGET_NSIG_BPW] |= mask; +} + +static inline int target_sigismember(const target_sigset_t *set, int signum) +{ + signum--; + abi_ulong mask = (abi_ulong)1 << (signum % TARGET_NSIG_BPW); + return (set->__bits[signum / TARGET_NSIG_BPW] & mask) != 0; +} + +/* Adjust the signal context to rewind out of safe-syscall if we're in it */ +static inline void rewind_if_in_safe_syscall(void *puc) +{ + ucontext_t *uc = (ucontext_t *)puc; + uintptr_t pcreg = host_signal_pc(uc); + + if (pcreg > (uintptr_t)safe_syscall_start + && pcreg < (uintptr_t)safe_syscall_end) { + host_signal_set_pc(uc, (uintptr_t)safe_syscall_start); + } +} + +/* + * Note: The following take advantage of the BSD signal property that all + * signals are available on all architectures. + */ +static void host_to_target_sigset_internal(target_sigset_t *d, + const sigset_t *s) +{ + int i; + + target_sigemptyset(d); + for (i = 1; i <= NSIG; i++) { + if (sigismember(s, i)) { + target_sigaddset(d, host_to_target_signal(i)); + } + } +} + +void host_to_target_sigset(target_sigset_t *d, const sigset_t *s) +{ + target_sigset_t d1; + int i; + + host_to_target_sigset_internal(&d1, s); + for (i = 0; i < _SIG_WORDS; i++) { + d->__bits[i] = tswap32(d1.__bits[i]); + } +} + +static void target_to_host_sigset_internal(sigset_t *d, + const target_sigset_t *s) +{ + int i; + + sigemptyset(d); + for (i = 1; i <= TARGET_NSIG; i++) { + if (target_sigismember(s, i)) { + sigaddset(d, target_to_host_signal(i)); + } + } +} + +void target_to_host_sigset(sigset_t *d, const target_sigset_t *s) +{ + target_sigset_t s1; + int i; + + for (i = 0; i < TARGET_NSIG_WORDS; i++) { + s1.__bits[i] = tswap32(s->__bits[i]); + } + target_to_host_sigset_internal(d, &s1); +} + +static bool has_trapno(int tsig) +{ + return tsig == TARGET_SIGILL || + tsig == TARGET_SIGFPE || + tsig == TARGET_SIGSEGV || + tsig == TARGET_SIGBUS || + tsig == TARGET_SIGTRAP; +} + +/* Siginfo conversion. */ + +/* + * Populate tinfo w/o swapping based on guessing which fields are valid. + */ +static inline void host_to_target_siginfo_noswap(target_siginfo_t *tinfo, + const siginfo_t *info) +{ + int sig = host_to_target_signal(info->si_signo); + int si_code = info->si_code; + int si_type; + + /* + * Make sure we that the variable portion of the target siginfo is zeroed + * out so we don't leak anything into that. + */ + memset(&tinfo->_reason, 0, sizeof(tinfo->_reason)); + + /* + * This is awkward, because we have to use a combination of the si_code and + * si_signo to figure out which of the union's members are valid.o We + * therefore make our best guess. + * + * Once we have made our guess, we record it in the top 16 bits of + * the si_code, so that tswap_siginfo() later can use it. + * tswap_siginfo() will strip these top bits out before writing + * si_code to the guest (sign-extending the lower bits). + */ + tinfo->si_signo = sig; + tinfo->si_errno = info->si_errno; + tinfo->si_code = info->si_code; + tinfo->si_pid = info->si_pid; + tinfo->si_uid = info->si_uid; + tinfo->si_status = info->si_status; + tinfo->si_addr = (abi_ulong)(unsigned long)info->si_addr; + /* + * si_value is opaque to kernel. On all FreeBSD platforms, + * sizeof(sival_ptr) >= sizeof(sival_int) so the following + * always will copy the larger element. + */ + tinfo->si_value.sival_ptr = + (abi_ulong)(unsigned long)info->si_value.sival_ptr; + + switch (si_code) { + /* + * All the SI_xxx codes that are defined here are global to + * all the signals (they have values that none of the other, + * more specific signal info will set). + */ + case SI_USER: + case SI_LWP: + case SI_KERNEL: + case SI_QUEUE: + case SI_ASYNCIO: + /* + * Only the fixed parts are valid (though FreeBSD doesn't always + * set all the fields to non-zero values. + */ + si_type = QEMU_SI_NOINFO; + break; + case SI_TIMER: + tinfo->_reason._timer._timerid = info->_reason._timer._timerid; + tinfo->_reason._timer._overrun = info->_reason._timer._overrun; + si_type = QEMU_SI_TIMER; + break; + case SI_MESGQ: + tinfo->_reason._mesgq._mqd = info->_reason._mesgq._mqd; + si_type = QEMU_SI_MESGQ; + break; + default: + /* + * We have to go based on the signal number now to figure out + * what's valid. + */ + if (has_trapno(sig)) { + tinfo->_reason._fault._trapno = info->_reason._fault._trapno; + si_type = QEMU_SI_FAULT; + } +#ifdef TARGET_SIGPOLL + /* + * FreeBSD never had SIGPOLL, but emulates it for Linux so there's + * a chance it may popup in the future. + */ + if (sig == TARGET_SIGPOLL) { + tinfo->_reason._poll._band = info->_reason._poll._band; + si_type = QEMU_SI_POLL; + } +#endif + /* + * Unsure that this can actually be generated, and our support for + * capsicum is somewhere between weak and non-existant, but if we get + * one, then we know what to save. + */ + if (sig == TARGET_SIGTRAP) { + tinfo->_reason._capsicum._syscall = + info->_reason._capsicum._syscall; + si_type = QEMU_SI_CAPSICUM; + } + break; + } + tinfo->si_code = deposit32(si_code, 24, 8, si_type); +} + +static void tswap_siginfo(target_siginfo_t *tinfo, const target_siginfo_t *info) +{ + int si_type = extract32(info->si_code, 24, 8); + int si_code = sextract32(info->si_code, 0, 24); + + __put_user(info->si_signo, &tinfo->si_signo); + __put_user(info->si_errno, &tinfo->si_errno); + __put_user(si_code, &tinfo->si_code); /* Zero out si_type, it's internal */ + __put_user(info->si_pid, &tinfo->si_pid); + __put_user(info->si_uid, &tinfo->si_uid); + __put_user(info->si_status, &tinfo->si_status); + __put_user(info->si_addr, &tinfo->si_addr); + /* + * Unswapped, because we passed it through mostly untouched. si_value is + * opaque to the kernel, so we didn't bother with potentially wasting cycles + * to swap it into host byte order. + */ + tinfo->si_value.sival_ptr = info->si_value.sival_ptr; + + /* + * We can use our internal marker of which fields in the structure + * are valid, rather than duplicating the guesswork of + * host_to_target_siginfo_noswap() here. + */ + switch (si_type) { + case QEMU_SI_NOINFO: /* No additional info */ + break; + case QEMU_SI_FAULT: + __put_user(info->_reason._fault._trapno, + &tinfo->_reason._fault._trapno); + break; + case QEMU_SI_TIMER: + __put_user(info->_reason._timer._timerid, + &tinfo->_reason._timer._timerid); + __put_user(info->_reason._timer._overrun, + &tinfo->_reason._timer._overrun); + break; + case QEMU_SI_MESGQ: + __put_user(info->_reason._mesgq._mqd, &tinfo->_reason._mesgq._mqd); + break; + case QEMU_SI_POLL: + /* Note: Not generated on FreeBSD */ + __put_user(info->_reason._poll._band, &tinfo->_reason._poll._band); + break; + case QEMU_SI_CAPSICUM: + __put_user(info->_reason._capsicum._syscall, + &tinfo->_reason._capsicum._syscall); + break; + default: + g_assert_not_reached(); + } +} + +int block_signals(void) +{ + TaskState *ts = (TaskState *)thread_cpu->opaque; + sigset_t set; + + /* + * It's OK to block everything including SIGSEGV, because we won't run any + * further guest code before unblocking signals in + * process_pending_signals(). We depend on the FreeBSD behaivor here where + * this will only affect this thread's signal mask. We don't use + * pthread_sigmask which might seem more correct because that routine also + * does odd things with SIGCANCEL to implement pthread_cancel(). + */ + sigfillset(&set); + sigprocmask(SIG_SETMASK, &set, 0); + + return qatomic_xchg(&ts->signal_pending, 1); +} + +/* Returns 1 if given signal should dump core if not handled. */ +static int core_dump_signal(int sig) +{ + switch (sig) { + case TARGET_SIGABRT: + case TARGET_SIGFPE: + case TARGET_SIGILL: + case TARGET_SIGQUIT: + case TARGET_SIGSEGV: + case TARGET_SIGTRAP: + case TARGET_SIGBUS: + return 1; + default: + return 0; + } +} + +/* Abort execution with signal. */ +static void QEMU_NORETURN dump_core_and_abort(int target_sig) +{ + CPUArchState *env = thread_cpu->env_ptr; + CPUState *cpu = env_cpu(env); + TaskState *ts = cpu->opaque; + int core_dumped = 0; + int host_sig; + struct sigaction act; + + host_sig = target_to_host_signal(target_sig); + gdb_signalled(env, target_sig); + + /* Dump core if supported by target binary format */ + if (core_dump_signal(target_sig) && (ts->bprm->core_dump != NULL)) { + stop_all_tasks(); + core_dumped = + ((*ts->bprm->core_dump)(target_sig, env) == 0); + } + if (core_dumped) { + struct rlimit nodump; + + /* + * We already dumped the core of target process, we don't want + * a coredump of qemu itself. + */ + getrlimit(RLIMIT_CORE, &nodump); + nodump.rlim_cur = 0; + setrlimit(RLIMIT_CORE, &nodump); + (void) fprintf(stderr, "qemu: uncaught target signal %d (%s) " + "- %s\n", target_sig, strsignal(host_sig), "core dumped"); + } + + /* + * The proper exit code for dying from an uncaught signal is + * -. The kernel doesn't allow exit() or _exit() to pass + * a negative value. To get the proper exit code we need to + * actually die from an uncaught signal. Here the default signal + * handler is installed, we send ourself a signal and we wait for + * it to arrive. + */ + memset(&act, 0, sizeof(act)); + sigfillset(&act.sa_mask); + act.sa_handler = SIG_DFL; + sigaction(host_sig, &act, NULL); + + kill(getpid(), host_sig); + + /* + * Make sure the signal isn't masked (just reuse the mask inside + * of act). + */ + sigdelset(&act.sa_mask, host_sig); + sigsuspend(&act.sa_mask); + + /* unreachable */ + abort(); +} /* * Queue a signal so that it will be send to the virtual CPU as soon as * possible. */ -void queue_signal(CPUArchState *env, int sig, target_siginfo_t *info) +void queue_signal(CPUArchState *env, int sig, int si_type, + target_siginfo_t *info) { - qemu_log_mask(LOG_UNIMP, "No signal queueing, dropping signal %d\n", sig); + CPUState *cpu = env_cpu(env); + TaskState *ts = cpu->opaque; + + trace_user_queue_signal(env, sig); + + info->si_code = deposit32(info->si_code, 24, 8, si_type); + + ts->sync_signal.info = *info; + ts->sync_signal.pending = sig; + /* Signal that a new signal is pending. */ + qatomic_set(&ts->signal_pending, 1); + return; +} + +static int fatal_signal(int sig) +{ + + switch (sig) { + case TARGET_SIGCHLD: + case TARGET_SIGURG: + case TARGET_SIGWINCH: + case TARGET_SIGINFO: + /* Ignored by default. */ + return 0; + case TARGET_SIGCONT: + case TARGET_SIGSTOP: + case TARGET_SIGTSTP: + case TARGET_SIGTTIN: + case TARGET_SIGTTOU: + /* Job control signals. */ + return 0; + default: + return 1; + } +} + +/* + * Force a synchronously taken QEMU_SI_FAULT signal. For QEMU the + * 'force' part is handled in process_pending_signals(). + */ +void force_sig_fault(int sig, int code, abi_ulong addr) +{ + CPUState *cpu = thread_cpu; + CPUArchState *env = cpu->env_ptr; + target_siginfo_t info = {}; + + info.si_signo = sig; + info.si_errno = 0; + info.si_code = code; + info.si_addr = addr; + queue_signal(env, sig, QEMU_SI_FAULT, &info); +} + +static void host_signal_handler(int host_sig, siginfo_t *info, void *puc) +{ + CPUArchState *env = thread_cpu->env_ptr; + CPUState *cpu = env_cpu(env); + TaskState *ts = cpu->opaque; + target_siginfo_t tinfo; + ucontext_t *uc = puc; + struct emulated_sigtable *k; + int guest_sig; + uintptr_t pc = 0; + bool sync_sig = false; + + /* + * Non-spoofed SIGSEGV and SIGBUS are synchronous, and need special + * handling wrt signal blocking and unwinding. + */ + if ((host_sig == SIGSEGV || host_sig == SIGBUS) && info->si_code > 0) { + MMUAccessType access_type; + uintptr_t host_addr; + abi_ptr guest_addr; + bool is_write; + + host_addr = (uintptr_t)info->si_addr; + + /* + * Convert forcefully to guest address space: addresses outside + * reserved_va are still valid to report via SEGV_MAPERR. + */ + guest_addr = h2g_nocheck(host_addr); + + pc = host_signal_pc(uc); + is_write = host_signal_write(info, uc); + access_type = adjust_signal_pc(&pc, is_write); + + if (host_sig == SIGSEGV) { + bool maperr = true; + + if (info->si_code == SEGV_ACCERR && h2g_valid(host_addr)) { + /* If this was a write to a TB protected page, restart. */ + if (is_write && + handle_sigsegv_accerr_write(cpu, &uc->uc_sigmask, + pc, guest_addr)) { + return; + } + + /* + * With reserved_va, the whole address space is PROT_NONE, + * which means that we may get ACCERR when we want MAPERR. + */ + if (page_get_flags(guest_addr) & PAGE_VALID) { + maperr = false; + } else { + info->si_code = SEGV_MAPERR; + } + } + + sigprocmask(SIG_SETMASK, &uc->uc_sigmask, NULL); + cpu_loop_exit_sigsegv(cpu, guest_addr, access_type, maperr, pc); + } else { + sigprocmask(SIG_SETMASK, &uc->uc_sigmask, NULL); + if (info->si_code == BUS_ADRALN) { + cpu_loop_exit_sigbus(cpu, guest_addr, access_type, pc); + } + } + + sync_sig = true; + } + + /* Get the target signal number. */ + guest_sig = host_to_target_signal(host_sig); + if (guest_sig < 1 || guest_sig > TARGET_NSIG) { + return; + } + trace_user_host_signal(cpu, host_sig, guest_sig); + + host_to_target_siginfo_noswap(&tinfo, info); + + k = &ts->sigtab[guest_sig - 1]; + k->info = tinfo; + k->pending = guest_sig; + ts->signal_pending = 1; + + /* + * For synchronous signals, unwind the cpu state to the faulting + * insn and then exit back to the main loop so that the signal + * is delivered immediately. + */ + if (sync_sig) { + cpu->exception_index = EXCP_INTERRUPT; + cpu_loop_exit_restore(cpu, pc); + } + + rewind_if_in_safe_syscall(puc); + + /* + * Block host signals until target signal handler entered. We + * can't block SIGSEGV or SIGBUS while we're executing guest + * code in case the guest code provokes one in the window between + * now and it getting out to the main loop. Signals will be + * unblocked again in process_pending_signals(). + */ + sigfillset(&uc->uc_sigmask); + sigdelset(&uc->uc_sigmask, SIGSEGV); + sigdelset(&uc->uc_sigmask, SIGBUS); + + /* Interrupt the virtual CPU as soon as possible. */ + cpu_exit(thread_cpu); +} + +/* do_sigaltstack() returns target values and errnos. */ +/* compare to kern/kern_sig.c sys_sigaltstack() and kern_sigaltstack() */ +abi_long do_sigaltstack(abi_ulong uss_addr, abi_ulong uoss_addr, abi_ulong sp) +{ + TaskState *ts = (TaskState *)thread_cpu->opaque; + int ret; + target_stack_t oss; + + if (uoss_addr) { + /* Save current signal stack params */ + oss.ss_sp = tswapl(ts->sigaltstack_used.ss_sp); + oss.ss_size = tswapl(ts->sigaltstack_used.ss_size); + oss.ss_flags = tswapl(sas_ss_flags(ts, sp)); + } + + if (uss_addr) { + target_stack_t *uss; + target_stack_t ss; + size_t minstacksize = TARGET_MINSIGSTKSZ; + + ret = -TARGET_EFAULT; + if (!lock_user_struct(VERIFY_READ, uss, uss_addr, 1)) { + goto out; + } + __get_user(ss.ss_sp, &uss->ss_sp); + __get_user(ss.ss_size, &uss->ss_size); + __get_user(ss.ss_flags, &uss->ss_flags); + unlock_user_struct(uss, uss_addr, 0); + + ret = -TARGET_EPERM; + if (on_sig_stack(ts, sp)) { + goto out; + } + + ret = -TARGET_EINVAL; + if (ss.ss_flags != TARGET_SS_DISABLE + && ss.ss_flags != TARGET_SS_ONSTACK + && ss.ss_flags != 0) { + goto out; + } + + if (ss.ss_flags == TARGET_SS_DISABLE) { + ss.ss_size = 0; + ss.ss_sp = 0; + } else { + ret = -TARGET_ENOMEM; + if (ss.ss_size < minstacksize) { + goto out; + } + } + + ts->sigaltstack_used.ss_sp = ss.ss_sp; + ts->sigaltstack_used.ss_size = ss.ss_size; + } + + if (uoss_addr) { + ret = -TARGET_EFAULT; + if (copy_to_user(uoss_addr, &oss, sizeof(oss))) { + goto out; + } + } + + ret = 0; +out: + return ret; +} + +/* do_sigaction() return host values and errnos */ +int do_sigaction(int sig, const struct target_sigaction *act, + struct target_sigaction *oact) +{ + struct target_sigaction *k; + struct sigaction act1; + int host_sig; + int ret = 0; + + if (sig < 1 || sig > TARGET_NSIG) { + return -TARGET_EINVAL; + } + + if ((sig == TARGET_SIGKILL || sig == TARGET_SIGSTOP) && + act != NULL && act->_sa_handler != TARGET_SIG_DFL) { + return -TARGET_EINVAL; + } + + if (block_signals()) { + return -TARGET_ERESTART; + } + + k = &sigact_table[sig - 1]; + if (oact) { + oact->_sa_handler = tswapal(k->_sa_handler); + oact->sa_flags = tswap32(k->sa_flags); + oact->sa_mask = k->sa_mask; + } + if (act) { + k->_sa_handler = tswapal(act->_sa_handler); + k->sa_flags = tswap32(act->sa_flags); + k->sa_mask = act->sa_mask; + + /* Update the host signal state. */ + host_sig = target_to_host_signal(sig); + if (host_sig != SIGSEGV && host_sig != SIGBUS) { + memset(&act1, 0, sizeof(struct sigaction)); + sigfillset(&act1.sa_mask); + act1.sa_flags = SA_SIGINFO; + if (k->sa_flags & TARGET_SA_RESTART) { + act1.sa_flags |= SA_RESTART; + } + /* + * Note: It is important to update the host kernel signal mask to + * avoid getting unexpected interrupted system calls. + */ + if (k->_sa_handler == TARGET_SIG_IGN) { + act1.sa_sigaction = (void *)SIG_IGN; + } else if (k->_sa_handler == TARGET_SIG_DFL) { + if (fatal_signal(sig)) { + act1.sa_sigaction = host_signal_handler; + } else { + act1.sa_sigaction = (void *)SIG_DFL; + } + } else { + act1.sa_sigaction = host_signal_handler; + } + ret = sigaction(host_sig, &act1, NULL); + } + } + return ret; +} + +static inline abi_ulong get_sigframe(struct target_sigaction *ka, + CPUArchState *env, size_t frame_size) +{ + TaskState *ts = (TaskState *)thread_cpu->opaque; + abi_ulong sp; + + /* Use default user stack */ + sp = get_sp_from_cpustate(env); + + if ((ka->sa_flags & TARGET_SA_ONSTACK) && sas_ss_flags(ts, sp) == 0) { + sp = ts->sigaltstack_used.ss_sp + ts->sigaltstack_used.ss_size; + } + +/* TODO: make this a target_arch function / define */ +#if defined(TARGET_ARM) + return (sp - frame_size) & ~7; +#elif defined(TARGET_AARCH64) + return (sp - frame_size) & ~15; +#else + return sp - frame_size; +#endif +} + +/* compare to $M/$M/exec_machdep.c sendsig and sys/kern/kern_sig.c sigexit */ + +static void setup_frame(int sig, int code, struct target_sigaction *ka, + target_sigset_t *set, target_siginfo_t *tinfo, CPUArchState *env) +{ + struct target_sigframe *frame; + abi_ulong frame_addr; + int i; + + frame_addr = get_sigframe(ka, env, sizeof(*frame)); + trace_user_setup_frame(env, frame_addr); + if (!lock_user_struct(VERIFY_WRITE, frame, frame_addr, 0)) { + unlock_user_struct(frame, frame_addr, 1); + dump_core_and_abort(TARGET_SIGILL); + return; + } + + memset(frame, 0, sizeof(*frame)); + setup_sigframe_arch(env, frame_addr, frame, 0); + + for (i = 0; i < TARGET_NSIG_WORDS; i++) { + __put_user(set->__bits[i], &frame->sf_uc.uc_sigmask.__bits[i]); + } + + if (tinfo) { + frame->sf_si.si_signo = tinfo->si_signo; + frame->sf_si.si_errno = tinfo->si_errno; + frame->sf_si.si_code = tinfo->si_code; + frame->sf_si.si_pid = tinfo->si_pid; + frame->sf_si.si_uid = tinfo->si_uid; + frame->sf_si.si_status = tinfo->si_status; + frame->sf_si.si_addr = tinfo->si_addr; + /* see host_to_target_siginfo_noswap() for more details */ + frame->sf_si.si_value.sival_ptr = tinfo->si_value.sival_ptr; + /* + * At this point, whatever is in the _reason union is complete + * and in target order, so just copy the whole thing over, even + * if it's too large for this specific signal. + * host_to_target_siginfo_noswap() and tswap_siginfo() have ensured + * that's so. + */ + memcpy(&frame->sf_si._reason, &tinfo->_reason, + sizeof(tinfo->_reason)); + } + + set_sigtramp_args(env, sig, frame, frame_addr, ka); + + unlock_user_struct(frame, frame_addr, 1); +} + +static int reset_signal_mask(target_ucontext_t *ucontext) +{ + int i; + sigset_t blocked; + target_sigset_t target_set; + TaskState *ts = (TaskState *)thread_cpu->opaque; + + for (i = 0; i < TARGET_NSIG_WORDS; i++) { + if (__get_user(target_set.__bits[i], + &ucontext->uc_sigmask.__bits[i])) { + return -TARGET_EFAULT; + } + } + target_to_host_sigset_internal(&blocked, &target_set); + ts->signal_mask = blocked; + + return 0; +} + +/* See sys/$M/$M/exec_machdep.c sigreturn() */ +long do_sigreturn(CPUArchState *env, abi_ulong addr) +{ + long ret; + abi_ulong target_ucontext; + target_ucontext_t *ucontext = NULL; + + /* Get the target ucontext address from the stack frame */ + ret = get_ucontext_sigreturn(env, addr, &target_ucontext); + if (is_error(ret)) { + return ret; + } + trace_user_do_sigreturn(env, addr); + if (!lock_user_struct(VERIFY_READ, ucontext, target_ucontext, 0)) { + goto badframe; + } + + /* Set the register state back to before the signal. */ + if (set_mcontext(env, &ucontext->uc_mcontext, 1)) { + goto badframe; + } + + /* And reset the signal mask. */ + if (reset_signal_mask(ucontext)) { + goto badframe; + } + + unlock_user_struct(ucontext, target_ucontext, 0); + return -TARGET_EJUSTRETURN; + +badframe: + if (ucontext != NULL) { + unlock_user_struct(ucontext, target_ucontext, 0); + } + return -TARGET_EFAULT; } void signal_init(void) { + TaskState *ts = (TaskState *)thread_cpu->opaque; + struct sigaction act; + struct sigaction oact; + int i; + int host_sig; + + /* Set the signal mask from the host mask. */ + sigprocmask(0, 0, &ts->signal_mask); + + sigfillset(&act.sa_mask); + act.sa_sigaction = host_signal_handler; + act.sa_flags = SA_SIGINFO; + + for (i = 1; i <= TARGET_NSIG; i++) { +#ifdef CONFIG_GPROF + if (i == TARGET_SIGPROF) { + continue; + } +#endif + host_sig = target_to_host_signal(i); + sigaction(host_sig, NULL, &oact); + if (oact.sa_sigaction == (void *)SIG_IGN) { + sigact_table[i - 1]._sa_handler = TARGET_SIG_IGN; + } else if (oact.sa_sigaction == (void *)SIG_DFL) { + sigact_table[i - 1]._sa_handler = TARGET_SIG_DFL; + } + /* + * If there's already a handler installed then something has + * gone horribly wrong, so don't even try to handle that case. + * Install some handlers for our own use. We need at least + * SIGSEGV and SIGBUS, to detect exceptions. We can not just + * trap all signals because it affects syscall interrupt + * behavior. But do trap all default-fatal signals. + */ + if (fatal_signal(i)) { + sigaction(host_sig, &act, NULL); + } + } } -void process_pending_signals(CPUArchState *cpu_env) +static void handle_pending_signal(CPUArchState *env, int sig, + struct emulated_sigtable *k) { + CPUState *cpu = env_cpu(env); + TaskState *ts = cpu->opaque; + struct target_sigaction *sa; + int code; + sigset_t set; + abi_ulong handler; + target_siginfo_t tinfo; + target_sigset_t target_old_set; + + trace_user_handle_signal(env, sig); + + k->pending = 0; + + sig = gdb_handlesig(cpu, sig); + if (!sig) { + sa = NULL; + handler = TARGET_SIG_IGN; + } else { + sa = &sigact_table[sig - 1]; + handler = sa->_sa_handler; + } + + if (do_strace) { + print_taken_signal(sig, &k->info); + } + + if (handler == TARGET_SIG_DFL) { + /* + * default handler : ignore some signal. The other are job + * control or fatal. + */ + if (sig == TARGET_SIGTSTP || sig == TARGET_SIGTTIN || + sig == TARGET_SIGTTOU) { + kill(getpid(), SIGSTOP); + } else if (sig != TARGET_SIGCHLD && sig != TARGET_SIGURG && + sig != TARGET_SIGINFO && sig != TARGET_SIGWINCH && + sig != TARGET_SIGCONT) { + dump_core_and_abort(sig); + } + } else if (handler == TARGET_SIG_IGN) { + /* ignore sig */ + } else if (handler == TARGET_SIG_ERR) { + dump_core_and_abort(sig); + } else { + /* compute the blocked signals during the handler execution */ + sigset_t *blocked_set; + + target_to_host_sigset(&set, &sa->sa_mask); + /* + * SA_NODEFER indicates that the current signal should not be + * blocked during the handler. + */ + if (!(sa->sa_flags & TARGET_SA_NODEFER)) { + sigaddset(&set, target_to_host_signal(sig)); + } + + /* + * Save the previous blocked signal state to restore it at the + * end of the signal execution (see do_sigreturn). + */ + host_to_target_sigset_internal(&target_old_set, &ts->signal_mask); + + blocked_set = ts->in_sigsuspend ? + &ts->sigsuspend_mask : &ts->signal_mask; + sigorset(&ts->signal_mask, blocked_set, &set); + ts->in_sigsuspend = false; + sigprocmask(SIG_SETMASK, &ts->signal_mask, NULL); + + /* XXX VM86 on x86 ??? */ + + code = k->info.si_code; /* From host, so no si_type */ + /* prepare the stack frame of the virtual CPU */ + if (sa->sa_flags & TARGET_SA_SIGINFO) { + tswap_siginfo(&tinfo, &k->info); + setup_frame(sig, code, sa, &target_old_set, &tinfo, env); + } else { + setup_frame(sig, code, sa, &target_old_set, NULL, env); + } + if (sa->sa_flags & TARGET_SA_RESETHAND) { + sa->_sa_handler = TARGET_SIG_DFL; + } + } +} + +void process_pending_signals(CPUArchState *env) +{ + CPUState *cpu = env_cpu(env); + int sig; + sigset_t *blocked_set, set; + struct emulated_sigtable *k; + TaskState *ts = cpu->opaque; + + while (qatomic_read(&ts->signal_pending)) { + sigfillset(&set); + sigprocmask(SIG_SETMASK, &set, 0); + + restart_scan: + sig = ts->sync_signal.pending; + if (sig) { + /* + * Synchronous signals are forced by the emulated CPU in some way. + * If they are set to ignore, restore the default handler (see + * sys/kern_sig.c trapsignal() and execsigs() for this behavior) + * though maybe this is done only when forcing exit for non SIGCHLD. + */ + if (sigismember(&ts->signal_mask, target_to_host_signal(sig)) || + sigact_table[sig - 1]._sa_handler == TARGET_SIG_IGN) { + sigdelset(&ts->signal_mask, target_to_host_signal(sig)); + sigact_table[sig - 1]._sa_handler = TARGET_SIG_DFL; + } + handle_pending_signal(env, sig, &ts->sync_signal); + } + + k = ts->sigtab; + for (sig = 1; sig <= TARGET_NSIG; sig++, k++) { + blocked_set = ts->in_sigsuspend ? + &ts->sigsuspend_mask : &ts->signal_mask; + if (k->pending && + !sigismember(blocked_set, target_to_host_signal(sig))) { + handle_pending_signal(env, sig, k); + /* + * Restart scan from the beginning, as handle_pending_signal + * might have resulted in a new synchronous signal (eg SIGSEGV). + */ + goto restart_scan; + } + } + + /* + * Unblock signals and check one more time. Unblocking signals may cause + * us to take another host signal, which will set signal_pending again. + */ + qatomic_set(&ts->signal_pending, 0); + ts->in_sigsuspend = false; + set = ts->signal_mask; + sigdelset(&set, SIGSEGV); + sigdelset(&set, SIGBUS); + sigprocmask(SIG_SETMASK, &set, 0); + } + ts->in_sigsuspend = false; } void cpu_loop_exit_sigsegv(CPUState *cpu, target_ulong addr, MMUAccessType access_type, bool maperr, uintptr_t ra) { - qemu_log_mask(LOG_UNIMP, "No signal support for SIGSEGV\n"); - /* unreachable */ - abort(); + const struct TCGCPUOps *tcg_ops = CPU_GET_CLASS(cpu)->tcg_ops; + + if (tcg_ops->record_sigsegv) { + tcg_ops->record_sigsegv(cpu, addr, access_type, maperr, ra); + } + + force_sig_fault(TARGET_SIGSEGV, + maperr ? TARGET_SEGV_MAPERR : TARGET_SEGV_ACCERR, + addr); + cpu->exception_index = EXCP_INTERRUPT; + cpu_loop_exit_restore(cpu, ra); } void cpu_loop_exit_sigbus(CPUState *cpu, target_ulong addr, MMUAccessType access_type, uintptr_t ra) { - qemu_log_mask(LOG_UNIMP, "No signal support for SIGBUS\n"); - /* unreachable */ - abort(); + const struct TCGCPUOps *tcg_ops = CPU_GET_CLASS(cpu)->tcg_ops; + + if (tcg_ops->record_sigbus) { + tcg_ops->record_sigbus(cpu, addr, access_type, ra); + } + + force_sig_fault(TARGET_SIGBUS, TARGET_BUS_ADRALN, addr); + cpu->exception_index = EXCP_INTERRUPT; + cpu_loop_exit_restore(cpu, ra); } diff --git a/bsd-user/strace.c b/bsd-user/strace.c index be40b8a20c..a77d10dd6b 100644 --- a/bsd-user/strace.c +++ b/bsd-user/strace.c @@ -31,6 +31,24 @@ int do_strace; /* * Utility functions */ +static const char * +get_comma(int last) +{ + return (last) ? "" : ","; +} + +/* + * Prints out raw parameter using given format. Caller needs + * to do byte swapping if needed. + */ +static void +print_raw_param(const char *fmt, abi_long param, int last) +{ + char format[64]; + + (void)snprintf(format, sizeof(format), "%s%s", fmt, get_comma(last)); + gemu_log(format, param); +} static void print_sysctl(const struct syscallname *name, abi_long arg1, abi_long arg2, abi_long arg3, abi_long arg4, abi_long arg5, @@ -239,3 +257,82 @@ void print_openbsd_syscall_ret(int num, abi_long ret) print_syscall_ret(num, ret, openbsd_scnames, ARRAY_SIZE(openbsd_scnames)); } + +static void +print_signal(abi_ulong arg, int last) +{ + const char *signal_name = NULL; + switch (arg) { + case TARGET_SIGHUP: + signal_name = "SIGHUP"; + break; + case TARGET_SIGINT: + signal_name = "SIGINT"; + break; + case TARGET_SIGQUIT: + signal_name = "SIGQUIT"; + break; + case TARGET_SIGILL: + signal_name = "SIGILL"; + break; + case TARGET_SIGABRT: + signal_name = "SIGABRT"; + break; + case TARGET_SIGFPE: + signal_name = "SIGFPE"; + break; + case TARGET_SIGKILL: + signal_name = "SIGKILL"; + break; + case TARGET_SIGSEGV: + signal_name = "SIGSEGV"; + break; + case TARGET_SIGPIPE: + signal_name = "SIGPIPE"; + break; + case TARGET_SIGALRM: + signal_name = "SIGALRM"; + break; + case TARGET_SIGTERM: + signal_name = "SIGTERM"; + break; + case TARGET_SIGUSR1: + signal_name = "SIGUSR1"; + break; + case TARGET_SIGUSR2: + signal_name = "SIGUSR2"; + break; + case TARGET_SIGCHLD: + signal_name = "SIGCHLD"; + break; + case TARGET_SIGCONT: + signal_name = "SIGCONT"; + break; + case TARGET_SIGSTOP: + signal_name = "SIGSTOP"; + break; + case TARGET_SIGTTIN: + signal_name = "SIGTTIN"; + break; + case TARGET_SIGTTOU: + signal_name = "SIGTTOU"; + break; + } + if (signal_name == NULL) { + print_raw_param("%ld", arg, last); + return; + } + gemu_log("%s%s", signal_name, get_comma(last)); +} + +void print_taken_signal(int target_signum, const target_siginfo_t *tinfo) +{ + /* + * Print the strace output for a signal being taken: + * --- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=0} --- + */ + gemu_log("%d ", getpid()); + gemu_log("--- "); + print_signal(target_signum, 1); + gemu_log(" ---\n"); +} diff --git a/bsd-user/syscall_defs.h b/bsd-user/syscall_defs.h index 04a1a886d7..62b472b990 100644 --- a/bsd-user/syscall_defs.h +++ b/bsd-user/syscall_defs.h @@ -21,6 +21,7 @@ #define _SYSCALL_DEFS_H_ #include +#include #include "errno_defs.h" diff --git a/bsd-user/trace-events b/bsd-user/trace-events new file mode 100644 index 0000000000..843896f627 --- /dev/null +++ b/bsd-user/trace-events @@ -0,0 +1,11 @@ +# See docs/tracing.txt for syntax documentation. + +# bsd-user/signal.c +user_setup_frame(void *env, uint64_t frame_addr) "env=%p frame_addr=0x%"PRIx64 +user_setup_rt_frame(void *env, uint64_t frame_addr) "env=%p frame_addr=0x%"PRIx64 +user_do_rt_sigreturn(void *env, uint64_t frame_addr) "env=%p frame_addr=0x%"PRIx64 +user_do_sigreturn(void *env, uint64_t frame_addr) "env=%p frame_addr=0x%"PRIx64 +user_dump_core_and_abort(void *env, int target_sig, int host_sig) "env=%p signal %d (host %d)" +user_handle_signal(void *env, int target_sig) "env=%p signal %d" +user_host_signal(void *env, int host_sig, int target_sig) "env=%p signal %d (target %d(" +user_queue_signal(void *env, int target_sig) "env=%p signal %d" diff --git a/bsd-user/trace.h b/bsd-user/trace.h new file mode 100644 index 0000000000..593c0204ad --- /dev/null +++ b/bsd-user/trace.h @@ -0,0 +1 @@ +#include "trace/trace-bsd_user.h" diff --git a/bsd-user/x86_64/signal.c b/bsd-user/x86_64/signal.c index 8885152a7d..c3875bc4c6 100644 --- a/bsd-user/x86_64/signal.c +++ b/bsd-user/x86_64/signal.c @@ -30,6 +30,19 @@ abi_long set_sigtramp_args(CPUX86State *regs, return 0; } +/* + * Compare to amd64/amd64/exec_machdep.c sendsig() + * Assumes that the memory is locked if frame points to user memory. + */ +abi_long setup_sigframe_arch(CPUX86State *env, abi_ulong frame_addr, + struct target_sigframe *frame, int flags) +{ + target_mcontext_t *mcp = &frame->sf_uc.uc_mcontext; + + get_mcontext(env, mcp, flags); + return 0; +} + /* Compare to amd64/amd64/machdep.c get_mcontext() */ abi_long get_mcontext(CPUX86State *regs, target_mcontext_t *mcp, int flags) diff --git a/bsd-user/x86_64/target_arch_cpu.h b/bsd-user/x86_64/target_arch_cpu.h index 5172b230f0..0a9c0f0894 100644 --- a/bsd-user/x86_64/target_arch_cpu.h +++ b/bsd-user/x86_64/target_arch_cpu.h @@ -20,6 +20,7 @@ #define _TARGET_ARCH_CPU_H_ #include "target_arch.h" +#include "signal-common.h" #define TARGET_DEFAULT_CPU_MODEL "qemu64" @@ -237,9 +238,9 @@ static inline void target_cpu_clone_regs(CPUX86State *env, target_ulong newsp) env->regs[R_EAX] = 0; } -static inline void target_cpu_reset(CPUArchState *cpu) +static inline void target_cpu_reset(CPUArchState *env) { - cpu_reset(env_cpu(cpu)); + cpu_reset(env_cpu(env)); } #endif /* ! _TARGET_ARCH_CPU_H_ */ diff --git a/meson.build b/meson.build index 5dbc9a7a36..5f43355071 100644 --- a/meson.build +++ b/meson.build @@ -2458,9 +2458,12 @@ trace_events_subdirs = [ 'monitor', 'util', ] -if have_user +if have_linux_user trace_events_subdirs += [ 'linux-user' ] endif +if have_bsd_user + trace_events_subdirs += [ 'bsd-user' ] +endif if have_block trace_events_subdirs += [ 'authz', @@ -2947,6 +2950,7 @@ foreach target : target_dirs if 'CONFIG_BSD_USER' in config_target base_dir = 'bsd-user' target_inc += include_directories('bsd-user/' / targetos) + target_inc += include_directories('bsd-user/host/' / host_arch) dir = base_dir / abi arch_srcs += files(dir / 'signal.c', dir / 'target_arch_cpu.c') endif