[4/4] GDB: introduce ability to disable frame unwinders

Message ID 20240306125135.766567-5-blarsen@redhat.com
State New
Headers
Series Modernize frame unwinders and add disable feature |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm fail Testing failed
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 fail Testing failed

Commit Message

Guinevere Larsen March 6, 2024, 12:51 p.m. UTC
  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.

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.
---
 gdb/NEWS                                      |   7 +
 gdb/doc/gdb.texinfo                           |  24 +++
 gdb/frame-unwind.c                            | 161 +++++++++++++++++-
 gdb/frame-unwind.h                            |  13 ++
 gdb/testsuite/gdb.base/frame-unwind-disable.c |  21 +++
 .../gdb.base/frame-unwind-disable.exp         | 114 +++++++++++++
 6 files changed, 336 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
  

Comments

Eli Zaretskii March 6, 2024, 1:47 p.m. UTC | #1
> From: Guinevere Larsen <blarsen@redhat.com>
> Cc: Guinevere Larsen <blarsen@redhat.com>
> Date: Wed,  6 Mar 2024 13:51:35 +0100
> 
>  gdb/NEWS                                      |   7 +
>  gdb/doc/gdb.texinfo                           |  24 +++
>  gdb/frame-unwind.c                            | 161 +++++++++++++++++-
>  gdb/frame-unwind.h                            |  13 ++
>  gdb/testsuite/gdb.base/frame-unwind-disable.c |  21 +++
>  .../gdb.base/frame-unwind-disable.exp         | 114 +++++++++++++
>  6 files changed, 336 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
> @@ -43,6 +43,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 [-name]
> +maintenance frame-unwinder enable [-name]
> +  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
                                         ^^
Two spaces between sentences, please.

> +@kindex maint frame-unwinder disable
> +@kindex maint frame-unwinder enable
> +@item maint frame-unwinder disable [OPTION] UNWINDER
> +@item maint frame-unwinder enable [OPTION] UNWINDER

OPTION and UNWINDER should be @var{option} and @var{unwinder},
respectively.

> +The meaning of the argument @samp{unwinder} depends on the @samp{option}

The correct markup here is @var, not @samp, since these are not
literal symbols, but instead names of parameters that stand for
something else.  Likewise elsewhere in the gdb.texinfo part of the
patch.

> +@table @samp
> +@item @code{-all}
> +ignore @samp{unwinder} and disable/enable all unwinders

This should be a complete sentence: begin with a capital letter and
end with a period.

> +@item @code{-class}
> +@samp{unwinder} is the class on frame unwinders to be disabled or enabled.
                                ^^
This should probably be "of"?

> +The class may include the prefix @code{FRAME_UNWINDER_}, but it is not
> +required.  This is the default option.

What do you mean by the last sentence?  What is "this" that is the
default option?  And what does "default" mean for an option, since an
option is by definition something that doesn't happen by default.

> +Disable one or more frame unwinder(s).\n\
> +Usage: maint frame-unwinder disable [OPTION] UNWINDER\n\
> +\n\
> +The meaning of UNWINDER depends on the OPTION given. These are the possibilities:\n\
                                                      ^^
> +\t-all    - UNWINDER is ignored. All available unwinders will be disabled\n\
                                  ^^

Two spaces between sentences in doc strings as well.

> +The meaning of UNWINDER depends on the OPTION given. These are the possibilities:\n\
> +\t-all    - UNWINDER is ignored. All available unwinders will be enabled\n\

Likewise here.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  
Guinevere Larsen March 6, 2024, 2:07 p.m. UTC | #2
On 06/03/2024 14:47, Eli Zaretskii wrote:
>> From: Guinevere Larsen <blarsen@redhat.com>
>> Cc: Guinevere Larsen <blarsen@redhat.com>
>> Date: Wed,  6 Mar 2024 13:51:35 +0100
>>
>>   gdb/NEWS                                      |   7 +
>>   gdb/doc/gdb.texinfo                           |  24 +++
>>   gdb/frame-unwind.c                            | 161 +++++++++++++++++-
>>   gdb/frame-unwind.h                            |  13 ++
>>   gdb/testsuite/gdb.base/frame-unwind-disable.c |  21 +++
>>   .../gdb.base/frame-unwind-disable.exp         | 114 +++++++++++++
>>   6 files changed, 336 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
>> @@ -43,6 +43,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 [-name]
>> +maintenance frame-unwinder enable [-name]
>> +  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
>                                           ^^
> Two spaces between sentences, please.
>
>> +@kindex maint frame-unwinder disable
>> +@kindex maint frame-unwinder enable
>> +@item maint frame-unwinder disable [OPTION] UNWINDER
>> +@item maint frame-unwinder enable [OPTION] UNWINDER
> OPTION and UNWINDER should be @var{option} and @var{unwinder},
> respectively.
>
>> +The meaning of the argument @samp{unwinder} depends on the @samp{option}
> The correct markup here is @var, not @samp, since these are not
> literal symbols, but instead names of parameters that stand for
> something else.  Likewise elsewhere in the gdb.texinfo part of the
> patch.

Ah, thanks for explaining! Should I also change the @samp in the table 
section?

>
>> +@table @samp
>> +@item @code{-all}
>> +ignore @samp{unwinder} and disable/enable all unwinders
> This should be a complete sentence: begin with a capital letter and
> end with a period.
>
>> +@item @code{-class}
>> +@samp{unwinder} is the class on frame unwinders to be disabled or enabled.
>                                  ^^
> This should probably be "of"?
>
>> +The class may include the prefix @code{FRAME_UNWINDER_}, but it is not
>> +required.  This is the default option.
> What do you mean by the last sentence?  What is "this" that is the
> default option?  And what does "default" mean for an option, since an
> option is by definition something that doesn't happen by default.

option is an optional parameter, and if the user decides to not provide 
it, the command will work as if the user had provided the -class option.

I can see where the confusion came from, but I'm not really sure how to 
improve the wording.

>
>> +Disable one or more frame unwinder(s).\n\
>> +Usage: maint frame-unwinder disable [OPTION] UNWINDER\n\
>> +\n\
>> +The meaning of UNWINDER depends on the OPTION given. These are the possibilities:\n\
>                                                        ^^
>> +\t-all    - UNWINDER is ignored. All available unwinders will be disabled\n\
>                                    ^^
>
> Two spaces between sentences in doc strings as well.
>
>> +The meaning of UNWINDER depends on the OPTION given. These are the possibilities:\n\
>> +\t-all    - UNWINDER is ignored. All available unwinders will be enabled\n\
> Likewise here.
>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
>
  
Eli Zaretskii March 6, 2024, 2:16 p.m. UTC | #3
> Date: Wed, 6 Mar 2024 15:07:17 +0100
> Cc: gdb-patches@sourceware.org
> From: Guinevere Larsen <blarsen@redhat.com>
> 
> >> +The meaning of the argument @samp{unwinder} depends on the @samp{option}
> > The correct markup here is @var, not @samp, since these are not
> > literal symbols, but instead names of parameters that stand for
> > something else.  Likewise elsewhere in the gdb.texinfo part of the
> > patch.
> 
> Ah, thanks for explaining! Should I also change the @samp in the table 
> section?

Only for @samp{unwinder} and @samp{option}.  The rest are literals,
like "-all".


> >> +The class may include the prefix @code{FRAME_UNWINDER_}, but it is not
> >> +required.  This is the default option.
> > What do you mean by the last sentence?  What is "this" that is the
> > default option?  And what does "default" mean for an option, since an
> > option is by definition something that doesn't happen by default.
> 
> option is an optional parameter, and if the user decides to not provide 
> it, the command will work as if the user had provided the -class option.

Then I suggest to rephrase like this:

  By default, @var{unwinder} is interpreted as a class, even if
  @samp{-class} was omitted.
  
Tom Tromey March 8, 2024, 5:22 p.m. UTC | #4
>>>>> Guinevere Larsen <blarsen@redhat.com> writes:

 
> +static enum frame_unwind_class
> +str_to_frame_unwind_class (const char **c_str)

Comment.

> +{
> +  std::string full_name = "FRAME_UNWIND_";
> +  const int start_length = full_name.length ();
> +  if (strncasecmp (*c_str, full_name.c_str (), start_length) == 0)
> +    full_name = *c_str;

In an earlier patch I was wondering how useful those FRAME_UNWIND_
strings were for the maint output.  Maybe just using human names here
would be overall better.

Then this could just call streq.

Anyway if you want to keep this approach, it's weird to allocate a
string and then use the C API.  Perhaps a string_view would be better.

 
> +/* 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 ();

Stray blank line.

> +  if (args == nullptr)
> +    {
> +      error (_("specify which frame unwinder(s) should be %s"),
> +	     (enable)? "enabled" : "disabled");
> +    }

No braces.

> +  /* 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);
> +	}

This also looks over-braced.

> +  add_cmd ("disable", class_maintenance, maintenance_disable_frame_unwinders,
> +	   _("\
> +Disable one or more frame unwinder(s).\n\
> +Usage: maint frame-unwinder disable [OPTION] UNWINDER\n\
> +\n\
> +The meaning of UNWINDER depends on the OPTION given. These are the possibilities:\n\
> +\t-all    - UNWINDER is ignored. All available unwinders will be disabled\n\
> +\t-name   - UNWINDER is the exact name of the frame unwinder is to be disabled\n\
> +\t-class  - UNWINDER is the class of unwinders to be disabled.\n\

I guess I'd write that more like

disable [-all | -name UNWINDER | -class NAME]

or something like that, rather than spelling out that UNWINDER is
ignored in one case.


I think there's probably a bug in bugzilla about disabling unwinders, so
this should probably have a Bug: trailer.

Tom
  
Guinevere Larsen March 11, 2024, 2:09 p.m. UTC | #5
On 08/03/2024 18:22, Tom Tromey wrote:
>>>>>> Guinevere Larsen <blarsen@redhat.com> writes:
>   
>> +static enum frame_unwind_class
>> +str_to_frame_unwind_class (const char **c_str)
> Comment.
>
>> +{
>> +  std::string full_name = "FRAME_UNWIND_";
>> +  const int start_length = full_name.length ();
>> +  if (strncasecmp (*c_str, full_name.c_str (), start_length) == 0)
>> +    full_name = *c_str;
> In an earlier patch I was wondering how useful those FRAME_UNWIND_
> strings were for the maint output.  Maybe just using human names here
> would be overall better.
>
> Then this could just call streq.

I don't want to call streq because I want case insensitive matching, but 
I will drop the FRAME_UNWIND_ prefix from printing.

>
> Anyway if you want to keep this approach, it's weird to allocate a
> string and then use the C API.  Perhaps a string_view would be better.
I used the string because at that moment it felt safer than dealing with 
strcat, and originally I was going to use the operator equals rather 
than strcasecmp. I'll drop it
>
>   
>> +/* 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 ();
> Stray blank line.
>
>> +  if (args == nullptr)
>> +    {
>> +      error (_("specify which frame unwinder(s) should be %s"),
>> +	     (enable)? "enabled" : "disabled");
>> +    }
> No braces.
>
>> +  /* 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);
>> +	}
> This also looks over-braced.
>
>> +  add_cmd ("disable", class_maintenance, maintenance_disable_frame_unwinders,
>> +	   _("\
>> +Disable one or more frame unwinder(s).\n\
>> +Usage: maint frame-unwinder disable [OPTION] UNWINDER\n\
>> +\n\
>> +The meaning of UNWINDER depends on the OPTION given. These are the possibilities:\n\
>> +\t-all    - UNWINDER is ignored. All available unwinders will be disabled\n\
>> +\t-name   - UNWINDER is the exact name of the frame unwinder is to be disabled\n\
>> +\t-class  - UNWINDER is the class of unwinders to be disabled.\n\
> I guess I'd write that more like
>
> disable [-all | -name UNWINDER | -class NAME]
>
> or something like that, rather than spelling out that UNWINDER is
> ignored in one case.
That sounds like a much better solution to what I came up with hahaha. 
Thank you!
>
>
> I think there's probably a bug in bugzilla about disabling unwinders, so
> this should probably have a Bug: trailer.
Is there? :o I'll go looking for it.
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 2638b3e0d9c..5598a8d5a09 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -43,6 +43,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 [-name]
+maintenance frame-unwinder enable [-name]
+  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.
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 34cd567f811..79290576654 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -41921,6 +41921,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 [OPTION] UNWINDER
+@item maint frame-unwinder enable [OPTION] UNWINDER
+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 the argument @samp{unwinder} depends on the @samp{option}
+provided. The following options are accepted:
+
+@table @samp
+@item @code{-all}
+ignore @samp{unwinder} and disable/enable all unwinders
+@item @code{-class}
+@samp{unwinder} is the class on frame unwinders to be disabled or enabled.
+The class may include the prefix @code{FRAME_UNWINDER_}, but it is not
+required.  This is the default option.
+@item @code{-name}
+@samp{unwinder} is the exact name of the unwinder to be disabled or enabled.
+@end table
+
+@samp{Unwinder} is always case insensitive
+
 @kindex maint set worker-threads
 @kindex maint show worker-threads
 @item maint set worker-threads
diff --git a/gdb/frame-unwind.c b/gdb/frame-unwind.c
index 2e35732d763..945276ef1e7 100644
--- a/gdb/frame-unwind.c
+++ b/gdb/frame-unwind.c
@@ -88,6 +88,23 @@  frame_unwinder_class_str (frame_unwind_class uclass)
   return location->second.c_str ();
 }
 
+static enum frame_unwind_class
+str_to_frame_unwind_class (const char **c_str)
+{
+  std::string full_name = "FRAME_UNWIND_";
+  const int start_length = full_name.length ();
+  if (strncasecmp (*c_str, full_name.c_str (), start_length) == 0)
+    full_name = *c_str;
+  else
+    full_name += *c_str;
+  for (const auto& it : unwind_class_conversion)
+    {
+      if (strcasecmp(it.second.c_str (), full_name.c_str ()) == 0)
+	return it.first;
+    }
+  error (_("Unknown frame unwind class: %s"), *c_str);
+}
+
 void
 frame_unwind_prepend_unwinder (struct gdbarch *gdbarch,
 				const struct frame_unwind *unwinder)
@@ -185,25 +202,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
@@ -416,10 +455,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 (25, ui_left, "class", "Class");
+  uiout->table_header (8, ui_left, "enabled", "Enabled");
   uiout->table_body ();
 
   for (const struct frame_unwind* unwinder: table)
@@ -427,15 +467,95 @@  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 ()
@@ -447,4 +567,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 [OPTION] UNWINDER\n\
+\n\
+The meaning of UNWINDER depends on the OPTION given. These are the possibilities:\n\
+\t-all    - UNWINDER is ignored. All available unwinders will be disabled\n\
+\t-name   - UNWINDER is the exact name of the frame unwinder is to be disabled\n\
+\t-class  - UNWINDER is the class of unwinders to be disabled.\n\
+\n\
+UNWINDER is 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 [OPTION] UNWINDER\n\
+\n\
+The meaning of UNWINDER depends on the OPTION given. These are the possibilities:\n\
+\t-all    - UNWINDER is ignored. All available unwinders will be enabled\n\
+\t-name   - UNWINDER is the exact name of the frame unwinder is to be enabled\n\
+\t-class  - UNWINDER is the class of unwinders to be enabled.\n\
+\n\
+UNWINDER is case insensitive."),
+	   & maint_frame_unwinder);
 }
diff --git a/gdb/frame-unwind.h b/gdb/frame-unwind.h
index eff36f7eab5..3a0394d249a 100644
--- a/gdb/frame-unwind.h
+++ b/gdb/frame-unwind.h
@@ -175,6 +175,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 *n, frame_type t, frame_unwind_class c,
 		       const struct frame_data *d)
@@ -200,6 +203,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,
diff --git a/gdb/testsuite/gdb.base/frame-unwind-disable.c b/gdb/testsuite/gdb.base/frame-unwind-disable.c
new file mode 100644
index 00000000000..078517c011e
--- /dev/null
+++ b/gdb/testsuite/gdb.base/frame-unwind-disable.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.base/frame-unwind-disable.exp b/gdb/testsuite/gdb.base/frame-unwind-disable.exp
new file mode 100644
index 00000000000..4efc87ccfcf
--- /dev/null
+++ b/gdb/testsuite/gdb.base/frame-unwind-disable.exp
@@ -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"
+}