This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [PATCH 2/2] Don't delete thread_info if refcount isn't zero


Pedro Alves <palves@redhat.com> writes:

> I'm not sure what you mean by "wrong".  I'm saying that the old comment
> still makes sense.  That comment is talking about the 
> "find_inferior_ptid (old->thread->ptid) != NULL" line, which is a 
> lookup for an _inferior_ not a thread.  Both the inferior and thread_info

I thought it is about a thread.

> object are still around, but the process may have exited/been detached
> meanwhile, and consequently the inferior's "pid" field is now
> zero.  And in that case, we don't restore back the selected thread.

I move the comment close to "find_inferior_ptid (old->thread->ptid) != NULL"
line, to make it clearer.  Patch below is pushed in.

-- 
Yao (齐尧)
From 2e9d6d568506454eb8e5241439f91c05a99b4716 Mon Sep 17 00:00:00 2001
From: Yao Qi <yao.qi@linaro.org>
Date: Sat, 4 Mar 2017 22:31:40 +0000
Subject: [PATCH] Don't delete thread_info if refcount isn't zero

I build GDB with asan, and run test case hook-stop.exp, and threadapply.exp,
I got the following asan error,

=================================================================^M
^[[1m^[[31m==2291==ERROR: AddressSanitizer: heap-use-after-free on address 0x6160000999c4 at pc 0x000000826022 bp 0x7ffd28a8ff70 sp 0x7ffd28a8ff60^M
^[[1m^[[0m^[[1m^[[34mREAD of size 4 at 0x6160000999c4 thread T0^[[1m^[[0m^M
    #0 0x826021 in release_stop_context_cleanup ../../binutils-gdb/gdb/infrun.c:8203^M
    #1 0x72798a in do_my_cleanups ../../binutils-gdb/gdb/common/cleanups.c:154^M
    #2 0x727a32 in do_cleanups(cleanup*) ../../binutils-gdb/gdb/common/cleanups.c:176^M
    #3 0x826895 in normal_stop() ../../binutils-gdb/gdb/infrun.c:8381^M
    #4 0x815208 in fetch_inferior_event(void*) ../../binutils-gdb/gdb/infrun.c:4011^M
    #5 0x868aca in inferior_event_handler(inferior_event_type, void*) ../../binutils-gdb/gdb/inf-loop.c:44^M
....
^[[1m^[[32m0x6160000999c4 is located 68 bytes inside of 568-byte region [0x616000099980,0x616000099bb8)^M
^[[1m^[[0m^[[1m^[[35mfreed by thread T0 here:^[[1m^[[0m^M
    #0 0x7fb0bc1312ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)^M
    #1 0xb8c62f in xfree(void*) ../../binutils-gdb/gdb/common/common-utils.c:100^M
    #2 0x83df67 in free_thread ../../binutils-gdb/gdb/thread.c:207^M
    #3 0x83dfd2 in init_thread_list() ../../binutils-gdb/gdb/thread.c:223^M
    #4 0x805494 in kill_command ../../binutils-gdb/gdb/infcmd.c:2595^M
....

Detaching from program: /home/yao.qi/SourceCode/gnu/build-with-asan/gdb/testsuite/outputs/gdb.threads/threadapply/threadapply, process 2399^M
=================================================================^M
^[[1m^[[31m==2387==ERROR: AddressSanitizer: heap-use-after-free on address 0x6160000a98c0 at pc 0x00000083fd28 bp 0x7ffd401c3110 sp 0x7ffd401c3100^M
^[[1m^[[0m^[[1m^[[34mREAD of size 4 at 0x6160000a98c0 thread T0^[[1m^[[0m^M
    #0 0x83fd27 in thread_alive ../../binutils-gdb/gdb/thread.c:741^M
    #1 0x844277 in thread_apply_all_command ../../binutils-gdb/gdb/thread.c:1804^M
....
^M
^[[1m^[[32m0x6160000a98c0 is located 64 bytes inside of 568-byte region [0x6160000a9880,0x6160000a9ab8)^M
^[[1m^[[0m^[[1m^[[35mfreed by thread T0 here:^[[1m^[[0m^M
    #0 0x7f59a7e322ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)^M
    #1 0xb8c62f in xfree(void*) ../../binutils-gdb/gdb/common/common-utils.c:100^M
    #2 0x83df67 in free_thread ../../binutils-gdb/gdb/thread.c:207^M
    #3 0x83dfd2 in init_thread_list() ../../binutils-gdb/gdb/thread.c:223^M

This patch fixes the issue by deleting thread_info object if it is
deletable, otherwise, mark it as exited (by set_thread_exited).
Function set_thread_exited is shared from delete_thread_1.  This patch
also moves field "refcount" to private and methods incref and
decref.  Additionally, we stop using "ptid_t" in
"struct current_thread_cleanup" to reference threads, instead we use
"thread_info" directly.  Due to this change, we don't need
restore_current_thread_ptid_changed anymore.

gdb:

2017-04-10  Yao Qi  <yao.qi@linaro.org>

	PR gdb/19942
	* gdbthread.h (thread_info::deletable): New method.
	(thread_info::incref): New method.
	(thread_info::decref): New method.
	(thread_info::refcount): Move it to private.
	* infrun.c (save_stop_context): Call inc_refcount.
	(release_stop_context_cleanup): Likewise.
	* thread.c (set_thread_exited): New function.
	(init_thread_list): Delete "tp" only it is deletable, otherwise
	call set_thread_exited.
	(delete_thread_1): Call set_thread_exited.
	(current_thread_cleanup) <inferior_pid>: Remove.
	<thread>: New field.
	(restore_current_thread_ptid_changed): Removed.
	(do_restore_current_thread_cleanup): Adjust.
	(restore_current_thread_cleanup_dtor): Don't call
	find_thread_ptid.
	(set_thread_refcount): Use dec_refcount.
	(make_cleanup_restore_current_thread): Adjust.
	(thread_apply_all_command): Call inc_refcount.
	(_initialize_thread): Don't call
	observer_attach_thread_ptid_changed.

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index a1f8b49..68d8f34 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,30 @@
 2017-04-10  Yao Qi  <yao.qi@linaro.org>
 
+	PR gdb/19942
+	* gdbthread.h (thread_info::deletable): New method.
+	(thread_info::incref): New method.
+	(thread_info::decref): New method.
+	(thread_info::refcount): Move it to private.
+	* infrun.c (save_stop_context): Call inc_refcount.
+	(release_stop_context_cleanup): Likewise.
+	* thread.c (set_thread_exited): New function.
+	(init_thread_list): Delete "tp" only it is deletable, otherwise
+	call set_thread_exited.
+	(delete_thread_1): Call set_thread_exited.
+	(current_thread_cleanup) <inferior_pid>: Remove.
+	<thread>: New field.
+	(restore_current_thread_ptid_changed): Removed.
+	(do_restore_current_thread_cleanup): Adjust.
+	(restore_current_thread_cleanup_dtor): Don't call
+	find_thread_ptid.
+	(set_thread_refcount): Use dec_refcount.
+	(make_cleanup_restore_current_thread): Adjust.
+	(thread_apply_all_command): Call inc_refcount.
+	(_initialize_thread): Don't call
+	observer_attach_thread_ptid_changed.
+
+2017-04-10  Yao Qi  <yao.qi@linaro.org>
+
 	* thread.c (delete_thread_1): Hoist code on marking thread as
 	exited.
 
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 9a16fe6..4cd7390 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -183,6 +183,27 @@ public:
   explicit thread_info (inferior *inf, ptid_t ptid);
   ~thread_info ();
 
+  bool deletable () const
+  {
+    /* If this is the current thread, or there's code out there that
+       relies on it existing (m_refcount > 0) we can't delete yet.  */
+    return (m_refcount == 0 && !ptid_equal (ptid, inferior_ptid));
+  }
+
+  /* Increase the refcount.  */
+  void incref ()
+  {
+    gdb_assert (m_refcount >= 0);
+    m_refcount++;
+  }
+
+  /* Decrease the refcount.  */
+  void decref ()
+  {
+    m_refcount--;
+    gdb_assert (m_refcount >= 0);
+  }
+
   struct thread_info *next = NULL;
   ptid_t ptid;			/* "Actual process id";
 				    In fact, this may be overloaded with 
@@ -254,11 +275,6 @@ public:
      but STATE will still be THREAD_RUNNING.  */
   enum thread_state state = THREAD_STOPPED;
 
-  /* If this is > 0, then it means there's code out there that relies
-     on this thread being listed.  Don't delete it from the lists even
-     if we detect it exiting.  */
-  int refcount = 0;
-
   /* State of GDB control of inferior thread execution.
      See `struct thread_control_state'.  */
   thread_control_state control {};
@@ -346,6 +362,13 @@ public:
      fields point to self.  */
   struct thread_info *step_over_prev = NULL;
   struct thread_info *step_over_next = NULL;
+
+private:
+
+  /* If this is > 0, then it means there's code out there that relies
+     on this thread being listed.  Don't delete it from the lists even
+     if we detect it exiting.  */
+  int m_refcount = 0;
 };
 
 /* Create an empty thread list, or empty the existing one.  */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index c8c2d6e..b5eb4ab 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -8165,7 +8165,7 @@ save_stop_context (void)
       /* Take a strong reference so that the thread can't be deleted
 	 yet.  */
       sc->thread = inferior_thread ();
-      sc->thread->refcount++;
+      sc->thread->incref ();
     }
   else
     sc->thread = NULL;
@@ -8182,7 +8182,7 @@ release_stop_context_cleanup (void *arg)
   struct stop_context *sc = (struct stop_context *) arg;
 
   if (sc->thread != NULL)
-    sc->thread->refcount--;
+    sc->thread->decref ();
   xfree (sc);
 }
 
diff --git a/gdb/thread.c b/gdb/thread.c
index 2e9da53..2d936cd 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -192,6 +192,27 @@ clear_thread_inferior_resources (struct thread_info *tp)
   thread_cancel_execution_command (tp);
 }
 
+/* Set the TP's state as exited.  */
+
+static void
+set_thread_exited (thread_info *tp, int silent)
+{
+  /* Dead threads don't need to step-over.  Remove from queue.  */
+  if (tp->step_over_next != NULL)
+    thread_step_over_chain_remove (tp);
+
+  if (tp->state != THREAD_EXITED)
+    {
+      observer_notify_thread_exit (tp, silent);
+
+      /* Tag it as exited.  */
+      tp->state = THREAD_EXITED;
+
+      /* Clear breakpoints, etc. associated with this thread.  */
+      clear_thread_inferior_resources (tp);
+    }
+}
+
 void
 init_thread_list (void)
 {
@@ -205,7 +226,10 @@ init_thread_list (void)
   for (tp = thread_list; tp; tp = tpnext)
     {
       tpnext = tp->next;
-      delete tp;
+      if (tp->deletable ())
+	delete tp;
+      else
+	set_thread_exited (tp, 1);
     }
 
   thread_list = NULL;
@@ -430,25 +454,9 @@ delete_thread_1 (ptid_t ptid, int silent)
   if (!tp)
     return;
 
-  /* Dead threads don't need to step-over.  Remove from queue.  */
-  if (tp->step_over_next != NULL)
-    thread_step_over_chain_remove (tp);
+  set_thread_exited (tp, silent);
 
-  if (tp->state != THREAD_EXITED)
-    {
-      observer_notify_thread_exit (tp, silent);
-
-      /* Tag it as exited.  */
-      tp->state = THREAD_EXITED;
-
-      /* Clear breakpoints, etc. associated with this thread.  */
-      clear_thread_inferior_resources (tp);
-    }
-
-  /* If this is the current thread, or there's code out there that
-     relies on it existing (refcount > 0) we can't delete yet.  */
-  if (tp->refcount > 0
-      || ptid_equal (tp->ptid, inferior_ptid))
+  if (!tp->deletable ())
     {
        /* Will be really deleted some other time.  */
        return;
@@ -1546,7 +1554,7 @@ struct current_thread_cleanup
      'current_thread_cleanup_chain' below.  */
   struct current_thread_cleanup *next;
 
-  ptid_t inferior_ptid;
+  thread_info *thread;
   struct frame_id selected_frame_id;
   int selected_frame_level;
   int was_stopped;
@@ -1561,37 +1569,21 @@ struct current_thread_cleanup
    succeeds.  */
 static struct current_thread_cleanup *current_thread_cleanup_chain;
 
-/* A thread_ptid_changed observer.  Update all currently installed
-   current_thread_cleanup cleanups that want to switch back to
-   OLD_PTID to switch back to NEW_PTID instead.  */
-
-static void
-restore_current_thread_ptid_changed (ptid_t old_ptid, ptid_t new_ptid)
-{
-  struct current_thread_cleanup *it;
-
-  for (it = current_thread_cleanup_chain; it != NULL; it = it->next)
-    {
-      if (ptid_equal (it->inferior_ptid, old_ptid))
-	it->inferior_ptid = new_ptid;
-    }
-}
-
 static void
 do_restore_current_thread_cleanup (void *arg)
 {
-  struct thread_info *tp;
   struct current_thread_cleanup *old = (struct current_thread_cleanup *) arg;
 
-  tp = find_thread_ptid (old->inferior_ptid);
-
-  /* If the previously selected thread belonged to a process that has
-     in the mean time been deleted (due to normal exit, detach, etc.),
-     then don't revert back to it, but instead simply drop back to no
-     thread selected.  */
-  if (tp
-      && find_inferior_ptid (tp->ptid) != NULL)
-    restore_current_thread (old->inferior_ptid);
+  /* If an entry of thread_info was previously selected, it won't be
+     deleted because we've increased its refcount.  The thread represented
+     by this thread_info entry may have already exited (due to normal exit,
+     detach, etc), so the thread_info.state is THREAD_EXITED.  */
+  if (old->thread != NULL
+      /* If the previously selected thread belonged to a process that has
+	 in the mean time exited (or killed, detached, etc.), then don't revert
+	 back to it, but instead simply drop back to no thread selected.  */
+      && find_inferior_ptid (old->thread->ptid) != NULL)
+    restore_current_thread (old->thread->ptid);
   else
     {
       restore_current_thread (null_ptid);
@@ -1619,9 +1611,9 @@ restore_current_thread_cleanup_dtor (void *arg)
 
   current_thread_cleanup_chain = current_thread_cleanup_chain->next;
 
-  tp = find_thread_ptid (old->inferior_ptid);
-  if (tp)
-    tp->refcount--;
+  if (old->thread != NULL)
+    old->thread->decref ();
+
   inf = find_inferior_id (old->inf_id);
   if (inf != NULL)
     inf->removable = old->was_removable;
@@ -1638,7 +1630,7 @@ set_thread_refcount (void *data)
     = (struct thread_array_cleanup *) data;
 
   for (k = 0; k != ta_cleanup->count; k++)
-    ta_cleanup->tp_array[k]->refcount--;
+    ta_cleanup->tp_array[k]->decref ();
 }
 
 struct cleanup *
@@ -1646,7 +1638,7 @@ make_cleanup_restore_current_thread (void)
 {
   struct current_thread_cleanup *old = XNEW (struct current_thread_cleanup);
 
-  old->inferior_ptid = inferior_ptid;
+  old->thread = NULL;
   old->inf_id = current_inferior ()->num;
   old->was_removable = current_inferior ()->removable;
 
@@ -1679,7 +1671,8 @@ make_cleanup_restore_current_thread (void)
       struct thread_info *tp = find_thread_ptid (inferior_ptid);
 
       if (tp)
-	tp->refcount++;
+	tp->incref ();
+      old->thread = tp;
     }
 
   current_inferior ()->removable = 0;
@@ -1796,7 +1789,7 @@ thread_apply_all_command (char *cmd, int from_tty)
       ALL_NON_EXITED_THREADS (tp)
         {
           tp_array[i] = tp;
-          tp->refcount++;
+          tp->incref ();
           i++;
         }
       /* Because we skipped exited threads, we may end up with fewer
@@ -2286,6 +2279,4 @@ Show printing of thread events (such as thread start and exit)."), NULL,
 
   create_internalvar_type_lazy ("_thread", &thread_funcs, NULL);
   create_internalvar_type_lazy ("_gthread", &gthread_funcs, NULL);
-
-  observer_attach_thread_ptid_changed (restore_current_thread_ptid_changed);
 }


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]