[PATCH/7.10,1/2] Linux gdbserver confused when event randomization returns a process exit event

Message ID 1438362229-27653-2-git-send-email-palves@redhat.com
State New, archived
Headers

Commit Message

Pedro Alves July 31, 2015, 5:03 p.m. UTC
  The tail end of linux_wait_1 isn't expecting that the select_event_lwp
machinery can pick a whole-process exit event to report to GDB.  When
that happens, both gdb and gdbserver end up quite confused:

 ...
 (gdb)
 [Thread 24971.24971] #1 stopped.
 0x0000003615a011f0 in ?? ()
 c&
 Continuing.
 (gdb) [New Thread 24971.24981]
 [New Thread 24983.24983]
 [New Thread 24971.24982]

 [Thread 24983.24983] #3 stopped.
 0x0000003615ebc7cc in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130
 130       pid = ARCH_FORK ();
 [New Thread 24984.24984]
 Error in re-setting breakpoint -16: PC register is not available
 Error in re-setting breakpoint -17: PC register is not available
 Error in re-setting breakpoint -18: PC register is not available
 Error in re-setting breakpoint -19: PC register is not available
 Error in re-setting breakpoint -24: PC register is not available
 Error in re-setting breakpoint -25: PC register is not available
 Error in re-setting breakpoint -26: PC register is not available
 Error in re-setting breakpoint -27: PC register is not available
 Error in re-setting breakpoint -28: PC register is not available
 Error in re-setting breakpoint -29: PC register is not available
 Error in re-setting breakpoint -30: PC register is not available
 PC register is not available
 (gdb)

gdb/testsuite/ChangeLog:
2015-07-31  Pedro Alves  <palves@redhat.com>

	* linux-low.c (add_lwp): Set waitstatus to TARGET_WAITKIND_IGNORE.
	(linux_thread_alive): Use lwp_is_marked_dead.
	(extended_event_reported): Delete.
	(linux_wait_1): Check if waitstatus is TARGET_WAITKIND_IGNORE
	instead of extended_event_reported.  Pass event_child->waitstatus
	to target_waitstatus_to_string instead of ourstatus.
	(mark_lwp_dead): Don't set the 'dead' flag.  Store the waitstatus
	as well.
	(lwp_is_marked_dead): New function.
	(lwp_running): Use lwp_is_marked_dead.
	* linux-low.h: Delete 'dead' field, and update 'waitstatus's
	comment.
---
 gdb/gdbserver/linux-low.c | 68 +++++++++++++++++++++++++++--------------------
 gdb/gdbserver/linux-low.h | 11 +++-----
 2 files changed, 43 insertions(+), 36 deletions(-)
  

Comments

Yao Qi Aug. 3, 2015, 10:58 a.m. UTC | #1
Pedro Alves <palves@redhat.com> writes:

> @@ -3128,7 +3116,7 @@ linux_wait_1 (ptid_t ptid,
>  		   || (gdb_breakpoint_here (event_child->stop_pc)
>  		       && gdb_condition_true_at_breakpoint (event_child->stop_pc)
>  		       && gdb_no_commands_at_breakpoint (event_child->stop_pc))
> -		   || extended_event_reported (&event_child->waitstatus));
> +		   || event_child->waitstatus.kind != TARGET_WAITKIND_IGNORE);
>  
>    run_breakpoint_commands (event_child->stop_pc);
>  
> @@ -3150,9 +3138,11 @@ linux_wait_1 (ptid_t ptid,
>  			  paddress (event_child->stop_pc),
>  			  paddress (event_child->step_range_start),
>  			  paddress (event_child->step_range_end));
> -	  if (extended_event_reported (&event_child->waitstatus))
> +	  if (event_child->waitstatus.kind != TARGET_WAITKIND_IGNORE)
>  	    {
> -	      char *str = target_waitstatus_to_string (ourstatus);
> +	      char *str;
> +
> +	      str = target_waitstatus_to_string (&event_child->waitstatus);
>  	      debug_printf ("LWP %ld: extended event with waitstatus %s\n",
>  			    lwpid_of (get_lwp_thread (event_child)), str);
>  	      xfree (str);

Looks this code is dead code even without your patch. This code is
guarded by "if (!report_to_gdb)", but if report_to_gdb is false,
"(event_child->waitstatus.kind != TARGET_WAITKIND_IGNORE)" is false too.
  

Patch

diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 82fb7f9..4cdedb4 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -264,6 +264,7 @@  static int linux_wait_for_event (ptid_t ptid, int *wstat, int options);
 static struct lwp_info *add_lwp (ptid_t ptid);
 static int linux_stopped_by_watchpoint (void);
 static void mark_lwp_dead (struct lwp_info *lwp, int wstat);
+static int lwp_is_marked_dead (struct lwp_info *lwp);
 static void proceed_all_lwps (void);
 static int finish_step_over (struct lwp_info *lwp);
 static int kill_lwp (unsigned long lwpid, int signo);
@@ -743,8 +744,9 @@  add_lwp (ptid_t ptid)
 {
   struct lwp_info *lwp;
 
-  lwp = (struct lwp_info *) xmalloc (sizeof (*lwp));
-  memset (lwp, 0, sizeof (*lwp));
+  lwp = (struct lwp_info *) xcalloc (1, sizeof (*lwp));
+
+  lwp->waitstatus.kind = TARGET_WAITKIND_IGNORE;
 
   if (the_low_target.new_thread != NULL)
     the_low_target.new_thread (lwp);
@@ -1393,7 +1395,7 @@  linux_thread_alive (ptid_t ptid)
      exited but we still haven't been able to report it to GDB, we'll
      hold on to the last lwp of the dead process.  */
   if (lwp != NULL)
-    return !lwp->dead;
+    return !lwp_is_marked_dead (lwp);
   else
     return 0;
 }
@@ -2747,20 +2749,6 @@  ignore_event (struct target_waitstatus *ourstatus)
   return null_ptid;
 }
 
-/* Return non-zero if WAITSTATUS reflects an extended linux
-   event.  Otherwise, return zero.  */
-
-static int
-extended_event_reported (const struct target_waitstatus *waitstatus)
-{
-  if (waitstatus == NULL)
-    return 0;
-
-  return (waitstatus->kind == TARGET_WAITKIND_FORKED
-	  || waitstatus->kind == TARGET_WAITKIND_VFORKED
-	  || waitstatus->kind == TARGET_WAITKIND_VFORK_DONE);
-}
-
 /* Wait for process, returns status.  */
 
 static ptid_t
@@ -3128,7 +3116,7 @@  linux_wait_1 (ptid_t ptid,
 		   || (gdb_breakpoint_here (event_child->stop_pc)
 		       && gdb_condition_true_at_breakpoint (event_child->stop_pc)
 		       && gdb_no_commands_at_breakpoint (event_child->stop_pc))
-		   || extended_event_reported (&event_child->waitstatus));
+		   || event_child->waitstatus.kind != TARGET_WAITKIND_IGNORE);
 
   run_breakpoint_commands (event_child->stop_pc);
 
@@ -3150,9 +3138,11 @@  linux_wait_1 (ptid_t ptid,
 			  paddress (event_child->stop_pc),
 			  paddress (event_child->step_range_start),
 			  paddress (event_child->step_range_end));
-	  if (extended_event_reported (&event_child->waitstatus))
+	  if (event_child->waitstatus.kind != TARGET_WAITKIND_IGNORE)
 	    {
-	      char *str = target_waitstatus_to_string (ourstatus);
+	      char *str;
+
+	      str = target_waitstatus_to_string (&event_child->waitstatus);
 	      debug_printf ("LWP %ld: extended event with waitstatus %s\n",
 			    lwpid_of (get_lwp_thread (event_child)), str);
 	      xfree (str);
@@ -3266,12 +3256,11 @@  linux_wait_1 (ptid_t ptid,
 	unstop_all_lwps (1, event_child);
     }
 
-  if (extended_event_reported (&event_child->waitstatus))
+  if (event_child->waitstatus.kind != TARGET_WAITKIND_IGNORE)
     {
-      /* If the reported event is a fork, vfork or exec, let GDB know.  */
-      ourstatus->kind = event_child->waitstatus.kind;
-      ourstatus->value = event_child->waitstatus.value;
-
+      /* If the reported event is an exit, fork, vfork or exec, let
+	 GDB know.  */
+      *ourstatus = event_child->waitstatus;
       /* Clear the event lwp's waitstatus since we handled it already.  */
       event_child->waitstatus.kind = TARGET_WAITKIND_IGNORE;
     }
@@ -3479,13 +3468,23 @@  suspend_and_send_sigstop_callback (struct inferior_list_entry *entry,
 static void
 mark_lwp_dead (struct lwp_info *lwp, int wstat)
 {
-  /* It's dead, really.  */
-  lwp->dead = 1;
-
   /* Store the exit status for later.  */
   lwp->status_pending_p = 1;
   lwp->status_pending = wstat;
 
+  /* Store in waitstatus as well, as there's nothing else to process
+     for this event.  */
+  if (WIFEXITED (wstat))
+    {
+      lwp->waitstatus.kind = TARGET_WAITKIND_EXITED;
+      lwp->waitstatus.value.integer = WEXITSTATUS (wstat);
+    }
+  else if (WIFSIGNALED (wstat))
+    {
+      lwp->waitstatus.kind = TARGET_WAITKIND_SIGNALLED;
+      lwp->waitstatus.value.sig = gdb_signal_from_host (WTERMSIG (wstat));
+    }
+
   /* Prevent trying to stop it.  */
   lwp->stopped = 1;
 
@@ -3493,6 +3492,17 @@  mark_lwp_dead (struct lwp_info *lwp, int wstat)
   lwp->stop_expected = 0;
 }
 
+/* Return true if LWP has exited already, and has a pending exit event
+   to report to GDB.  */
+
+static int
+lwp_is_marked_dead (struct lwp_info *lwp)
+{
+  return (lwp->status_pending_p
+	  && (WIFEXITED (lwp->status_pending)
+	      || WIFSIGNALED (lwp->status_pending)));
+}
+
 /* Wait for all children to stop for the SIGSTOPs we just queued.  */
 
 static void
@@ -3609,7 +3619,7 @@  lwp_running (struct inferior_list_entry *entry, void *data)
   struct thread_info *thread = (struct thread_info *) entry;
   struct lwp_info *lwp = get_thread_lwp (thread);
 
-  if (lwp->dead)
+  if (lwp_is_marked_dead (lwp))
     return 0;
   if (lwp->stopped)
     return 0;
diff --git a/gdb/gdbserver/linux-low.h b/gdb/gdbserver/linux-low.h
index 5a3697b..4afd6ef 100644
--- a/gdb/gdbserver/linux-low.h
+++ b/gdb/gdbserver/linux-low.h
@@ -261,16 +261,13 @@  struct lwp_info
      event already received in a wait()).  */
   int stopped;
 
-  /* If this flag is set, the lwp is known to be dead already (exit
-     event already received in a wait(), and is cached in
-     status_pending).  */
-  int dead;
-
   /* When stopped is set, the last wait status recorded for this lwp.  */
   int last_status;
 
-  /* This is used to store extended ptrace event information until
-     it is reported to GDB.  */
+  /* If WAITSTATUS->KIND != TARGET_WAITKIND_IGNORE, the waitstatus for
+     this LWP's last event, to pass to GDB without any further
+     processing.  This is used to store extended ptrace event
+     information or exit status until it can be reported to GDB.  */
   struct target_waitstatus waitstatus;
 
   /* When stopped is set, this is where the lwp last stopped, with