|  | /* 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 "inferior.h" | 
|  |  | 
|  | struct frame_unwind_table_entry | 
|  | { | 
|  | const struct frame_unwind *unwinder; | 
|  | struct frame_unwind_table_entry *next; | 
|  | }; | 
|  |  | 
|  | struct frame_unwind_table | 
|  | { | 
|  | struct frame_unwind_table_entry *list = nullptr; | 
|  | /* The head of the OSABI part of the search list.  */ | 
|  | struct frame_unwind_table_entry **osabi_head = nullptr; | 
|  | }; | 
|  |  | 
|  | static const registry<gdbarch>::key<struct frame_unwind_table> | 
|  | frame_unwind_data; | 
|  |  | 
|  | /* A helper function to add an unwinder to a list.  LINK says where to | 
|  | install the new unwinder.  The new link is returned.  */ | 
|  |  | 
|  | static struct frame_unwind_table_entry ** | 
|  | add_unwinder (struct obstack *obstack, const struct frame_unwind *unwinder, | 
|  | struct frame_unwind_table_entry **link) | 
|  | { | 
|  | *link = OBSTACK_ZALLOC (obstack, struct frame_unwind_table_entry); | 
|  | (*link)->unwinder = unwinder; | 
|  | return &(*link)->next; | 
|  | } | 
|  |  | 
|  | static struct frame_unwind_table * | 
|  | get_frame_unwind_table (struct gdbarch *gdbarch) | 
|  | { | 
|  | struct frame_unwind_table *table = frame_unwind_data.get (gdbarch); | 
|  | if (table != nullptr) | 
|  | return table; | 
|  |  | 
|  | table = new frame_unwind_table; | 
|  |  | 
|  | /* Start the table out with a few default sniffers.  OSABI code | 
|  | can't override this.  */ | 
|  | struct frame_unwind_table_entry **link = &table->list; | 
|  |  | 
|  | struct obstack *obstack = gdbarch_obstack (gdbarch); | 
|  | link = add_unwinder (obstack, &dummy_frame_unwind, link); | 
|  | /* 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.  */ | 
|  | link = add_unwinder (obstack, &dwarf2_tailcall_frame_unwind, link); | 
|  | link = add_unwinder (obstack, &inline_frame_unwind, link); | 
|  |  | 
|  | /* The insertion point for OSABI sniffers.  */ | 
|  | table->osabi_head = link; | 
|  | frame_unwind_data.set (gdbarch, table); | 
|  |  | 
|  | return table; | 
|  | } | 
|  |  | 
|  | void | 
|  | frame_unwind_prepend_unwinder (struct gdbarch *gdbarch, | 
|  | const struct frame_unwind *unwinder) | 
|  | { | 
|  | struct frame_unwind_table *table = get_frame_unwind_table (gdbarch); | 
|  | struct frame_unwind_table_entry *entry; | 
|  |  | 
|  | /* Insert the new entry at the start of the list.  */ | 
|  | entry = GDBARCH_OBSTACK_ZALLOC (gdbarch, struct frame_unwind_table_entry); | 
|  | entry->unwinder = unwinder; | 
|  | entry->next = (*table->osabi_head); | 
|  | (*table->osabi_head) = entry; | 
|  | } | 
|  |  | 
|  | void | 
|  | frame_unwind_append_unwinder (struct gdbarch *gdbarch, | 
|  | const struct frame_unwind *unwinder) | 
|  | { | 
|  | struct frame_unwind_table *table = get_frame_unwind_table (gdbarch); | 
|  | struct frame_unwind_table_entry **ip; | 
|  |  | 
|  | /* Find the end of the list and insert the new entry there.  */ | 
|  | for (ip = table->osabi_head; (*ip) != NULL; ip = &(*ip)->next); | 
|  | (*ip) = GDBARCH_OBSTACK_ZALLOC (gdbarch, struct frame_unwind_table_entry); | 
|  | (*ip)->unwinder = unwinder; | 
|  | } | 
|  |  | 
|  | /* Call SNIFFER from UNWINDER.  If it succeeded set UNWINDER for | 
|  | THIS_FRAME and return 1.  Otherwise the function keeps THIS_FRAME | 
|  | unchanged and returns 0.  */ | 
|  |  | 
|  | static int | 
|  | 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->sniffer (unwinder, 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 0; | 
|  | } | 
|  | throw; | 
|  | } | 
|  |  | 
|  | if (res) | 
|  | { | 
|  | frame_debug_printf ("yes"); | 
|  | return 1; | 
|  | } | 
|  | 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 0; | 
|  | } | 
|  | 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)); | 
|  |  | 
|  | struct gdbarch *gdbarch = get_frame_arch (this_frame); | 
|  | struct frame_unwind_table *table = get_frame_unwind_table (gdbarch); | 
|  | struct frame_unwind_table_entry *entry; | 
|  | const struct frame_unwind *unwinder_from_target; | 
|  |  | 
|  | unwinder_from_target = target_get_unwinder (); | 
|  | if (unwinder_from_target != NULL | 
|  | && frame_unwind_try_unwinder (this_frame, this_cache, | 
|  | unwinder_from_target)) | 
|  | return; | 
|  |  | 
|  | unwinder_from_target = target_get_tailcall_unwinder (); | 
|  | if (unwinder_from_target != NULL | 
|  | && frame_unwind_try_unwinder (this_frame, this_cache, | 
|  | unwinder_from_target)) | 
|  | return; | 
|  |  | 
|  | for (entry = table->list; entry != NULL; entry = entry->next) | 
|  | if (frame_unwind_try_unwinder (this_frame, this_cache, entry->unwinder)) | 
|  | return; | 
|  |  | 
|  | 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, 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); | 
|  | memcpy (reg_val->contents_raw ().data (), buf, | 
|  | register_size (gdbarch, regnum)); | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* Implement "maintenance info frame-unwinders" command.  */ | 
|  |  | 
|  | static void | 
|  | maintenance_info_frame_unwinders (const char *args, int from_tty) | 
|  | { | 
|  | gdbarch *gdbarch = current_inferior ()->arch (); | 
|  | struct frame_unwind_table *table = get_frame_unwind_table (gdbarch); | 
|  |  | 
|  | ui_out *uiout = current_uiout; | 
|  | ui_out_emit_table table_emitter (uiout, 2, -1, "FrameUnwinders"); | 
|  | uiout->table_header (27, ui_left, "name", "Name"); | 
|  | uiout->table_header (25, ui_left, "type", "Type"); | 
|  | uiout->table_body (); | 
|  |  | 
|  | for (struct frame_unwind_table_entry *entry = table->list; entry != NULL; | 
|  | entry = entry->next) | 
|  | { | 
|  | const char *name = entry->unwinder->name; | 
|  | const char *type = frame_type_str (entry->unwinder->type); | 
|  |  | 
|  | ui_out_emit_list tuple_emitter (uiout, nullptr); | 
|  | uiout->field_string ("name", name); | 
|  | uiout->field_string ("type", type); | 
|  | uiout->text ("\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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, " | 
|  | "starting with the highest priority."), | 
|  | &maintenanceinfolist); | 
|  | } |