61c9c4212d
John Marshall pointed out that darwin-nat.c fails to build: CXX darwin-nat.o ../../../binutils-gdb/gdb/darwin-nat.c:1709:8: error: must use 'class' tag to refer to type 'thread_info' in this scope for (thread_info *it : all_threads ()) /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/mach/thread_act.h:240:15: note: class 'thread_info' is hidden by a non-type declaration of 'thread_info' here kern_return_t thread_info Mach has a thread_info() function declared in that header, which darwin-nat.c #includes. This patch fixes the problem by reintroducing the struct keyword. gdb/ChangeLog 2019-05-16 Tom Tromey <tromey@adacore.com> * darwin-nat.c (thread_info_from_private_thread_info): Add struct keyword to foreach.
2521 lines
68 KiB
C
2521 lines
68 KiB
C
/* Darwin support for GDB, the GNU debugger.
|
||
Copyright (C) 2008-2019 Free Software Foundation, Inc.
|
||
|
||
Contributed by AdaCore.
|
||
|
||
This file is part of GDB.
|
||
|
||
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 3 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 "defs.h"
|
||
#include "top.h"
|
||
#include "inferior.h"
|
||
#include "target.h"
|
||
#include "symfile.h"
|
||
#include "symtab.h"
|
||
#include "objfiles.h"
|
||
#include "gdbcmd.h"
|
||
#include "gdbcore.h"
|
||
#include "gdbthread.h"
|
||
#include "regcache.h"
|
||
#include "event-top.h"
|
||
#include "inf-loop.h"
|
||
#include <sys/stat.h>
|
||
#include "inf-child.h"
|
||
#include "value.h"
|
||
#include "arch-utils.h"
|
||
#include "bfd.h"
|
||
#include "bfd/mach-o.h"
|
||
|
||
#include <copyfile.h>
|
||
#include <sys/ptrace.h>
|
||
#include <sys/signal.h>
|
||
#include <setjmp.h>
|
||
#include <sys/types.h>
|
||
#include <unistd.h>
|
||
#include <signal.h>
|
||
#include <ctype.h>
|
||
#include <sys/sysctl.h>
|
||
#include <sys/proc.h>
|
||
#include <libproc.h>
|
||
#include <sys/syscall.h>
|
||
#include <spawn.h>
|
||
|
||
#include <mach/mach_error.h>
|
||
#include <mach/mach_vm.h>
|
||
#include <mach/mach_init.h>
|
||
#include <mach/vm_map.h>
|
||
#include <mach/task.h>
|
||
#include <mach/mach_port.h>
|
||
#include <mach/thread_act.h>
|
||
#include <mach/port.h>
|
||
|
||
#include "darwin-nat.h"
|
||
#include "filenames.h"
|
||
#include "common/filestuff.h"
|
||
#include "common/gdb_unlinker.h"
|
||
#include "common/pathstuff.h"
|
||
#include "common/scoped_fd.h"
|
||
#include "nat/fork-inferior.h"
|
||
|
||
/* Quick overview.
|
||
Darwin kernel is Mach + BSD derived kernel. Note that they share the
|
||
same memory space and are linked together (ie there is no micro-kernel).
|
||
|
||
Although ptrace(2) is available on Darwin, it is not complete. We have
|
||
to use Mach calls to read and write memory and to modify registers. We
|
||
also use Mach to get inferior faults. As we cannot use select(2) or
|
||
signals with Mach port (the Mach communication channel), signals are
|
||
reported to gdb as an exception. Furthermore we detect death of the
|
||
inferior through a Mach notification message. This way we only wait
|
||
on Mach ports.
|
||
|
||
Some Mach documentation is available for Apple xnu source package or
|
||
from the web. */
|
||
|
||
|
||
#define PTRACE(CMD, PID, ADDR, SIG) \
|
||
darwin_ptrace(#CMD, CMD, (PID), (ADDR), (SIG))
|
||
|
||
static ptid_t darwin_wait (ptid_t ptid, struct target_waitstatus *status);
|
||
|
||
static void darwin_ptrace_me (void);
|
||
|
||
static void darwin_ptrace_him (int pid);
|
||
|
||
static void darwin_encode_reply (mig_reply_error_t *reply,
|
||
mach_msg_header_t *hdr, integer_t code);
|
||
|
||
static void darwin_setup_request_notification (struct inferior *inf);
|
||
static void darwin_deallocate_exception_ports (darwin_inferior *inf);
|
||
static void darwin_setup_exceptions (struct inferior *inf);
|
||
static void darwin_deallocate_threads (struct inferior *inf);
|
||
|
||
/* Task identifier of gdb. */
|
||
static task_t gdb_task;
|
||
|
||
/* A copy of mach_host_self (). */
|
||
mach_port_t darwin_host_self;
|
||
|
||
/* Exception port. */
|
||
mach_port_t darwin_ex_port;
|
||
|
||
/* Port set, to wait for answer on all ports. */
|
||
mach_port_t darwin_port_set;
|
||
|
||
/* Page size. */
|
||
static vm_size_t mach_page_size;
|
||
|
||
/* If Set, catch all mach exceptions (before they are converted to signals
|
||
by the kernel). */
|
||
static int enable_mach_exceptions;
|
||
|
||
/* Inferior that should report a fake stop event. */
|
||
static struct inferior *darwin_inf_fake_stop;
|
||
|
||
/* If non-NULL, the shell we actually invoke. See maybe_cache_shell
|
||
for details. */
|
||
static const char *copied_shell;
|
||
|
||
#define PAGE_TRUNC(x) ((x) & ~(mach_page_size - 1))
|
||
#define PAGE_ROUND(x) PAGE_TRUNC((x) + mach_page_size - 1)
|
||
|
||
/* This controls output of inferior debugging. */
|
||
static unsigned int darwin_debug_flag = 0;
|
||
|
||
/* Create a __TEXT __info_plist section in the executable so that gdb could
|
||
be signed. This is required to get an authorization for task_for_pid.
|
||
|
||
Once gdb is built, you must codesign it with any system-trusted signing
|
||
authority. See taskgated(8) for details. */
|
||
static const unsigned char info_plist[]
|
||
__attribute__ ((section ("__TEXT,__info_plist"),used)) =
|
||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\""
|
||
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
|
||
"<plist version=\"1.0\">\n"
|
||
"<dict>\n"
|
||
" <key>CFBundleIdentifier</key>\n"
|
||
" <string>org.gnu.gdb</string>\n"
|
||
" <key>CFBundleName</key>\n"
|
||
" <string>gdb</string>\n"
|
||
" <key>CFBundleVersion</key>\n"
|
||
" <string>1.0</string>\n"
|
||
" <key>SecTaskAccess</key>\n"
|
||
" <array>\n"
|
||
" <string>allowed</string>\n"
|
||
" <string>debug</string>\n"
|
||
" </array>\n"
|
||
"</dict>\n"
|
||
"</plist>\n";
|
||
|
||
static void inferior_debug (int level, const char *fmt, ...)
|
||
ATTRIBUTE_PRINTF (2, 3);
|
||
|
||
static void
|
||
inferior_debug (int level, const char *fmt, ...)
|
||
{
|
||
va_list ap;
|
||
|
||
if (darwin_debug_flag < level)
|
||
return;
|
||
|
||
va_start (ap, fmt);
|
||
printf_unfiltered (_("[%d inferior]: "), getpid ());
|
||
vprintf_unfiltered (fmt, ap);
|
||
va_end (ap);
|
||
}
|
||
|
||
void
|
||
mach_check_error (kern_return_t ret, const char *file,
|
||
unsigned int line, const char *func)
|
||
{
|
||
if (ret == KERN_SUCCESS)
|
||
return;
|
||
if (func == NULL)
|
||
func = _("[UNKNOWN]");
|
||
|
||
warning (_("Mach error at \"%s:%u\" in function \"%s\": %s (0x%lx)"),
|
||
file, line, func, mach_error_string (ret), (unsigned long) ret);
|
||
}
|
||
|
||
static const char *
|
||
unparse_exception_type (unsigned int i)
|
||
{
|
||
static char unknown_exception_buf[32];
|
||
|
||
switch (i)
|
||
{
|
||
case EXC_BAD_ACCESS:
|
||
return "EXC_BAD_ACCESS";
|
||
case EXC_BAD_INSTRUCTION:
|
||
return "EXC_BAD_INSTRUCTION";
|
||
case EXC_ARITHMETIC:
|
||
return "EXC_ARITHMETIC";
|
||
case EXC_EMULATION:
|
||
return "EXC_EMULATION";
|
||
case EXC_SOFTWARE:
|
||
return "EXC_SOFTWARE";
|
||
case EXC_BREAKPOINT:
|
||
return "EXC_BREAKPOINT";
|
||
case EXC_SYSCALL:
|
||
return "EXC_SYSCALL";
|
||
case EXC_MACH_SYSCALL:
|
||
return "EXC_MACH_SYSCALL";
|
||
case EXC_RPC_ALERT:
|
||
return "EXC_RPC_ALERT";
|
||
case EXC_CRASH:
|
||
return "EXC_CRASH";
|
||
default:
|
||
snprintf (unknown_exception_buf, 32, _("unknown (%d)"), i);
|
||
return unknown_exception_buf;
|
||
}
|
||
}
|
||
|
||
/* Set errno to zero, and then call ptrace with the given arguments.
|
||
If inferior debugging traces are on, then also print a debug
|
||
trace.
|
||
|
||
The returned value is the same as the value returned by ptrace,
|
||
except in the case where that value is -1 but errno is zero.
|
||
This case is documented to be a non-error situation, so we
|
||
return zero in that case. */
|
||
|
||
static int
|
||
darwin_ptrace (const char *name,
|
||
int request, int pid, caddr_t arg3, int arg4)
|
||
{
|
||
int ret;
|
||
|
||
errno = 0;
|
||
ret = ptrace (request, pid, arg3, arg4);
|
||
if (ret == -1 && errno == 0)
|
||
ret = 0;
|
||
|
||
inferior_debug (4, _("ptrace (%s, %d, 0x%lx, %d): %d (%s)\n"),
|
||
name, pid, (unsigned long) arg3, arg4, ret,
|
||
(ret != 0) ? safe_strerror (errno) : _("no error"));
|
||
return ret;
|
||
}
|
||
|
||
static int
|
||
cmp_thread_t (const void *l, const void *r)
|
||
{
|
||
thread_t tl = *(const thread_t *)l;
|
||
thread_t tr = *(const thread_t *)r;
|
||
return (int)(tl - tr);
|
||
}
|
||
|
||
static void
|
||
darwin_check_new_threads (struct inferior *inf)
|
||
{
|
||
kern_return_t kret;
|
||
thread_array_t thread_list;
|
||
unsigned int new_nbr;
|
||
unsigned int old_nbr;
|
||
unsigned int new_ix, old_ix;
|
||
darwin_inferior *darwin_inf = get_darwin_inferior (inf);
|
||
std::vector<darwin_thread_t *> new_thread_vec;
|
||
|
||
if (darwin_inf == nullptr)
|
||
return;
|
||
|
||
/* Get list of threads. */
|
||
kret = task_threads (darwin_inf->task, &thread_list, &new_nbr);
|
||
MACH_CHECK_ERROR (kret);
|
||
if (kret != KERN_SUCCESS)
|
||
return;
|
||
|
||
/* Sort the list. */
|
||
if (new_nbr > 1)
|
||
qsort (thread_list, new_nbr, sizeof (thread_t), cmp_thread_t);
|
||
|
||
old_nbr = darwin_inf->threads.size ();
|
||
|
||
/* Quick check for no changes. */
|
||
if (old_nbr == new_nbr)
|
||
{
|
||
size_t i;
|
||
|
||
for (i = 0; i < new_nbr; i++)
|
||
if (thread_list[i] != darwin_inf->threads[i]->gdb_port)
|
||
break;
|
||
if (i == new_nbr)
|
||
{
|
||
/* Deallocate ports. */
|
||
for (i = 0; i < new_nbr; i++)
|
||
{
|
||
kret = mach_port_deallocate (mach_task_self (), thread_list[i]);
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
|
||
/* Deallocate the buffer. */
|
||
kret = vm_deallocate (gdb_task, (vm_address_t) thread_list,
|
||
new_nbr * sizeof (int));
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* Full handling: detect new threads, remove dead threads. */
|
||
|
||
new_thread_vec.reserve (new_nbr);
|
||
|
||
for (new_ix = 0, old_ix = 0; new_ix < new_nbr || old_ix < old_nbr;)
|
||
{
|
||
thread_t new_id = (new_ix < new_nbr) ? thread_list[new_ix] : THREAD_NULL;
|
||
darwin_thread_t *old
|
||
= (old_ix < old_nbr) ? darwin_inf->threads[old_ix] : NULL;
|
||
thread_t old_id = old != NULL ? old->gdb_port : THREAD_NULL;
|
||
|
||
inferior_debug
|
||
(12, _(" new_ix:%d/%d, old_ix:%d/%d, new_id:0x%x old_id:0x%x\n"),
|
||
new_ix, new_nbr, old_ix, old_nbr, new_id, old_id);
|
||
|
||
if (old_id == new_id)
|
||
{
|
||
/* Thread still exist. */
|
||
new_thread_vec.push_back (old);
|
||
new_ix++;
|
||
old_ix++;
|
||
|
||
/* Deallocate the port. */
|
||
kret = mach_port_deallocate (gdb_task, new_id);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
continue;
|
||
}
|
||
if (new_ix < new_nbr && new_id == MACH_PORT_DEAD)
|
||
{
|
||
/* Ignore dead ports.
|
||
In some weird cases, we might get dead ports. They should
|
||
correspond to dead thread so they could safely be ignored. */
|
||
new_ix++;
|
||
continue;
|
||
}
|
||
if (new_ix < new_nbr && (old_ix == old_nbr || new_id < old_id))
|
||
{
|
||
/* A thread was created. */
|
||
darwin_thread_info *pti = new darwin_thread_info;
|
||
|
||
pti->gdb_port = new_id;
|
||
pti->msg_state = DARWIN_RUNNING;
|
||
|
||
/* Add the new thread. */
|
||
add_thread_with_info (ptid_t (inf->pid, 0, new_id), pti);
|
||
new_thread_vec.push_back (pti);
|
||
new_ix++;
|
||
continue;
|
||
}
|
||
if (old_ix < old_nbr && (new_ix == new_nbr || new_id > old_id))
|
||
{
|
||
/* A thread was removed. */
|
||
struct thread_info *thr
|
||
= find_thread_ptid (ptid_t (inf->pid, 0, old_id));
|
||
delete_thread (thr);
|
||
kret = mach_port_deallocate (gdb_task, old_id);
|
||
MACH_CHECK_ERROR (kret);
|
||
old_ix++;
|
||
continue;
|
||
}
|
||
gdb_assert_not_reached ("unexpected thread case");
|
||
}
|
||
|
||
darwin_inf->threads = std::move (new_thread_vec);
|
||
|
||
/* Deallocate the buffer. */
|
||
kret = vm_deallocate (gdb_task, (vm_address_t) thread_list,
|
||
new_nbr * sizeof (int));
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
|
||
static int
|
||
find_inferior_task_it (struct inferior *inf, void *port_ptr)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
return priv != nullptr && priv->task == *(task_t *)port_ptr;
|
||
}
|
||
|
||
static int
|
||
find_inferior_pid_it (struct inferior *inf, void *pid_ptr)
|
||
{
|
||
return inf->pid == *(int *)pid_ptr;
|
||
}
|
||
|
||
/* Return an inferior by task port. */
|
||
static struct inferior *
|
||
darwin_find_inferior_by_task (task_t port)
|
||
{
|
||
return iterate_over_inferiors (&find_inferior_task_it, &port);
|
||
}
|
||
|
||
/* Return an inferior by pid port. */
|
||
static struct inferior *
|
||
darwin_find_inferior_by_pid (int pid)
|
||
{
|
||
return iterate_over_inferiors (&find_inferior_pid_it, &pid);
|
||
}
|
||
|
||
/* Return a thread by port. */
|
||
static darwin_thread_t *
|
||
darwin_find_thread (struct inferior *inf, thread_t thread)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
if (priv != nullptr)
|
||
for (darwin_thread_t *t : priv->threads)
|
||
{
|
||
if (t->gdb_port == thread)
|
||
return t;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/* Suspend (ie stop) an inferior at Mach level. */
|
||
|
||
static void
|
||
darwin_suspend_inferior (struct inferior *inf)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
if (priv != nullptr && !priv->suspended)
|
||
{
|
||
kern_return_t kret;
|
||
|
||
kret = task_suspend (priv->task);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
priv->suspended = 1;
|
||
}
|
||
}
|
||
|
||
/* Resume an inferior at Mach level. */
|
||
|
||
static void
|
||
darwin_resume_inferior (struct inferior *inf)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
if (priv != nullptr && priv->suspended)
|
||
{
|
||
kern_return_t kret;
|
||
|
||
kret = task_resume (priv->task);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
priv->suspended = 0;
|
||
}
|
||
}
|
||
|
||
/* Iterator functions. */
|
||
|
||
static int
|
||
darwin_suspend_inferior_it (struct inferior *inf, void *arg)
|
||
{
|
||
darwin_suspend_inferior (inf);
|
||
darwin_check_new_threads (inf);
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
darwin_resume_inferior_it (struct inferior *inf, void *arg)
|
||
{
|
||
darwin_resume_inferior (inf);
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
darwin_dump_message (mach_msg_header_t *hdr, int disp_body)
|
||
{
|
||
printf_unfiltered (_("message header:\n"));
|
||
printf_unfiltered (_(" bits: 0x%x\n"), hdr->msgh_bits);
|
||
printf_unfiltered (_(" size: 0x%x\n"), hdr->msgh_size);
|
||
printf_unfiltered (_(" remote-port: 0x%x\n"), hdr->msgh_remote_port);
|
||
printf_unfiltered (_(" local-port: 0x%x\n"), hdr->msgh_local_port);
|
||
printf_unfiltered (_(" reserved: 0x%x\n"), hdr->msgh_reserved);
|
||
printf_unfiltered (_(" id: 0x%x\n"), hdr->msgh_id);
|
||
|
||
if (disp_body)
|
||
{
|
||
const unsigned char *data;
|
||
const unsigned int *ldata;
|
||
int size;
|
||
int i;
|
||
|
||
data = (unsigned char *)(hdr + 1);
|
||
size = hdr->msgh_size - sizeof (mach_msg_header_t);
|
||
|
||
if (hdr->msgh_bits & MACH_MSGH_BITS_COMPLEX)
|
||
{
|
||
mach_msg_body_t *bod = (mach_msg_body_t*)data;
|
||
mach_msg_port_descriptor_t *desc =
|
||
(mach_msg_port_descriptor_t *)(bod + 1);
|
||
int k;
|
||
NDR_record_t *ndr;
|
||
printf_unfiltered (_("body: descriptor_count=%u\n"),
|
||
bod->msgh_descriptor_count);
|
||
data += sizeof (mach_msg_body_t);
|
||
size -= sizeof (mach_msg_body_t);
|
||
for (k = 0; k < bod->msgh_descriptor_count; k++)
|
||
switch (desc[k].type)
|
||
{
|
||
case MACH_MSG_PORT_DESCRIPTOR:
|
||
printf_unfiltered
|
||
(_(" descr %d: type=%u (port) name=0x%x, dispo=%d\n"),
|
||
k, desc[k].type, desc[k].name, desc[k].disposition);
|
||
break;
|
||
default:
|
||
printf_unfiltered (_(" descr %d: type=%u\n"),
|
||
k, desc[k].type);
|
||
break;
|
||
}
|
||
data += bod->msgh_descriptor_count
|
||
* sizeof (mach_msg_port_descriptor_t);
|
||
size -= bod->msgh_descriptor_count
|
||
* sizeof (mach_msg_port_descriptor_t);
|
||
ndr = (NDR_record_t *)(desc + bod->msgh_descriptor_count);
|
||
printf_unfiltered
|
||
(_("NDR: mig=%02x if=%02x encod=%02x "
|
||
"int=%02x char=%02x float=%02x\n"),
|
||
ndr->mig_vers, ndr->if_vers, ndr->mig_encoding,
|
||
ndr->int_rep, ndr->char_rep, ndr->float_rep);
|
||
data += sizeof (NDR_record_t);
|
||
size -= sizeof (NDR_record_t);
|
||
}
|
||
|
||
printf_unfiltered (_(" data:"));
|
||
ldata = (const unsigned int *)data;
|
||
for (i = 0; i < size / sizeof (unsigned int); i++)
|
||
printf_unfiltered (" %08x", ldata[i]);
|
||
printf_unfiltered (_("\n"));
|
||
}
|
||
}
|
||
|
||
/* Adjust inferior data when a new task was created. */
|
||
|
||
static struct inferior *
|
||
darwin_find_new_inferior (task_t task_port, thread_t thread_port)
|
||
{
|
||
int task_pid;
|
||
struct inferior *inf;
|
||
kern_return_t kret;
|
||
mach_port_t prev;
|
||
|
||
/* Find the corresponding pid. */
|
||
kret = pid_for_task (task_port, &task_pid);
|
||
if (kret != KERN_SUCCESS)
|
||
{
|
||
MACH_CHECK_ERROR (kret);
|
||
return NULL;
|
||
}
|
||
|
||
/* Find the inferior for this pid. */
|
||
inf = darwin_find_inferior_by_pid (task_pid);
|
||
if (inf == NULL)
|
||
return NULL;
|
||
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
/* Deallocate saved exception ports. */
|
||
darwin_deallocate_exception_ports (priv);
|
||
|
||
/* No need to remove dead_name notification, but still... */
|
||
kret = mach_port_request_notification (gdb_task, priv->task,
|
||
MACH_NOTIFY_DEAD_NAME, 0,
|
||
MACH_PORT_NULL,
|
||
MACH_MSG_TYPE_MAKE_SEND_ONCE,
|
||
&prev);
|
||
if (kret != KERN_INVALID_ARGUMENT)
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
/* Replace old task port. */
|
||
kret = mach_port_deallocate (gdb_task, priv->task);
|
||
MACH_CHECK_ERROR (kret);
|
||
priv->task = task_port;
|
||
|
||
darwin_setup_request_notification (inf);
|
||
darwin_setup_exceptions (inf);
|
||
|
||
return inf;
|
||
}
|
||
|
||
/* Check data representation. */
|
||
|
||
static int
|
||
darwin_check_message_ndr (NDR_record_t *ndr)
|
||
{
|
||
if (ndr->mig_vers != NDR_PROTOCOL_2_0
|
||
|| ndr->if_vers != NDR_PROTOCOL_2_0
|
||
|| ndr->mig_encoding != NDR_record.mig_encoding
|
||
|| ndr->int_rep != NDR_record.int_rep
|
||
|| ndr->char_rep != NDR_record.char_rep
|
||
|| ndr->float_rep != NDR_record.float_rep)
|
||
return -1;
|
||
return 0;
|
||
}
|
||
|
||
/* Decode an exception message. */
|
||
|
||
static int
|
||
darwin_decode_exception_message (mach_msg_header_t *hdr,
|
||
struct inferior **pinf,
|
||
darwin_thread_t **pthread)
|
||
{
|
||
mach_msg_body_t *bod = (mach_msg_body_t*)(hdr + 1);
|
||
mach_msg_port_descriptor_t *desc = (mach_msg_port_descriptor_t *)(bod + 1);
|
||
NDR_record_t *ndr;
|
||
integer_t *data;
|
||
struct inferior *inf;
|
||
darwin_thread_t *thread;
|
||
task_t task_port;
|
||
thread_t thread_port;
|
||
kern_return_t kret;
|
||
int i;
|
||
|
||
/* Check message destination. */
|
||
if (hdr->msgh_local_port != darwin_ex_port)
|
||
return -1;
|
||
|
||
/* Check message header. */
|
||
if (!(hdr->msgh_bits & MACH_MSGH_BITS_COMPLEX))
|
||
return -1;
|
||
|
||
/* Check descriptors. */
|
||
if (hdr->msgh_size < (sizeof (*hdr) + sizeof (*bod) + 2 * sizeof (*desc)
|
||
+ sizeof (*ndr) + 2 * sizeof (integer_t))
|
||
|| bod->msgh_descriptor_count != 2
|
||
|| desc[0].type != MACH_MSG_PORT_DESCRIPTOR
|
||
|| desc[0].disposition != MACH_MSG_TYPE_MOVE_SEND
|
||
|| desc[1].type != MACH_MSG_PORT_DESCRIPTOR
|
||
|| desc[1].disposition != MACH_MSG_TYPE_MOVE_SEND)
|
||
return -1;
|
||
|
||
/* Check data representation. */
|
||
ndr = (NDR_record_t *)(desc + 2);
|
||
if (darwin_check_message_ndr (ndr) != 0)
|
||
return -1;
|
||
|
||
/* Ok, the hard work. */
|
||
data = (integer_t *)(ndr + 1);
|
||
|
||
task_port = desc[1].name;
|
||
thread_port = desc[0].name;
|
||
|
||
/* Find process by port. */
|
||
inf = darwin_find_inferior_by_task (task_port);
|
||
*pinf = inf;
|
||
|
||
if (inf == NULL && data[0] == EXC_SOFTWARE && data[1] == 2
|
||
&& data[2] == EXC_SOFT_SIGNAL && data[3] == SIGTRAP)
|
||
{
|
||
/* Not a known inferior, but a sigtrap. This happens on darwin 16.1.0,
|
||
as a new Mach task is created when a process exec. */
|
||
inf = darwin_find_new_inferior (task_port, thread_port);
|
||
*pinf = inf;
|
||
|
||
if (inf == NULL)
|
||
{
|
||
/* Deallocate task_port, unless it was saved. */
|
||
kret = mach_port_deallocate (mach_task_self (), task_port);
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* We got new rights to the task, get rid of it. Do not get rid of
|
||
thread right, as we will need it to find the thread. */
|
||
kret = mach_port_deallocate (mach_task_self (), task_port);
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
|
||
if (inf == NULL)
|
||
{
|
||
/* Not a known inferior. This could happen if the child fork, as
|
||
the created process will inherit its exception port.
|
||
FIXME: should the exception port be restored ? */
|
||
kern_return_t kret;
|
||
mig_reply_error_t reply;
|
||
|
||
inferior_debug
|
||
(4, _("darwin_decode_exception_message: unknown task 0x%x\n"),
|
||
task_port);
|
||
|
||
/* Free thread port (we don't know it). */
|
||
kret = mach_port_deallocate (mach_task_self (), thread_port);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
darwin_encode_reply (&reply, hdr, KERN_SUCCESS);
|
||
|
||
kret = mach_msg (&reply.Head, MACH_SEND_MSG | MACH_SEND_INTERRUPT,
|
||
reply.Head.msgh_size, 0,
|
||
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
|
||
MACH_PORT_NULL);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Find thread by port. */
|
||
/* Check for new threads. Do it early so that the port in the exception
|
||
message can be deallocated. */
|
||
darwin_check_new_threads (inf);
|
||
|
||
/* Free the thread port (as gdb knows the thread, it has already has a right
|
||
for it, so this just decrement a reference counter). */
|
||
kret = mach_port_deallocate (mach_task_self (), thread_port);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
thread = darwin_find_thread (inf, thread_port);
|
||
if (thread == NULL)
|
||
return -1;
|
||
*pthread = thread;
|
||
|
||
/* The thread should be running. However we have observed cases where a
|
||
thread got a SIGTTIN message after being stopped. */
|
||
gdb_assert (thread->msg_state != DARWIN_MESSAGE);
|
||
|
||
/* Finish decoding. */
|
||
thread->event.header = *hdr;
|
||
thread->event.thread_port = thread_port;
|
||
thread->event.task_port = task_port;
|
||
thread->event.ex_type = data[0];
|
||
thread->event.data_count = data[1];
|
||
|
||
if (hdr->msgh_size < (sizeof (*hdr) + sizeof (*bod) + 2 * sizeof (*desc)
|
||
+ sizeof (*ndr) + 2 * sizeof (integer_t)
|
||
+ data[1] * sizeof (integer_t)))
|
||
return -1;
|
||
for (i = 0; i < data[1]; i++)
|
||
thread->event.ex_data[i] = data[2 + i];
|
||
|
||
thread->msg_state = DARWIN_MESSAGE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Decode dead_name notify message. */
|
||
|
||
static int
|
||
darwin_decode_notify_message (mach_msg_header_t *hdr, struct inferior **pinf)
|
||
{
|
||
NDR_record_t *ndr = (NDR_record_t *)(hdr + 1);
|
||
integer_t *data = (integer_t *)(ndr + 1);
|
||
struct inferior *inf;
|
||
task_t task_port;
|
||
|
||
/* Check message header. */
|
||
if (hdr->msgh_bits & MACH_MSGH_BITS_COMPLEX)
|
||
return -1;
|
||
|
||
/* Check descriptors. */
|
||
if (hdr->msgh_size < (sizeof (*hdr) + sizeof (*ndr) + sizeof (integer_t)))
|
||
return -2;
|
||
|
||
/* Check data representation. */
|
||
if (darwin_check_message_ndr (ndr) != 0)
|
||
return -3;
|
||
|
||
task_port = data[0];
|
||
|
||
/* Find process by port. */
|
||
inf = darwin_find_inferior_by_task (task_port);
|
||
*pinf = inf;
|
||
|
||
/* Check message destination. */
|
||
if (inf != NULL)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
if (hdr->msgh_local_port != priv->notify_port)
|
||
return -4;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
darwin_encode_reply (mig_reply_error_t *reply, mach_msg_header_t *hdr,
|
||
integer_t code)
|
||
{
|
||
mach_msg_header_t *rh = &reply->Head;
|
||
|
||
rh->msgh_bits = MACH_MSGH_BITS (MACH_MSGH_BITS_REMOTE (hdr->msgh_bits), 0);
|
||
rh->msgh_remote_port = hdr->msgh_remote_port;
|
||
rh->msgh_size = (mach_msg_size_t) sizeof (mig_reply_error_t);
|
||
rh->msgh_local_port = MACH_PORT_NULL;
|
||
rh->msgh_id = hdr->msgh_id + 100;
|
||
|
||
reply->NDR = NDR_record;
|
||
reply->RetCode = code;
|
||
}
|
||
|
||
static void
|
||
darwin_send_reply (struct inferior *inf, darwin_thread_t *thread)
|
||
{
|
||
kern_return_t kret;
|
||
mig_reply_error_t reply;
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
darwin_encode_reply (&reply, &thread->event.header, KERN_SUCCESS);
|
||
|
||
kret = mach_msg (&reply.Head, MACH_SEND_MSG | MACH_SEND_INTERRUPT,
|
||
reply.Head.msgh_size, 0,
|
||
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
|
||
MACH_PORT_NULL);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
priv->pending_messages--;
|
||
}
|
||
|
||
/* Wrapper around the __pthread_kill syscall. We use this instead of the
|
||
pthread_kill function to be able to send a signal to any kind of thread,
|
||
including GCD threads. */
|
||
|
||
static int
|
||
darwin_pthread_kill (darwin_thread_t *thread, int nsignal)
|
||
{
|
||
DIAGNOSTIC_PUSH;
|
||
DIAGNOSTIC_IGNORE_DEPRECATED_DECLARATIONS;
|
||
int res = syscall (SYS___pthread_kill, thread->gdb_port, nsignal);
|
||
DIAGNOSTIC_POP;
|
||
return res;
|
||
}
|
||
|
||
static void
|
||
darwin_resume_thread (struct inferior *inf, darwin_thread_t *thread,
|
||
int step, int nsignal)
|
||
{
|
||
inferior_debug
|
||
(3, _("darwin_resume_thread: state=%d, thread=0x%x, step=%d nsignal=%d\n"),
|
||
thread->msg_state, thread->gdb_port, step, nsignal);
|
||
|
||
switch (thread->msg_state)
|
||
{
|
||
case DARWIN_MESSAGE:
|
||
if (thread->event.ex_type == EXC_SOFTWARE
|
||
&& thread->event.ex_data[0] == EXC_SOFT_SIGNAL)
|
||
{
|
||
/* Either deliver a new signal or cancel the signal received. */
|
||
int res = PTRACE (PT_THUPDATE, inf->pid,
|
||
(caddr_t) (uintptr_t) thread->gdb_port, nsignal);
|
||
if (res < 0)
|
||
inferior_debug (1, _("ptrace THUP: res=%d\n"), res);
|
||
}
|
||
else if (nsignal)
|
||
{
|
||
/* Note: ptrace is allowed only if the process is stopped.
|
||
Directly send the signal to the thread. */
|
||
int res = darwin_pthread_kill (thread, nsignal);
|
||
inferior_debug (4, _("darwin_resume_thread: kill 0x%x %d: %d\n"),
|
||
thread->gdb_port, nsignal, res);
|
||
thread->signaled = 1;
|
||
}
|
||
|
||
/* Set or reset single step. */
|
||
inferior_debug (4, _("darwin_set_sstep (thread=0x%x, enable=%d)\n"),
|
||
thread->gdb_port, step);
|
||
darwin_set_sstep (thread->gdb_port, step);
|
||
thread->single_step = step;
|
||
|
||
darwin_send_reply (inf, thread);
|
||
thread->msg_state = DARWIN_RUNNING;
|
||
break;
|
||
|
||
case DARWIN_RUNNING:
|
||
break;
|
||
|
||
case DARWIN_STOPPED:
|
||
kern_return_t kret = thread_resume (thread->gdb_port);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
thread->msg_state = DARWIN_RUNNING;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Resume all threads of the inferior. */
|
||
|
||
static void
|
||
darwin_resume_inferior_threads (struct inferior *inf, int step, int nsignal)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
if (priv != nullptr)
|
||
for (darwin_thread_t *thread : priv->threads)
|
||
darwin_resume_thread (inf, thread, step, nsignal);
|
||
}
|
||
|
||
struct resume_inferior_threads_param
|
||
{
|
||
int step;
|
||
int nsignal;
|
||
};
|
||
|
||
static int
|
||
darwin_resume_inferior_threads_it (struct inferior *inf, void *param)
|
||
{
|
||
int step = ((struct resume_inferior_threads_param *)param)->step;
|
||
int nsignal = ((struct resume_inferior_threads_param *)param)->nsignal;
|
||
|
||
darwin_resume_inferior_threads (inf, step, nsignal);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Suspend all threads of INF. */
|
||
|
||
static void
|
||
darwin_suspend_inferior_threads (struct inferior *inf)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
for (darwin_thread_t *thread : priv->threads)
|
||
{
|
||
switch (thread->msg_state)
|
||
{
|
||
case DARWIN_STOPPED:
|
||
case DARWIN_MESSAGE:
|
||
break;
|
||
case DARWIN_RUNNING:
|
||
{
|
||
kern_return_t kret = thread_suspend (thread->gdb_port);
|
||
MACH_CHECK_ERROR (kret);
|
||
thread->msg_state = DARWIN_STOPPED;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void
|
||
darwin_nat_target::resume (ptid_t ptid, int step, enum gdb_signal signal)
|
||
{
|
||
struct target_waitstatus status;
|
||
|
||
int nsignal;
|
||
|
||
inferior_debug
|
||
(2, _("darwin_resume: pid=%d, tid=0x%lx, step=%d, signal=%d\n"),
|
||
ptid.pid (), ptid.tid (), step, signal);
|
||
|
||
if (signal == GDB_SIGNAL_0)
|
||
nsignal = 0;
|
||
else
|
||
nsignal = gdb_signal_to_host (signal);
|
||
|
||
/* Don't try to single step all threads. */
|
||
if (step)
|
||
ptid = inferior_ptid;
|
||
|
||
/* minus_one_ptid is RESUME_ALL. */
|
||
if (ptid == minus_one_ptid)
|
||
{
|
||
struct resume_inferior_threads_param param;
|
||
|
||
param.nsignal = nsignal;
|
||
param.step = step;
|
||
|
||
/* Resume threads. */
|
||
iterate_over_inferiors (darwin_resume_inferior_threads_it, ¶m);
|
||
/* Resume tasks. */
|
||
iterate_over_inferiors (darwin_resume_inferior_it, NULL);
|
||
}
|
||
else
|
||
{
|
||
struct inferior *inf = find_inferior_ptid (ptid);
|
||
long tid = ptid.tid ();
|
||
|
||
/* Stop the inferior (should be useless). */
|
||
darwin_suspend_inferior (inf);
|
||
|
||
if (tid == 0)
|
||
darwin_resume_inferior_threads (inf, step, nsignal);
|
||
else
|
||
{
|
||
darwin_thread_t *thread;
|
||
|
||
/* Suspend threads of the task. */
|
||
darwin_suspend_inferior_threads (inf);
|
||
|
||
/* Resume the selected thread. */
|
||
thread = darwin_find_thread (inf, tid);
|
||
gdb_assert (thread);
|
||
darwin_resume_thread (inf, thread, step, nsignal);
|
||
}
|
||
|
||
/* Resume the task. */
|
||
darwin_resume_inferior (inf);
|
||
}
|
||
}
|
||
|
||
static ptid_t
|
||
darwin_decode_message (mach_msg_header_t *hdr,
|
||
darwin_thread_t **pthread,
|
||
struct inferior **pinf,
|
||
struct target_waitstatus *status)
|
||
{
|
||
darwin_thread_t *thread;
|
||
struct inferior *inf;
|
||
|
||
/* Exception message. 2401 == 0x961 is exc. */
|
||
if (hdr->msgh_id == 2401)
|
||
{
|
||
int res;
|
||
|
||
/* Decode message. */
|
||
res = darwin_decode_exception_message (hdr, &inf, &thread);
|
||
|
||
if (res < 0)
|
||
{
|
||
/* Should not happen... */
|
||
printf_unfiltered
|
||
(_("darwin_wait: ill-formatted message (id=0x%x)\n"), hdr->msgh_id);
|
||
/* FIXME: send a failure reply? */
|
||
status->kind = TARGET_WAITKIND_IGNORE;
|
||
return minus_one_ptid;
|
||
}
|
||
if (inf == NULL)
|
||
{
|
||
status->kind = TARGET_WAITKIND_IGNORE;
|
||
return minus_one_ptid;
|
||
}
|
||
*pinf = inf;
|
||
*pthread = thread;
|
||
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
priv->pending_messages++;
|
||
|
||
status->kind = TARGET_WAITKIND_STOPPED;
|
||
thread->msg_state = DARWIN_MESSAGE;
|
||
|
||
inferior_debug (4, _("darwin_wait: thread=0x%x, got %s\n"),
|
||
thread->gdb_port,
|
||
unparse_exception_type (thread->event.ex_type));
|
||
|
||
switch (thread->event.ex_type)
|
||
{
|
||
case EXC_BAD_ACCESS:
|
||
status->value.sig = GDB_EXC_BAD_ACCESS;
|
||
break;
|
||
case EXC_BAD_INSTRUCTION:
|
||
status->value.sig = GDB_EXC_BAD_INSTRUCTION;
|
||
break;
|
||
case EXC_ARITHMETIC:
|
||
status->value.sig = GDB_EXC_ARITHMETIC;
|
||
break;
|
||
case EXC_EMULATION:
|
||
status->value.sig = GDB_EXC_EMULATION;
|
||
break;
|
||
case EXC_SOFTWARE:
|
||
if (thread->event.ex_data[0] == EXC_SOFT_SIGNAL)
|
||
{
|
||
status->value.sig =
|
||
gdb_signal_from_host (thread->event.ex_data[1]);
|
||
inferior_debug (5, _(" (signal %d: %s)\n"),
|
||
thread->event.ex_data[1],
|
||
gdb_signal_to_name (status->value.sig));
|
||
|
||
/* If the thread is stopped because it has received a signal
|
||
that gdb has just sent, continue. */
|
||
if (thread->signaled)
|
||
{
|
||
thread->signaled = 0;
|
||
darwin_send_reply (inf, thread);
|
||
thread->msg_state = DARWIN_RUNNING;
|
||
status->kind = TARGET_WAITKIND_IGNORE;
|
||
}
|
||
}
|
||
else
|
||
status->value.sig = GDB_EXC_SOFTWARE;
|
||
break;
|
||
case EXC_BREAKPOINT:
|
||
/* Many internal GDB routines expect breakpoints to be reported
|
||
as GDB_SIGNAL_TRAP, and will report GDB_EXC_BREAKPOINT
|
||
as a spurious signal. */
|
||
status->value.sig = GDB_SIGNAL_TRAP;
|
||
break;
|
||
default:
|
||
status->value.sig = GDB_SIGNAL_UNKNOWN;
|
||
break;
|
||
}
|
||
|
||
return ptid_t (inf->pid, 0, thread->gdb_port);
|
||
}
|
||
else if (hdr->msgh_id == 0x48)
|
||
{
|
||
/* MACH_NOTIFY_DEAD_NAME: notification for exit. */
|
||
int res;
|
||
|
||
res = darwin_decode_notify_message (hdr, &inf);
|
||
|
||
if (res < 0)
|
||
{
|
||
/* Should not happen... */
|
||
printf_unfiltered
|
||
(_("darwin_wait: ill-formatted message (id=0x%x, res=%d)\n"),
|
||
hdr->msgh_id, res);
|
||
}
|
||
|
||
*pinf = NULL;
|
||
*pthread = NULL;
|
||
|
||
if (res < 0 || inf == NULL)
|
||
{
|
||
status->kind = TARGET_WAITKIND_IGNORE;
|
||
return minus_one_ptid;
|
||
}
|
||
|
||
if (inf != NULL)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
if (!priv->no_ptrace)
|
||
{
|
||
pid_t res;
|
||
int wstatus;
|
||
|
||
res = wait4 (inf->pid, &wstatus, 0, NULL);
|
||
if (res < 0 || res != inf->pid)
|
||
{
|
||
printf_unfiltered (_("wait4: res=%d: %s\n"),
|
||
res, safe_strerror (errno));
|
||
status->kind = TARGET_WAITKIND_IGNORE;
|
||
return minus_one_ptid;
|
||
}
|
||
if (WIFEXITED (wstatus))
|
||
{
|
||
status->kind = TARGET_WAITKIND_EXITED;
|
||
status->value.integer = WEXITSTATUS (wstatus);
|
||
}
|
||
else
|
||
{
|
||
status->kind = TARGET_WAITKIND_SIGNALLED;
|
||
status->value.sig = gdb_signal_from_host (WTERMSIG (wstatus));
|
||
}
|
||
|
||
inferior_debug (4, _("darwin_wait: pid=%d exit, status=0x%x\n"),
|
||
res, wstatus);
|
||
|
||
/* Looks necessary on Leopard and harmless... */
|
||
wait4 (inf->pid, &wstatus, 0, NULL);
|
||
|
||
inferior_ptid = ptid_t (inf->pid, 0, 0);
|
||
return inferior_ptid;
|
||
}
|
||
else
|
||
{
|
||
inferior_debug (4, _("darwin_wait: pid=%d\n"), inf->pid);
|
||
status->kind = TARGET_WAITKIND_EXITED;
|
||
status->value.integer = 0; /* Don't know. */
|
||
return ptid_t (inf->pid, 0, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Unknown message. */
|
||
warning (_("darwin: got unknown message, id: 0x%x"), hdr->msgh_id);
|
||
status->kind = TARGET_WAITKIND_IGNORE;
|
||
return minus_one_ptid;
|
||
}
|
||
|
||
static int
|
||
cancel_breakpoint (ptid_t ptid)
|
||
{
|
||
/* Arrange for a breakpoint to be hit again later. We will handle
|
||
the current event, eventually we will resume this thread, and this
|
||
breakpoint will trap again.
|
||
|
||
If we do not do this, then we run the risk that the user will
|
||
delete or disable the breakpoint, but the thread will have already
|
||
tripped on it. */
|
||
|
||
struct regcache *regcache = get_thread_regcache (ptid);
|
||
struct gdbarch *gdbarch = regcache->arch ();
|
||
CORE_ADDR pc;
|
||
|
||
pc = regcache_read_pc (regcache) - gdbarch_decr_pc_after_break (gdbarch);
|
||
if (breakpoint_inserted_here_p (regcache->aspace (), pc))
|
||
{
|
||
inferior_debug (4, "cancel_breakpoint for thread 0x%lx\n",
|
||
(unsigned long) ptid.tid ());
|
||
|
||
/* Back up the PC if necessary. */
|
||
if (gdbarch_decr_pc_after_break (gdbarch))
|
||
regcache_write_pc (regcache, pc);
|
||
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static ptid_t
|
||
darwin_wait (ptid_t ptid, struct target_waitstatus *status)
|
||
{
|
||
kern_return_t kret;
|
||
union
|
||
{
|
||
mach_msg_header_t hdr;
|
||
char data[0x100];
|
||
} msgin;
|
||
mach_msg_header_t *hdr = &msgin.hdr;
|
||
ptid_t res;
|
||
darwin_thread_t *thread;
|
||
struct inferior *inf;
|
||
|
||
inferior_debug
|
||
(2, _("darwin_wait: waiting for a message pid=%d thread=%lx\n"),
|
||
ptid.pid (), ptid.tid ());
|
||
|
||
/* Handle fake stop events at first. */
|
||
if (darwin_inf_fake_stop != NULL)
|
||
{
|
||
inf = darwin_inf_fake_stop;
|
||
darwin_inf_fake_stop = NULL;
|
||
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
status->kind = TARGET_WAITKIND_STOPPED;
|
||
status->value.sig = GDB_SIGNAL_TRAP;
|
||
thread = priv->threads[0];
|
||
thread->msg_state = DARWIN_STOPPED;
|
||
return ptid_t (inf->pid, 0, thread->gdb_port);
|
||
}
|
||
|
||
do
|
||
{
|
||
/* set_sigint_trap (); */
|
||
|
||
/* Wait for a message. */
|
||
kret = mach_msg (&msgin.hdr, MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0,
|
||
sizeof (msgin.data), darwin_port_set, 0, MACH_PORT_NULL);
|
||
|
||
/* clear_sigint_trap (); */
|
||
|
||
if (kret == MACH_RCV_INTERRUPTED)
|
||
{
|
||
status->kind = TARGET_WAITKIND_IGNORE;
|
||
return minus_one_ptid;
|
||
}
|
||
|
||
if (kret != MACH_MSG_SUCCESS)
|
||
{
|
||
inferior_debug (5, _("mach_msg: ret=0x%x\n"), kret);
|
||
status->kind = TARGET_WAITKIND_SPURIOUS;
|
||
return minus_one_ptid;
|
||
}
|
||
|
||
/* Debug: display message. */
|
||
if (darwin_debug_flag > 10)
|
||
darwin_dump_message (hdr, darwin_debug_flag > 11);
|
||
|
||
res = darwin_decode_message (hdr, &thread, &inf, status);
|
||
if (res == minus_one_ptid)
|
||
continue;
|
||
|
||
/* Early return in case an inferior has exited. */
|
||
if (inf == NULL)
|
||
return res;
|
||
}
|
||
while (status->kind == TARGET_WAITKIND_IGNORE);
|
||
|
||
/* Stop all tasks. */
|
||
iterate_over_inferiors (darwin_suspend_inferior_it, NULL);
|
||
|
||
/* Read pending messages. */
|
||
while (1)
|
||
{
|
||
struct target_waitstatus status2;
|
||
ptid_t ptid2;
|
||
|
||
kret = mach_msg (&msgin.hdr,
|
||
MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0,
|
||
sizeof (msgin.data), darwin_port_set, 1, MACH_PORT_NULL);
|
||
|
||
if (kret == MACH_RCV_TIMED_OUT)
|
||
break;
|
||
if (kret != MACH_MSG_SUCCESS)
|
||
{
|
||
inferior_debug
|
||
(5, _("darwin_wait: mach_msg(pending) ret=0x%x\n"), kret);
|
||
break;
|
||
}
|
||
|
||
/* Debug: display message. */
|
||
if (darwin_debug_flag > 10)
|
||
darwin_dump_message (hdr, darwin_debug_flag > 11);
|
||
|
||
ptid2 = darwin_decode_message (hdr, &thread, &inf, &status2);
|
||
|
||
if (inf != NULL && thread != NULL
|
||
&& thread->event.ex_type == EXC_BREAKPOINT)
|
||
{
|
||
if (thread->single_step
|
||
|| cancel_breakpoint (ptid_t (inf->pid, 0, thread->gdb_port)))
|
||
{
|
||
gdb_assert (thread->msg_state == DARWIN_MESSAGE);
|
||
darwin_send_reply (inf, thread);
|
||
thread->msg_state = DARWIN_RUNNING;
|
||
}
|
||
else
|
||
inferior_debug
|
||
(3, _("darwin_wait: thread 0x%x hit a non-gdb breakpoint\n"),
|
||
thread->gdb_port);
|
||
}
|
||
else
|
||
inferior_debug (3, _("darwin_wait: unhandled pending message\n"));
|
||
}
|
||
return res;
|
||
}
|
||
|
||
ptid_t
|
||
darwin_nat_target::wait (ptid_t ptid, struct target_waitstatus *status,
|
||
int options)
|
||
{
|
||
return darwin_wait (ptid, status);
|
||
}
|
||
|
||
void
|
||
darwin_nat_target::interrupt ()
|
||
{
|
||
struct inferior *inf = current_inferior ();
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
/* FIXME: handle in no_ptrace mode. */
|
||
gdb_assert (!priv->no_ptrace);
|
||
::kill (inf->pid, SIGINT);
|
||
}
|
||
|
||
/* Deallocate threads port and vector. */
|
||
|
||
static void
|
||
darwin_deallocate_threads (struct inferior *inf)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
for (darwin_thread_t *t : priv->threads)
|
||
{
|
||
kern_return_t kret = mach_port_deallocate (gdb_task, t->gdb_port);
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
|
||
priv->threads.clear ();
|
||
}
|
||
|
||
void
|
||
darwin_nat_target::mourn_inferior ()
|
||
{
|
||
struct inferior *inf = current_inferior ();
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
kern_return_t kret;
|
||
mach_port_t prev;
|
||
|
||
/* Deallocate threads. */
|
||
darwin_deallocate_threads (inf);
|
||
|
||
/* Remove notify_port from darwin_port_set. */
|
||
kret = mach_port_move_member (gdb_task,
|
||
priv->notify_port, MACH_PORT_NULL);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
/* Remove task port dead_name notification. */
|
||
kret = mach_port_request_notification (gdb_task, priv->task,
|
||
MACH_NOTIFY_DEAD_NAME, 0,
|
||
MACH_PORT_NULL,
|
||
MACH_MSG_TYPE_MAKE_SEND_ONCE,
|
||
&prev);
|
||
/* This can fail if the task is dead. */
|
||
inferior_debug (4, "task=0x%x, prev=0x%x, notify_port=0x%x\n",
|
||
priv->task, prev, priv->notify_port);
|
||
|
||
if (kret == KERN_SUCCESS)
|
||
{
|
||
kret = mach_port_deallocate (gdb_task, prev);
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
|
||
/* Destroy notify_port. */
|
||
kret = mach_port_destroy (gdb_task, priv->notify_port);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
/* Deallocate saved exception ports. */
|
||
darwin_deallocate_exception_ports (priv);
|
||
|
||
/* Deallocate task port. */
|
||
kret = mach_port_deallocate (gdb_task, priv->task);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
inf->priv = NULL;
|
||
|
||
inf_child_target::mourn_inferior ();
|
||
}
|
||
|
||
static void
|
||
darwin_reply_to_all_pending_messages (struct inferior *inf)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
for (darwin_thread_t *t : priv->threads)
|
||
{
|
||
if (t->msg_state == DARWIN_MESSAGE)
|
||
darwin_resume_thread (inf, t, 0, 0);
|
||
}
|
||
}
|
||
|
||
static void
|
||
darwin_stop_inferior (struct inferior *inf)
|
||
{
|
||
struct target_waitstatus wstatus;
|
||
ptid_t ptid;
|
||
int res;
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
gdb_assert (inf != NULL);
|
||
|
||
darwin_suspend_inferior (inf);
|
||
|
||
darwin_reply_to_all_pending_messages (inf);
|
||
|
||
if (priv->no_ptrace)
|
||
return;
|
||
|
||
res = kill (inf->pid, SIGSTOP);
|
||
if (res != 0)
|
||
warning (_("cannot kill: %s"), safe_strerror (errno));
|
||
|
||
/* Wait until the process is really stopped. */
|
||
while (1)
|
||
{
|
||
ptid = darwin_wait (inferior_ptid, &wstatus);
|
||
if (wstatus.kind == TARGET_WAITKIND_STOPPED
|
||
&& wstatus.value.sig == GDB_SIGNAL_STOP)
|
||
break;
|
||
}
|
||
}
|
||
|
||
static kern_return_t
|
||
darwin_save_exception_ports (darwin_inferior *inf)
|
||
{
|
||
kern_return_t kret;
|
||
|
||
inf->exception_info.count =
|
||
sizeof (inf->exception_info.ports) / sizeof (inf->exception_info.ports[0]);
|
||
|
||
kret = task_get_exception_ports
|
||
(inf->task, EXC_MASK_ALL, inf->exception_info.masks,
|
||
&inf->exception_info.count, inf->exception_info.ports,
|
||
inf->exception_info.behaviors, inf->exception_info.flavors);
|
||
return kret;
|
||
}
|
||
|
||
static kern_return_t
|
||
darwin_restore_exception_ports (darwin_inferior *inf)
|
||
{
|
||
int i;
|
||
kern_return_t kret;
|
||
|
||
for (i = 0; i < inf->exception_info.count; i++)
|
||
{
|
||
kret = task_set_exception_ports
|
||
(inf->task, inf->exception_info.masks[i], inf->exception_info.ports[i],
|
||
inf->exception_info.behaviors[i], inf->exception_info.flavors[i]);
|
||
if (kret != KERN_SUCCESS)
|
||
return kret;
|
||
}
|
||
|
||
return KERN_SUCCESS;
|
||
}
|
||
|
||
/* Deallocate saved exception ports. */
|
||
|
||
static void
|
||
darwin_deallocate_exception_ports (darwin_inferior *inf)
|
||
{
|
||
int i;
|
||
kern_return_t kret;
|
||
|
||
for (i = 0; i < inf->exception_info.count; i++)
|
||
{
|
||
kret = mach_port_deallocate (gdb_task, inf->exception_info.ports[i]);
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
inf->exception_info.count = 0;
|
||
}
|
||
|
||
static void
|
||
darwin_setup_exceptions (struct inferior *inf)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
kern_return_t kret;
|
||
exception_mask_t mask;
|
||
|
||
kret = darwin_save_exception_ports (priv);
|
||
if (kret != KERN_SUCCESS)
|
||
error (_("Unable to save exception ports, task_get_exception_ports"
|
||
"returned: %d"),
|
||
kret);
|
||
|
||
/* Set exception port. */
|
||
if (enable_mach_exceptions)
|
||
mask = EXC_MASK_ALL;
|
||
else
|
||
mask = EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT;
|
||
kret = task_set_exception_ports (priv->task, mask, darwin_ex_port,
|
||
EXCEPTION_DEFAULT, THREAD_STATE_NONE);
|
||
if (kret != KERN_SUCCESS)
|
||
error (_("Unable to set exception ports, task_set_exception_ports"
|
||
"returned: %d"),
|
||
kret);
|
||
}
|
||
|
||
void
|
||
darwin_nat_target::kill ()
|
||
{
|
||
struct inferior *inf = current_inferior ();
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
struct target_waitstatus wstatus;
|
||
ptid_t ptid;
|
||
kern_return_t kret;
|
||
int res;
|
||
|
||
if (inferior_ptid == null_ptid)
|
||
return;
|
||
|
||
gdb_assert (inf != NULL);
|
||
|
||
kret = darwin_restore_exception_ports (priv);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
darwin_reply_to_all_pending_messages (inf);
|
||
|
||
res = ::kill (inf->pid, 9);
|
||
|
||
if (res == 0)
|
||
{
|
||
/* On MacOS version Sierra, the darwin_restore_exception_ports call
|
||
does not work as expected.
|
||
When the kill function is called, the SIGKILL signal is received
|
||
by gdb whereas it should have been received by the kernel since
|
||
the exception ports have been restored.
|
||
This behavior is not the expected one thus gdb does not reply to
|
||
the received SIGKILL message. This situation leads to a "busy"
|
||
resource from the kernel point of view and the inferior is never
|
||
released, causing it to remain as a zombie process, even after
|
||
GDB exits.
|
||
To work around this, we mark all the threads of the inferior as
|
||
signaled thus darwin_decode_message function knows that the kill
|
||
signal was sent by gdb and will take the appropriate action
|
||
(cancel signal and reply to the signal message). */
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
for (darwin_thread_t *thread : priv->threads)
|
||
thread->signaled = 1;
|
||
|
||
darwin_resume_inferior (inf);
|
||
|
||
ptid = darwin_wait (inferior_ptid, &wstatus);
|
||
}
|
||
else if (errno != ESRCH)
|
||
warning (_("Failed to kill inferior: kill (%d, 9) returned [%s]"),
|
||
inf->pid, safe_strerror (errno));
|
||
|
||
target_mourn_inferior (inferior_ptid);
|
||
}
|
||
|
||
static void
|
||
darwin_setup_request_notification (struct inferior *inf)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
kern_return_t kret;
|
||
mach_port_t prev_not;
|
||
|
||
kret = mach_port_request_notification (gdb_task, priv->task,
|
||
MACH_NOTIFY_DEAD_NAME, 0,
|
||
priv->notify_port,
|
||
MACH_MSG_TYPE_MAKE_SEND_ONCE,
|
||
&prev_not);
|
||
if (kret != KERN_SUCCESS)
|
||
error (_("Termination notification request failed, "
|
||
"mach_port_request_notification\n"
|
||
"returned: %d"),
|
||
kret);
|
||
if (prev_not != MACH_PORT_NULL)
|
||
{
|
||
/* This is unexpected, as there should not be any previously
|
||
registered notification request. But this is not a fatal
|
||
issue, so just emit a warning. */
|
||
warning (_("\
|
||
A task termination request was registered before the debugger registered\n\
|
||
its own. This is unexpected, but should otherwise not have any actual\n\
|
||
impact on the debugging session."));
|
||
}
|
||
}
|
||
|
||
static void
|
||
darwin_attach_pid (struct inferior *inf)
|
||
{
|
||
kern_return_t kret;
|
||
|
||
darwin_inferior *priv = new darwin_inferior;
|
||
inf->priv.reset (priv);
|
||
|
||
try
|
||
{
|
||
kret = task_for_pid (gdb_task, inf->pid, &priv->task);
|
||
if (kret != KERN_SUCCESS)
|
||
{
|
||
int status;
|
||
|
||
if (!inf->attach_flag)
|
||
{
|
||
kill (inf->pid, 9);
|
||
waitpid (inf->pid, &status, 0);
|
||
}
|
||
|
||
error
|
||
(_("Unable to find Mach task port for process-id %d: %s (0x%lx).\n"
|
||
" (please check gdb is codesigned - see taskgated(8))"),
|
||
inf->pid, mach_error_string (kret), (unsigned long) kret);
|
||
}
|
||
|
||
inferior_debug (2, _("inferior task: 0x%x, pid: %d\n"),
|
||
priv->task, inf->pid);
|
||
|
||
if (darwin_ex_port == MACH_PORT_NULL)
|
||
{
|
||
/* Create a port to get exceptions. */
|
||
kret = mach_port_allocate (gdb_task, MACH_PORT_RIGHT_RECEIVE,
|
||
&darwin_ex_port);
|
||
if (kret != KERN_SUCCESS)
|
||
error (_("Unable to create exception port, mach_port_allocate "
|
||
"returned: %d"),
|
||
kret);
|
||
|
||
kret = mach_port_insert_right (gdb_task, darwin_ex_port,
|
||
darwin_ex_port,
|
||
MACH_MSG_TYPE_MAKE_SEND);
|
||
if (kret != KERN_SUCCESS)
|
||
error (_("Unable to create exception port, mach_port_insert_right "
|
||
"returned: %d"),
|
||
kret);
|
||
|
||
/* Create a port set and put ex_port in it. */
|
||
kret = mach_port_allocate (gdb_task, MACH_PORT_RIGHT_PORT_SET,
|
||
&darwin_port_set);
|
||
if (kret != KERN_SUCCESS)
|
||
error (_("Unable to create port set, mach_port_allocate "
|
||
"returned: %d"),
|
||
kret);
|
||
|
||
kret = mach_port_move_member (gdb_task, darwin_ex_port,
|
||
darwin_port_set);
|
||
if (kret != KERN_SUCCESS)
|
||
error (_("Unable to move exception port into new port set, "
|
||
"mach_port_move_member\n"
|
||
"returned: %d"),
|
||
kret);
|
||
}
|
||
|
||
/* Create a port to be notified when the child task terminates. */
|
||
kret = mach_port_allocate (gdb_task, MACH_PORT_RIGHT_RECEIVE,
|
||
&priv->notify_port);
|
||
if (kret != KERN_SUCCESS)
|
||
error (_("Unable to create notification port, mach_port_allocate "
|
||
"returned: %d"),
|
||
kret);
|
||
|
||
kret = mach_port_move_member (gdb_task,
|
||
priv->notify_port, darwin_port_set);
|
||
if (kret != KERN_SUCCESS)
|
||
error (_("Unable to move notification port into new port set, "
|
||
"mach_port_move_member\n"
|
||
"returned: %d"),
|
||
kret);
|
||
|
||
darwin_setup_request_notification (inf);
|
||
|
||
darwin_setup_exceptions (inf);
|
||
}
|
||
catch (const gdb_exception &ex)
|
||
{
|
||
exit_inferior (inf);
|
||
inferior_ptid = null_ptid;
|
||
|
||
throw;
|
||
}
|
||
|
||
target_ops *darwin_ops = get_native_target ();
|
||
if (!target_is_pushed (darwin_ops))
|
||
push_target (darwin_ops);
|
||
}
|
||
|
||
/* Get the thread_info object corresponding to this darwin_thread_info. */
|
||
|
||
static struct thread_info *
|
||
thread_info_from_private_thread_info (darwin_thread_info *pti)
|
||
{
|
||
for (struct thread_info *it : all_threads ())
|
||
{
|
||
darwin_thread_info *iter_pti = get_darwin_thread_info (it);
|
||
|
||
if (iter_pti->gdb_port == pti->gdb_port)
|
||
return it;
|
||
}
|
||
|
||
gdb_assert_not_reached ("did not find gdb thread for darwin thread");
|
||
}
|
||
|
||
static void
|
||
darwin_init_thread_list (struct inferior *inf)
|
||
{
|
||
darwin_check_new_threads (inf);
|
||
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
gdb_assert (!priv->threads.empty ());
|
||
|
||
darwin_thread_info *first_pti = priv->threads.front ();
|
||
struct thread_info *first_thread
|
||
= thread_info_from_private_thread_info (first_pti);
|
||
|
||
inferior_ptid = first_thread->ptid;
|
||
}
|
||
|
||
/* The child must synchronize with gdb: gdb must set the exception port
|
||
before the child call PTRACE_SIGEXC. We use a pipe to achieve this.
|
||
FIXME: is there a lighter way ? */
|
||
static int ptrace_fds[2];
|
||
|
||
static void
|
||
darwin_ptrace_me (void)
|
||
{
|
||
int res;
|
||
char c;
|
||
|
||
/* Close write end point. */
|
||
if (close (ptrace_fds[1]) < 0)
|
||
trace_start_error_with_name ("close");
|
||
|
||
/* Wait until gdb is ready. */
|
||
res = read (ptrace_fds[0], &c, 1);
|
||
if (res != 0)
|
||
trace_start_error (_("unable to read from pipe, read returned: %d"), res);
|
||
|
||
if (close (ptrace_fds[0]) < 0)
|
||
trace_start_error_with_name ("close");
|
||
|
||
/* Get rid of privileges. */
|
||
if (setegid (getgid ()) < 0)
|
||
trace_start_error_with_name ("setegid");
|
||
|
||
/* Set TRACEME. */
|
||
if (PTRACE (PT_TRACE_ME, 0, 0, 0) < 0)
|
||
trace_start_error_with_name ("PTRACE");
|
||
|
||
/* Redirect signals to exception port. */
|
||
if (PTRACE (PT_SIGEXC, 0, 0, 0) < 0)
|
||
trace_start_error_with_name ("PTRACE");
|
||
}
|
||
|
||
/* Dummy function to be sure fork_inferior uses fork(2) and not vfork(2). */
|
||
static void
|
||
darwin_pre_ptrace (void)
|
||
{
|
||
if (pipe (ptrace_fds) != 0)
|
||
{
|
||
ptrace_fds[0] = -1;
|
||
ptrace_fds[1] = -1;
|
||
error (_("unable to create a pipe: %s"), safe_strerror (errno));
|
||
}
|
||
|
||
mark_fd_no_cloexec (ptrace_fds[0]);
|
||
mark_fd_no_cloexec (ptrace_fds[1]);
|
||
}
|
||
|
||
static void
|
||
darwin_ptrace_him (int pid)
|
||
{
|
||
struct inferior *inf = current_inferior ();
|
||
|
||
darwin_attach_pid (inf);
|
||
|
||
/* Let's the child run. */
|
||
close (ptrace_fds[0]);
|
||
close (ptrace_fds[1]);
|
||
|
||
unmark_fd_no_cloexec (ptrace_fds[0]);
|
||
unmark_fd_no_cloexec (ptrace_fds[1]);
|
||
|
||
darwin_init_thread_list (inf);
|
||
|
||
gdb_startup_inferior (pid, START_INFERIOR_TRAPS_EXPECTED);
|
||
}
|
||
|
||
static void
|
||
darwin_execvp (const char *file, char * const argv[], char * const env[])
|
||
{
|
||
posix_spawnattr_t attr;
|
||
short ps_flags = 0;
|
||
int res;
|
||
|
||
res = posix_spawnattr_init (&attr);
|
||
if (res != 0)
|
||
{
|
||
fprintf_unfiltered
|
||
(gdb_stderr, "Cannot initialize attribute for posix_spawn\n");
|
||
return;
|
||
}
|
||
|
||
/* Do like execve: replace the image. */
|
||
ps_flags = POSIX_SPAWN_SETEXEC;
|
||
|
||
/* Disable ASLR. The constant doesn't look to be available outside the
|
||
kernel include files. */
|
||
#ifndef _POSIX_SPAWN_DISABLE_ASLR
|
||
#define _POSIX_SPAWN_DISABLE_ASLR 0x0100
|
||
#endif
|
||
ps_flags |= _POSIX_SPAWN_DISABLE_ASLR;
|
||
res = posix_spawnattr_setflags (&attr, ps_flags);
|
||
if (res != 0)
|
||
{
|
||
fprintf_unfiltered (gdb_stderr, "Cannot set posix_spawn flags\n");
|
||
return;
|
||
}
|
||
|
||
posix_spawnp (NULL, argv[0], NULL, &attr, argv, env);
|
||
}
|
||
|
||
/* Read kernel version, and return TRUE if this host may have System
|
||
Integrity Protection (Sierra or later). */
|
||
|
||
static bool
|
||
may_have_sip ()
|
||
{
|
||
char str[16];
|
||
size_t sz = sizeof (str);
|
||
int ret;
|
||
|
||
ret = sysctlbyname ("kern.osrelease", str, &sz, NULL, 0);
|
||
if (ret == 0 && sz < sizeof (str))
|
||
{
|
||
unsigned long ver = strtoul (str, NULL, 10);
|
||
if (ver >= 16)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/* A helper for maybe_cache_shell. This copies the shell to the
|
||
cache. It will throw an exception on any failure. */
|
||
|
||
static void
|
||
copy_shell_to_cache (const char *shell, const std::string &new_name)
|
||
{
|
||
scoped_fd from_fd (gdb_open_cloexec (shell, O_RDONLY, 0));
|
||
if (from_fd.get () < 0)
|
||
error (_("Could not open shell (%s) for reading: %s"),
|
||
shell, safe_strerror (errno));
|
||
|
||
std::string new_dir = ldirname (new_name.c_str ());
|
||
if (!mkdir_recursive (new_dir.c_str ()))
|
||
error (_("Could not make cache directory \"%s\": %s"),
|
||
new_dir.c_str (), safe_strerror (errno));
|
||
|
||
gdb::char_vector temp_name = make_temp_filename (new_name);
|
||
scoped_fd to_fd (gdb_mkostemp_cloexec (&temp_name[0]));
|
||
gdb::unlinker unlink_file_on_error (temp_name.data ());
|
||
|
||
if (to_fd.get () < 0)
|
||
error (_("Could not open temporary file \"%s\" for writing: %s"),
|
||
temp_name.data (), safe_strerror (errno));
|
||
|
||
if (fcopyfile (from_fd.get (), to_fd.get (), nullptr,
|
||
COPYFILE_STAT | COPYFILE_DATA) != 0)
|
||
error (_("Could not copy shell to cache as \"%s\": %s"),
|
||
temp_name.data (), safe_strerror (errno));
|
||
|
||
/* Be sure that the caching is atomic so that we don't get bad
|
||
results from multiple copies of gdb running at the same time. */
|
||
if (rename (temp_name.data (), new_name.c_str ()) != 0)
|
||
error (_("Could not rename shell cache file to \"%s\": %s"),
|
||
new_name.c_str (), safe_strerror (errno));
|
||
|
||
unlink_file_on_error.keep ();
|
||
}
|
||
|
||
/* If $SHELL is restricted, try to cache a copy. Starting with El
|
||
Capitan, macOS introduced System Integrity Protection. Among other
|
||
things, this prevents certain executables from being ptrace'd. In
|
||
particular, executables in /bin, like most shells, are affected.
|
||
To work around this, while preserving command-line glob expansion
|
||
and redirections, gdb will cache a copy of the shell. Return true
|
||
if all is well -- either the shell is not subject to SIP or it has
|
||
been successfully cached. Returns false if something failed. */
|
||
|
||
static bool
|
||
maybe_cache_shell ()
|
||
{
|
||
/* SF_RESTRICTED is defined in sys/stat.h and lets us determine if a
|
||
given file is subject to SIP. */
|
||
#ifdef SF_RESTRICTED
|
||
|
||
/* If a check fails we want to revert -- maybe the user deleted the
|
||
cache while gdb was running, or something like that. */
|
||
copied_shell = nullptr;
|
||
|
||
const char *shell = get_shell ();
|
||
if (!IS_ABSOLUTE_PATH (shell))
|
||
{
|
||
warning (_("This version of macOS has System Integrity Protection.\n\
|
||
Normally gdb would try to work around this by caching a copy of your shell,\n\
|
||
but because your shell (%s) is not an absolute path, this is being skipped."),
|
||
shell);
|
||
return false;
|
||
}
|
||
|
||
struct stat sb;
|
||
if (stat (shell, &sb) < 0)
|
||
{
|
||
warning (_("This version of macOS has System Integrity Protection.\n\
|
||
Normally gdb would try to work around this by caching a copy of your shell,\n\
|
||
but because gdb could not stat your shell (%s), this is being skipped.\n\
|
||
The error was: %s"),
|
||
shell, safe_strerror (errno));
|
||
return false;
|
||
}
|
||
|
||
if ((sb.st_flags & SF_RESTRICTED) == 0)
|
||
return true;
|
||
|
||
/* Put the copy somewhere like ~/Library/Caches/gdb/bin/sh. */
|
||
std::string new_name = get_standard_cache_dir ();
|
||
/* There's no need to insert a directory separator here, because
|
||
SHELL is known to be absolute. */
|
||
new_name.append (shell);
|
||
|
||
/* Maybe it was cached by some earlier gdb. */
|
||
if (stat (new_name.c_str (), &sb) != 0 || !S_ISREG (sb.st_mode))
|
||
{
|
||
try
|
||
{
|
||
copy_shell_to_cache (shell, new_name);
|
||
}
|
||
catch (const gdb_exception_error &ex)
|
||
{
|
||
warning (_("This version of macOS has System Integrity Protection.\n\
|
||
Because `startup-with-shell' is enabled, gdb tried to work around SIP by\n\
|
||
caching a copy of your shell. However, this failed:\n\
|
||
%s\n\
|
||
If you correct the problem, gdb will automatically try again the next time\n\
|
||
you \"run\". To prevent these attempts, you can use:\n\
|
||
set startup-with-shell off"),
|
||
ex.what ());
|
||
return false;
|
||
}
|
||
|
||
printf_filtered (_("Note: this version of macOS has System Integrity Protection.\n\
|
||
Because `startup-with-shell' is enabled, gdb has worked around this by\n\
|
||
caching a copy of your shell. The shell used by \"run\" is now:\n\
|
||
%s\n"),
|
||
new_name.c_str ());
|
||
}
|
||
|
||
/* We need to make sure that the new name has the correct lifetime. */
|
||
static std::string saved_shell = std::move (new_name);
|
||
copied_shell = saved_shell.c_str ();
|
||
|
||
#endif /* SF_RESTRICTED */
|
||
|
||
return true;
|
||
}
|
||
|
||
void
|
||
darwin_nat_target::create_inferior (const char *exec_file,
|
||
const std::string &allargs,
|
||
char **env, int from_tty)
|
||
{
|
||
gdb::optional<scoped_restore_tmpl<int>> restore_startup_with_shell;
|
||
|
||
if (startup_with_shell && may_have_sip ())
|
||
{
|
||
if (!maybe_cache_shell ())
|
||
{
|
||
warning (_("startup-with-shell is now temporarily disabled"));
|
||
restore_startup_with_shell.emplace (&startup_with_shell, 0);
|
||
}
|
||
}
|
||
|
||
/* Do the hard work. */
|
||
fork_inferior (exec_file, allargs, env, darwin_ptrace_me,
|
||
darwin_ptrace_him, darwin_pre_ptrace, copied_shell,
|
||
darwin_execvp);
|
||
}
|
||
|
||
|
||
/* Set things up such that the next call to darwin_wait will immediately
|
||
return a fake stop event for inferior INF.
|
||
|
||
This assumes that the inferior's thread list has been initialized,
|
||
as it will suspend the inferior's first thread. */
|
||
|
||
static void
|
||
darwin_setup_fake_stop_event (struct inferior *inf)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
darwin_thread_t *thread;
|
||
kern_return_t kret;
|
||
|
||
gdb_assert (darwin_inf_fake_stop == NULL);
|
||
darwin_inf_fake_stop = inf;
|
||
|
||
/* When detecting a fake pending stop event, darwin_wait returns
|
||
an event saying that the first thread is in a DARWIN_STOPPED
|
||
state. To make that accurate, we need to suspend that thread
|
||
as well. Otherwise, we'll try resuming it when resuming the
|
||
inferior, and get a warning because the thread's suspend count
|
||
is already zero, making the resume request useless. */
|
||
thread = priv->threads[0];
|
||
kret = thread_suspend (thread->gdb_port);
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
|
||
/* Attach to process PID, then initialize for debugging it
|
||
and wait for the trace-trap that results from attaching. */
|
||
void
|
||
darwin_nat_target::attach (const char *args, int from_tty)
|
||
{
|
||
pid_t pid;
|
||
struct inferior *inf;
|
||
|
||
pid = parse_pid_to_attach (args);
|
||
|
||
if (pid == getpid ()) /* Trying to masturbate? */
|
||
error (_("I refuse to debug myself!"));
|
||
|
||
if (from_tty)
|
||
{
|
||
char *exec_file = get_exec_file (0);
|
||
|
||
if (exec_file)
|
||
printf_unfiltered (_("Attaching to program: %s, %s\n"), exec_file,
|
||
target_pid_to_str (ptid_t (pid)).c_str ());
|
||
else
|
||
printf_unfiltered (_("Attaching to %s\n"),
|
||
target_pid_to_str (ptid_t (pid)).c_str ());
|
||
}
|
||
|
||
if (pid == 0 || ::kill (pid, 0) < 0)
|
||
error (_("Can't attach to process %d: %s (%d)"),
|
||
pid, safe_strerror (errno), errno);
|
||
|
||
inferior_ptid = ptid_t (pid);
|
||
inf = current_inferior ();
|
||
inferior_appeared (inf, pid);
|
||
inf->attach_flag = 1;
|
||
|
||
darwin_attach_pid (inf);
|
||
|
||
darwin_suspend_inferior (inf);
|
||
|
||
darwin_init_thread_list (inf);
|
||
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
darwin_check_osabi (priv, inferior_ptid.tid ());
|
||
|
||
darwin_setup_fake_stop_event (inf);
|
||
|
||
priv->no_ptrace = 1;
|
||
}
|
||
|
||
/* Take a program previously attached to and detaches it.
|
||
The program resumes execution and will no longer stop
|
||
on signals, etc. We'd better not have left any breakpoints
|
||
in the program or it'll die when it hits one. For this
|
||
to work, it may be necessary for the process to have been
|
||
previously attached. It *might* work if the program was
|
||
started via fork. */
|
||
|
||
void
|
||
darwin_nat_target::detach (inferior *inf, int from_tty)
|
||
{
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
kern_return_t kret;
|
||
int res;
|
||
|
||
/* Display message. */
|
||
target_announce_detach (from_tty);
|
||
|
||
/* If ptrace() is in use, stop the process. */
|
||
if (!priv->no_ptrace)
|
||
darwin_stop_inferior (inf);
|
||
|
||
kret = darwin_restore_exception_ports (priv);
|
||
MACH_CHECK_ERROR (kret);
|
||
|
||
if (!priv->no_ptrace)
|
||
{
|
||
res = PTRACE (PT_DETACH, inf->pid, 0, 0);
|
||
if (res != 0)
|
||
printf_unfiltered (_("Unable to detach from process-id %d: %s (%d)"),
|
||
inf->pid, safe_strerror (errno), errno);
|
||
}
|
||
|
||
darwin_reply_to_all_pending_messages (inf);
|
||
|
||
/* When using ptrace, we have just performed a PT_DETACH, which
|
||
resumes the inferior. On the other hand, when we are not using
|
||
ptrace, we need to resume its execution ourselves. */
|
||
if (priv->no_ptrace)
|
||
darwin_resume_inferior (inf);
|
||
|
||
mourn_inferior ();
|
||
}
|
||
|
||
std::string
|
||
darwin_nat_target::pid_to_str (ptid_t ptid)
|
||
{
|
||
long tid = ptid.tid ();
|
||
|
||
if (tid != 0)
|
||
return string_printf (_("Thread 0x%lx of process %u"),
|
||
tid, ptid.pid ());
|
||
|
||
return normal_pid_to_str (ptid);
|
||
}
|
||
|
||
bool
|
||
darwin_nat_target::thread_alive (ptid_t ptid)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
/* If RDADDR is not NULL, read inferior task's LEN bytes from ADDR and
|
||
copy it to RDADDR in gdb's address space.
|
||
If WRADDR is not NULL, write gdb's LEN bytes from WRADDR and copy it
|
||
to ADDR in inferior task's address space.
|
||
Return 0 on failure; number of bytes read / writen otherwise. */
|
||
|
||
static int
|
||
darwin_read_write_inferior (task_t task, CORE_ADDR addr,
|
||
gdb_byte *rdaddr, const gdb_byte *wraddr,
|
||
ULONGEST length)
|
||
{
|
||
kern_return_t kret;
|
||
mach_vm_size_t res_length = 0;
|
||
|
||
inferior_debug (8, _("darwin_read_write_inferior(task=0x%x, %s, len=%s)\n"),
|
||
task, core_addr_to_string (addr), pulongest (length));
|
||
|
||
/* First read. */
|
||
if (rdaddr != NULL)
|
||
{
|
||
mach_vm_size_t count;
|
||
|
||
/* According to target.h(to_xfer_partial), one and only one may be
|
||
non-null. */
|
||
gdb_assert (wraddr == NULL);
|
||
|
||
kret = mach_vm_read_overwrite (task, addr, length,
|
||
(mach_vm_address_t) rdaddr, &count);
|
||
if (kret != KERN_SUCCESS)
|
||
{
|
||
inferior_debug
|
||
(1, _("darwin_read_write_inferior: mach_vm_read failed at %s: %s"),
|
||
core_addr_to_string (addr), mach_error_string (kret));
|
||
return 0;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/* See above. */
|
||
gdb_assert (wraddr != NULL);
|
||
|
||
while (length != 0)
|
||
{
|
||
mach_vm_address_t offset = addr & (mach_page_size - 1);
|
||
mach_vm_address_t region_address = (mach_vm_address_t) (addr - offset);
|
||
mach_vm_size_t aligned_length =
|
||
(mach_vm_size_t) PAGE_ROUND (offset + length);
|
||
vm_region_submap_short_info_data_64_t info;
|
||
mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
|
||
natural_t region_depth = 1000;
|
||
mach_vm_address_t region_start = region_address;
|
||
mach_vm_size_t region_length;
|
||
mach_vm_size_t write_length;
|
||
|
||
/* Read page protection. */
|
||
kret = mach_vm_region_recurse
|
||
(task, ®ion_start, ®ion_length, ®ion_depth,
|
||
(vm_region_recurse_info_t) &info, &count);
|
||
|
||
if (kret != KERN_SUCCESS)
|
||
{
|
||
inferior_debug (1, _("darwin_read_write_inferior: "
|
||
"mach_vm_region_recurse failed at %s: %s\n"),
|
||
core_addr_to_string (region_address),
|
||
mach_error_string (kret));
|
||
return res_length;
|
||
}
|
||
|
||
inferior_debug
|
||
(9, _("darwin_read_write_inferior: "
|
||
"mach_vm_region_recurse addr=%s, start=%s, len=%s\n"),
|
||
core_addr_to_string (region_address),
|
||
core_addr_to_string (region_start),
|
||
core_addr_to_string (region_length));
|
||
|
||
/* Check for holes in memory. */
|
||
if (region_start > region_address)
|
||
{
|
||
warning (_("No memory at %s (vs %s+0x%x). Nothing written"),
|
||
core_addr_to_string (region_address),
|
||
core_addr_to_string (region_start),
|
||
(unsigned)region_length);
|
||
return res_length;
|
||
}
|
||
|
||
/* Adjust the length. */
|
||
region_length -= (region_address - region_start);
|
||
if (region_length > aligned_length)
|
||
region_length = aligned_length;
|
||
|
||
/* Make the pages RW. */
|
||
if (!(info.protection & VM_PROT_WRITE))
|
||
{
|
||
vm_prot_t prot = VM_PROT_READ | VM_PROT_WRITE;
|
||
|
||
kret = mach_vm_protect (task, region_address, region_length,
|
||
FALSE, prot);
|
||
if (kret != KERN_SUCCESS)
|
||
{
|
||
prot |= VM_PROT_COPY;
|
||
kret = mach_vm_protect (task, region_address, region_length,
|
||
FALSE, prot);
|
||
}
|
||
if (kret != KERN_SUCCESS)
|
||
{
|
||
warning (_("darwin_read_write_inferior: "
|
||
"mach_vm_protect failed at %s "
|
||
"(len=0x%lx, prot=0x%x): %s"),
|
||
core_addr_to_string (region_address),
|
||
(unsigned long) region_length, (unsigned) prot,
|
||
mach_error_string (kret));
|
||
return res_length;
|
||
}
|
||
}
|
||
|
||
if (offset + length > region_length)
|
||
write_length = region_length - offset;
|
||
else
|
||
write_length = length;
|
||
|
||
/* Write. */
|
||
kret = mach_vm_write (task, addr, (vm_offset_t) wraddr, write_length);
|
||
if (kret != KERN_SUCCESS)
|
||
{
|
||
warning (_("darwin_read_write_inferior: mach_vm_write failed: %s"),
|
||
mach_error_string (kret));
|
||
return res_length;
|
||
}
|
||
|
||
/* Restore page rights. */
|
||
if (!(info.protection & VM_PROT_WRITE))
|
||
{
|
||
kret = mach_vm_protect (task, region_address, region_length,
|
||
FALSE, info.protection);
|
||
if (kret != KERN_SUCCESS)
|
||
{
|
||
warning (_("darwin_read_write_inferior: "
|
||
"mach_vm_protect restore failed at %s "
|
||
"(len=0x%lx): %s"),
|
||
core_addr_to_string (region_address),
|
||
(unsigned long) region_length,
|
||
mach_error_string (kret));
|
||
}
|
||
}
|
||
|
||
addr += write_length;
|
||
wraddr += write_length;
|
||
res_length += write_length;
|
||
length -= write_length;
|
||
}
|
||
|
||
return res_length;
|
||
}
|
||
|
||
/* Read LENGTH bytes at offset ADDR of task_dyld_info for TASK, and copy them
|
||
to RDADDR (in big endian).
|
||
Return 0 on failure; number of bytes read / written otherwise. */
|
||
|
||
#ifdef TASK_DYLD_INFO_COUNT
|
||
/* This is not available in Darwin 9. */
|
||
static enum target_xfer_status
|
||
darwin_read_dyld_info (task_t task, CORE_ADDR addr, gdb_byte *rdaddr,
|
||
ULONGEST length, ULONGEST *xfered_len)
|
||
{
|
||
struct task_dyld_info task_dyld_info;
|
||
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
|
||
kern_return_t kret;
|
||
|
||
if (addr != 0 || length > sizeof (mach_vm_address_t))
|
||
return TARGET_XFER_EOF;
|
||
|
||
kret = task_info (task, TASK_DYLD_INFO,
|
||
(task_info_t) &task_dyld_info, &count);
|
||
MACH_CHECK_ERROR (kret);
|
||
if (kret != KERN_SUCCESS)
|
||
return TARGET_XFER_E_IO;
|
||
|
||
store_unsigned_integer (rdaddr, length, BFD_ENDIAN_BIG,
|
||
task_dyld_info.all_image_info_addr);
|
||
*xfered_len = (ULONGEST) length;
|
||
return TARGET_XFER_OK;
|
||
}
|
||
#endif
|
||
|
||
|
||
|
||
enum target_xfer_status
|
||
darwin_nat_target::xfer_partial (enum target_object object, const char *annex,
|
||
gdb_byte *readbuf, const gdb_byte *writebuf,
|
||
ULONGEST offset, ULONGEST len,
|
||
ULONGEST *xfered_len)
|
||
{
|
||
struct inferior *inf = current_inferior ();
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
|
||
inferior_debug
|
||
(8, _("darwin_xfer_partial(%s, %s, rbuf=%s, wbuf=%s) pid=%u\n"),
|
||
core_addr_to_string (offset), pulongest (len),
|
||
host_address_to_string (readbuf), host_address_to_string (writebuf),
|
||
inf->pid);
|
||
|
||
switch (object)
|
||
{
|
||
case TARGET_OBJECT_MEMORY:
|
||
{
|
||
int l = darwin_read_write_inferior (priv->task, offset,
|
||
readbuf, writebuf, len);
|
||
|
||
if (l == 0)
|
||
return TARGET_XFER_EOF;
|
||
else
|
||
{
|
||
gdb_assert (l > 0);
|
||
*xfered_len = (ULONGEST) l;
|
||
return TARGET_XFER_OK;
|
||
}
|
||
}
|
||
#ifdef TASK_DYLD_INFO_COUNT
|
||
case TARGET_OBJECT_DARWIN_DYLD_INFO:
|
||
if (writebuf != NULL || readbuf == NULL)
|
||
{
|
||
/* Support only read. */
|
||
return TARGET_XFER_E_IO;
|
||
}
|
||
return darwin_read_dyld_info (priv->task, offset, readbuf, len,
|
||
xfered_len);
|
||
#endif
|
||
default:
|
||
return TARGET_XFER_E_IO;
|
||
}
|
||
|
||
}
|
||
|
||
static void
|
||
set_enable_mach_exceptions (const char *args, int from_tty,
|
||
struct cmd_list_element *c)
|
||
{
|
||
if (inferior_ptid != null_ptid)
|
||
{
|
||
struct inferior *inf = current_inferior ();
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
exception_mask_t mask;
|
||
kern_return_t kret;
|
||
|
||
if (enable_mach_exceptions)
|
||
mask = EXC_MASK_ALL;
|
||
else
|
||
{
|
||
darwin_restore_exception_ports (priv);
|
||
mask = EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT;
|
||
}
|
||
kret = task_set_exception_ports (priv->task, mask, darwin_ex_port,
|
||
EXCEPTION_DEFAULT, THREAD_STATE_NONE);
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
}
|
||
|
||
char *
|
||
darwin_nat_target::pid_to_exec_file (int pid)
|
||
{
|
||
static char path[PATH_MAX];
|
||
int res;
|
||
|
||
res = proc_pidinfo (pid, PROC_PIDPATHINFO, 0, path, PATH_MAX);
|
||
if (res >= 0)
|
||
return path;
|
||
else
|
||
return NULL;
|
||
}
|
||
|
||
ptid_t
|
||
darwin_nat_target::get_ada_task_ptid (long lwp, long thread)
|
||
{
|
||
struct inferior *inf = current_inferior ();
|
||
darwin_inferior *priv = get_darwin_inferior (inf);
|
||
kern_return_t kret;
|
||
mach_port_name_array_t names;
|
||
mach_msg_type_number_t names_count;
|
||
mach_port_type_array_t types;
|
||
mach_msg_type_number_t types_count;
|
||
long res = 0;
|
||
|
||
/* First linear search. */
|
||
for (darwin_thread_t *t : priv->threads)
|
||
{
|
||
if (t->inf_port == lwp)
|
||
return ptid_t (inferior_ptid.pid (), 0, t->gdb_port);
|
||
}
|
||
|
||
/* Maybe the port was never extract. Do it now. */
|
||
|
||
/* First get inferior port names. */
|
||
kret = mach_port_names (priv->task, &names, &names_count, &types,
|
||
&types_count);
|
||
MACH_CHECK_ERROR (kret);
|
||
if (kret != KERN_SUCCESS)
|
||
return null_ptid;
|
||
|
||
/* For each name, copy the right in the gdb space and then compare with
|
||
our view of the inferior threads. We don't forget to deallocate the
|
||
right. */
|
||
for (int i = 0; i < names_count; i++)
|
||
{
|
||
mach_port_t local_name;
|
||
mach_msg_type_name_t local_type;
|
||
|
||
/* We just need to know the corresponding name in gdb name space.
|
||
So extract and deallocate the right. */
|
||
kret = mach_port_extract_right (priv->task, names[i],
|
||
MACH_MSG_TYPE_COPY_SEND,
|
||
&local_name, &local_type);
|
||
if (kret != KERN_SUCCESS)
|
||
continue;
|
||
mach_port_deallocate (gdb_task, local_name);
|
||
|
||
for (darwin_thread_t *t : priv->threads)
|
||
{
|
||
if (t->gdb_port == local_name)
|
||
{
|
||
t->inf_port = names[i];
|
||
if (names[i] == lwp)
|
||
res = t->gdb_port;
|
||
}
|
||
}
|
||
}
|
||
|
||
vm_deallocate (gdb_task, (vm_address_t) names,
|
||
names_count * sizeof (mach_port_t));
|
||
|
||
if (res)
|
||
return ptid_t (inferior_ptid.pid (), 0, res);
|
||
else
|
||
return null_ptid;
|
||
}
|
||
|
||
bool
|
||
darwin_nat_target::supports_multi_process ()
|
||
{
|
||
return true;
|
||
}
|
||
|
||
void
|
||
_initialize_darwin_nat ()
|
||
{
|
||
kern_return_t kret;
|
||
|
||
gdb_task = mach_task_self ();
|
||
darwin_host_self = mach_host_self ();
|
||
|
||
/* Read page size. */
|
||
kret = host_page_size (darwin_host_self, &mach_page_size);
|
||
if (kret != KERN_SUCCESS)
|
||
{
|
||
mach_page_size = 0x1000;
|
||
MACH_CHECK_ERROR (kret);
|
||
}
|
||
|
||
inferior_debug (2, _("GDB task: 0x%lx, pid: %d\n"),
|
||
(unsigned long) mach_task_self (), getpid ());
|
||
|
||
add_setshow_zuinteger_cmd ("darwin", class_obscure,
|
||
&darwin_debug_flag, _("\
|
||
Set if printing inferior communication debugging statements."), _("\
|
||
Show if printing inferior communication debugging statements."), NULL,
|
||
NULL, NULL,
|
||
&setdebuglist, &showdebuglist);
|
||
|
||
add_setshow_boolean_cmd ("mach-exceptions", class_support,
|
||
&enable_mach_exceptions, _("\
|
||
Set if mach exceptions are caught."), _("\
|
||
Show if mach exceptions are caught."), _("\
|
||
When this mode is on, all low level exceptions are reported before being\n\
|
||
reported by the kernel."),
|
||
&set_enable_mach_exceptions, NULL,
|
||
&setlist, &showlist);
|
||
}
|