aboutsummaryrefslogtreecommitdiff
path: root/gdb/event-top.c
diff options
context:
space:
mode:
authorAndrew Burgess <andrew.burgess@embecosm.com>2021-06-10 16:57:24 +0100
committerAndrew Burgess <andrew.burgess@embecosm.com>2021-08-11 12:35:14 +0100
commit6aa4f97c2b8a3fe3775d90c7485f4ace610fb103 (patch)
tree095dbbd8f4f9cde32549e2f8d38ab919199684c6 /gdb/event-top.c
parent270135645b50a2fb8a4dac216584e8056167ffcc (diff)
downloadgdb-6aa4f97c2b8a3fe3775d90c7485f4ace610fb103.zip
gdb-6aa4f97c2b8a3fe3775d90c7485f4ace610fb103.tar.gz
gdb-6aa4f97c2b8a3fe3775d90c7485f4ace610fb103.tar.bz2
gdb: print backtrace on fatal SIGSEGV
This commit adds a new maintenance feature, the ability to print a (limited) backtrace if GDB dies due to a fatal signal. The backtrace is produced using the backtrace and backtrace_symbols_fd functions which are declared in the execinfo.h header, and both of which are async signal safe. A configure check has been added to check for these features, if they are not available then the new code is not compiled into GDB and the backtrace will not be printed. The motivation for this new feature is to aid in debugging GDB in situations where GDB has crashed at a users site, but the user is reluctant to share core files, possibly due to concerns about what might be in the memory image within the core file. Such a user might be happy to share a simple backtrace that was written to stderr. The production of the backtrace is on by default, but can switched off using the new commands: maintenance set backtrace-on-fatal-signal on|off maintenance show backtrace-on-fatal-signal Right now, I have hooked this feature in to GDB's existing handling of SIGSEGV only, but this will be extended to more signals in a later commit. One additional change I have made in this commit is that, when we decide GDB should terminate due to the fatal signal, we now raise the same fatal signal rather than raising SIGABRT. Currently, this is only effecting our handling of SIGSEGV. So, previously, if GDB hit a SEGV then we would terminate GDB with a SIGABRT. After this commit we will terminate GDB with a SIGSEGV. This feels like an improvement to me, we should still get a core dump, but in many shells, the user will see a more specific message once GDB exits, in bash for example "Segmentation fault" rather than "Aborted". Finally then, here is an example of the output a user would see if GDB should hit an internal SIGSEGV: Fatal signal: Segmentation fault ----- Backtrace ----- ./gdb/gdb[0x8078e6] ./gdb/gdb[0x807b20] /lib64/libpthread.so.0(+0x14b20)[0x7f6648c92b20] /lib64/libc.so.6(__poll+0x4f)[0x7f66484d3a5f] ./gdb/gdb[0x1540f4c] ./gdb/gdb[0x154034a] ./gdb/gdb[0x9b002d] ./gdb/gdb[0x9b014d] ./gdb/gdb[0x9b1aa6] ./gdb/gdb[0x9b1b0c] ./gdb/gdb[0x41756d] /lib64/libc.so.6(__libc_start_main+0xf3)[0x7f66484041a3] ./gdb/gdb[0x41746e] --------------------- A fatal error internal to GDB has been detected, further debugging is not possible. GDB will now terminate. This is a bug, please report it. For instructions, see: <https://www.gnu.org/software/gdb/bugs/>. Segmentation fault (core dumped) It is disappointing that backtrace_symbols_fd does not actually map the addresses back to symbols, this appears, in part, to be due to GDB not being built with -rdynamic as the manual page for backtrace_symbols_fd suggests, however, even when I do add -rdynamic to the build of GDB I only see symbols for some addresses. We could potentially look at alternative libraries to provide the backtrace (e.g. libunwind) however, the solution presented here, which is available as part of glibc is probably a good baseline from which we might improve things in future.
Diffstat (limited to 'gdb/event-top.c')
-rw-r--r--gdb/event-top.c140
1 files changed, 129 insertions, 11 deletions
diff --git a/gdb/event-top.c b/gdb/event-top.c
index 972b540..210440a 100644
--- a/gdb/event-top.c
+++ b/gdb/event-top.c
@@ -46,6 +46,10 @@
#include "readline/readline.h"
#include "readline/history.h"
+#ifdef HAVE_EXECINFO_H
+# include <execinfo.h>
+#endif /* HAVE_EXECINFO_H */
+
/* readline defines this. */
#undef savestring
@@ -96,6 +100,38 @@ bool exec_done_display_p = false;
run again. */
int call_stdin_event_handler_again_p;
+/* When true GDB will produce a minimal backtrace when a fatal signal is
+ reached (within GDB code). */
+static bool bt_on_fatal_signal
+#ifdef HAVE_EXECINFO_BACKTRACE
+ = true;
+#else
+ = false;
+#endif /* HAVE_EXECINFO_BACKTRACE */
+
+/* Implement 'maintenance show backtrace-on-fatal-signal'. */
+
+static void
+show_bt_on_fatal_signal (struct ui_file *file, int from_tty,
+ struct cmd_list_element *cmd, const char *value)
+{
+ fprintf_filtered (file, _("Backtrace on a fatal signal is %s.\n"), value);
+}
+
+/* Implement 'maintenance set backtrace-on-fatal-signal'. */
+
+static void
+set_bt_on_fatal_signal (const char *args, int from_tty, cmd_list_element *c)
+{
+#ifndef HAVE_EXECINFO_BACKTRACE
+ if (bt_on_fatal_signal)
+ {
+ bt_on_fatal_signal = false;
+ error (_("support for this feature is not compiled into GDB"));
+ }
+#endif
+}
+
/* Signal handling variables. */
/* Each of these is a pointer to a function that the event loop will
invoke if the corresponding signal has received. The real signal
@@ -846,6 +882,84 @@ gdb_readline_no_editing_callback (gdb_client_data client_data)
}
+/* Attempt to unblock signal SIG, return true if the signal was unblocked,
+ otherwise, return false. */
+
+static bool
+unblock_signal (int sig)
+{
+#if HAVE_SIGPROCMASK
+ sigset_t sigset;
+ sigemptyset (&sigset);
+ sigaddset (&sigset, sig);
+ gdb_sigmask (SIG_UNBLOCK, &sigset, 0);
+ return true;
+#endif
+
+ return false;
+}
+
+/* Called to handle fatal signals. SIG is the signal number. */
+
+static void ATTRIBUTE_NORETURN
+handle_fatal_signal (int sig)
+{
+#ifdef HAVE_EXECINFO_BACKTRACE
+ const auto sig_write = [] (const char *msg) -> void
+ {
+ gdb_stderr->write_async_safe (msg, strlen (msg));
+ };
+
+ if (bt_on_fatal_signal)
+ {
+ sig_write ("\n\n");
+ sig_write (_("Fatal signal: "));
+ sig_write (strsignal (sig));
+ sig_write ("\n");
+
+ /* Allow up to 25 frames of backtrace. */
+ void *buffer[25];
+ int frames = backtrace (buffer, ARRAY_SIZE (buffer));
+ sig_write (_("----- Backtrace -----\n"));
+ if (gdb_stderr->fd () > -1)
+ {
+ backtrace_symbols_fd (buffer, frames, gdb_stderr->fd ());
+ if (frames == ARRAY_SIZE (buffer))
+ sig_write (_("Backtrace might be incomplete.\n"));
+ }
+ else
+ sig_write (_("Backtrace unavailable\n"));
+ sig_write ("---------------------\n");
+ sig_write (_("A fatal error internal to GDB has been detected, "
+ "further\ndebugging is not possible. GDB will now "
+ "terminate.\n\n"));
+ sig_write (_("This is a bug, please report it."));
+ if (REPORT_BUGS_TO[0] != '\0')
+ {
+ sig_write (_(" For instructions, see:\n"));
+ sig_write (REPORT_BUGS_TO);
+ sig_write (".");
+ }
+ sig_write ("\n\n");
+
+ gdb_stderr->flush ();
+ }
+#endif /* HAVE_EXECINF_BACKTRACE */
+
+ /* If possible arrange for SIG to have its default behaviour (which
+ should be to terminate the current process), unblock SIG, and reraise
+ the signal. This ensures GDB terminates with the expected signal. */
+ if (signal (sig, SIG_DFL) != SIG_ERR
+ && unblock_signal (sig))
+ raise (sig);
+
+ /* The above failed, so try to use SIGABRT to terminate GDB. */
+#ifdef SIGABRT
+ signal (SIGABRT, SIG_DFL);
+#endif
+ abort (); /* ARI: abort */
+}
+
/* The SIGSEGV handler for this thread, or NULL if there is none. GDB
always installs a global SIGSEGV handler, and then lets threads
indicate their interest in handling the signal by setting this
@@ -887,7 +1001,7 @@ handle_sigsegv (int sig)
install_handle_sigsegv ();
if (thread_local_segv_handler == nullptr)
- abort (); /* ARI: abort */
+ handle_fatal_signal (sig);
thread_local_segv_handler (sig);
}
@@ -1160,16 +1274,7 @@ async_sigtstp_handler (gdb_client_data arg)
char *prompt = get_prompt ();
signal (SIGTSTP, SIG_DFL);
-#if HAVE_SIGPROCMASK
- {
- sigset_t zero;
-
- sigemptyset (&zero);
- gdb_sigmask (SIG_SETMASK, &zero, 0);
- }
-#elif HAVE_SIGSETMASK
- sigsetmask (0);
-#endif
+ unblock_signal (SIGTSTP);
raise (SIGTSTP);
signal (SIGTSTP, handle_sigtstp);
printf_unfiltered ("%s", prompt);
@@ -1320,4 +1425,17 @@ Control whether to show event loop-related debug messages."),
set_debug_event_loop_command,
show_debug_event_loop_command,
&setdebuglist, &showdebuglist);
+
+ add_setshow_boolean_cmd ("backtrace-on-fatal-signal", class_maintenance,
+ &bt_on_fatal_signal, _("\
+Set whether to produce a backtrace if GDB receives a fatal signal."), _("\
+Show whether GDB will produce a backtrace if it receives a fatal signal."), _("\
+Use \"on\" to enable, \"off\" to disable.\n\
+If enabled, GDB will produce a minimal backtrace if it encounters a fatal\n\
+signal from within GDB itself. This is a mechanism to help diagnose\n\
+crashes within GDB, not a mechanism for debugging inferiors."),
+ set_bt_on_fatal_signal,
+ show_bt_on_fatal_signal,
+ &maintenance_set_cmdlist,
+ &maintenance_show_cmdlist);
}