> From: Guinevere Larsen <blarsen@redhat.com>
> Cc: Guinevere Larsen <blarsen@redhat.com>
> Date: Mon, 8 Apr 2024 17:19:14 -0300
>
> Sometimes, in the GDB testsuite, we want to test the ability of specific
> unwinders to handle some piece of code. Usually this is done by trying
> to outsmart GDB, or by coercing the compiler to remove information that
> GDB would rely on. Both approaches have problems as GDB gets smarter
> with time, and that compilers might differ in version and behavior, or
> simply introduce new useful information. This was requested back in 2003
> in PR backtrace/8434.
>
> To improve our ability to thoroughly test GDB, this patch introduces a
> new maintenance command that allows a user to disable some unwinders,
> based on either the name of the unwinder or on its class. With this
> change, it will now be possible for GDB to not find any frame unwinders
> for a given frame, which would previously cause GDB to assert. GDB will
> now check if any frame unwinder has been disabled, and if some has, it
> will just error out instead of asserting.
>
> Unwinders can be disabled or re-enabled in 3 different ways:
> * Disabling/enabling all at once (using '-all').
> * By specifying an unwinder class to be disabled (option '-class').
> * By specifying the name of an unwinder (option '-name').
>
> If you give no options to the command, GDB assumes the input is an
> unwinder class. '-class' would make no difference if used, is just here
> for completeness.
>
> This command is meant to be used once the inferior is already at the
> desired location for the test. An example session would be:
>
> (gdb) start
> Temporary breakpoint 1, main () at omp.c:17
> 17 func();
> (gdb) maint frame-unwinder disable ARCH
> (gdb) bt
> \#0 main () at omp.c:17
> (gdb) maint frame-unwinder enable ARCH
> (gdb) cont
> Continuing.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=8434
> ---
> gdb/NEWS | 7 +
> gdb/doc/gdb.texinfo | 24 +++
> gdb/frame-unwind.c | 154 +++++++++++++++++-
> gdb/frame-unwind.h | 13 ++
> gdb/testsuite/gdb.base/frame-unwind-disable.c | 21 +++
> .../gdb.base/frame-unwind-disable.exp | 114 +++++++++++++
> 6 files changed, 329 insertions(+), 4 deletions(-)
> create mode 100644 gdb/testsuite/gdb.base/frame-unwind-disable.c
> create mode 100644 gdb/testsuite/gdb.base/frame-unwind-disable.exp
Thanks.
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -54,6 +54,13 @@ disable missing-debug-handler LOCUS HANDLER
> maintenance info linux-lwps
> List all LWPs under control of the linux-nat target.
>
> +maintenance frame-unwinder disable [-all | -name NAME | [-class] CLASS]
> +maintenance frame-unwinder enable [-all | -name NAME | [-class] CLASS]
> + Enable or disable frame unwinders. This is only meant to be used when
> + testing unwinders themselves, and you want to ensure that a fallback
> + algorithm won't obscure a regression. GDB is not expected to behave
> + well if you try to execute the inferior with unwinders disabled.
This part is OK.
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -42022,6 +42022,30 @@ architecture, then enabling this flag does not cause them to be used.
> @item maint info frame-unwinders
> List the frame unwinders currently in effect, starting with the highest priority.
>
> +@kindex maint frame-unwinder disable
> +@kindex maint frame-unwinder enable
> +@item maint frame-unwinder disable [@code{-all} | @code{-name} @var{NAME} | [@code{-class}] @var{CLASS}]
> +@item maint frame-unwinder enable [@code{-all} | @code{-name} @var{NAME} | [@code{-class}] @var{CLASS}]
The symbols in @var should be in lower-case (they will be up-cased in
the Info output, but in HTML and printed output they will be in
italics instead).
> +Disable or enable frame unwinders. This is meant only as a testing tool, and
> +@value{GDBN} is not guaranteed to work properly if it is unable to find
> +valid frame unwinders.
> +
> +The meaning of each of the invocations are as follows:
> +
> +@table @samp
> +@item @code{-all}
> +Disable or enable all unwinders.
> +@item @code{-name} @var{NAME}
> +@var{NAME} is the exact name of the unwinder to be disabled or enabled.
> +@item [@code{-class}] @var{CLASS}
> +@var{CLASS} is the class of frame unwinders to be disabled or enabled.
> +The class may include the prefix @code{FRAME_UNWINDER_}, but it is not
> +required.
> +@end table
> +
> +@var{NAME} and @var{CLASS} are always case insensitive. If no option
> +starting wth @code{-} is given, @value{GDBN} assumes @code{-class}.
I think this command should be moved to after "maint info
frame-unwinders", and it should say that the list of available
unwinders is shown by that command. Also, I suppose what you call
"class" is what "maint info frame-unwinders" displays as "Type"? Or
is it something else? If the latter, the manual should tell how to
know which classes are available.
> + add_cmd ("disable", class_maintenance, maintenance_disable_frame_unwinders,
> + _("\
> +Disable one or more frame unwinder(s).\n\
> +Usage: maint frame-unwinder disable [-all | -name NAME | [-class] CLASS]\n\
> +\n\
> +These are the meanings of the options:\n\
> +\t-all - All available unwinders will be disabled\n\
> +\t-name - NAME is the exact name of the frame unwinder is to be disabled\n\
^^
That "is" should be removed.
> + add_cmd ("enable", class_maintenance, maintenance_enable_frame_unwinders,
> + _("\
> +Enable one or more frame unwinder(s).\n\
> +Usage: maint frame-unwinder enable [-all | -name NAME | [-class] CLASS]\n\
> +\n\
> +These are the meanings of the options:\n\
> +\t-all - All available unwinders will be enabled\n\
> +\t-name - NAME is the exact name of the frame unwinder is to be enabled\n\
Likewise here.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
@@ -54,6 +54,13 @@ disable missing-debug-handler LOCUS HANDLER
maintenance info linux-lwps
List all LWPs under control of the linux-nat target.
+maintenance frame-unwinder disable [-all | -name NAME | [-class] CLASS]
+maintenance frame-unwinder enable [-all | -name NAME | [-class] CLASS]
+ Enable or disable frame unwinders. This is only meant to be used when
+ testing unwinders themselves, and you want to ensure that a fallback
+ algorithm won't obscure a regression. GDB is not expected to behave
+ well if you try to execute the inferior with unwinders disabled.
+
set remote thread-options-packet
show remote thread-options-packet
Set/show the use of the thread options packet.
@@ -42022,6 +42022,30 @@ architecture, then enabling this flag does not cause them to be used.
@item maint info frame-unwinders
List the frame unwinders currently in effect, starting with the highest priority.
+@kindex maint frame-unwinder disable
+@kindex maint frame-unwinder enable
+@item maint frame-unwinder disable [@code{-all} | @code{-name} @var{NAME} | [@code{-class}] @var{CLASS}]
+@item maint frame-unwinder enable [@code{-all} | @code{-name} @var{NAME} | [@code{-class}] @var{CLASS}]
+Disable or enable frame unwinders. This is meant only as a testing tool, and
+@value{GDBN} is not guaranteed to work properly if it is unable to find
+valid frame unwinders.
+
+The meaning of each of the invocations are as follows:
+
+@table @samp
+@item @code{-all}
+Disable or enable all unwinders.
+@item @code{-name} @var{NAME}
+@var{NAME} is the exact name of the unwinder to be disabled or enabled.
+@item [@code{-class}] @var{CLASS}
+@var{CLASS} is the class of frame unwinders to be disabled or enabled.
+The class may include the prefix @code{FRAME_UNWINDER_}, but it is not
+required.
+@end table
+
+@var{NAME} and @var{CLASS} are always case insensitive. If no option
+starting wth @code{-} is given, @value{GDBN} assumes @code{-class}.
+
@kindex maint set worker-threads
@kindex maint show worker-threads
@item maint set worker-threads
@@ -87,6 +87,23 @@ frame_unwinder_class_str (frame_unwind_class uclass)
return location->second;
}
+/* Case insensitive search for a frame_unwind_class based on the given
+ string. */
+static enum frame_unwind_class
+str_to_frame_unwind_class (const char *class_str)
+{
+ const char *prefix = "FRAME_UNWIND_";
+ /* Skip the prefix if present. */
+ if (strncasecmp (class_str, prefix, strlen(prefix)) == 0)
+ class_str += strlen(prefix);
+ for (const auto& it : unwind_class_conversion)
+ {
+ if (strcasecmp(it.second, class_str) == 0)
+ return it.first;
+ }
+ error (_("Unknown frame unwind class: %s"), class_str);
+}
+
void
frame_unwind_prepend_unwinder (struct gdbarch *gdbarch,
const struct frame_unwind *unwinder)
@@ -184,25 +201,47 @@ frame_unwind_find_by_frame (const frame_info_ptr &this_frame, void **this_cache)
const struct frame_unwind *unwinder_from_target;
+ /* If we see a disabled unwinder, we assume some test is being run on
+ GDB, and we don't want to assert at the end of this function. */
+ bool seen_disabled_unwinder = false;
+
unwinder_from_target = target_get_unwinder ();
if (unwinder_from_target != NULL
+ && unwinder_from_target->enabled ()
&& frame_unwind_try_unwinder (this_frame, this_cache,
unwinder_from_target))
return;
+ else if (unwinder_from_target != nullptr
+ && !unwinder_from_target->enabled ())
+ seen_disabled_unwinder = true;
unwinder_from_target = target_get_tailcall_unwinder ();
if (unwinder_from_target != NULL
+ && unwinder_from_target->enabled ()
&& frame_unwind_try_unwinder (this_frame, this_cache,
unwinder_from_target))
return;
+ else if (unwinder_from_target != nullptr
+ && !unwinder_from_target->enabled ())
+ seen_disabled_unwinder |= true;
struct gdbarch *gdbarch = get_frame_arch (this_frame);
std::vector<const frame_unwind*> table = get_frame_unwind_table (gdbarch);
for (auto unwinder: table)
- if (frame_unwind_try_unwinder (this_frame, this_cache, unwinder))
- return;
+ {
+ if (!unwinder->enabled ())
+ {
+ seen_disabled_unwinder |= true;
+ continue;
+ }
+ if (frame_unwind_try_unwinder (this_frame, this_cache, unwinder))
+ return;
+ }
- internal_error (_("frame_unwind_find_by_frame failed"));
+ if (seen_disabled_unwinder)
+ error (_("frame_unwind_find_by_frame failed"));
+ else
+ internal_error (_("frame_unwind_find_by_frame failed"));
}
/* A default frame sniffer which always accepts the frame. Used by
@@ -396,10 +435,11 @@ maintenance_info_frame_unwinders (const char *args, int from_tty)
std::vector<const frame_unwind*> table = get_frame_unwind_table (gdbarch);
ui_out *uiout = current_uiout;
- ui_out_emit_table table_emitter (uiout, 3, -1, "FrameUnwinders");
+ ui_out_emit_table table_emitter (uiout, 4, -1, "FrameUnwinders");
uiout->table_header (27, ui_left, "name", "Name");
uiout->table_header (25, ui_left, "type", "Type");
uiout->table_header (9, ui_left, "class", "Class");
+ uiout->table_header (8, ui_left, "enabled", "Enabled");
uiout->table_body ();
for (const struct frame_unwind* unwinder: table)
@@ -407,15 +447,88 @@ maintenance_info_frame_unwinders (const char *args, int from_tty)
const char *name = unwinder->name ();
const char *type = frame_type_str (unwinder->type ());
const char *uclass = frame_unwinder_class_str (unwinder->unwinder_class ());
+ const char *enabled = (unwinder->enabled ()) ? "Y" : "N";
ui_out_emit_list tuple_emitter (uiout, nullptr);
uiout->field_string ("name", name);
uiout->field_string ("type", type);
uiout->field_string ("class", uclass);
+ uiout->field_string ("enabled", enabled);
uiout->text ("\n");
}
}
+/* Helper function to both enable and disable frame unwinders.
+ if ENABLE is true, this call will be enabling unwinders,
+ otherwise the unwinders will be disabled. */
+static void
+enable_disable_frame_unwinders (const char *args, int from_tty, bool enable)
+{
+ reinit_frame_cache ();
+ if (args == nullptr)
+ error (_("specify which frame unwinder(s) should be %s"),
+ (enable)? "enabled" : "disabled");
+ struct gdbarch* gdbarch = current_inferior ()->arch ();
+ std::vector<const frame_unwind *> unwinder_list
+ = gdbarch_get_unwinder_list (gdbarch);
+
+ /* First see if the user wants to change all unwinders. */
+ if (check_for_argument (&args, "-all"))
+ {
+ for (const frame_unwind *u : unwinder_list)
+ u->set_enabled (enable);
+ return;
+ }
+
+ /* If user entered a specific unwinder name, handle it here. If the
+ unwinder is already at the expected state, error out. */
+ if (check_for_argument (&args, "-name"))
+ {
+ bool did_something = false;
+ for (const frame_unwind *unwinder : unwinder_list)
+ {
+ if (strcasecmp (unwinder->name (), args) == 0)
+ {
+ if (unwinder->enabled () == enable)
+ error (_("unwinder %s is already %s"),
+ unwinder->name (),
+ (unwinder->enabled ()) ? ("enabled") : ("disabled"));
+ unwinder->set_enabled (enable);
+
+ did_something = true;
+ break;
+ }
+ }
+ if (!did_something)
+ error (_("couldn't find unwinder named %s"),args);
+ }
+ else
+ {
+ /* Discard '-class' argument, if provided. */
+ check_for_argument (&args, "-class");
+ enum frame_unwind_class dclass = str_to_frame_unwind_class (args);
+ for (auto unwinder: unwinder_list)
+ {
+ if (unwinder->unwinder_class () == dclass)
+ unwinder->set_enabled (enable);
+ }
+ }
+}
+
+/* Implement "maint frame-unwinders disable" command. */
+static void
+maintenance_disable_frame_unwinders (const char *args, int from_tty)
+{
+ enable_disable_frame_unwinders (args, from_tty, false);
+}
+
+/* Implement "maint frame-unwinders disable" command. */
+static void
+maintenance_enable_frame_unwinders (const char *args, int from_tty)
+{
+ enable_disable_frame_unwinders (args, from_tty, true);
+}
+
void _initialize_frame_unwind ();
void
_initialize_frame_unwind ()
@@ -427,4 +540,37 @@ _initialize_frame_unwind ()
_("List the frame unwinders currently in effect, "
"starting with the highest priority."),
&maintenanceinfolist);
+
+ /* Add "maint frame-unwinder disable/enable". */
+ static struct cmd_list_element *maint_frame_unwinder;
+
+ add_basic_prefix_cmd ("frame-unwinder", class_maintenance,
+ _("Commands handling frame unwinders."),
+ &maint_frame_unwinder, 0, &maintenancelist);
+
+ add_cmd ("disable", class_maintenance, maintenance_disable_frame_unwinders,
+ _("\
+Disable one or more frame unwinder(s).\n\
+Usage: maint frame-unwinder disable [-all | -name NAME | [-class] CLASS]\n\
+\n\
+These are the meanings of the options:\n\
+\t-all - All available unwinders will be disabled\n\
+\t-name - NAME is the exact name of the frame unwinder is to be disabled\n\
+\t-class - CLASS is the class of unwinders to be disabled.\n\
+\n\
+UNWINDER and NAME are case insensitive."),
+ & maint_frame_unwinder);
+
+ add_cmd ("enable", class_maintenance, maintenance_enable_frame_unwinders,
+ _("\
+Enable one or more frame unwinder(s).\n\
+Usage: maint frame-unwinder enable [-all | -name NAME | [-class] CLASS]\n\
+\n\
+These are the meanings of the options:\n\
+\t-all - All available unwinders will be enabled\n\
+\t-name - NAME is the exact name of the frame unwinder is to be enabled\n\
+\t-class - CLASS is the class of unwinders to be enabled.\n\
+\n\
+UNWINDER and NAME are case insensitive."),
+ & maint_frame_unwinder);
}
@@ -180,6 +180,9 @@ class frame_unwind
unwinding. */
enum frame_unwind_class m_unwinder_class;
const struct frame_data *m_unwind_data;
+
+ /* Whether this unwinder can be used when sniffing. */
+ mutable bool m_enabled = true;
public:
frame_unwind (const char *name, frame_type type, frame_unwind_class uclass,
const struct frame_data *data)
@@ -207,6 +210,16 @@ class frame_unwind
return m_unwind_data;
}
+ bool enabled () const
+ {
+ return m_enabled;
+ }
+
+ void set_enabled (bool state) const
+ {
+ m_enabled = state;
+ }
+
/* Default stop_reason function. It reports NO_REASON, unless the
frame is the outermost. */
virtual enum unwind_stop_reason stop_reason (const frame_info_ptr &this_frame,
new file mode 100644
@@ -0,0 +1,21 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2024 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/>. */
+
+int main ()
+{
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,114 @@
+# Copyright 2024 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 multiple situations in which we may use the maintenance command to
+# disable and enable frame unwinders, and check that they really are
+# disabled when they say the are.
+
+standard_testfile .c
+
+# Proc to check if the unwinder of the given name is in the desired state.
+# STATE can be either Y or N.
+proc check_unwinder_state { unwinder_name state {testname ""} } {
+ if {${testname} == ""} {
+ set testname "checking state ${state} for ${unwinder_name}"
+ }
+ gdb_test "maint info frame-unwinders" \
+ ".*${unwinder_name}\\s+\\w+\\s+\\w+.+${state}.*" \
+ "${testname}"
+}
+
+proc check_unwinder_class { unwinder_class state {testname ""} } {
+ set should_pass true
+ if {$testname == ""} {
+ set testname "checking if $unwinder_class state is $state"
+ }
+ gdb_test_multiple "maint info frame-unwinders" "collect for $testname" -lbl {
+ -re "^\\w+\\s+\\w+\\s+${unwinder_class}\\s+\(.\)\\s+" {
+ set cur_state $expec_out(1, string)
+ if {$cur_state != $state} {
+ set should_pass false
+ }
+ exp_continue
+ }
+ -re "${::gdb_prompt} $" {
+ pass $gdb_test_name
+ }
+ }
+
+ gdb_assert {$should_pass == true} "$testname"
+}
+
+if {[prepare_for_testing "failed to prepare" ${testfile} ${srcfile} ]} {
+ return -1
+}
+
+if {![runto_main]} {
+ untested "couldn't run to main"
+ return
+}
+
+# Test disabling all unwinders
+check_unwinder_class "FRAME_UNWIND_\\w+" "Y" \
+ "Checking all unwinders before any changes"
+gdb_test_no_output "maint frame-unwinder disable -all"
+check_unwinder_class "FRAME_UNWIND_\\w+" "N" \
+ "Checking all unwinders were properly disabled"
+
+# Test if GDB can still make a backtrace once all unwinders are disabled.
+# It should be impossible.
+gdb_test "backtrace" ".*frame_unwind_find_by_frame failed.*" \
+ "confirm that no suitable unwinders are found"
+
+# Reenable all unwinders
+gdb_test_no_output "maint frame-unwinder enable -all"
+check_unwinder_class "FRAME_UNWIND_\\w+" "Y" \
+ "Checking all unwinders after re-enabling them all"
+
+# Check that we are able to get backtraces once again.
+gdb_test "backtrace" ".0\\s+main .. at.*" \
+ "confirm we now get usable backtraces"
+
+# Check if we can disable an unwinder based on the name.
+check_unwinder_state "dummy" "Y"
+gdb_test_no_output "maint frame-unwinder disable -name dummy"
+check_unwinder_state "dummy" "N"
+# And verify what happens if you try to disable it again.
+gdb_test "maint frame-unwinder disable -name dummy" \
+ "unwinder dummy is already disabled" \
+ "disable already disabled unwinder"
+check_unwinder_state "dummy" "N" "check if dummy continues disabled"
+
+foreach class {GDB ARCH DEBUGINFO EXTENSION} {
+ # Disable all unwinders of type CLASS, and check that the command worked.
+ gdb_test_no_output "maint frame-unwinder disable ${class}"
+ check_unwinder_class "FRAME_UNWINDER_${class}" "N"
+}
+
+# Now check if we are able to enable a single unwinder, and what happens if we
+# enable it twice.
+gdb_test_no_output "maint frame-unwinder enable -name dummy"
+check_unwinder_state "dummy" "Y" "successfully enabled dummy unwinder"
+gdb_test "maint frame-unwinder enable -name dummy" \
+ "unwinder dummy is already enabled" \
+ "enable already enabled unwinder"
+check_unwinder_state "dummy" "Y" "check if dummy continues enabled"
+
+foreach class {GDB ARCH DEBUGINFO EXTENSION} {
+ # Enable all unwinders of type CLASS, and check that the command worked.
+ # using "-class" option to ensure it works. Should make no difference.
+ gdb_test_no_output "maint frame-unwinder enable -class ${class}"
+ check_unwinder_class "FRAME_UNWINDER_${class}" "Y"
+}