diff mbox

[12/16,v2] Extended-remote follow exec

Message ID 1408580964-27916-13-git-send-email-donb@codesourcery.com
State New
Headers show

Commit Message

Don Breazeal Aug. 21, 2014, 12:29 a.m. UTC
This patch implements support for exec events in gdbserver on linux, in multiprocess mode (target extended-remote).  Follow-exec-mode and rerun behave as expected.  Catchpoints for exec are not yet implemented since it will be easier to implement catchpoints for fork, vfork, and exec all at the same time.

TESTING
---------
The patch was tested on GNU/Linux x86_64 for native, remote, and
extended-remote targets.  The test results for native-gdbserver were
unchanged.  Thirteen tests that used to fail for native-extended-gdbserver
on Linux pass with this patch, and the non-ldr-exc-*.exp tests all pass in
all-stop mode and extended-remote.

One caveat: when an exec is detected, gdbserver emits a couple of warnings:
    gdbserver: unexpected r_debug version 0
    gdbserver: unexpected r_debug version 0
However, debugging of shared libraries that are loaded by the exec'd
program works just fine.  These messages are caused by gdbserver making
an attempt to initialize the solib hook before the r_debug structure has
been initialized.  A fix is provided in patch 14 of this patch series.

IMPLEMENTATION
----------------
Support for exec events in single-threaded programs was a fairly
straightforward replication of the implementation in native GDB:

1) Enable exec events via ptrace options.

2) Add support for handling the exec events to the handle_extended_wait and
linux_wait_for_event_filtered.  Detect the exec event, then find and save
the pathname of the executable file being exec'd.

3) Implement an additional "stop reason", "exec", in the RSP stop reply
packet "T".

Existing GDB code takes care of handling the exec event on the host side
without modification.

Support for exec events in multi-threaded programs required some additional
work that required a couple of significant changes to existing code.  In a
nutshell, the changes are to:

4) Use the PTRACE_EVENT_EXIT extended event to handle thread exit, while
not exposing any change in exit handling to the user.

5) Recognize when the exec'ing thread has vanished (become the thread group
leader) in send_sigstop.  Native GDB does this differently.

The rationale for #4 is discussed in the previous patch of this series.

Rationale for item 5: when a non-leader thread exec's, all the other
threads are terminated and the exec'ing thread changes its thread id to
that of the old leader (the process id) as part of the exec.  There is no
event reported for the "exit" of the exec'ing thread; it appears to have
vanished.

Determining that the exec'ing thread has "vanished" in native GDB is done
by calling waitpid(PID), and if it returns ECHILD it means that the thread
is gone.  We don't want to use waitpid(PID) in gdbserver, based on the
discussion in

https://www.sourceware.org/ml/gdb-patches/2014-02/msg00828.html.

An alternative is to send a signal to each thread and look for an ESRCH (No
such process) error.  In all-stop mode this can be done in the normal
course of events, since when gdbserver reports an exec event it stops all
the other threads with a SIGSTOP.  In non-stop mode, when an exec event has
been detected, we can call stop_all_lwps/unstop_all_lwps to accomplish the
same thing.

Thanks
--Don

gdb/
2014-08-20  Don Breazeal  <donb@codesourcery.com>

	* linux-nat.c (linux_handle_extended_wait): Change call to
	linux_child_pid_to_exec_file to call to linux_proc_pid_to_exec_file.
	(linux_child_pid_to_exec_file): Deleted function, moved to
	common code.
	(linux_target_install_ops): Change linux_child_pid_to_exec_file to
	linux_proc_pid_to_exec_file.
	* nat/linux-procfs.c (linux_proc_pid_to_exec_file): New function.
	* nat/linux-procfs.h (linux_proc_pid_to_exec_file): Declare.
	* nat/linux-ptrace.c (linux_test_for_traceexit): Add
	PTRACE_O_TRACEEXEC.
	* remote.c (remote_parse_stop_reply): New stop reason "exec" for
	'T' Stop Reply Packet.

gdb/gdbserver/
2014-08-20  Don Breazeal  <donb@codesourcery.com>

	* linux-low.c (handle_extended_wait): Handle PTRACE_EVENT_EXEC.
	(check_zombie_leaders): Update comment.
	(linux_low_filter_event): Handle exec event.
	(linux_wait_for_event_filtered): Update comment.
	(extended_event_reported): Add exec event to the list of extended
	events.
	(send_sigstop): Check return value from kill_lwp and delete lwp
	on 'No such process' error.
	(initialize_low): Add PTRACE_O_TRACEEXIT.
	* remote-utils.c (prepare_resume_reply): New stop reason "exec" for
	'T' Stop Reply Packet.

---
 gdb/gdbserver/linux-low.c    |   91 +++++++++++++++++++++++++++++++++++++++--
 gdb/gdbserver/remote-utils.c |   19 +++++++++
 gdb/linux-nat.c              |   22 +---------
 gdb/nat/linux-procfs.c       |   18 ++++++++
 gdb/nat/linux-procfs.h       |    4 ++
 gdb/nat/linux-ptrace.c       |    9 +++-
 gdb/remote.c                 |   25 +++++++++++-
 7 files changed, 159 insertions(+), 29 deletions(-)
diff mbox

Patch

diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index aa9a51e..f8bab6c 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -565,6 +565,21 @@  handle_extended_wait (struct lwp_info *event_child, int *wstatp)
       ptrace (PTRACE_CONT, lwpid, (PTRACE_TYPE_ARG3) 0, (PTRACE_TYPE_ARG4) 0);
       return ret;
     }
+  else if (event == PTRACE_EVENT_EXEC)
+    {
+      if (debug_threads)
+	{
+	  debug_printf ("HEW: Got exec event from LWP %ld\n",
+			lwpid_of (event_thr));
+	}
+
+      event_child->waitstatus.kind = TARGET_WAITKIND_EXECD;
+      event_child->waitstatus.value.execd_pathname
+	= xstrdup (linux_proc_pid_to_exec_file (NULL, lwpid_of (event_thr)));
+
+      /* Report the event.  */
+      return 0;
+    }
 
   internal_error (__FILE__, __LINE__, _("unknown ptrace event %d"), event);
 }
@@ -2036,6 +2051,56 @@  linux_low_filter_event (ptid_t filter_ptid, int lwpid, int *wstatp)
 
   child = find_lwp_pid (pid_to_ptid (lwpid));
 
+  /* Check for stop events reported by a process we didn't already
+     know about - anything not already in our LWP list.
+
+     If we're expecting to receive stopped processes after
+     fork, vfork, and clone events, then we'll just add the
+     new one to our list and go back to waiting for the event
+     to be reported - the stopped process might be returned
+     from waitpid before or after the event is.
+
+     But note the case of a non-leader thread exec'ing after the
+     leader having exited, and gone from our lists.  On an exec,
+     the Linux kernel destroys all other threads (except the execing
+     one) in the thread group, and resets the execing thread's tid
+     to the tgid.  No exit notification is sent for the execing
+     thread -- from the ptracer's perspective, it appears as though
+     the execing thread just vanishes.  When they are available, we
+     use exit events (PTRACE_EVENT_EXIT) to detect thread exit
+     reliably.  As soon as all other threads (if any) are reaped or
+     have reported their PTRACE_EVENT_EXIT events, the execing
+     thread changes it's tid to the tgid, and the previous (zombie)
+     leader vanishes, giving place to the "new" leader.  The lwp
+     entry for the previous leader is deleted when we handle its
+     exit event, and we re-add the new one here.  */
+
+  if (WIFSTOPPED (wstat) && (child == NULL) && (WSTOPSIG (wstat) == SIGTRAP)
+      && (linux_ptrace_get_extended_event (wstat) == PTRACE_EVENT_EXEC))
+    {
+      ptid_t child_ptid;
+
+      /* A multi-thread exec after we had seen the leader exiting.  */
+      if (debug_threads)
+	debug_printf ("LLW: Re-adding thread group leader LWP %d.\n", lwpid);
+
+      child_ptid = ptid_build (lwpid, lwpid, 0);
+      child = add_lwp (child_ptid);
+      child->stopped = 1;
+      current_inferior = child->thread;
+
+      if (non_stop && stopping_threads == NOT_STOPPING_THREADS)
+	{
+	  /* Make sure we delete the lwp entry for the exec'ing thread,
+	     which will have vanished.  We do this by sending a signal
+	     to all the other threads in the lwp list, deleting any
+	     that are not found.  Note that in all-stop mode this will
+	     happen before reporting the event.  */
+	  stop_all_lwps (0, child);
+	  unstop_all_lwps (0, child);
+	}
+    }
+
   /* If we didn't find a process, one of two things presumably happened:
      - A process we started and then detached from has exited.  Ignore it.
      - A process we are controlling has forked and the new child's stop
@@ -2368,8 +2433,7 @@  linux_wait_for_event_filtered (ptid_t wait_ptid, ptid_t filter_ptid,
 	 - When a non-leader thread execs, that thread just vanishes
 	   without reporting an exit (so we'd hang if we waited for it
 	   explicitly in that case).  The exec event is reported to
-	   the TGID pid (although we don't currently enable exec
-	   events).  */
+	   the TGID pid.  */
       errno = 0;
       ret = my_waitpid (-1, wstatp, options | WNOHANG);
 
@@ -2774,7 +2838,8 @@  extended_event_reported (const struct target_waitstatus *waitstatus)
 
   return (waitstatus->kind == TARGET_WAITKIND_FORKED
 	  || waitstatus->kind == TARGET_WAITKIND_VFORKED
-	  || waitstatus->kind == TARGET_WAITKIND_VFORK_DONE);
+	  || waitstatus->kind == TARGET_WAITKIND_VFORK_DONE
+	  || waitstatus->kind == TARGET_WAITKIND_EXECD);
 }
 
 /* Wait for process, returns status.  */
@@ -3401,6 +3466,7 @@  static void
 send_sigstop (struct lwp_info *lwp)
 {
   int pid;
+  int ret;
 
   pid = lwpid_of (get_lwp_thread (lwp));
 
@@ -3418,7 +3484,21 @@  send_sigstop (struct lwp_info *lwp)
     debug_printf ("Sending sigstop to lwp %d\n", pid);
 
   lwp->stop_expected = 1;
-  kill_lwp (pid, SIGSTOP);
+  errno = 0;
+  ret = kill_lwp (pid, SIGSTOP);
+  if (ret == -1 && errno == ESRCH)
+    {
+      /* If the kill fails with "No such process", on GNU/Linux we know
+	 that the LWP has vanished - it is not a zombie, it is gone.
+	 This is due to a thread other than the thread group leader
+	 calling exec.  See comments in linux_low_filter_event regarding
+	 PTRACE_EVENT_EXEC.  */
+      delete_lwp (lwp);
+      set_desired_inferior (0);
+
+      if (debug_threads)
+	debug_printf ("send_sigstop: lwp %d has vanished\n", pid);
+    }
 }
 
 static int
@@ -6489,6 +6569,7 @@  initialize_low (void)
   linux_ptrace_set_requested_options (PTRACE_O_TRACEFORK
 				      | PTRACE_O_TRACEVFORK
 				      | PTRACE_O_TRACEVFORKDONE
-				      | PTRACE_O_TRACEEXIT);
+				      | PTRACE_O_TRACEEXIT
+				      | PTRACE_O_TRACEEXEC);
   linux_ptrace_check_options ();
 }
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index e816e5e..2b3e638 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1107,6 +1107,7 @@  prepare_resume_reply (char *buf, ptid_t ptid,
     case TARGET_WAITKIND_STOPPED:
     case TARGET_WAITKIND_FORKED:
     case TARGET_WAITKIND_VFORKED:
+    case TARGET_WAITKIND_EXECD:
       {
 	struct thread_info *saved_inferior;
 	const char **regp;
@@ -1123,6 +1124,24 @@  prepare_resume_reply (char *buf, ptid_t ptid,
 		      ptid_get_pid (status->value.related_pid),
 		      ptid_get_lwp (status->value.related_pid));
 	  }
+	else if ((status->kind == TARGET_WAITKIND_EXECD) && multi_process)
+	  {
+	    enum gdb_signal signal = GDB_SIGNAL_TRAP;
+	    const char *event = "exec";
+	    char hexified_pathname[PATH_MAX];
+
+	    sprintf (buf, "T%02x%s:", signal, event);
+	    buf += strlen (buf);
+
+	    /* Encode pathname to hexified format.  */
+	    bin2hex ((const gdb_byte *) status->value.execd_pathname,
+		      hexified_pathname, strlen(status->value.execd_pathname));
+
+	    sprintf (buf, "%s;", hexified_pathname);
+	    xfree (status->value.execd_pathname);
+	    status->value.execd_pathname = NULL;
+	    buf += strlen (buf);
+	  }
 	else
 	  sprintf (buf, "T%02x", status->value.sig);
 
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index b7bb978..0003640 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -682,7 +682,6 @@  linux_nat_pass_signals (struct target_ops *self,
 /* Prototypes for local functions.  */
 static int stop_wait_callback (struct lwp_info *lp, void *data);
 static int linux_thread_alive (ptid_t ptid);
-static char *linux_child_pid_to_exec_file (struct target_ops *self, int pid);
 
 
 
@@ -2011,7 +2010,7 @@  linux_handle_extended_wait (struct lwp_info *lp, int status,
 
       ourstatus->kind = TARGET_WAITKIND_EXECD;
       ourstatus->value.execd_pathname
-	= xstrdup (linux_child_pid_to_exec_file (NULL, pid));
+	= xstrdup (linux_proc_pid_to_exec_file (NULL, pid));
 
       return 0;
     }
@@ -3850,23 +3849,6 @@  linux_nat_thread_name (struct target_ops *self, struct thread_info *thr)
   return result;
 }
 
-/* Accepts an integer PID; Returns a string representing a file that
-   can be opened to get the symbols for the child process.  */
-
-static char *
-linux_child_pid_to_exec_file (struct target_ops *self, int pid)
-{
-  static char buf[PATH_MAX];
-  char name[PATH_MAX];
-
-  xsnprintf (name, PATH_MAX, "/proc/%d/exe", pid);
-  memset (buf, 0, PATH_MAX);
-  if (readlink (name, buf, PATH_MAX - 1) <= 0)
-    strcpy (buf, name);
-
-  return buf;
-}
-
 /* Records the thread's register state for the corefile note
    section.  */
 
@@ -4315,7 +4297,7 @@  linux_target_install_ops (struct target_ops *t)
   t->to_insert_exec_catchpoint = linux_child_insert_exec_catchpoint;
   t->to_remove_exec_catchpoint = linux_child_remove_exec_catchpoint;
   t->to_set_syscall_catchpoint = linux_child_set_syscall_catchpoint;
-  t->to_pid_to_exec_file = linux_child_pid_to_exec_file;
+  t->to_pid_to_exec_file = linux_proc_pid_to_exec_file;
   t->to_post_startup_inferior = linux_child_post_startup_inferior;
   t->to_post_attach = linux_child_post_attach;
   t->to_follow_fork = linux_child_follow_fork;
diff --git a/gdb/nat/linux-procfs.c b/gdb/nat/linux-procfs.c
index 84fc890..fa664f1 100644
--- a/gdb/nat/linux-procfs.c
+++ b/gdb/nat/linux-procfs.c
@@ -24,6 +24,7 @@ 
 
 #include "linux-procfs.h"
 #include "filestuff.h"
+#include "target.h"
 
 /* Return the TGID of LWPID from /proc/pid/status.  Returns -1 if not
    found.  */
@@ -118,3 +119,20 @@  linux_proc_pid_is_zombie (pid_t pid)
 {
   return linux_proc_pid_has_state (pid, "Z (zombie)");
 }
+
+/* Accepts an integer PID; Returns a string representing a file that
+   can be opened to get the symbols for the child process.  */
+
+char *
+linux_proc_pid_to_exec_file (struct target_ops *self, int pid)
+{
+  static char buf[PATH_MAX];
+  char name[PATH_MAX];
+
+  xsnprintf (name, PATH_MAX, "/proc/%d/exe", pid);
+  memset (buf, 0, PATH_MAX);
+  if (readlink (name, buf, PATH_MAX - 1) <= 0)
+    strcpy (buf, name);
+
+  return buf;
+}
diff --git a/gdb/nat/linux-procfs.h b/gdb/nat/linux-procfs.h
index d13fff7..d3581de 100644
--- a/gdb/nat/linux-procfs.h
+++ b/gdb/nat/linux-procfs.h
@@ -40,4 +40,8 @@  extern int linux_proc_pid_is_stopped (pid_t pid);
 
 extern int linux_proc_pid_is_zombie (pid_t pid);
 
+/* Return pathname of exec file for process with PID.  */
+
+extern char *linux_proc_pid_to_exec_file (struct target_ops *self, int pid);
+
 #endif /* COMMON_LINUX_PROCFS_H */
diff --git a/gdb/nat/linux-ptrace.c b/gdb/nat/linux-ptrace.c
index e63bcd8..1c02b71 100644
--- a/gdb/nat/linux-ptrace.c
+++ b/gdb/nat/linux-ptrace.c
@@ -493,6 +493,7 @@  linux_test_for_traceexit (int child_pid)
   if (ret != 0)
     return;
 
+
   /* We don't know for sure that the feature is available; old
      versions of PTRACE_SETOPTIONS ignored unknown options.  So
      see if the process exit will generate a PTRACE_EVENT_EXIT.
@@ -513,8 +514,12 @@  linux_test_for_traceexit (int child_pid)
       if (ret == child_pid && WIFSTOPPED (status)
 	  && linux_ptrace_get_extended_event (status) == PTRACE_EVENT_EXIT)
 	{
-	  /* PTRACE_O_TRACEEXIT is supported.  */
-	  available_ptrace_options |= PTRACE_O_TRACEEXIT;
+	  /* PTRACE_O_TRACEEXIT is supported.
+
+	     We use exit events to implement support for exec events.
+	     Because exit events are supported, we can assume exec events
+	     are also supported, so we add them as well.  */
+	  available_ptrace_options |= PTRACE_O_TRACEEXIT | PTRACE_O_TRACEEXEC;
 	}
     }
 }
diff --git a/gdb/remote.c b/gdb/remote.c
index 6694ca2..d2256fd 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -5491,11 +5491,13 @@  remote_parse_stop_reply (char *buf, struct stop_reply *event)
 	     pnum and set p1 to point to the character following it.
 	     Otherwise p1 points to p.  */
 
-	  /* If this packet is an awatch packet, don't parse the 'a'
-	     as a register number.  */
+	  /* If this packet has a stop reason string that starts
+	     with a character that could be a hex digit, don't parse
+	     it as a register number.  */
 
 	  if (strncmp (p, "awatch", strlen("awatch")) != 0
 	      && strncmp (p, "core", strlen ("core") != 0)
+	      && strncmp (p, "exec", strlen ("exec") != 0)
 	      && strncmp (p, "fork", strlen ("fork") != 0))
 	    {
 	      /* Read the ``P'' register number.  */
@@ -5568,6 +5570,25 @@  Packet: '%s'\n"),
 		  event->ws.kind = TARGET_WAITKIND_VFORK_DONE;
 		  p = p_temp;
 		}
+	      else if (strncmp (p, "exec", p1 - p) == 0)
+		{
+		  ULONGEST pid;
+		  char pathname[PATH_MAX];
+
+		  p = unpack_varlen_hex (++p1, &pid);
+
+		  /* Save the pathname for event reporting and for
+		     the next run command.  */
+		  hex2bin (p1, (gdb_byte *) pathname, (p - p1)/2);
+		  /* Add the null terminator.  */
+		  pathname[(p - p1)/2] = '\0';
+		  /* This is freed during event handling.  */
+		  event->ws.value.execd_pathname = xstrdup (pathname);
+		  event->ws.kind = TARGET_WAITKIND_EXECD;
+		  /* Save the pathname for the next run command.  */
+		  xfree (remote_exec_file);
+		  remote_exec_file = xstrdup (pathname);
+		}
 	      else
 		{
 		  /* Silently skip unknown optional info.  */