blob: 2f2aa89feb334f3c86ba82168a92f81b2ab2d911 [file] [log] [blame]
/*
* Emulation of Linux signals
*
* Copyright (c) 2003 Fabrice Bellard
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <sys/ucontext.h>
#include <sys/resource.h>
#include "qemu.h"
#include "qemu-common.h"
#include "signal-common.h"
#include "target_signal.h"
//#define DEBUG_SIGNAL
struct target_sigaltstack target_sigaltstack_used = {
.ss_sp = 0,
.ss_size = 0,
.ss_flags = TARGET_SS_DISABLE,
};
static struct target_sigaction sigact_table[TARGET_NSIG];
static void host_signal_handler(int host_signum, siginfo_t *info,
void *puc);
static uint8_t host_to_target_signal_table[_NSIG] = {
[SIGHUP] = TARGET_SIGHUP,
[SIGINT] = TARGET_SIGINT,
[SIGQUIT] = TARGET_SIGQUIT,
[SIGILL] = TARGET_SIGILL,
[SIGTRAP] = TARGET_SIGTRAP,
[SIGABRT] = TARGET_SIGABRT,
/* [SIGIOT] = TARGET_SIGIOT,*/
[SIGBUS] = TARGET_SIGBUS,
[SIGFPE] = TARGET_SIGFPE,
[SIGKILL] = TARGET_SIGKILL,
[SIGUSR1] = TARGET_SIGUSR1,
[SIGSEGV] = TARGET_SIGSEGV,
[SIGUSR2] = TARGET_SIGUSR2,
[SIGPIPE] = TARGET_SIGPIPE,
[SIGALRM] = TARGET_SIGALRM,
[SIGTERM] = TARGET_SIGTERM,
#ifdef SIGSTKFLT
[SIGSTKFLT] = TARGET_SIGSTKFLT,
#endif
[SIGCHLD] = TARGET_SIGCHLD,
[SIGCONT] = TARGET_SIGCONT,
[SIGSTOP] = TARGET_SIGSTOP,
[SIGTSTP] = TARGET_SIGTSTP,
[SIGTTIN] = TARGET_SIGTTIN,
[SIGTTOU] = TARGET_SIGTTOU,
[SIGURG] = TARGET_SIGURG,
[SIGXCPU] = TARGET_SIGXCPU,
[SIGXFSZ] = TARGET_SIGXFSZ,
[SIGVTALRM] = TARGET_SIGVTALRM,
[SIGPROF] = TARGET_SIGPROF,
[SIGWINCH] = TARGET_SIGWINCH,
[SIGIO] = TARGET_SIGIO,
[SIGPWR] = TARGET_SIGPWR,
[SIGSYS] = TARGET_SIGSYS,
/* next signals stay the same */
/* Nasty hack: Reverse SIGRTMIN and SIGRTMAX to avoid overlap with
host libpthread signals. This assumes no one actually uses SIGRTMAX :-/
To fix this properly we need to do manual signal delivery multiplexed
over a single host signal. */
[__SIGRTMIN] = __SIGRTMAX,
[__SIGRTMAX] = __SIGRTMIN,
};
static uint8_t target_to_host_signal_table[_NSIG];
int host_to_target_signal(int sig)
{
if (sig >= _NSIG)
return sig;
return host_to_target_signal_table[sig];
}
int target_to_host_signal(int sig)
{
if (sig >= _NSIG)
return sig;
return target_to_host_signal_table[sig];
}
static inline void target_sigaddset(target_sigset_t *set, int signum)
{
signum--;
abi_ulong mask = (abi_ulong)1 << (signum % TARGET_NSIG_BPW);
set->sig[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->sig[signum / TARGET_NSIG_BPW] & mask) != 0);
}
static void host_to_target_sigset_internal(target_sigset_t *d,
const sigset_t *s)
{
int i;
target_sigemptyset(d);
for (i = 1; i <= TARGET_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 < TARGET_NSIG_WORDS; i++)
d->sig[i] = tswapal(d1.sig[i]);
}
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.sig[i] = tswapal(s->sig[i]);
target_to_host_sigset_internal(d, &s1);
}
void host_to_target_old_sigset(abi_ulong *old_sigset,
const sigset_t *sigset)
{
target_sigset_t d;
host_to_target_sigset(&d, sigset);
*old_sigset = d.sig[0];
}
void target_to_host_old_sigset(sigset_t *sigset,
const abi_ulong *old_sigset)
{
target_sigset_t d;
int i;
d.sig[0] = *old_sigset;
for(i = 1;i < TARGET_NSIG_WORDS; i++)
d.sig[i] = 0;
target_to_host_sigset(sigset, &d);
}
/* siginfo conversion */
static inline void host_to_target_siginfo_noswap(target_siginfo_t *tinfo,
const siginfo_t *info)
{
int sig;
sig = host_to_target_signal(info->si_signo);
tinfo->si_signo = sig;
tinfo->si_errno = 0;
tinfo->si_code = info->si_code;
if (sig == SIGILL || sig == SIGFPE || sig == SIGSEGV ||
sig == SIGBUS || sig == SIGTRAP) {
/* should never come here, but who knows. The information for
the target is irrelevant */
tinfo->_sifields._sigfault._addr = 0;
} else if (sig == SIGIO) {
tinfo->_sifields._sigpoll._fd = info->si_fd;
} else if (sig >= TARGET_SIGRTMIN) {
tinfo->_sifields._rt._pid = info->si_pid;
tinfo->_sifields._rt._uid = info->si_uid;
/* XXX: potential problem if 64 bit */
tinfo->_sifields._rt._sigval.sival_ptr =
(abi_ulong)(unsigned long)info->si_value.sival_ptr;
}
}
void tswap_siginfo(target_siginfo_t *tinfo,
const target_siginfo_t *info)
{
int sig;
sig = info->si_signo;
tinfo->si_signo = tswap32(sig);
tinfo->si_errno = tswap32(info->si_errno);
tinfo->si_code = tswap32(info->si_code);
if (sig == SIGILL || sig == SIGFPE || sig == SIGSEGV ||
sig == SIGBUS || sig == SIGTRAP) {
tinfo->_sifields._sigfault._addr =
tswapal(info->_sifields._sigfault._addr);
} else if (sig == SIGIO) {
tinfo->_sifields._sigpoll._fd = tswap32(info->_sifields._sigpoll._fd);
} else if (sig >= TARGET_SIGRTMIN) {
tinfo->_sifields._rt._pid = tswap32(info->_sifields._rt._pid);
tinfo->_sifields._rt._uid = tswap32(info->_sifields._rt._uid);
tinfo->_sifields._rt._sigval.sival_ptr =
tswapal(info->_sifields._rt._sigval.sival_ptr);
}
}
void host_to_target_siginfo(target_siginfo_t *tinfo, const siginfo_t *info)
{
host_to_target_siginfo_noswap(tinfo, info);
tswap_siginfo(tinfo, tinfo);
}
/* XXX: we support only POSIX RT signals are used. */
/* XXX: find a solution for 64 bit (additional malloced data is needed) */
void target_to_host_siginfo(siginfo_t *info, const target_siginfo_t *tinfo)
{
info->si_signo = tswap32(tinfo->si_signo);
info->si_errno = tswap32(tinfo->si_errno);
info->si_code = tswap32(tinfo->si_code);
info->si_pid = tswap32(tinfo->_sifields._rt._pid);
info->si_uid = tswap32(tinfo->_sifields._rt._uid);
info->si_value.sival_ptr =
(void *)(long)tswapal(tinfo->_sifields._rt._sigval.sival_ptr);
}
static int fatal_signal (int sig)
{
switch (sig) {
case TARGET_SIGCHLD:
case TARGET_SIGURG:
case TARGET_SIGWINCH:
/* 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;
}
}
/* 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);
}
}
void signal_init(void)
{
struct sigaction act;
struct sigaction oact;
int i, j;
int host_sig;
/* generate signal conversion tables */
for(i = 1; i < _NSIG; i++) {
if (host_to_target_signal_table[i] == 0)
host_to_target_signal_table[i] = i;
}
for(i = 1; i < _NSIG; i++) {
j = host_to_target_signal_table[i];
target_to_host_signal_table[j] = i;
}
/* set all host signal handlers. ALL signals are blocked during
the handlers to serialize them. */
memset(sigact_table, 0, sizeof(sigact_table));
sigfillset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = host_signal_handler;
for(i = 1; i <= TARGET_NSIG; i++) {
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);
}
}
/* signal queue handling */
static inline struct sigqueue *alloc_sigqueue(CPUState *env)
{
TaskState *ts = env->opaque;
struct sigqueue *q = ts->first_free;
if (!q)
return NULL;
ts->first_free = q->next;
return q;
}
static inline void free_sigqueue(CPUState *env, struct sigqueue *q)
{
TaskState *ts = env->opaque;
q->next = ts->first_free;
ts->first_free = q;
}
/* abort execution with signal */
void QEMU_NORETURN force_sig(int target_sig)
{
TaskState *ts = (TaskState *)thread_env->opaque;
int host_sig, core_dumped = 0;
struct sigaction act;
host_sig = target_to_host_signal(target_sig);
gdb_signalled(thread_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, thread_env) == 0);
}
if (core_dumped) {
/* we already dumped the core of target process, we don't want
* a coredump of qemu itself */
struct rlimit nodump;
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
* -<signal>. 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. */
sigfillset(&act.sa_mask);
act.sa_handler = SIG_DFL;
sigaction(host_sig, &act, NULL);
/* For some reason raise(host_sig) doesn't send the signal when
* statically linked on x86-64. */
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 */
int queue_signal(CPUState *env, int sig, target_siginfo_t *info)
{
TaskState *ts = env->opaque;
struct emulated_sigtable *k;
struct sigqueue *q, **pq;
abi_ulong handler;
int queue;
#if defined(DEBUG_SIGNAL)
fprintf(stderr, "queue_signal: sig=%d\n",
sig);
#endif
k = &ts->sigtab[sig - 1];
queue = gdb_queuesig ();
handler = sigact_table[sig - 1]._sa_handler;
if (!queue && handler == TARGET_SIG_DFL) {
if (sig == TARGET_SIGTSTP || sig == TARGET_SIGTTIN || sig == TARGET_SIGTTOU) {
kill(getpid(),SIGSTOP);
return 0;
} else
/* default handler : ignore some signal. The other are fatal */
if (sig != TARGET_SIGCHLD &&
sig != TARGET_SIGURG &&
sig != TARGET_SIGWINCH &&
sig != TARGET_SIGCONT) {
force_sig(sig);
} else {
return 0; /* indicate ignored */
}
} else if (!queue && handler == TARGET_SIG_IGN) {
/* ignore signal */
return 0;
} else if (!queue && handler == TARGET_SIG_ERR) {
force_sig(sig);
} else {
pq = &k->first;
if (sig < TARGET_SIGRTMIN) {
/* if non real time signal, we queue exactly one signal */
if (!k->pending)
q = &k->info;
else
return 0;
} else {
if (!k->pending) {
/* first signal */
q = &k->info;
} else {
q = alloc_sigqueue(env);
if (!q)
return -EAGAIN;
while (*pq != NULL)
pq = &(*pq)->next;
}
}
*pq = q;
q->info = *info;
q->next = NULL;
k->pending = 1;
/* signal that a new signal is pending */
ts->signal_pending = 1;
return 1; /* indicates that the signal was queued */
}
}
static void host_signal_handler(int host_signum, siginfo_t *info,
void *puc)
{
int sig;
target_siginfo_t tinfo;
/* the CPU emulator uses some host signals to detect exceptions,
we forward to it some signals */
if ((host_signum == SIGSEGV || host_signum == SIGBUS)
&& info->si_code > 0) {
if (cpu_signal_handler(host_signum, info, puc))
return;
}
/* get target signal number */
sig = host_to_target_signal(host_signum);
if (sig < 1 || sig > TARGET_NSIG)
return;
#if defined(DEBUG_SIGNAL)
fprintf(stderr, "qemu: got signal %d\n", sig);
#endif
host_to_target_siginfo_noswap(&tinfo, info);
if (queue_signal(thread_env, sig, &tinfo) == 1) {
/* interrupt the virtual CPU as soon as possible */
cpu_exit(thread_env);
}
}
/* do_sigaltstack() returns target values and errnos. */
/* compare linux/kernel/signal.c:do_sigaltstack() */
abi_long do_sigaltstack(abi_ulong uss_addr, abi_ulong uoss_addr, abi_ulong sp)
{
int ret;
struct target_sigaltstack oss;
/* XXX: test errors */
if(uoss_addr)
{
__put_user(target_sigaltstack_used.ss_sp, &oss.ss_sp);
__put_user(target_sigaltstack_used.ss_size, &oss.ss_size);
__put_user(sas_ss_flags(sp), &oss.ss_flags);
}
if(uss_addr)
{
struct target_sigaltstack *uss;
struct target_sigaltstack ss;
ret = -TARGET_EFAULT;
if (!lock_user_struct(VERIFY_READ, uss, uss_addr, 1)
|| __get_user(ss.ss_sp, &uss->ss_sp)
|| __get_user(ss.ss_size, &uss->ss_size)
|| __get_user(ss.ss_flags, &uss->ss_flags))
goto out;
unlock_user_struct(uss, uss_addr, 0);
ret = -TARGET_EPERM;
if (on_sig_stack(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 < MINSIGSTKSZ)
goto out;
}
target_sigaltstack_used.ss_sp = ss.ss_sp;
target_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 || sig == TARGET_SIGKILL || sig == TARGET_SIGSTOP)
return -EINVAL;
k = &sigact_table[sig - 1];
#if defined(DEBUG_SIGNAL)
fprintf(stderr, "sigaction sig=%d act=0x%p, oact=0x%p\n",
sig, act, oact);
#endif
if (oact) {
oact->_sa_handler = tswapal(k->_sa_handler);
oact->sa_flags = tswapal(k->sa_flags);
#if !defined(TARGET_MIPS)
oact->sa_restorer = tswapal(k->sa_restorer);
#endif
oact->sa_mask = k->sa_mask;
}
if (act) {
/* FIXME: This is not threadsafe. */
k->_sa_handler = tswapal(act->_sa_handler);
k->sa_flags = tswapal(act->sa_flags);
#if !defined(TARGET_MIPS)
k->sa_restorer = tswapal(act->sa_restorer);
#endif
k->sa_mask = act->sa_mask;
/* we update the host linux signal state */
host_sig = target_to_host_signal(sig);
if (host_sig != SIGSEGV && host_sig != SIGBUS) {
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
ignore state to avoid getting unexpected interrupted
syscalls */
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;
}
void process_pending_signals(CPUState *cpu_env)
{
int sig;
abi_ulong handler;
sigset_t set, old_set;
target_sigset_t target_old_set;
struct emulated_sigtable *k;
struct target_sigaction *sa;
struct sigqueue *q;
TaskState *ts = cpu_env->opaque;
if (!ts->signal_pending)
return;
/* FIXME: This is not threadsafe. */
k = ts->sigtab;
for(sig = 1; sig <= TARGET_NSIG; sig++) {
if (k->pending)
goto handle_signal;
k++;
}
/* if no signal is pending, just return */
ts->signal_pending = 0;
return;
handle_signal:
#ifdef DEBUG_SIGNAL
fprintf(stderr, "qemu: process signal %d\n", sig);
#endif
/* dequeue signal */
q = k->first;
k->first = q->next;
if (!k->first)
k->pending = 0;
sig = gdb_handlesig (cpu_env, sig);
if (!sig) {
sa = NULL;
handler = TARGET_SIG_IGN;
} else {
sa = &sigact_table[sig - 1];
handler = sa->_sa_handler;
}
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_SIGWINCH &&
sig != TARGET_SIGCONT) {
force_sig(sig);
}
} else if (handler == TARGET_SIG_IGN) {
/* ignore sig */
} else if (handler == TARGET_SIG_ERR) {
force_sig(sig);
} else {
/* compute the blocked signals during the handler execution */
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));
/* block signals in the handler using Linux */
sigprocmask(SIG_BLOCK, &set, &old_set);
/* 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, &old_set);
/* if the CPU is in VM86 mode, we restore the 32 bit values */
#if defined(TARGET_I386) && !defined(TARGET_X86_64)
{
CPUX86State *env = cpu_env;
if (env->eflags & VM_MASK)
save_v86_state(env);
}
#endif
/* prepare the stack frame of the virtual CPU */
if (sa->sa_flags & TARGET_SA_SIGINFO)
setup_rt_frame(sig, sa, &q->info, &target_old_set, cpu_env);
else
setup_frame(sig, sa, &target_old_set, cpu_env);
if (sa->sa_flags & TARGET_SA_RESETHAND)
sa->_sa_handler = TARGET_SIG_DFL;
}
if (q != &k->info)
free_sigqueue(cpu_env, q);
}