/* Darwin support for GDB, the GNU debugger.
- Copyright (C) 2008-2018 Free Software Foundation, Inc.
+ Copyright (C) 2008-2019 Free Software Foundation, Inc.
Contributed by AdaCore.
#include "arch-utils.h"
#include "bfd.h"
#include "bfd/mach-o.h"
+#include "gdbarch.h"
+#include <copyfile.h>
#include <sys/ptrace.h>
#include <sys/signal.h>
#include <setjmp.h>
#include <mach/port.h>
#include "darwin-nat.h"
-#include "common/filestuff.h"
+#include "filenames.h"
+#include "gdbsupport/filestuff.h"
+#include "gdbsupport/gdb_unlinker.h"
+#include "gdbsupport/pathstuff.h"
+#include "gdbsupport/scoped_fd.h"
#include "nat/fork-inferior.h"
/* Quick overview.
/* If Set, catch all mach exceptions (before they are converted to signals
by the kernel). */
-static int enable_mach_exceptions;
+static bool 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)
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);
{
darwin_inferior *priv = get_darwin_inferior (inf);
- return priv->task == *(task_t *)port_ptr;
+ return priv != nullptr && priv->task == *(task_t *)port_ptr;
}
static int
{
darwin_inferior *priv = get_darwin_inferior (inf);
- for (darwin_thread_t *t : priv->threads)
- {
- if (t->gdb_port == thread)
- return t;
- }
+ if (priv != nullptr)
+ for (darwin_thread_t *t : priv->threads)
+ {
+ if (t->gdb_port == thread)
+ return t;
+ }
return NULL;
}
{
darwin_inferior *priv = get_darwin_inferior (inf);
- if (!priv->suspended)
+ if (priv != nullptr && !priv->suspended)
{
kern_return_t kret;
{
darwin_inferior *priv = get_darwin_inferior (inf);
- if (priv->suspended)
+ if (priv != nullptr && priv->suspended)
{
kern_return_t kret;
/* 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
NDR_record_t *ndr = (NDR_record_t *)(hdr + 1);
integer_t *data = (integer_t *)(ndr + 1);
struct inferior *inf;
- darwin_thread_t *thread;
task_t task_port;
- thread_t thread_port;
- kern_return_t kret;
- int i;
/* Check message header. */
if (hdr->msgh_bits & MACH_MSGH_BITS_COMPLEX)
inf = darwin_find_inferior_by_task (task_port);
*pinf = inf;
- darwin_inferior *priv = get_darwin_inferior (inf);
-
/* Check message destination. */
- if (inf != NULL && hdr->msgh_local_port != priv->notify_port)
- return -4;
+ if (inf != NULL)
+ {
+ darwin_inferior *priv = get_darwin_inferior (inf);
+ if (hdr->msgh_local_port != priv->notify_port)
+ return -4;
+ }
return 0;
}
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)
{
- kern_return_t kret;
- int res;
-
inferior_debug
(3, _("darwin_resume_thread: state=%d, thread=0x%x, step=%d nsignal=%d\n"),
thread->msg_state, thread->gdb_port, step, nsignal);
&& thread->event.ex_data[0] == EXC_SOFT_SIGNAL)
{
/* Either deliver a new signal or cancel the signal received. */
- res = PTRACE (PT_THUPDATE, inf->pid,
- (caddr_t) (uintptr_t) thread->gdb_port, nsignal);
+ 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);
}
{
/* Note: ptrace is allowed only if the process is stopped.
Directly send the signal to the thread. */
- res = syscall (SYS___pthread_kill, thread->gdb_port, nsignal);
+ 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;
break;
case DARWIN_STOPPED:
- kret = thread_resume (thread->gdb_port);
+ kern_return_t kret = thread_resume (thread->gdb_port);
MACH_CHECK_ERROR (kret);
thread->msg_state = DARWIN_RUNNING;
{
darwin_inferior *priv = get_darwin_inferior (inf);
- for (darwin_thread_t *thread : priv->threads)
- darwin_resume_thread (inf, thread, step, nsignal);
+ if (priv != nullptr)
+ for (darwin_thread_t *thread : priv->threads)
+ darwin_resume_thread (inf, thread, step, nsignal);
}
struct resume_inferior_threads_param
darwin_nat_target::resume (ptid_t ptid, int step, enum gdb_signal signal)
{
struct target_waitstatus status;
- int pid;
- kern_return_t kret;
- int res;
int nsignal;
- struct inferior *inf;
inferior_debug
(2, _("darwin_resume: pid=%d, tid=0x%lx, step=%d, signal=%d\n"),
- ptid_get_pid (ptid), ptid_get_tid (ptid), step, signal);
+ ptid.pid (), ptid.tid (), step, signal);
if (signal == GDB_SIGNAL_0)
nsignal = 0;
ptid = inferior_ptid;
/* minus_one_ptid is RESUME_ALL. */
- if (ptid_equal (ptid, minus_one_ptid))
+ if (ptid == minus_one_ptid)
{
struct resume_inferior_threads_param param;
else
{
struct inferior *inf = find_inferior_ptid (ptid);
- long tid = ptid_get_tid (ptid);
+ long tid = ptid.tid ();
/* Stop the inferior (should be useless). */
darwin_suspend_inferior (inf);
if (!priv->no_ptrace)
{
- pid_t res;
+ pid_t res_pid;
int wstatus;
- res = wait4 (inf->pid, &wstatus, 0, NULL);
- if (res < 0 || res != inf->pid)
+ res_pid = wait4 (inf->pid, &wstatus, 0, NULL);
+ if (res_pid < 0 || res_pid != inf->pid)
{
printf_unfiltered (_("wait4: res=%d: %s\n"),
- res, safe_strerror (errno));
+ res_pid, safe_strerror (errno));
status->kind = TARGET_WAITKIND_IGNORE;
return minus_one_ptid;
}
}
inferior_debug (4, _("darwin_wait: pid=%d exit, status=0x%x\n"),
- res, wstatus);
+ res_pid, wstatus);
/* Looks necessary on Leopard and harmless... */
wait4 (inf->pid, &wstatus, 0, NULL);
if (breakpoint_inserted_here_p (regcache->aspace (), pc))
{
inferior_debug (4, "cancel_breakpoint for thread 0x%lx\n",
- (unsigned long) ptid_get_tid (ptid));
+ (unsigned long) ptid.tid ());
/* Back up the PC if necessary. */
if (gdbarch_decr_pc_after_break (gdbarch))
inferior_debug
(2, _("darwin_wait: waiting for a message pid=%d thread=%lx\n"),
- ptid_get_pid (ptid), ptid_get_tid (ptid));
+ ptid.pid (), ptid.tid ());
/* Handle fake stop events at first. */
if (darwin_inf_fake_stop != NULL)
darwin_dump_message (hdr, darwin_debug_flag > 11);
res = darwin_decode_message (hdr, &thread, &inf, status);
- if (ptid_equal (res, minus_one_ptid))
+ if (res == minus_one_ptid)
continue;
/* Early return in case an inferior has exited. */
darwin_inferior *priv = get_darwin_inferior (inf);
kern_return_t kret;
mach_port_t prev;
- int i;
/* Deallocate threads. */
darwin_deallocate_threads (inf);
{
struct target_waitstatus wstatus;
ptid_t ptid;
- kern_return_t kret;
- int status;
int res;
darwin_inferior *priv = get_darwin_inferior (inf);
{
darwin_inferior *priv = get_darwin_inferior (inf);
kern_return_t kret;
- int traps_expected;
exception_mask_t mask;
kret = darwin_save_exception_ports (priv);
struct target_waitstatus wstatus;
ptid_t ptid;
kern_return_t kret;
- int status;
int res;
- if (ptid_equal (inferior_ptid, null_ptid))
+ if (inferior_ptid == null_ptid)
return;
gdb_assert (inf != NULL);
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). */
+ for (darwin_thread_t *thread : priv->threads)
+ thread->signaled = 1;
+
darwin_resume_inferior (inf);
ptid = darwin_wait (inferior_ptid, &wstatus);
darwin_attach_pid (struct inferior *inf)
{
kern_return_t kret;
- mach_port_t prev_port;
- int traps_expected;
- mach_port_t prev_not;
- exception_mask_t mask;
darwin_inferior *priv = new darwin_inferior;
inf->priv.reset (priv);
- TRY
+ try
{
kret = task_for_pid (gdb_task, inf->pid, &priv->task);
if (kret != KERN_SUCCESS)
darwin_setup_exceptions (inf);
}
- CATCH (ex, RETURN_MASK_ALL)
+ catch (const gdb_exception &ex)
{
- inf->pid = 0;
- inf->priv.reset ();
+ exit_inferior (inf);
inferior_ptid = null_ptid;
- throw_exception (ex);
+ throw;
}
- END_CATCH
target_ops *darwin_ops = get_native_target ();
if (!target_is_pushed (darwin_ops))
static struct thread_info *
thread_info_from_private_thread_info (darwin_thread_info *pti)
{
- struct thread_info *it;
-
- ALL_THREADS (it)
+ 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)
- break;
+ return it;
}
- gdb_assert (it != NULL);
-
- return it;
+ gdb_assert_not_reached ("did not find gdb thread for darwin thread");
}
static void
static void
darwin_ptrace_him (int pid)
{
- task_t itask;
- kern_return_t kret;
- mach_port_t prev_port;
- int traps_expected;
struct inferior *inf = current_inferior ();
darwin_attach_pid (inf);
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<bool>> 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, NULL,
+ darwin_ptrace_him, darwin_pre_ptrace, copied_shell,
darwin_execvp);
}
\f
darwin_nat_target::attach (const char *args, int from_tty)
{
pid_t pid;
- pid_t pid2;
- int wstatus;
- int res;
struct inferior *inf;
- kern_return_t kret;
pid = parse_pid_to_attach (args);
if (exec_file)
printf_unfiltered (_("Attaching to program: %s, %s\n"), exec_file,
- target_pid_to_str (pid_to_ptid (pid)));
+ target_pid_to_str (ptid_t (pid)).c_str ());
else
printf_unfiltered (_("Attaching to %s\n"),
- target_pid_to_str (pid_to_ptid (pid)));
-
- gdb_flush (gdb_stdout);
+ 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 = pid_to_ptid (pid);
+ inferior_ptid = ptid_t (pid);
inf = current_inferior ();
inferior_appeared (inf, pid);
inf->attach_flag = 1;
darwin_inferior *priv = get_darwin_inferior (inf);
- darwin_check_osabi (priv, ptid_get_tid (inferior_ptid));
+ darwin_check_osabi (priv, inferior_ptid.tid ());
darwin_setup_fake_stop_event (inf);
void
darwin_nat_target::detach (inferior *inf, int from_tty)
{
- pid_t pid = ptid_get_pid (inferior_ptid);
darwin_inferior *priv = get_darwin_inferior (inf);
kern_return_t kret;
int res;
mourn_inferior ();
}
-const char *
+std::string
darwin_nat_target::pid_to_str (ptid_t ptid)
{
- static char buf[80];
- long tid = ptid_get_tid (ptid);
+ long tid = ptid.tid ();
if (tid != 0)
- {
- snprintf (buf, sizeof (buf), _("Thread 0x%lx of process %u"),
- tid, ptid_get_pid (ptid));
- return buf;
- }
+ return string_printf (_("Thread 0x%lx of process %u"),
+ tid, ptid.pid ());
return normal_pid_to_str (ptid);
}
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. */
+ Return 0 on failure; number of bytes read / written otherwise. */
static int
darwin_read_write_inferior (task_t task, CORE_ADDR addr,
{
kern_return_t kret;
mach_vm_size_t res_length = 0;
- pointer_t copied;
- mach_msg_type_number_t copy_count;
- mach_vm_size_t remaining_length;
- mach_vm_address_t region_address;
- mach_vm_size_t region_length;
inferior_debug (8, _("darwin_read_write_inferior(task=0x%x, %s, len=%s)\n"),
task, core_addr_to_string (addr), pulongest (length));
{
struct task_dyld_info task_dyld_info;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
- int sz = TASK_DYLD_INFO_COUNT * sizeof (natural_t);
kern_return_t kret;
if (addr != 0 || length > sizeof (mach_vm_address_t))
set_enable_mach_exceptions (const char *args, int from_tty,
struct cmd_list_element *c)
{
- if (!ptid_equal (inferior_ptid, null_ptid))
+ if (inferior_ptid != null_ptid)
{
struct inferior *inf = current_inferior ();
darwin_inferior *priv = get_darwin_inferior (inf);
for (darwin_thread_t *t : priv->threads)
{
if (t->inf_port == lwp)
- return ptid_t (ptid_get_pid (inferior_ptid), 0, t->gdb_port);
+ return ptid_t (inferior_ptid.pid (), 0, t->gdb_port);
}
/* Maybe the port was never extract. Do it now. */
names_count * sizeof (mach_port_t));
if (res)
- return ptid_t (ptid_get_pid (inferior_ptid), 0, res);
+ return ptid_t (inferior_ptid.pid (), 0, res);
else
return null_ptid;
}