[v3,6/8] Introduce thread-safe way to handle SIGSEGV

Message ID 20190529212916.23721-7-tom@tromey.com
State New, archived
Headers

Commit Message

Tom Tromey May 29, 2019, 9:29 p.m. UTC
  The gdb demangler installs a SIGSEGV handler in order to protect gdb
from demangler bugs.  However, this is not thread-safe, as signal
handlers are global to the process.

This patch changes gdb to always install a global SIGSEGV handler, and
then lets thread indicate their interest in handling the signal by
setting a thread-local variable.

This patch then arranges for the demangler code to use this; being
sure to arrange for calls to warning and the like to be done on the
main thread.

gdb/ChangeLog
2019-05-29  Tom Tromey  <tom@tromey.com>

	* event-top.h (segv_handler): Declare.
	* event-top.c (segv_handler): New global.
	(handle_sigsegv): New function.
	(async_init_signals): Install SIGSEGV handler.
	* cp-support.c (gdb_demangle_jmp_buf): Change type.  Now
	thread-local.
	(report_failed_demangle): New function.
	(gdb_demangle): Make core_dump_allowed atomic.  Remove signal
	handler-setting code, instead use segv_handler.  Run warning code
	on main thread.
---
 gdb/ChangeLog    |  13 ++++++
 gdb/cp-support.c | 114 +++++++++++++++++++++++------------------------
 gdb/event-top.c  |  37 +++++++++++++++
 gdb/event-top.h  |   3 ++
 4 files changed, 109 insertions(+), 58 deletions(-)
  

Comments

Pedro Alves May 30, 2019, 1:40 p.m. UTC | #1
On 5/29/19 10:29 PM, Tom Tromey wrote:
> The gdb demangler installs a SIGSEGV handler in order to protect gdb
> from demangler bugs.  However, this is not thread-safe, as signal
> handlers are global to the process.
> 
> This patch changes gdb to always install a global SIGSEGV handler, and
> then lets thread indicate their interest in handling the signal by

"lets threads"

> setting a thread-local variable.
> 
> This patch then arranges for the demangler code to use this; being
> sure to arrange for calls to warning and the like to be done on the
> main thread.

The design is explained here, but the patch itself doesn't include
any comment in that direction.  I think future readers of the code
would benefit a lot such commentary.

> 
> gdb/ChangeLog
> 2019-05-29  Tom Tromey  <tom@tromey.com>
> 
> 	* event-top.h (segv_handler): Declare.
> 	* event-top.c (segv_handler): New global.
> 	(handle_sigsegv): New function.
> 	(async_init_signals): Install SIGSEGV handler.
> 	* cp-support.c (gdb_demangle_jmp_buf): Change type.  Now
> 	thread-local.
> 	(report_failed_demangle): New function.
> 	(gdb_demangle): Make core_dump_allowed atomic.  Remove signal
> 	handler-setting code, instead use segv_handler.  Run warning code
> 	on main thread.
> ---
>  gdb/ChangeLog    |  13 ++++++
>  gdb/cp-support.c | 114 +++++++++++++++++++++++------------------------
>  gdb/event-top.c  |  37 +++++++++++++++
>  gdb/event-top.h  |   3 ++
>  4 files changed, 109 insertions(+), 58 deletions(-)
> 
> diff --git a/gdb/cp-support.c b/gdb/cp-support.c
> index 4afb79a4ea9..60bbbc23ec3 100644
> --- a/gdb/cp-support.c
> +++ b/gdb/cp-support.c
> @@ -37,6 +37,9 @@
>  #include "common/gdb_setjmp.h"
>  #include "safe-ctype.h"
>  #include "common/selftest.h"
> +#include <atomic>
> +#include "event-top.h"
> +#include "ser-event.h"
>  
>  #define d_left(dc) (dc)->u.s_binary.left
>  #define d_right(dc) (dc)->u.s_binary.right
> @@ -1480,7 +1483,7 @@ static int catch_demangler_crashes = 1;
>  
>  /* Stack context and environment for demangler crash recovery.  */
>  
> -static SIGJMP_BUF gdb_demangle_jmp_buf;
> +static thread_local SIGJMP_BUF *gdb_demangle_jmp_buf;
>  
>  /* If nonzero, attempt to dump core from the signal handler.  */
>  
> @@ -1499,7 +1502,43 @@ gdb_demangle_signal_handler (int signo)
>        gdb_demangle_attempt_core_dump = 0;
>      }
>  
> -  SIGLONGJMP (gdb_demangle_jmp_buf, signo);
> +  SIGLONGJMP (*gdb_demangle_jmp_buf, signo);
> +}
> +
> +/* A helper for gdb_demangle that reports a demangling failure.  */
> +
> +static void
> +report_failed_demangle (const char *name, int core_dump_allowed,
> +			int crash_signal)
> +{
> +  static int error_reported = 0;

bool, while at it?

> +
> +  if (!error_reported)
> +    {
> +      std::string short_msg
> +	= string_printf (_("unable to demangle '%s' "
> +			   "(demangler failed with signal %d)"),
> +			 name, crash_signal);
> +
> +      std::string long_msg
> +	= string_printf ("%s:%d: %s: %s", __FILE__, __LINE__,
> +			 "demangler-warning", short_msg.c_str ());
> +
> +      target_terminal::scoped_restore_terminal_state term_state;
> +      target_terminal::ours_for_output ();
> +
> +      begin_line ();
> +      if (core_dump_allowed)
> +	fprintf_unfiltered (gdb_stderr,
> +			    _("%s\nAttempting to dump core.\n"),
> +			    long_msg.c_str ());
> +      else
> +	warn_cant_dump_core (long_msg.c_str ());
> +
> +      demangler_warning (__FILE__, __LINE__, "%s", short_msg.c_str ());
> +
> +      error_reported = 1;
> +    }
>  }
>  
>  #endif
> @@ -1513,12 +1552,7 @@ gdb_demangle (const char *name, int options)
>    int crash_signal = 0;
>  
>  #ifdef HAVE_WORKING_FORK
> -#if defined (HAVE_SIGACTION) && defined (SA_RESTART)
> -  struct sigaction sa, old_sa;
> -#else
> -  sighandler_t ofunc;
> -#endif
> -  static int core_dump_allowed = -1;
> +  static std::atomic<int> core_dump_allowed (-1);
>  
>    if (core_dump_allowed == -1)
>      {
> @@ -1528,23 +1562,15 @@ gdb_demangle (const char *name, int options)
>  	gdb_demangle_attempt_core_dump = 0;

Are accesses to gdb_demangle_attempt_core_dump going to be racy?
Maybe not, didn't think that much about it.  If they are, I wonder whether it
wouldn't be better if this whole can_dump_core check block were moved to the
main thread, before threads are spun?  Say, to a gdb_demangle_init() function?
Not sure, might not be convenient form an abstraction-hiding perspective.

>      }
>  
> -  if (catch_demangler_crashes)
> -    {
> -#if defined (HAVE_SIGACTION) && defined (SA_RESTART)
> -      sa.sa_handler = gdb_demangle_signal_handler;
> -      sigemptyset (&sa.sa_mask);
> -#ifdef HAVE_SIGALTSTACK
> -      sa.sa_flags = SA_ONSTACK;
> -#else
> -      sa.sa_flags = 0;
> -#endif
> -      sigaction (SIGSEGV, &sa, &old_sa);
> -#else
> -      ofunc = signal (SIGSEGV, gdb_demangle_signal_handler);
> -#endif
> +  scoped_restore restore_segv
> +    = make_scoped_restore (&segv_handler,
> +			   catch_demangler_crashes
> +			   ? gdb_demangle_signal_handler
> +			   : nullptr);
>  
> -      crash_signal = SIGSETJMP (gdb_demangle_jmp_buf);
> -    }
> +  SIGJMP_BUF jmp_buf;
> +  if (catch_demangler_crashes)
> +    crash_signal = SIGSETJMP (jmp_buf);
>  #endif

gdb_demangle_jmp_buf is now a pointer, but I'm not seeing
anything set it?

>  
>    if (crash_signal == 0)
> @@ -1553,42 +1579,14 @@ gdb_demangle (const char *name, int options)
>  #ifdef HAVE_WORKING_FORK
>    if (catch_demangler_crashes)
>      {
> -#if defined (HAVE_SIGACTION) && defined (SA_RESTART)
> -      sigaction (SIGSEGV, &old_sa, NULL);
> -#else
> -      signal (SIGSEGV, ofunc);
> -#endif
> -
>        if (crash_signal != 0)
>  	{
> -	  static int error_reported = 0;
> -
> -	  if (!error_reported)
> -	    {
> -	      std::string short_msg
> -		= string_printf (_("unable to demangle '%s' "
> -				   "(demangler failed with signal %d)"),
> -				 name, crash_signal);
> -
> -	      std::string long_msg
> -		= string_printf ("%s:%d: %s: %s", __FILE__, __LINE__,
> -				 "demangler-warning", short_msg.c_str ());
> -
> -	      target_terminal::scoped_restore_terminal_state term_state;
> -	      target_terminal::ours_for_output ();
> -
> -	      begin_line ();
> -	      if (core_dump_allowed)
> -		fprintf_unfiltered (gdb_stderr,
> -				    _("%s\nAttempting to dump core.\n"),
> -				    long_msg.c_str ());
> -	      else
> -		warn_cant_dump_core (long_msg.c_str ());
> -
> -	      demangler_warning (__FILE__, __LINE__, "%s", short_msg.c_str ());
> -
> -	      error_reported = 1;
> -	    }
> +	  std::string copy = name;

I don't think you need this copy, because ...

> +	  run_on_main_thread ([=] ()

... you're already copying here, with "[=]".

> +            {

tabs vs spaces.

> +	      report_failed_demangle (copy.c_str (), core_dump_allowed,
> +				      crash_signal);
> +	    });
>  
>  	  result = NULL;
>  	}
> diff --git a/gdb/event-top.c b/gdb/event-top.c
> index 93b7d2d28bc..f1466c0c2ba 100644
> --- a/gdb/event-top.c
> +++ b/gdb/event-top.c
> @@ -849,6 +849,29 @@ gdb_readline_no_editing_callback (gdb_client_data client_data)
>  }
>  
>  
> +/* See event-top.h.  */
> +
> +thread_local void (*segv_handler) (int);

I think it'd help readability if this were named something
that indicates it's thread-local, like e.g., thr_segv_handler.

> +
> +/* Handler for SIGSEGV.  */
> +
> +static void
> +handle_sigsegv (int sig)
> +{
> +  if (segv_handler == nullptr)
> +    {
> +      signal (sig, nullptr);
> +      raise (sig);
> +    }
> +  else
> +    {
> +      signal (sig, handle_sigsegv);

Doesn't this lose the SA_ONSTACK?

> +      segv_handler (sig);
> +    }
> +}
> +
> +
> +
>  /* The serial event associated with the QUIT flag.  set_quit_flag sets
>     this, and check_quit_flag clears it.  Used by interruptible_select
>     to be able to do interruptible I/O with no race with the SIGINT
> @@ -916,6 +939,20 @@ async_init_signals (void)
>    sigtstp_token =
>      create_async_signal_handler (async_sigtstp_handler, NULL);
>  #endif
> +
> +#if defined (HAVE_SIGACTION) && defined (SA_RESTART)
> +  struct sigaction sa, old_sa;
> +  sa.sa_handler = handle_sigsegv;
> +  sigemptyset (&sa.sa_mask);
> +#ifdef HAVE_SIGALTSTACK
> +  sa.sa_flags = SA_ONSTACK;
> +#else
> +  sa.sa_flags = 0;
> +#endif
> +  sigaction (SIGSEGV, &sa, &old_sa);
> +#else
> +  signal (SIGSEGV, handle_sigsegv);
> +#endif
>  }
>  
>  /* See defs.h.  */
> diff --git a/gdb/event-top.h b/gdb/event-top.h
> index 4c3e6cb8612..849f40d9192 100644
> --- a/gdb/event-top.h
> +++ b/gdb/event-top.h
> @@ -70,4 +70,7 @@ extern void gdb_rl_callback_handler_install (const char *prompt);
>     currently installed.  */
>  extern void gdb_rl_callback_handler_reinstall (void);
>  
> +/* The SIGSEGV handler for this thread, or NULL if there is none.  */
> +extern thread_local void (*segv_handler) (int);
> +
>  #endif
> 

Thanks,
Pedro Alves
  
Tom Tromey June 9, 2019, 3:43 p.m. UTC | #2
>>>>> "Pedro" == Pedro Alves <palves@redhat.com> writes:

>> +      signal (sig, handle_sigsegv);

Pedro> Doesn't this lose the SA_ONSTACK?

Yeah.  I'm glad you caught this since it points out that we have to call
sigaltstack per-thread.

Tom
  

Patch

diff --git a/gdb/cp-support.c b/gdb/cp-support.c
index 4afb79a4ea9..60bbbc23ec3 100644
--- a/gdb/cp-support.c
+++ b/gdb/cp-support.c
@@ -37,6 +37,9 @@ 
 #include "common/gdb_setjmp.h"
 #include "safe-ctype.h"
 #include "common/selftest.h"
+#include <atomic>
+#include "event-top.h"
+#include "ser-event.h"
 
 #define d_left(dc) (dc)->u.s_binary.left
 #define d_right(dc) (dc)->u.s_binary.right
@@ -1480,7 +1483,7 @@  static int catch_demangler_crashes = 1;
 
 /* Stack context and environment for demangler crash recovery.  */
 
-static SIGJMP_BUF gdb_demangle_jmp_buf;
+static thread_local SIGJMP_BUF *gdb_demangle_jmp_buf;
 
 /* If nonzero, attempt to dump core from the signal handler.  */
 
@@ -1499,7 +1502,43 @@  gdb_demangle_signal_handler (int signo)
       gdb_demangle_attempt_core_dump = 0;
     }
 
-  SIGLONGJMP (gdb_demangle_jmp_buf, signo);
+  SIGLONGJMP (*gdb_demangle_jmp_buf, signo);
+}
+
+/* A helper for gdb_demangle that reports a demangling failure.  */
+
+static void
+report_failed_demangle (const char *name, int core_dump_allowed,
+			int crash_signal)
+{
+  static int error_reported = 0;
+
+  if (!error_reported)
+    {
+      std::string short_msg
+	= string_printf (_("unable to demangle '%s' "
+			   "(demangler failed with signal %d)"),
+			 name, crash_signal);
+
+      std::string long_msg
+	= string_printf ("%s:%d: %s: %s", __FILE__, __LINE__,
+			 "demangler-warning", short_msg.c_str ());
+
+      target_terminal::scoped_restore_terminal_state term_state;
+      target_terminal::ours_for_output ();
+
+      begin_line ();
+      if (core_dump_allowed)
+	fprintf_unfiltered (gdb_stderr,
+			    _("%s\nAttempting to dump core.\n"),
+			    long_msg.c_str ());
+      else
+	warn_cant_dump_core (long_msg.c_str ());
+
+      demangler_warning (__FILE__, __LINE__, "%s", short_msg.c_str ());
+
+      error_reported = 1;
+    }
 }
 
 #endif
@@ -1513,12 +1552,7 @@  gdb_demangle (const char *name, int options)
   int crash_signal = 0;
 
 #ifdef HAVE_WORKING_FORK
-#if defined (HAVE_SIGACTION) && defined (SA_RESTART)
-  struct sigaction sa, old_sa;
-#else
-  sighandler_t ofunc;
-#endif
-  static int core_dump_allowed = -1;
+  static std::atomic<int> core_dump_allowed (-1);
 
   if (core_dump_allowed == -1)
     {
@@ -1528,23 +1562,15 @@  gdb_demangle (const char *name, int options)
 	gdb_demangle_attempt_core_dump = 0;
     }
 
-  if (catch_demangler_crashes)
-    {
-#if defined (HAVE_SIGACTION) && defined (SA_RESTART)
-      sa.sa_handler = gdb_demangle_signal_handler;
-      sigemptyset (&sa.sa_mask);
-#ifdef HAVE_SIGALTSTACK
-      sa.sa_flags = SA_ONSTACK;
-#else
-      sa.sa_flags = 0;
-#endif
-      sigaction (SIGSEGV, &sa, &old_sa);
-#else
-      ofunc = signal (SIGSEGV, gdb_demangle_signal_handler);
-#endif
+  scoped_restore restore_segv
+    = make_scoped_restore (&segv_handler,
+			   catch_demangler_crashes
+			   ? gdb_demangle_signal_handler
+			   : nullptr);
 
-      crash_signal = SIGSETJMP (gdb_demangle_jmp_buf);
-    }
+  SIGJMP_BUF jmp_buf;
+  if (catch_demangler_crashes)
+    crash_signal = SIGSETJMP (jmp_buf);
 #endif
 
   if (crash_signal == 0)
@@ -1553,42 +1579,14 @@  gdb_demangle (const char *name, int options)
 #ifdef HAVE_WORKING_FORK
   if (catch_demangler_crashes)
     {
-#if defined (HAVE_SIGACTION) && defined (SA_RESTART)
-      sigaction (SIGSEGV, &old_sa, NULL);
-#else
-      signal (SIGSEGV, ofunc);
-#endif
-
       if (crash_signal != 0)
 	{
-	  static int error_reported = 0;
-
-	  if (!error_reported)
-	    {
-	      std::string short_msg
-		= string_printf (_("unable to demangle '%s' "
-				   "(demangler failed with signal %d)"),
-				 name, crash_signal);
-
-	      std::string long_msg
-		= string_printf ("%s:%d: %s: %s", __FILE__, __LINE__,
-				 "demangler-warning", short_msg.c_str ());
-
-	      target_terminal::scoped_restore_terminal_state term_state;
-	      target_terminal::ours_for_output ();
-
-	      begin_line ();
-	      if (core_dump_allowed)
-		fprintf_unfiltered (gdb_stderr,
-				    _("%s\nAttempting to dump core.\n"),
-				    long_msg.c_str ());
-	      else
-		warn_cant_dump_core (long_msg.c_str ());
-
-	      demangler_warning (__FILE__, __LINE__, "%s", short_msg.c_str ());
-
-	      error_reported = 1;
-	    }
+	  std::string copy = name;
+	  run_on_main_thread ([=] ()
+            {
+	      report_failed_demangle (copy.c_str (), core_dump_allowed,
+				      crash_signal);
+	    });
 
 	  result = NULL;
 	}
diff --git a/gdb/event-top.c b/gdb/event-top.c
index 93b7d2d28bc..f1466c0c2ba 100644
--- a/gdb/event-top.c
+++ b/gdb/event-top.c
@@ -849,6 +849,29 @@  gdb_readline_no_editing_callback (gdb_client_data client_data)
 }
 
 
+/* See event-top.h.  */
+
+thread_local void (*segv_handler) (int);
+
+/* Handler for SIGSEGV.  */
+
+static void
+handle_sigsegv (int sig)
+{
+  if (segv_handler == nullptr)
+    {
+      signal (sig, nullptr);
+      raise (sig);
+    }
+  else
+    {
+      signal (sig, handle_sigsegv);
+      segv_handler (sig);
+    }
+}
+
+
+
 /* The serial event associated with the QUIT flag.  set_quit_flag sets
    this, and check_quit_flag clears it.  Used by interruptible_select
    to be able to do interruptible I/O with no race with the SIGINT
@@ -916,6 +939,20 @@  async_init_signals (void)
   sigtstp_token =
     create_async_signal_handler (async_sigtstp_handler, NULL);
 #endif
+
+#if defined (HAVE_SIGACTION) && defined (SA_RESTART)
+  struct sigaction sa, old_sa;
+  sa.sa_handler = handle_sigsegv;
+  sigemptyset (&sa.sa_mask);
+#ifdef HAVE_SIGALTSTACK
+  sa.sa_flags = SA_ONSTACK;
+#else
+  sa.sa_flags = 0;
+#endif
+  sigaction (SIGSEGV, &sa, &old_sa);
+#else
+  signal (SIGSEGV, handle_sigsegv);
+#endif
 }
 
 /* See defs.h.  */
diff --git a/gdb/event-top.h b/gdb/event-top.h
index 4c3e6cb8612..849f40d9192 100644
--- a/gdb/event-top.h
+++ b/gdb/event-top.h
@@ -70,4 +70,7 @@  extern void gdb_rl_callback_handler_install (const char *prompt);
    currently installed.  */
 extern void gdb_rl_callback_handler_reinstall (void);
 
+/* The SIGSEGV handler for this thread, or NULL if there is none.  */
+extern thread_local void (*segv_handler) (int);
+
 #endif