mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2025-01-18 12:24:38 +08:00
abfc3190ce
The recent commit 3919cf8a70
caused a
build breakage on the 32-bit debian try bot. This seems related to how
the patch allocates space for the frame unwinder table in a gdbarch.
This commit changes that allocation to be in-place.
675 lines
20 KiB
C
675 lines
20 KiB
C
/* Definitions for frame unwinder, for GDB, the GNU debugger.
|
|
|
|
Copyright (C) 2003-2024 Free Software Foundation, Inc.
|
|
|
|
This file is part of GDB.
|
|
|
|
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/>. */
|
|
|
|
#include "extract-store-integer.h"
|
|
#include "frame.h"
|
|
#include "frame-unwind.h"
|
|
#include "dummy-frame.h"
|
|
#include "inline-frame.h"
|
|
#include "value.h"
|
|
#include "regcache.h"
|
|
#include "gdbsupport/gdb_obstack.h"
|
|
#include "target.h"
|
|
#include "gdbarch.h"
|
|
#include "dwarf2/frame-tailcall.h"
|
|
#include "cli/cli-cmds.h"
|
|
#include "cli/cli-option.h"
|
|
#include "inferior.h"
|
|
|
|
/* Conversion list between the enum for frame_unwind_class and
|
|
string. */
|
|
static const char * unwind_class_conversion[] =
|
|
{
|
|
"GDB",
|
|
"EXTENSION",
|
|
"DEBUGINFO",
|
|
"ARCH",
|
|
nullptr
|
|
};
|
|
|
|
/* Default sniffers, that must always be the first in the unwinder list,
|
|
no matter the architecture. */
|
|
static constexpr std::initializer_list<const frame_unwind *>
|
|
standard_unwinders =
|
|
{
|
|
&dummy_frame_unwind,
|
|
/* The DWARF tailcall sniffer must come before the inline sniffer.
|
|
Otherwise, we can end up in a situation where a DWARF frame finds
|
|
tailcall information, but then the inline sniffer claims a frame
|
|
before the tailcall sniffer, resulting in confusion. This is
|
|
safe to do always because the tailcall sniffer can only ever be
|
|
activated if the newer frame was created using the DWARF
|
|
unwinder, and it also found tailcall information. */
|
|
&dwarf2_tailcall_frame_unwind,
|
|
&inline_frame_unwind,
|
|
};
|
|
|
|
/* If an unwinder should be prepended to the list, this is the
|
|
index in which it should be inserted. */
|
|
static constexpr int prepend_unwinder_index = standard_unwinders.size ();
|
|
|
|
static const registry<gdbarch>::key<std::vector<const frame_unwind *>>
|
|
frame_unwind_data;
|
|
|
|
/* Retrieve the list of frame unwinders available in GDBARCH.
|
|
If this list is empty, it is initialized before being returned. */
|
|
static std::vector<const frame_unwind *> &
|
|
get_frame_unwind_table (struct gdbarch *gdbarch)
|
|
{
|
|
std::vector<const frame_unwind *> *table = frame_unwind_data.get (gdbarch);
|
|
if (table == nullptr)
|
|
table = frame_unwind_data.emplace (gdbarch, standard_unwinders.begin (),
|
|
standard_unwinders.end ());
|
|
|
|
return *table;
|
|
}
|
|
|
|
static const char *
|
|
frame_unwinder_class_str (frame_unwind_class uclass)
|
|
{
|
|
gdb_assert (uclass < UNWIND_CLASS_NUMBER);
|
|
return unwind_class_conversion[uclass];
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
for (int i = 0; i < UNWIND_CLASS_NUMBER; i++)
|
|
{
|
|
if (strcasecmp (unwind_class_conversion[i], class_str) == 0)
|
|
return (frame_unwind_class) i;
|
|
}
|
|
|
|
error (_("Unknown frame unwind class: %s"), class_str);
|
|
}
|
|
|
|
void
|
|
frame_unwind_prepend_unwinder (struct gdbarch *gdbarch,
|
|
const struct frame_unwind *unwinder)
|
|
{
|
|
std::vector<const frame_unwind *> &table = get_frame_unwind_table (gdbarch);
|
|
|
|
table.insert (table.begin () + prepend_unwinder_index, unwinder);
|
|
}
|
|
|
|
void
|
|
frame_unwind_append_unwinder (struct gdbarch *gdbarch,
|
|
const struct frame_unwind *unwinder)
|
|
{
|
|
get_frame_unwind_table (gdbarch).push_back (unwinder);
|
|
}
|
|
|
|
/* Call SNIFFER from UNWINDER. If it succeeded set UNWINDER for
|
|
THIS_FRAME and return true. Otherwise the function keeps THIS_FRAME
|
|
unchanged and returns false. */
|
|
|
|
static bool
|
|
frame_unwind_try_unwinder (const frame_info_ptr &this_frame, void **this_cache,
|
|
const struct frame_unwind *unwinder)
|
|
{
|
|
int res = 0;
|
|
|
|
unsigned int entry_generation = get_frame_cache_generation ();
|
|
|
|
frame_prepare_for_sniffer (this_frame, unwinder);
|
|
|
|
try
|
|
{
|
|
frame_debug_printf ("trying unwinder \"%s\"", unwinder->name ());
|
|
res = unwinder->sniff (this_frame, this_cache);
|
|
}
|
|
catch (const gdb_exception &ex)
|
|
{
|
|
frame_debug_printf ("caught exception: %s", ex.message->c_str ());
|
|
|
|
/* Catch all exceptions, caused by either interrupt or error.
|
|
Reset *THIS_CACHE, unless something reinitialized the frame
|
|
cache meanwhile, in which case THIS_FRAME/THIS_CACHE are now
|
|
dangling. */
|
|
if (get_frame_cache_generation () == entry_generation)
|
|
{
|
|
*this_cache = NULL;
|
|
frame_cleanup_after_sniffer (this_frame);
|
|
}
|
|
|
|
if (ex.error == NOT_AVAILABLE_ERROR)
|
|
{
|
|
/* This usually means that not even the PC is available,
|
|
thus most unwinders aren't able to determine if they're
|
|
the best fit. Keep trying. Fallback prologue unwinders
|
|
should always accept the frame. */
|
|
return false;
|
|
}
|
|
throw;
|
|
}
|
|
|
|
if (res)
|
|
{
|
|
frame_debug_printf ("yes");
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
frame_debug_printf ("no");
|
|
/* Don't set *THIS_CACHE to NULL here, because sniffer has to do
|
|
so. */
|
|
frame_cleanup_after_sniffer (this_frame);
|
|
return false;
|
|
}
|
|
gdb_assert_not_reached ("frame_unwind_try_unwinder");
|
|
}
|
|
|
|
/* Iterate through sniffers for THIS_FRAME frame until one returns with an
|
|
unwinder implementation. THIS_FRAME->UNWIND must be NULL, it will get set
|
|
by this function. Possibly initialize THIS_CACHE. */
|
|
|
|
void
|
|
frame_unwind_find_by_frame (const frame_info_ptr &this_frame, void **this_cache)
|
|
{
|
|
FRAME_SCOPED_DEBUG_ENTER_EXIT;
|
|
frame_debug_printf ("this_frame=%d", frame_relative_level (this_frame));
|
|
|
|
/* If we see a disabled unwinder, we assume some test is being run on
|
|
GDB, and we don't want to internal_error at the end of this function. */
|
|
bool seen_disabled_unwinder = false;
|
|
/* Lambda to factor out the logic of checking if an unwinder is enabled,
|
|
testing it and otherwise recording if we saw a disable unwinder. */
|
|
auto test_unwinder = [&] (const struct frame_unwind *unwinder)
|
|
{
|
|
if (unwinder == nullptr)
|
|
return false;
|
|
|
|
if (!unwinder->enabled ())
|
|
{
|
|
seen_disabled_unwinder = true;
|
|
return false;
|
|
}
|
|
|
|
return frame_unwind_try_unwinder (this_frame,
|
|
this_cache,
|
|
unwinder);
|
|
};
|
|
|
|
if (test_unwinder (target_get_unwinder ()))
|
|
return;
|
|
|
|
if (test_unwinder (target_get_tailcall_unwinder ()))
|
|
return;
|
|
|
|
struct gdbarch *gdbarch = get_frame_arch (this_frame);
|
|
std::vector<const frame_unwind *> &table = get_frame_unwind_table (gdbarch);
|
|
for (const auto &unwinder : table)
|
|
{
|
|
if (test_unwinder (unwinder))
|
|
return;
|
|
}
|
|
|
|
if (seen_disabled_unwinder)
|
|
error (_("Required frame unwinder may have been disabled"
|
|
", see 'maint info frame-unwinders'"));
|
|
else
|
|
internal_error (_("frame_unwind_find_by_frame failed"));
|
|
}
|
|
|
|
/* A default frame sniffer which always accepts the frame. Used by
|
|
fallback prologue unwinders. */
|
|
|
|
int
|
|
default_frame_sniffer (const struct frame_unwind *self,
|
|
const frame_info_ptr &this_frame,
|
|
void **this_prologue_cache)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/* The default frame unwinder stop_reason callback. */
|
|
|
|
enum unwind_stop_reason
|
|
default_frame_unwind_stop_reason (const frame_info_ptr &this_frame,
|
|
void **this_cache)
|
|
{
|
|
struct frame_id this_id = get_frame_id (this_frame);
|
|
|
|
if (this_id == outer_frame_id)
|
|
return UNWIND_OUTERMOST;
|
|
else
|
|
return UNWIND_NO_REASON;
|
|
}
|
|
|
|
/* See frame-unwind.h. */
|
|
|
|
CORE_ADDR
|
|
default_unwind_pc (struct gdbarch *gdbarch, const frame_info_ptr &next_frame)
|
|
{
|
|
int pc_regnum = gdbarch_pc_regnum (gdbarch);
|
|
CORE_ADDR pc = frame_unwind_register_unsigned (next_frame, pc_regnum);
|
|
pc = gdbarch_addr_bits_remove (gdbarch, pc);
|
|
return pc;
|
|
}
|
|
|
|
/* See frame-unwind.h. */
|
|
|
|
CORE_ADDR
|
|
default_unwind_sp (struct gdbarch *gdbarch, const frame_info_ptr &next_frame)
|
|
{
|
|
int sp_regnum = gdbarch_sp_regnum (gdbarch);
|
|
return frame_unwind_register_unsigned (next_frame, sp_regnum);
|
|
}
|
|
|
|
/* Helper functions for value-based register unwinding. These return
|
|
a (possibly lazy) value of the appropriate type. */
|
|
|
|
/* Return a value which indicates that FRAME did not save REGNUM. */
|
|
|
|
struct value *
|
|
frame_unwind_got_optimized (const frame_info_ptr &frame, int regnum)
|
|
{
|
|
struct gdbarch *gdbarch = frame_unwind_arch (frame);
|
|
struct type *type = register_type (gdbarch, regnum);
|
|
|
|
return value::allocate_optimized_out (type);
|
|
}
|
|
|
|
/* Return a value which indicates that FRAME copied REGNUM into
|
|
register NEW_REGNUM. */
|
|
|
|
struct value *
|
|
frame_unwind_got_register (const frame_info_ptr &frame,
|
|
int regnum, int new_regnum)
|
|
{
|
|
return value_of_register_lazy (get_next_frame_sentinel_okay (frame),
|
|
new_regnum);
|
|
}
|
|
|
|
/* Return a value which indicates that FRAME saved REGNUM in memory at
|
|
ADDR. */
|
|
|
|
struct value *
|
|
frame_unwind_got_memory (const frame_info_ptr &frame, int regnum, CORE_ADDR addr)
|
|
{
|
|
struct gdbarch *gdbarch = frame_unwind_arch (frame);
|
|
struct value *v = value_at_lazy (register_type (gdbarch, regnum), addr);
|
|
|
|
v->set_stack (true);
|
|
return v;
|
|
}
|
|
|
|
/* Return a value which indicates that FRAME's saved version of
|
|
REGNUM has a known constant (computed) value of VAL. */
|
|
|
|
struct value *
|
|
frame_unwind_got_constant (const frame_info_ptr &frame, int regnum,
|
|
ULONGEST val)
|
|
{
|
|
struct gdbarch *gdbarch = frame_unwind_arch (frame);
|
|
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
|
struct value *reg_val;
|
|
|
|
reg_val = value::zero (register_type (gdbarch, regnum), not_lval);
|
|
store_unsigned_integer (reg_val->contents_writeable ().data (),
|
|
register_size (gdbarch, regnum), byte_order, val);
|
|
return reg_val;
|
|
}
|
|
|
|
struct value *
|
|
frame_unwind_got_bytes (const frame_info_ptr &frame, int regnum,
|
|
gdb::array_view<const gdb_byte> buf)
|
|
{
|
|
struct gdbarch *gdbarch = frame_unwind_arch (frame);
|
|
struct value *reg_val;
|
|
|
|
reg_val = value::zero (register_type (gdbarch, regnum), not_lval);
|
|
gdb::array_view<gdb_byte> val_contents = reg_val->contents_raw ();
|
|
|
|
/* The value's contents buffer is zeroed on allocation so if buf is
|
|
smaller, the remaining space will be filled with zero.
|
|
|
|
This can happen when unwinding through signal frames. For example, if
|
|
an AArch64 program doesn't use SVE, then the Linux kernel will only
|
|
save in the signal frame the first 128 bits of the vector registers,
|
|
which is their minimum size, even if the vector length says they're
|
|
bigger. */
|
|
gdb_assert (buf.size () <= val_contents.size ());
|
|
|
|
memcpy (val_contents.data (), buf.data (), buf.size ());
|
|
return reg_val;
|
|
}
|
|
|
|
/* Return a value which indicates that FRAME's saved version of REGNUM
|
|
has a known constant (computed) value of ADDR. Convert the
|
|
CORE_ADDR to a target address if necessary. */
|
|
|
|
struct value *
|
|
frame_unwind_got_address (const frame_info_ptr &frame, int regnum,
|
|
CORE_ADDR addr)
|
|
{
|
|
struct gdbarch *gdbarch = frame_unwind_arch (frame);
|
|
struct value *reg_val;
|
|
|
|
reg_val = value::zero (register_type (gdbarch, regnum), not_lval);
|
|
pack_long (reg_val->contents_writeable ().data (),
|
|
register_type (gdbarch, regnum), addr);
|
|
return reg_val;
|
|
}
|
|
|
|
/* See frame-unwind.h. */
|
|
|
|
enum unwind_stop_reason
|
|
frame_unwind_legacy::stop_reason (const frame_info_ptr &this_frame,
|
|
void **this_prologue_cache) const
|
|
{
|
|
return m_stop_reason (this_frame, this_prologue_cache);
|
|
}
|
|
|
|
/* See frame-unwind.h. */
|
|
|
|
void
|
|
frame_unwind_legacy::this_id (const frame_info_ptr &this_frame,
|
|
void **this_prologue_cache,
|
|
struct frame_id *id) const
|
|
{
|
|
return m_this_id (this_frame, this_prologue_cache, id);
|
|
}
|
|
|
|
/* See frame-unwind.h. */
|
|
|
|
struct value *
|
|
frame_unwind_legacy::prev_register (const frame_info_ptr &this_frame,
|
|
void **this_prologue_cache,
|
|
int regnum) const
|
|
{
|
|
return m_prev_register (this_frame, this_prologue_cache, regnum);
|
|
}
|
|
|
|
/* See frame-unwind.h. */
|
|
|
|
int
|
|
frame_unwind_legacy::sniff (const frame_info_ptr &this_frame,
|
|
void **this_prologue_cache) const
|
|
{
|
|
return m_sniffer (this, this_frame, this_prologue_cache);
|
|
}
|
|
|
|
/* See frame-unwind.h. */
|
|
|
|
void
|
|
frame_unwind_legacy::dealloc_cache (frame_info *self, void *this_cache) const
|
|
{
|
|
if (m_dealloc_cache != nullptr)
|
|
m_dealloc_cache (self, this_cache);
|
|
}
|
|
|
|
/* See frame-unwind.h. */
|
|
|
|
struct gdbarch *
|
|
frame_unwind_legacy::prev_arch (const frame_info_ptr &this_frame,
|
|
void **this_prologue_cache) const
|
|
{
|
|
if (m_prev_arch == nullptr)
|
|
return frame_unwind::prev_arch (this_frame, this_prologue_cache);
|
|
return m_prev_arch (this_frame, this_prologue_cache);
|
|
}
|
|
|
|
/* Implement "maintenance info frame-unwinders" command. */
|
|
|
|
static void
|
|
maintenance_info_frame_unwinders (const char *args, int from_tty)
|
|
{
|
|
gdbarch *gdbarch = current_inferior ()->arch ();
|
|
std::vector<const frame_unwind *> &table = get_frame_unwind_table (gdbarch);
|
|
|
|
ui_out *uiout = current_uiout;
|
|
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 auto &unwinder : table)
|
|
{
|
|
ui_out_emit_list tuple_emitter (uiout, nullptr);
|
|
uiout->field_string ("name", unwinder->name ());
|
|
uiout->field_string ("type", frame_type_str (unwinder->type ()));
|
|
uiout->field_string ("class", frame_unwinder_class_str (
|
|
unwinder->unwinder_class ()));
|
|
uiout->field_string ("enabled", unwinder->enabled () ? "Y" : "N");
|
|
uiout->text ("\n");
|
|
}
|
|
}
|
|
|
|
/* Options for disabling frame unwinders. */
|
|
struct maint_frame_unwind_options
|
|
{
|
|
std::string unwinder_name;
|
|
const char *unwinder_class = nullptr;
|
|
bool all = false;
|
|
};
|
|
|
|
static const gdb::option::option_def maint_frame_unwind_opt_defs[] = {
|
|
|
|
gdb::option::flag_option_def<maint_frame_unwind_options> {
|
|
"all",
|
|
[] (maint_frame_unwind_options *opt) { return &opt->all; },
|
|
N_("Change the state of all unwinders")
|
|
},
|
|
gdb::option::string_option_def<maint_frame_unwind_options> {
|
|
"name",
|
|
[] (maint_frame_unwind_options *opt) { return &opt->unwinder_name; },
|
|
nullptr,
|
|
N_("The name of the unwinder to have its state changed")
|
|
},
|
|
gdb::option::enum_option_def<maint_frame_unwind_options> {
|
|
"class",
|
|
unwind_class_conversion,
|
|
[] (maint_frame_unwind_options *opt) { return &opt->unwinder_class; },
|
|
nullptr,
|
|
N_("The class of unwinders to have their states changed")
|
|
}
|
|
};
|
|
|
|
static inline gdb::option::option_def_group
|
|
make_frame_unwind_enable_disable_options (maint_frame_unwind_options *opts)
|
|
{
|
|
return {{maint_frame_unwind_opt_defs}, opts};
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
if (args == nullptr)
|
|
{
|
|
if (enable)
|
|
error (_("Specify which frame unwinder(s) should be enabled"));
|
|
else
|
|
error (_("Specify which frame unwinder(s) should be disabled"));
|
|
}
|
|
|
|
struct gdbarch* gdbarch = current_inferior ()->arch ();
|
|
std::vector<const frame_unwind *> unwinder_list
|
|
= get_frame_unwind_table (gdbarch);
|
|
|
|
maint_frame_unwind_options opts;
|
|
gdb::option::process_options
|
|
(&args, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR,
|
|
make_frame_unwind_enable_disable_options (&opts));
|
|
|
|
if ((opts.all && !opts.unwinder_name.empty ())
|
|
|| (opts.all && opts.unwinder_class != nullptr)
|
|
|| (!opts.unwinder_name.empty () && opts.unwinder_class != nullptr))
|
|
error (_("Options are mutually exclusive"));
|
|
|
|
/* First see if the user wants to change all unwinders. */
|
|
if (opts.all)
|
|
{
|
|
for (const frame_unwind *u : unwinder_list)
|
|
u->set_enabled (enable);
|
|
|
|
reinit_frame_cache ();
|
|
return;
|
|
}
|
|
|
|
/* If user entered a specific unwinder name, handle it here. If the
|
|
unwinder is already at the expected state, error out. */
|
|
if (!opts.unwinder_name.empty ())
|
|
{
|
|
bool did_something = false;
|
|
for (const frame_unwind *unwinder : unwinder_list)
|
|
{
|
|
if (strcasecmp (unwinder->name (),
|
|
opts.unwinder_name.c_str ()) == 0)
|
|
{
|
|
if (unwinder->enabled () == enable)
|
|
{
|
|
if (unwinder->enabled ())
|
|
error (_("unwinder %s is already enabled"),
|
|
unwinder->name ());
|
|
else
|
|
error (_("unwinder %s is already disabled"),
|
|
unwinder->name ());
|
|
}
|
|
unwinder->set_enabled (enable);
|
|
|
|
did_something = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!did_something)
|
|
error (_("couldn't find unwinder named %s"),
|
|
opts.unwinder_name.c_str ());
|
|
}
|
|
else
|
|
{
|
|
if (opts.unwinder_class == nullptr)
|
|
opts.unwinder_class = args;
|
|
enum frame_unwind_class dclass = str_to_frame_unwind_class
|
|
(opts.unwinder_class);
|
|
for (const frame_unwind *unwinder: unwinder_list)
|
|
{
|
|
if (unwinder->unwinder_class () == dclass)
|
|
unwinder->set_enabled (enable);
|
|
}
|
|
}
|
|
|
|
reinit_frame_cache ();
|
|
}
|
|
|
|
/* Completer for the "maint frame-unwinder enable|disable" commands. */
|
|
|
|
static void
|
|
enable_disable_frame_unwinders_completer (struct cmd_list_element *ignore,
|
|
completion_tracker &tracker,
|
|
const char *text,
|
|
const char * /*word*/)
|
|
{
|
|
maint_frame_unwind_options opts;
|
|
const auto group = make_frame_unwind_enable_disable_options (&opts);
|
|
|
|
const char *start = text;
|
|
if (gdb::option::complete_options
|
|
(tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group))
|
|
return;
|
|
|
|
/* Only complete a class name as a stand-alone operand when no options
|
|
are given. */
|
|
if (start == text)
|
|
complete_on_enum (tracker, unwind_class_conversion, text, text);
|
|
return;
|
|
}
|
|
|
|
/* Implement "maint frame-unwinder 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-unwinder enable" 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 ()
|
|
{
|
|
/* Add "maint info frame-unwinders". */
|
|
add_cmd ("frame-unwinders",
|
|
class_maintenance,
|
|
maintenance_info_frame_unwinders,
|
|
_("\
|
|
List the frame unwinders currently in effect.\n\
|
|
Unwinders are listed 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);
|
|
|
|
cmd_list_element *c
|
|
= 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 to be disabled\n\
|
|
\t-class - CLASS is the class of unwinders to be disabled. Valid classes are:\n\
|
|
\t\tGDB - Unwinders added by GDB core;\n\
|
|
\t\tEXTENSION - Unwinders added by extension languages;\n\
|
|
\t\tDEBUGINFO - Unwinders that handle debug information;\n\
|
|
\t\tARCH - Unwinders that use architecture-specific information;\n\
|
|
\n\
|
|
UNWINDER and NAME are case insensitive."),
|
|
&maint_frame_unwinder);
|
|
set_cmd_completer_handle_brkchars (c,
|
|
enable_disable_frame_unwinders_completer);
|
|
|
|
c =
|
|
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 to be enabled\n\
|
|
\t-class - CLASS is the class of unwinders to be enabled. Valid classes are:\n\
|
|
\t\tGDB - Unwinders added by GDB core;\n\
|
|
\t\tEXTENSION - Unwinders added by extension languages;\n\
|
|
\t\tDEBUGINFO - Unwinders that handle debug information;\n\
|
|
\t\tARCH - Unwinders that use architecture-specific information;\n\
|
|
\n\
|
|
UNWINDER and NAME are case insensitive."),
|
|
&maint_frame_unwinder);
|
|
set_cmd_completer_handle_brkchars (c,
|
|
enable_disable_frame_unwinders_completer);
|
|
}
|