gdb: print backtrace for internal error/warning
authorAndrew Burgess <andrew.burgess@embecosm.com>
Thu, 12 Aug 2021 17:24:59 +0000 (18:24 +0100)
committerAndrew Burgess <andrew.burgess@embecosm.com>
Tue, 28 Sep 2021 11:21:22 +0000 (12:21 +0100)
This commit builds on previous work to allow GDB to print a backtrace
of itself when GDB encounters an internal-error or internal-warning.
This fixes PR gdb/26377.

There's not many places where we call internal_warning, and I guess in
most cases the user would probably continue their debug session.  And
so, in order to avoid cluttering up the output, by default, printing
of a backtrace is off for internal-warnings.

In contrast, printing of a backtrace is on by default for
internal-errors, as I figure that in most cases hitting an
internal-error is going to be the end of the debug session.

Whether a backtrace is printed or not can be controlled with the new
settings:

  maintenance set internal-error backtrace on|off
  maintenance show internal-error backtrace

  maintenance set internal-warning backtrace on|off
  maintenance show internal-warning backtrace

Here is an example of what an internal-error now looks like with the
backtrace included:

  (gdb) maintenance internal-error blah
  ../../src.dev-3/gdb/maint.c:82: internal-error: blah
  A problem internal to GDB has been detected,
  further debugging may prove unreliable.
  ----- Backtrace -----
  0x5c61ca gdb_internal_backtrace_1
   ../../src.dev-3/gdb/bt-utils.c:123
  0x5c626d _Z22gdb_internal_backtracev
   ../../src.dev-3/gdb/bt-utils.c:165
  0xe33237 internal_vproblem
   ../../src.dev-3/gdb/utils.c:393
  0xe33539 _Z15internal_verrorPKciS0_P13__va_list_tag
   ../../src.dev-3/gdb/utils.c:470
  0x1549652 _Z14internal_errorPKciS0_z
   ../../src.dev-3/gdbsupport/errors.cc:55
  0x9c7982 maintenance_internal_error
   ../../src.dev-3/gdb/maint.c:82
  0x636f57 do_simple_func
   ../../src.dev-3/gdb/cli/cli-decode.c:97
   .... snip, lots more backtrace lines ....
  ---------------------
  ../../src.dev-3/gdb/maint.c:82: internal-error: blah
  A problem internal to GDB has been detected,
  further debugging may prove unreliable.
  Quit this debugging session? (y or n) y

  This is a bug, please report it.  For instructions, see:
  <https://www.gnu.org/software/gdb/bugs/>.

  ../../src.dev-3/gdb/maint.c:82: internal-error: blah
  A problem internal to GDB has been detected,
  further debugging may prove unreliable.
  Create a core file of GDB? (y or n) n

My hope is that this backtrace might make it slightly easier to
diagnose GDB issues if all that is provided is the console output, I
find that we frequently get reports of an assert being hit that is
located in pretty generic code (frame.c, value.c, etc) and it is not
always obvious how we might have arrived at the assert.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=26377

gdb/NEWS
gdb/doc/gdb.texinfo
gdb/testsuite/gdb.base/bt-on-error-and-warning.exp [new file with mode: 0644]
gdb/testsuite/gdb.base/bt-on-fatal-signal.exp
gdb/utils.c

index d7c29c82edba605f7047bdbfa343450aa741cb51..1e25cb8cc3694384eb191e02f4fb695ad6eb4536 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -19,6 +19,14 @@ show source open
   to open and read source code files, which can be useful if the files
   are located over a slow network connection.
 
+maint set internal-error backtrace on|off
+maint show internal-error backtrace
+maint set internal-warning backtrace on|off
+maint show internal-warning backtrace
+  GDB can now print a backtrace of itself when it encounters either an
+  internal-error, or an internal-warning.  This is on by default for
+  internal-error and off by default for internal-warning.
+
 * Python API
 
   ** New function gdb.add_history(), which takes a gdb.Value object
index fcfdc26ac1ad52976624f4c1a0d0651699035424..d4e4174be5d863bfcab342c279c162acfe35c996 100644 (file)
@@ -39257,6 +39257,19 @@ demangler warnings always create a core file and this cannot be
 disabled.
 @end table
 
+@kindex maint set internal-error
+@kindex maint show internal-error
+@kindex maint set internal-warning
+@kindex maint show internal-warning
+@item maint set internal-error backtrace @r{[}on|off@r{]}
+@itemx maint show internal-error backtrace
+@itemx maint set internal-warning backtrace @r{[}on|off@r{]}
+@itemx maint show internal-warning backtrace
+When @value{GDBN} reports an internal problem (error or warning) it is
+possible to have a backtrace of @value{GDBN} printed to stderr.  This
+is @samp{on} by default for @code{internal-error} and @samp{off} by
+default for @code{internal-warning}.
+
 @kindex maint packet
 @item maint packet @var{text}
 If @value{GDBN} is talking to an inferior via the serial protocol,
diff --git a/gdb/testsuite/gdb.base/bt-on-error-and-warning.exp b/gdb/testsuite/gdb.base/bt-on-error-and-warning.exp
new file mode 100644 (file)
index 0000000..d988cf7
--- /dev/null
@@ -0,0 +1,118 @@
+# Copyright 2021 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test that GDB can print a backtrace when it encounters an internal
+# error or an internal warning, and that this functionality can be
+# switched off.
+
+standard_testfile bt-on-fatal-signal.c
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
+    return -1
+}
+
+# Check we can run to main.  If this works this time then we just
+# assume that it will work later on (when we repeatedly restart GDB).
+if ![runto_main] then {
+    untested "run to main"
+    return -1
+}
+
+# Check that the backtrace-on-fatal-signal feature is supported.  If
+# this target doesn't have the backtrace function available then
+# trying to turn this on will give an error, in which case we just
+# skip this test.
+gdb_test_multiple "maint set internal-error backtrace on" \
+    "check backtrace is supported" {
+    -re "support for this feature is not compiled into GDB" {
+       untested "feature not supported"
+       return -1
+    }
+    -re "$gdb_prompt $" {
+       pass $gdb_test_name
+    }
+}
+
+# MODE should be either 'on' or 'off', while PROBLEM_TYPE should be
+# 'internal-error' or 'internal-warning'.  This proc sets the
+# backtrace printing for PROBLEM_TYPE to MODE, then uses 'maint
+# PROBLEM_TYPE foobar' to raise a fake error or warning.
+#
+# We then check that a backtrace either is, or isn't printed, inline
+# with MODE.
+proc run_test {problem_type mode} {
+
+    with_test_prefix "problem=${problem_type}, mode=${mode}" {
+       gdb_test_no_output "maint set ${problem_type} backtrace ${mode}"
+
+       set header_lines 0
+       set bt_lines 0
+
+       gdb_test_multiple "maint ${problem_type} foobar" "scan for backtrace" {
+           -early -re "^\r\n" {
+               exp_continue
+           }
+           -early -re "^maint ${problem_type} foobar\r\n" {
+               exp_continue
+           }
+           -early -re "^\[^\r\n\]+: ${problem_type}: foobar\r\n" {
+               incr header_lines
+               exp_continue
+           }
+           -early -re "^A problem internal to GDB has been detected,\r\n" {
+               incr header_lines
+               exp_continue
+           }
+           -early -re "^further debugging may prove unreliable\\.\r\n" {
+               incr header_lines
+               exp_continue
+           }
+           -early -re "^----- Backtrace -----\r\n" {
+               incr bt_lines
+               exp_continue
+           }
+           -early -re "^\[^-\].+\r\n---------------------\r\n" {
+               incr bt_lines
+               exp_continue
+           }
+           eof {
+               fail ${gdb_test_name}
+               return
+           }
+           -re "$::gdb_prompt $" {
+               pass ${gdb_test_name}
+           }
+       }
+
+       gdb_assert { ${header_lines} == 3 }
+       if { $mode == "on" } {
+           gdb_assert { ${bt_lines} == 2 }
+       } else {
+           gdb_assert { ${bt_lines} == 0 }
+       }
+    }
+}
+
+# For each problem type (error or warning) raise a fake problem using
+# the maintenance commands and check that a backtrace is (or isn't)
+# printed, depending on the user setting.
+foreach problem_type { internal-error internal-warning } {
+    gdb_test_no_output "maint set ${problem_type} corefile no"
+    gdb_test_no_output "maint set ${problem_type} quit no"
+
+    foreach mode { on off } {
+       run_test ${problem_type} ${mode}
+    }
+}
index 1f0d61f00ed5fe992253038f5c614510f72ac70c..8875d00fdb115926df3d54fc338d24ede2a47a49 100644 (file)
@@ -135,39 +135,3 @@ foreach test_data {{SEGV "Segmentation fault"} \
        gdb_exit
     }
 }
-
-# Check that when we get an internal error and choose to dump core, we
-# don't print a backtrace to the console.
-with_test_prefix "internal-error" {
-    # Restart GDB.
-    clean_restart $binfile
-
-    set saw_bt_start false
-
-    gdb_test_multiple "maint internal-error foo" "" {
-       -early -re "internal-error: foo\r\n" {
-           exp_continue
-       }
-       -early -re "^A problem internal to GDB has been detected,\r\n" {
-           exp_continue
-       }
-       -early -re "^further debugging may prove unreliable\\.\r\n" {
-           exp_continue
-       }
-       -early -re "^Quit this debugging session\\? \\(y or n\\)" {
-           send_gdb "y\n"
-           exp_continue
-       }
-       -early -re "^Create a core file of GDB\\? \\(y or n\\)" {
-           send_gdb "y\n"
-           exp_continue
-       }
-       -early -re "----- Backtrace -----\r\n" {
-           set saw_bt_start true
-           exp_continue
-       }
-       eof {
-           gdb_assert { [expr ! $saw_bt_start] }
-       }
-    }
-}
index 0a7c270b40db2646170a609b5189df4fee9fafaa..f6f90d7365baa00d9ebc8e570806df69e731ef39 100644 (file)
@@ -75,6 +75,7 @@
 #include "gdbarch.h"
 #include "cli-out.h"
 #include "gdbsupport/gdb-safe-ctype.h"
+#include "bt-utils.h"
 
 void (*deprecated_error_begin_hook) (void);
 
@@ -304,6 +305,13 @@ struct internal_problem
 
   /* Like SHOULD_QUIT, but whether GDB should dump core.  */
   const char *should_dump_core;
+
+  /* Like USER_SETTABLE_SHOULD_QUIT but for SHOULD_PRINT_BACKTRACE.  */
+  bool user_settable_should_print_backtrace;
+
+  /* When this is true GDB will print a backtrace when a problem of this
+     type is encountered.  */
+  bool should_print_backtrace;
 };
 
 /* Report a problem, internal to GDB, to the user.  Once the problem
@@ -377,9 +385,13 @@ internal_vproblem (struct internal_problem *problem,
   /* Emit the message unless query will emit it below.  */
   if (problem->should_quit != internal_problem_ask
       || !confirm
-      || !filtered_printing_initialized ())
+      || !filtered_printing_initialized ()
+      || problem->should_print_backtrace)
     fprintf_unfiltered (gdb_stderr, "%s\n", reason.c_str ());
 
+  if (problem->should_print_backtrace)
+    gdb_internal_backtrace ();
+
   if (problem->should_quit == internal_problem_ask)
     {
       /* Default (yes/batch case) is to quit GDB.  When in batch mode
@@ -449,6 +461,7 @@ internal_vproblem (struct internal_problem *problem,
 
 static struct internal_problem internal_error_problem = {
   "internal-error", true, internal_problem_ask, true, internal_problem_ask,
+  true, GDB_PRINT_INTERNAL_BACKTRACE_INIT_ON
 };
 
 void
@@ -460,6 +473,7 @@ internal_verror (const char *file, int line, const char *fmt, va_list ap)
 
 static struct internal_problem internal_warning_problem = {
   "internal-warning", true, internal_problem_ask, true, internal_problem_ask,
+  true, false
 };
 
 void
@@ -470,6 +484,7 @@ internal_vwarning (const char *file, int line, const char *fmt, va_list ap)
 
 static struct internal_problem demangler_warning_problem = {
   "demangler-warning", true, internal_problem_ask, false, internal_problem_no,
+  false, false
 };
 
 void
@@ -571,6 +586,25 @@ add_internal_problem_command (struct internal_problem *problem)
                            set_cmd_list,
                            show_cmd_list);
     }
+
+  if (problem->user_settable_should_print_backtrace)
+    {
+      std::string set_bt_doc
+       = string_printf (_("Set whether GDB should print a backtrace of "
+                          "GDB when %s is detected."), problem->name);
+      std::string show_bt_doc
+       = string_printf (_("Show whether GDB will print a backtrace of "
+                          "GDB when %s is detected."), problem->name);
+      add_setshow_boolean_cmd ("backtrace", class_maintenance,
+                              &problem->should_print_backtrace,
+                              set_bt_doc.c_str (),
+                              show_bt_doc.c_str (),
+                              NULL, /* help_doc */
+                              gdb_internal_backtrace_set_cmd,
+                              NULL, /* showfunc */
+                              set_cmd_list,
+                              show_cmd_list);
+    }
 }
 
 /* Return a newly allocated string, containing the PREFIX followed