[PATCH 1/4 v3] Remote Linux ptrace exit events

Don Breazeal
2014-05-23T22:49:47Z

This patch implements support for the extended ptrace event PTRACE_EVENT_EXIT on Linux. This is a preparatory patch for exec event support.

The use of this event is entirely internal to gdbserver; these events are not reported to GDB or the user. When this event occurs, if the reporting thread is not the last thread in a process, its lwp entry is simply deleted, since this is what happens in the absence of exit events. If it is the last thread of a process, the wait status is set to the actual wait status of the thread, retrieved by PTRACE_O_GETMESSAGE, instead of the status that indicates the extended event, and the existing mechanisms for handling thread exit proceed as usual.

The only purpose in using the exit events instead of the existing wait mechanisms is to ensure that the exit of a thread group leader is detected reliably when a non-leader thread calls exec. The "Patch 0" part of this patch contains a detailed explanation of this.

Regards,
--Don

gdb/
2014-05-22 Don Breazeal <donb@codesourcery.com>

* common/linux-ptrace.c (linux_test_for_tracefork)
[GDBSERVER]: Add support for PTRACE_EVENT_EXIT if the OS
supports it.
(linux_test_for_traceexit): New function.
(linux_check_ptrace_features): Call linux_test_for_traceexit.
(linux_is_extended_waitstatus): New function.
(linux_is_extended_exit): New function.
(linux_is_extended_fork): New function.
* common/linux-ptrace.h (linux_is_extended_waitstatus): New
declaration.
(linux_is_extended_exit): New declaration.
(linux_is_extended_fork): New declaration.

gdbserver/
2014-05-22 Don Breazeal <donb@codesourcery.com>

* linux-low.c (handle_extended_wait): Improve comment block.
Change return type from void to int, change wait status arg
to pointer, add support for PTRACE_EVENT_EXIT. Call
internal_error for unknown event types.
(get_stop_pc): Use linux_is_extended_waitstatus instead of
hardcoded shift operation.
(get_detach_signal): Use linux_is_extended_waitstatus instead
of hardcoded shift operation.
(linux_low_filter_event): Handle new return value from
handle_extended_wait. Check for extended events as needed.
Allow status arg to be modified.
(linux_wait_for_event_filtered): Allow status arg to be
modified by call to linux_low_filter_event.

---
gdb/common/linux-ptrace.c | 64 +++++++++++++++++++++++++++++++++++
gdb/common/linux-ptrace.h | 3 ++
gdb/gdbserver/linux-low.c | 82 +++++++++++++++++++++++++++++++++++++--------
3 files changed, 135 insertions(+), 14 deletions(-)

diff --git a/gdb/common/linux-ptrace.c b/gdb/common/linux-ptrace.c
index efbd1ea..af1d306 100644
--- a/gdb/common/linux-ptrace.c
+++ b/gdb/common/linux-ptrace.c
@@ -310,6 +310,7 @@ linux_child_function (gdb_byte *child_stack)

static void linux_test_for_tracesysgood (int child_pid);
static void linux_test_for_tracefork (int child_pid);
+static void linux_test_for_traceexit (int child_pid);

/* Determine ptrace features available on this target. */

@@ -341,6 +342,8 @@ linux_check_ptrace_features (void)

linux_test_for_tracefork (child_pid);

+ linux_test_for_traceexit (child_pid);
+
/* Clean things up and kill any pending children. */
do
{
@@ -461,6 +464,43 @@ linux_test_for_tracefork (int child_pid)
"(%d, status 0x%x)"), ret, status);
}

+/* Determine if PTRACE_O_TRACEEXIT can be used to follow exit
+ events. */
+
+static void
+linux_test_for_traceexit (int child_pid)
+{
+ int ret, status;
+
+#ifdef GDBSERVER
+ /* Test for PTRACE_O_TRACEEXIT. First try to set the option.
+ If this fails, we know for sure that it is not supported. */
+ ret = ptrace (PTRACE_SETOPTIONS, child_pid, (PTRACE_TYPE_ARG3) 0,
+ (PTRACE_TYPE_ARG4) PTRACE_O_TRACEEXIT);
+ 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. */
+ ret = ptrace (PTRACE_CONT, child_pid, (PTRACE_TYPE_ARG3) 0,
+ (PTRACE_TYPE_ARG4) 0);
+ if (ret != 0)
+ warning (_("linux_test_for_traceexit: failed to resume child"));
+
+ ret = my_waitpid (child_pid, &status, 0);
+
+ /* Check if we received an exit event notification. */
+ if (ret == child_pid && WIFSTOPPED (status)
+ && linux_is_extended_exit (status))
+ {
+ /* PTRACE_O_TRACEEXIT is supported. */
+ current_ptrace_options |= PTRACE_O_TRACEEXIT;
+ }
+#endif
+}
+
+
/* Enable reporting of all currently supported ptrace events. */

void
@@ -542,3 +582,27 @@ linux_ptrace_init_warnings (void)

linux_ptrace_test_ret_to_nx ();
}
+
+/* Check wait status for any extended event. */
+
+int
+linux_is_extended_waitstatus (int wstat)
+{
+ return (wstat >> 16 != 0);
+}
+
+/* Check wait status for extended exit event. */
+
+int
+linux_is_extended_exit (int wstat)
+{
+ return (wstat >> 16 == PTRACE_EVENT_EXIT);
+}
+
+/* Check wait status for extended fork event. */
+
+int
+linux_is_extended_fork (int wstat)
+{
+ return (wstat >> 16 == PTRACE_EVENT_FORK);
+}
diff --git a/gdb/common/linux-ptrace.h b/gdb/common/linux-ptrace.h
index 881d9c9..eb93862 100644
--- a/gdb/common/linux-ptrace.h
+++ b/gdb/common/linux-ptrace.h
@@ -90,5 +90,8 @@ extern int linux_supports_tracefork (void);
extern int linux_supports_traceclone (void);
extern int linux_supports_tracevforkdone (void);
extern int linux_supports_tracesysgood (void);
+extern int linux_is_extended_waitstatus (int wstat);
+extern int linux_is_extended_exit (int wstat);
+extern int linux_is_extended_fork (int wstat);

#endif /* COMMON_LINUX_PTRACE_H */
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 1932ff2..5404404 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -224,6 +224,7 @@ static void proceed_all_lwps (void);
static int finish_step_over (struct lwp_info *lwp);
static CORE_ADDR get_stop_pc (struct lwp_info *lwp);
static int kill_lwp (unsigned long lwpid, int signo);
+static int num_lwps (int pid);

/* True if the low target can hardware single-step. Such targets
don't need a BREAKPOINT_REINSERT_ADDR callback. */
@@ -368,12 +369,15 @@ linux_add_process (int pid, int attached)
}

/* Handle a GNU/Linux extended wait response. If we see a clone
- event, we need to add the new LWP to our list (and not report the
- trap to higher layers). */
+ event, we need to add the new lwp EVENT_CHILD to our list (and not
+ report the trap to higher layers). This function returns non-zero
+ if the event should be ignored and we should wait again. The wait
+ status in WSTATP may be modified if an exit event occurred. */

-static void
-handle_extended_wait (struct lwp_info *event_child, int wstat)
+static int
+handle_extended_wait (struct lwp_info *event_child, int *wstatp)
{
+ int wstat = *wstatp;
int event = wstat >> 16;
struct thread_info *event_thr = get_lwp_thread (event_child);
struct lwp_info *new_lwp;
@@ -452,7 +456,47 @@ handle_extended_wait (struct lwp_info *event_child, int wstat)
threads, it will have a pending SIGSTOP; we may as well
collect it now. */
linux_resume_one_lwp (event_child, event_child->stepping, 0, NULL);
+
+ /* Don't report the event. */
+ return 1;
+ }
+ else if (event == PTRACE_EVENT_EXIT)
+ {
+ unsigned long exit_status;
+ unsigned long lwpid = lwpid_of (event_thr);
+ int ret;
+
+ if (debug_threads)
+ debug_printf ("HEW: Got exit event from LWP %ld\n",
+ lwpid_of (event_thr));
+
+ ptrace (PTRACE_GETEVENTMSG, lwpid_of (event_thr), (PTRACE_TYPE_ARG3) 0,
+ &exit_status);
+
+ if (num_lwps (pid_of (event_thr)) > 1)
+ {
+ /* If there is at least one more LWP, then the program still
+ exists and the exit should not be reported to GDB. */
+ delete_lwp (event_child);
+ ret = 1;
+ }
+ else
+ {
+ /* Set the exit status to the actual exit status, so normal
+ WIFEXITED/WIFSIGNALLED processing and reporting for the
+ last lwp in the process can proceed from here. */
+ *wstatp = exit_status;
+ ret = 0;
+ }
+
+ /* Resume the thread so that it actually exits. Subsequent exit
+ events for LWPs that were deleted above will be ignored. */
+ ptrace (PTRACE_CONT, lwpid, (PTRACE_TYPE_ARG3) 0,
+ (PTRACE_TYPE_ARG4) 0);
+ return ret;
}
+ internal_error (__FILE__, __LINE__,
+ _("unknown ptrace event %d"), event);
}

/* Return the PC as read from the regcache of LWP, without any
@@ -516,7 +560,7 @@ get_stop_pc (struct lwp_info *lwp)
if (WSTOPSIG (lwp->last_status) == SIGTRAP
&& !lwp->stepping
&& !lwp->stopped_by_watchpoint
- && lwp->last_status >> 16 == 0)
+ && !linux_is_extended_waitstatus (lwp->last_status))
stop_pc -= the_low_target.decr_pc_after_break;

if (debug_threads)
@@ -1036,7 +1080,7 @@ get_detach_signal (struct thread_info *thread)
}

/* Extended wait statuses aren't real SIGTRAPs. */
- if (WSTOPSIG (status) == SIGTRAP && status >> 16 != 0)
+ if (WSTOPSIG (status) == SIGTRAP && linux_is_extended_waitstatus (status))
{
if (debug_threads)
debug_printf ("GPS: lwp %s had stopped with extended "
@@ -1721,11 +1765,13 @@ cancel_breakpoint (struct lwp_info *lwp)

/* Do low-level handling of the event, and check if we should go on
and pass it to caller code. Return the affected lwp if we are, or
- NULL otherwise. */
+ NULL otherwise. The wait status in WSTATP may be modified if an
+ exit event occurred. */

static struct lwp_info *
-linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat)
+linux_low_filter_event (ptid_t filter_ptid, int lwpid, int *wstatp)
{
+ int wstat = *wstatp;
struct lwp_info *child;
struct thread_info *thread;

@@ -1777,7 +1823,7 @@ linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat)
architecture being defined already (so that CHILD has a valid
regcache), and on LAST_STATUS being set (to check for SIGTRAP or
not). */
- if (WIFSTOPPED (wstat))
+ if (WIFSTOPPED (wstat) && !linux_is_extended_exit (wstat))
{
if (debug_threads
&& the_low_target.get_pc != NULL)
@@ -1813,7 +1859,8 @@ linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat)
changes the debug registers meanwhile, we have the cached data we
can rely on. */

- if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP)
+ if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP
+ && !linux_is_extended_waitstatus (wstat))
{
if (the_low_target.stopped_by_watchpoint == NULL)
{
@@ -1849,10 +1896,17 @@ linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat)
}

if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP
- && wstat >> 16 != 0)
+ && linux_is_extended_waitstatus (wstat))
{
- handle_extended_wait (child, wstat);
- return NULL;
+ if (handle_extended_wait (child, &wstat))
+ return NULL;
+ else
+ {
+ /* Update caller's wstat in case handle_extended_wait fixed
+ it up to reflect the actual status. */
+ *wstatp = wstat;
+ return child;
+ }
}

if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGSTOP
@@ -2072,7 +2126,7 @@ linux_wait_for_event_filtered (ptid_t wait_ptid, ptid_t filter_ptid,
}

event_child = linux_low_filter_event (filter_ptid,
- ret, *wstatp);
+ ret, wstatp);
if (event_child != NULL)
{
/* We got an event to report to the core. */
--
1.7.0.4