531 lines
14 KiB
C
531 lines
14 KiB
C
/* $NetBSD: mach_exception.c,v 1.14 2009/03/14 21:04:18 dsl Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2003 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Emmanuel Dreyfus
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: mach_exception.c,v 1.14 2009/03/14 21:04:18 dsl Exp $");
|
|
|
|
#include "opt_compat_darwin.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/signal.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#ifdef COMPAT_DARWIN
|
|
#include <compat/darwin/darwin_exec.h>
|
|
#endif
|
|
|
|
#include <compat/mach/mach_types.h>
|
|
#include <compat/mach/mach_exec.h>
|
|
#include <compat/mach/mach_errno.h>
|
|
#include <compat/mach/mach_thread.h>
|
|
#include <compat/mach/mach_exception.h>
|
|
#include <compat/mach/mach_message.h>
|
|
#include <compat/mach/mach_services.h>
|
|
#include <compat/mach/mach_sysctl.h>
|
|
|
|
#include <machine/mach_machdep.h>
|
|
|
|
static void mach_siginfo_to_exception(const struct ksiginfo *, int *);
|
|
|
|
/*
|
|
* Exception handler
|
|
* Mach does not use signals. But systems based on Mach (e.g.: Darwin),
|
|
* can use both Mach exceptions and UNIX signals. In order to allow the
|
|
* Mach layer to intercept the exception and inhibit UNIX signals, we have
|
|
* mach_trapsignal1 returning an error. If it returns 0, then the
|
|
* exception was intercepted at the Mach level, and no signal should
|
|
* be produced. Else, a signal might be sent. darwin_trapinfo calls
|
|
* mach_trapinfo1 and handle signals if it gets a non zero return value.
|
|
*/
|
|
void
|
|
mach_trapsignal(struct lwp *l, struct ksiginfo *ksi)
|
|
{
|
|
if (mach_trapsignal1(l, ksi) != 0)
|
|
trapsignal(l, ksi);
|
|
return;
|
|
}
|
|
|
|
int
|
|
mach_trapsignal1(struct lwp *l, struct ksiginfo *ksi)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
struct mach_emuldata *med;
|
|
int exc_no;
|
|
int code[2];
|
|
|
|
/* Don't inhinbit non maskable signals */
|
|
if (sigprop[ksi->ksi_signo] & SA_CANTMASK)
|
|
return EINVAL;
|
|
|
|
med = (struct mach_emuldata *)p->p_emuldata;
|
|
|
|
switch (ksi->ksi_signo) {
|
|
case SIGILL:
|
|
exc_no = MACH_EXC_BAD_INSTRUCTION;
|
|
break;
|
|
case SIGFPE:
|
|
exc_no = MACH_EXC_ARITHMETIC;
|
|
break;
|
|
case SIGSEGV:
|
|
case SIGBUS:
|
|
exc_no = MACH_EXC_BAD_ACCESS;
|
|
break;
|
|
case SIGTRAP:
|
|
exc_no = MACH_EXC_BREAKPOINT;
|
|
break;
|
|
default: /* SIGCHLD, SIGPOLL */
|
|
return EINVAL;
|
|
break;
|
|
}
|
|
|
|
mach_siginfo_to_exception(ksi, code);
|
|
|
|
return mach_exception(l, exc_no, code);
|
|
}
|
|
|
|
int
|
|
mach_exception(struct lwp *exc_l, int exc, int *code)
|
|
/* exc_l: currently running lwp */
|
|
{
|
|
int behavior, flavor;
|
|
mach_msg_header_t *msgh;
|
|
size_t msglen;
|
|
struct mach_right *exc_mr;
|
|
struct mach_emuldata *exc_med;
|
|
struct mach_lwp_emuldata *exc_mle;
|
|
struct mach_emuldata *catcher_med;
|
|
struct mach_right *kernel_mr;
|
|
struct lwp *catcher_l; /* The lwp catching the exception */
|
|
struct mach_right *exc_task;
|
|
struct mach_right *exc_thread;
|
|
struct mach_port *exc_port;
|
|
struct mach_exc_info *mei;
|
|
int error = 0;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
if (exc_l == NULL) {
|
|
printf("mach_exception: exc_l = %p\n", exc_l);
|
|
return ESRCH;
|
|
}
|
|
#endif
|
|
#ifdef DEBUG_MACH
|
|
printf("mach_exception: %d.%d, exc %d, code (%d, %d)\n",
|
|
exc_l->l_proc->p_pid, exc_l->l_lid, exc, code[0], code[1]);
|
|
#endif
|
|
|
|
/*
|
|
* It's extremely useful to have the ability of catching
|
|
* the process at the time it dies.
|
|
*/
|
|
if (mach_exception_hang) {
|
|
struct proc *p = exc_l->l_proc;
|
|
|
|
sigminusset(&contsigmask, &exc_l->l_sigpendset->sp_set);
|
|
lwp_lock(exc_l);
|
|
p->p_pptr->p_nstopchild++;
|
|
p->p_stat = SSTOP;
|
|
exc_l->l_stat = LSSTOP;
|
|
p->p_nrlwps--;
|
|
KERNEL_UNLOCK_ALL(exc_l, &exc_l->l_biglocks);
|
|
mi_switch(exc_l);
|
|
KERNEL_LOCK(exc_l->l_biglocks, exc_l);
|
|
}
|
|
|
|
/*
|
|
* No exception if there is no exception port or if it has no receiver
|
|
*/
|
|
exc_mle = exc_l->l_emuldata;
|
|
exc_med = exc_l->l_proc->p_emuldata;
|
|
if ((exc_port = exc_med->med_exc[exc]) == NULL)
|
|
return EINVAL;
|
|
|
|
MACH_PORT_REF(exc_port);
|
|
if (exc_port->mp_recv == NULL) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
#ifdef DEBUG_MACH
|
|
printf("catcher is %d.%d, state %d\n",
|
|
exc_port->mp_recv->mr_lwp->l_proc->p_pid,
|
|
exc_port->mp_recv->mr_lwp->l_lid,
|
|
exc_port->mp_recv->mr_lwp->l_proc->p_stat);
|
|
#endif
|
|
/*
|
|
* Don't send exceptions to dying processes
|
|
*/
|
|
if (P_ZOMBIE(exc_port->mp_recv->mr_lwp->l_proc)) {
|
|
error = ESRCH;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* XXX Avoid a nasty deadlock because process in TX state
|
|
* (traced and suspended) are invulnerable to kill -9.
|
|
*
|
|
* The scenario:
|
|
* - the parent gets Child's signals though Mach exceptions
|
|
* - the parent is killed. Before calling the emulation hook
|
|
* mach_exit(), it will wait for the child
|
|
* - the child receives SIGHUP, which is turned into a Mach
|
|
* exception. The child sleeps awaiting for the parent
|
|
* to tell it to continue.
|
|
* For some reason I do not understand, it goes in the
|
|
* suspended state instead of the sleeping state.
|
|
* - Parents waits for the child, child is suspended, we
|
|
* are stuck.
|
|
*
|
|
* By preventing exception to traced processes with
|
|
* a dying parent, a signal is sent instead of the
|
|
* notification, this fixes the problem.
|
|
*/
|
|
if ((exc_l->l_proc->p_slflag & PSL_TRACED) &&
|
|
(exc_l->l_proc->p_pptr->p_sflag & PS_WEXIT)) {
|
|
#ifdef DEBUG_MACH
|
|
printf("mach_exception: deadlock avoided\n");
|
|
#endif
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (exc_port->mp_datatype != MACH_MP_EXC_INFO) {
|
|
#ifdef DIAGNOSTIC
|
|
printf("mach_exception: unexpected datatype");
|
|
#endif
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
mei = exc_port->mp_data;
|
|
behavior = mei->mei_behavior;
|
|
flavor = mei->mei_flavor;
|
|
|
|
/*
|
|
* We want the port names in the target process, that is,
|
|
* the process with receive right for exc_port.
|
|
*/
|
|
catcher_l = exc_port->mp_recv->mr_lwp;
|
|
catcher_med = catcher_l->l_proc->p_emuldata;
|
|
exc_mr = mach_right_get(exc_port, catcher_l, MACH_PORT_TYPE_SEND, 0);
|
|
kernel_mr = mach_right_get(catcher_med->med_kernel,
|
|
catcher_l, MACH_PORT_TYPE_SEND, 0);
|
|
|
|
exc_task = mach_right_get(exc_med->med_kernel,
|
|
catcher_l, MACH_PORT_TYPE_SEND, 0);
|
|
exc_thread = mach_right_get(exc_mle->mle_kernel,
|
|
catcher_l, MACH_PORT_TYPE_SEND, 0);
|
|
|
|
switch (behavior) {
|
|
case MACH_EXCEPTION_DEFAULT: {
|
|
mach_exception_raise_request_t *req;
|
|
|
|
req = malloc(sizeof(*req), M_EMULDATA, M_WAITOK | M_ZERO);
|
|
msglen = sizeof(*req);
|
|
msgh = (mach_msg_header_t *)req;
|
|
|
|
req->req_msgh.msgh_bits =
|
|
MACH_MSGH_REPLY_LOCAL_BITS(MACH_MSG_TYPE_MOVE_SEND) |
|
|
MACH_MSGH_REMOTE_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE);
|
|
req->req_msgh.msgh_size =
|
|
sizeof(*req) - sizeof(req->req_trailer);
|
|
req->req_msgh.msgh_remote_port = kernel_mr->mr_name;
|
|
req->req_msgh.msgh_local_port = exc_mr->mr_name;
|
|
req->req_msgh.msgh_id = MACH_EXC_RAISE_MSGID;
|
|
|
|
mach_add_port_desc(req, exc_thread->mr_name);
|
|
mach_add_port_desc(req, exc_task->mr_name);
|
|
|
|
req->req_exc = exc;
|
|
req->req_codecount = 2;
|
|
memcpy(&req->req_code[0], code, sizeof(req->req_code));
|
|
|
|
mach_set_trailer(req, msglen);
|
|
|
|
break;
|
|
}
|
|
|
|
case MACH_EXCEPTION_STATE: {
|
|
mach_exception_raise_state_request_t *req;
|
|
int dc;
|
|
|
|
req = malloc(sizeof(*req), M_EMULDATA, M_WAITOK | M_ZERO);
|
|
msglen = sizeof(*req);
|
|
msgh = (mach_msg_header_t *)req;
|
|
|
|
req->req_msgh.msgh_bits =
|
|
MACH_MSGH_REPLY_LOCAL_BITS(MACH_MSG_TYPE_MOVE_SEND) |
|
|
MACH_MSGH_REMOTE_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE);
|
|
req->req_msgh.msgh_size =
|
|
sizeof(*req) - sizeof(req->req_trailer);
|
|
req->req_msgh.msgh_remote_port = kernel_mr->mr_name;
|
|
req->req_msgh.msgh_local_port = exc_mr->mr_name;
|
|
req->req_msgh.msgh_id = MACH_EXCEPTION_STATE;
|
|
req->req_exc = exc;
|
|
req->req_codecount = 2;
|
|
memcpy(&req->req_code[0], code, sizeof(req->req_code));
|
|
req->req_flavor = flavor;
|
|
mach_thread_get_state_machdep(exc_l,
|
|
flavor, req->req_state, &dc);
|
|
|
|
msglen = msglen -
|
|
sizeof(req->req_state) +
|
|
(dc * sizeof(req->req_state[0]));
|
|
mach_set_trailer(req, msglen);
|
|
|
|
break;
|
|
}
|
|
|
|
case MACH_EXCEPTION_STATE_IDENTITY: {
|
|
mach_exception_raise_state_identity_request_t *req;
|
|
int dc;
|
|
|
|
req = malloc(sizeof(*req), M_EMULDATA, M_WAITOK | M_ZERO);
|
|
msglen = sizeof(*req);
|
|
msgh = (mach_msg_header_t *)req;
|
|
|
|
req->req_msgh.msgh_bits =
|
|
MACH_MSGH_REPLY_LOCAL_BITS(MACH_MSG_TYPE_MOVE_SEND) |
|
|
MACH_MSGH_REMOTE_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE);
|
|
req->req_msgh.msgh_size =
|
|
sizeof(*req) - sizeof(req->req_trailer);
|
|
req->req_msgh.msgh_remote_port = kernel_mr->mr_name;
|
|
req->req_msgh.msgh_local_port = exc_mr->mr_name;
|
|
req->req_msgh.msgh_id = MACH_EXC_RAISE_STATE_IDENTITY_MSGID;
|
|
req->req_body.msgh_descriptor_count = 2;
|
|
|
|
mach_add_port_desc(req, exc_thread->mr_name);
|
|
mach_add_port_desc(req, exc_task->mr_name);
|
|
|
|
req->req_exc = exc;
|
|
req->req_codecount = 2;
|
|
memcpy(&req->req_code[0], code, sizeof(req->req_code));
|
|
req->req_flavor = flavor;
|
|
mach_thread_get_state_machdep(exc_l,
|
|
flavor, req->req_state, &dc);
|
|
|
|
msglen = msglen -
|
|
sizeof(req->req_state) +
|
|
(dc * sizeof(req->req_state[0]));
|
|
|
|
mach_set_trailer(req, msglen);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
printf("unknown exception bevahior %d\n", behavior);
|
|
error = EINVAL;
|
|
goto out;
|
|
break;
|
|
}
|
|
|
|
mach_set_trailer(msgh, msglen);
|
|
|
|
/*
|
|
* Once an exception is sent on the exception port,
|
|
* no new exception will be taken until the catcher
|
|
* acknowledge the first one.
|
|
*/
|
|
rw_enter(&catcher_med->med_exclock, RW_WRITER);
|
|
|
|
/*
|
|
* If the catcher died, we are done.
|
|
*/
|
|
if (((exc_port = exc_med->med_exc[exc]) == NULL) ||
|
|
(exc_port->mp_recv == NULL) ||
|
|
(P_ZOMBIE(exc_port->mp_recv->mr_lwp->l_proc))) {
|
|
error = ESRCH;
|
|
goto out;
|
|
}
|
|
|
|
(void)mach_message_get(msgh, msglen, exc_port, NULL);
|
|
wakeup(exc_port->mp_recv->mr_sethead);
|
|
|
|
/*
|
|
* The thread that caused the exception is now
|
|
* supposed to wait for a reply to its message.
|
|
*/
|
|
#ifdef DEBUG_MACH
|
|
printf("mach_exception: %d.%d sleep on catcher_med->med_exclock = %p\n",
|
|
exc_l->l_proc->p_pid, exc_l->l_lid, &catcher_med->med_exclock);
|
|
#endif
|
|
error = tsleep(&catcher_med->med_exclock, PZERO, "mach_exc", 0);
|
|
#ifdef DEBUG_MACH
|
|
printf("mach_exception: %d.%d resumed, error = %d\n",
|
|
exc_l->l_proc->p_pid, exc_l->l_lid, error);
|
|
#endif
|
|
|
|
/*
|
|
* Unlock the catcher's exception handler
|
|
*/
|
|
rw_exit(&catcher_med->med_exclock);
|
|
|
|
out:
|
|
MACH_PORT_UNREF(exc_port);
|
|
return error;
|
|
}
|
|
|
|
static void
|
|
mach_siginfo_to_exception(const struct ksiginfo *ksi, int *code)
|
|
{
|
|
code[1] = (long)ksi->ksi_addr;
|
|
switch (ksi->ksi_signo) {
|
|
case SIGBUS:
|
|
switch (ksi->ksi_code) {
|
|
case BUS_ADRALN:
|
|
code[0] = MACH_BUS_ADRALN;
|
|
break;
|
|
default:
|
|
printf("untranslated siginfo signo %d, code %d\n",
|
|
ksi->ksi_signo, ksi->ksi_code);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SIGSEGV:
|
|
switch (ksi->ksi_code) {
|
|
case SEGV_MAPERR:
|
|
code[0] = MACH_SEGV_MAPERR;
|
|
break;
|
|
case SEGV_ACCERR:
|
|
code[0] = MACH_SEGV_ACCERR;
|
|
break;
|
|
default:
|
|
printf("untranslated siginfo signo %d, code %d\n",
|
|
ksi->ksi_signo, ksi->ksi_code);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SIGTRAP:
|
|
switch (ksi->ksi_code) {
|
|
case TRAP_BRKPT:
|
|
code[0] = MACH_TRAP_BRKPT;
|
|
code[1] = (long)ksi->ksi_addr;
|
|
break;
|
|
default:
|
|
printf("untranslated siginfo signo %d, code %d\n",
|
|
ksi->ksi_signo, ksi->ksi_code);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SIGILL:
|
|
switch (ksi->ksi_code) {
|
|
case ILL_ILLOPC:
|
|
case ILL_ILLOPN:
|
|
case ILL_ILLADR:
|
|
code[0] = MACH_ILL_ILLOPC;
|
|
break;
|
|
case ILL_PRVOPC:
|
|
case ILL_PRVREG:
|
|
code[0] = MACH_ILL_PRVOPC;
|
|
break;
|
|
case ILL_ILLTRP:
|
|
code[0] = MACH_ILL_ILLTRP;
|
|
break;
|
|
default:
|
|
printf("untranslated siginfo signo %d, code %d\n",
|
|
ksi->ksi_signo, ksi->ksi_code);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
printf("untranslated siginfo signo %d, code %d\n",
|
|
ksi->ksi_signo, ksi->ksi_code);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int
|
|
mach_exception_raise(struct mach_trap_args *args)
|
|
{
|
|
struct lwp *l = args->l;
|
|
mach_exception_raise_reply_t *rep;
|
|
struct mach_emuldata *med;
|
|
|
|
/*
|
|
* No typo here: the reply is in the sent message.
|
|
* The kernel is acting as a client that gets the
|
|
* reply message to its exception message.
|
|
*/
|
|
rep = args->smsg;
|
|
|
|
/*
|
|
* This message is sent by the process catching the
|
|
* exception to release the process that raised the exception.
|
|
* We wake it up if the return value is 0 (no error), else
|
|
* we should ignore this message.
|
|
*/
|
|
#ifdef DEBUG_MACH
|
|
printf("mach_excpetion_raise: retval = %ld\n", (long)rep->rep_retval);
|
|
#endif
|
|
if (rep->rep_retval != 0)
|
|
return 0;
|
|
|
|
med = l->l_proc->p_emuldata;
|
|
|
|
/*
|
|
* Check for unexpected exception acknowledge, whereas
|
|
* the kernel sent no exception message.
|
|
*/
|
|
if (!rw_lock_held(&med->med_exclock)) {
|
|
#ifdef DEBUG_MACH
|
|
printf("spurious mach_exception_raise\n");
|
|
#endif
|
|
return mach_msg_error(args, EINVAL);
|
|
}
|
|
|
|
/*
|
|
* Wakeup the thread that raised the exception.
|
|
*/
|
|
#ifdef DEBUG_MACH
|
|
printf("mach_exception_raise: wakeup at %p\n", &med->med_exclock);
|
|
#endif
|
|
wakeup(&med->med_exclock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
mach_exception_raise_state(struct mach_trap_args *args)
|
|
{
|
|
return mach_exception_raise(args);
|
|
}
|
|
|
|
int
|
|
mach_exception_raise_state_identity(struct mach_trap_args *args)
|
|
{
|
|
return mach_exception_raise(args);
|
|
}
|