| /* Inline frame unwinder for GDB. |
| |
| Copyright (C) 2008-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 "breakpoint.h" |
| #include "inline-frame.h" |
| #include "addrmap.h" |
| #include "block.h" |
| #include "frame-unwind.h" |
| #include "inferior.h" |
| #include "gdbthread.h" |
| #include "regcache.h" |
| #include "symtab.h" |
| #include "frame.h" |
| #include "cli/cli-cmds.h" |
| #include "cli/cli-style.h" |
| #include <algorithm> |
| |
| /* We need to save a few variables for every thread stopped at the |
| virtual call site of an inlined function. If there was always a |
| "struct thread_info", we could hang it off that; in the mean time, |
| keep our own list. */ |
| struct inline_state |
| { |
| inline_state (thread_info *thread_, int skipped_frames_, CORE_ADDR saved_pc_, |
| std::vector<const symbol *> &&function_symbols_) |
| : thread (thread_), skipped_frames (skipped_frames_), saved_pc (saved_pc_), |
| function_symbols (std::move (function_symbols_)) |
| {} |
| |
| /* The thread this data relates to. It should be a currently |
| stopped thread. */ |
| thread_info *thread; |
| |
| /* The number of inlined functions we are skipping. Each of these |
| functions can be stepped in to. */ |
| int skipped_frames; |
| |
| /* This is the PC used when calculating FUNCTION_SYMBOLS; used to check |
| whether we have moved to a new location by user request. If so, we |
| invalidate any skipped frames. */ |
| CORE_ADDR saved_pc; |
| |
| /* The list of all inline functions that start at SAVED_PC, except for |
| the last entry which will either be a non-inline function, or an |
| inline function that doesn't start at SAVED_PC. This last entry is |
| the function that "contains" all of the earlier functions. |
| |
| This list can be empty if SAVED_PC is for a code region which is not |
| covered by any function (inline or non-inline). */ |
| std::vector<const symbol *> function_symbols; |
| }; |
| |
| static std::vector<inline_state> inline_states; |
| |
| /* Locate saved inlined frame state for THREAD, if it exists and is |
| valid. */ |
| |
| static struct inline_state * |
| find_inline_frame_state (thread_info *thread) |
| { |
| auto state_it = std::find_if (inline_states.begin (), inline_states.end (), |
| [thread] (const inline_state &state) |
| { |
| return state.thread == thread; |
| }); |
| |
| if (state_it == inline_states.end ()) |
| return nullptr; |
| |
| inline_state &state = *state_it; |
| struct regcache *regcache = get_thread_regcache (thread); |
| CORE_ADDR current_pc = regcache_read_pc (regcache); |
| |
| if (current_pc != state.saved_pc) |
| { |
| /* PC has changed - this context is invalid. Use the |
| default behavior. */ |
| |
| unordered_remove (inline_states, state_it); |
| return nullptr; |
| } |
| |
| return &state; |
| } |
| |
| /* See inline-frame.h. */ |
| |
| void |
| clear_inline_frame_state (process_stratum_target *target, ptid_t filter_ptid) |
| { |
| gdb_assert (target != NULL); |
| |
| if (filter_ptid == minus_one_ptid || filter_ptid.is_pid ()) |
| { |
| auto matcher = [target, &filter_ptid] (const inline_state &state) |
| { |
| thread_info *t = state.thread; |
| return (t->inf->process_target () == target |
| && t->ptid.matches (filter_ptid)); |
| }; |
| |
| auto it = std::remove_if (inline_states.begin (), inline_states.end (), |
| matcher); |
| |
| inline_states.erase (it, inline_states.end ()); |
| |
| return; |
| } |
| |
| |
| auto matcher = [target, &filter_ptid] (const inline_state &state) |
| { |
| thread_info *t = state.thread; |
| return (t->inf->process_target () == target |
| && filter_ptid == t->ptid); |
| }; |
| |
| auto it = std::find_if (inline_states.begin (), inline_states.end (), |
| matcher); |
| |
| if (it != inline_states.end ()) |
| unordered_remove (inline_states, it); |
| } |
| |
| /* See inline-frame.h. */ |
| |
| void |
| clear_inline_frame_state (thread_info *thread) |
| { |
| auto it = std::find_if (inline_states.begin (), inline_states.end (), |
| [thread] (const inline_state &state) |
| { |
| return thread == state.thread; |
| }); |
| |
| if (it != inline_states.end ()) |
| unordered_remove (inline_states, it); |
| } |
| |
| static void |
| inline_frame_this_id (const frame_info_ptr &this_frame, |
| void **this_cache, |
| struct frame_id *this_id) |
| { |
| struct symbol *func; |
| |
| /* In order to have a stable frame ID for a given inline function, |
| we must get the stack / special addresses from the underlying |
| real frame's this_id method. So we must call |
| get_prev_frame_always. Because we are inlined into some |
| function, there must be previous frames, so this is safe - as |
| long as we're careful not to create any cycles. See related |
| comments in get_prev_frame_always_1. */ |
| frame_info_ptr prev_frame = get_prev_frame_always (this_frame); |
| if (prev_frame == nullptr) |
| error (_("failed to find previous frame when computing inline frame id")); |
| *this_id = get_frame_id (prev_frame); |
| |
| /* We need a valid frame ID, so we need to be based on a valid |
| frame. FSF submission NOTE: this would be a good assertion to |
| apply to all frames, all the time. That would fix the ambiguity |
| of null_frame_id (between "no/any frame" and "the outermost |
| frame"). This will take work. */ |
| gdb_assert (frame_id_p (*this_id)); |
| |
| /* Future work NOTE: Alexandre Oliva applied a patch to GCC 4.3 |
| which generates DW_AT_entry_pc for inlined functions when |
| possible. If this attribute is available, we should use it |
| in the frame ID (and eventually, to set breakpoints). */ |
| func = get_frame_function (this_frame); |
| gdb_assert (func != NULL); |
| (*this_id).code_addr = func->value_block ()->entry_pc (); |
| (*this_id).artificial_depth++; |
| } |
| |
| static struct value * |
| inline_frame_prev_register (const frame_info_ptr &this_frame, void **this_cache, |
| int regnum) |
| { |
| /* Use get_frame_register_value instead of |
| frame_unwind_got_register, to avoid requiring this frame's ID. |
| This frame's ID depends on the previous frame's ID (unusual), and |
| the previous frame's ID depends on this frame's unwound |
| registers. If unwinding registers from this frame called |
| get_frame_id, there would be a loop. |
| |
| Do not copy this code into any other unwinder! Inlined functions |
| are special; other unwinders must not have a dependency on the |
| previous frame's ID, and therefore can and should use |
| frame_unwind_got_register instead. */ |
| return get_frame_register_value (this_frame, regnum); |
| } |
| |
| /* Check whether we are at an inlining site that does not already |
| have an associated frame. */ |
| |
| static int |
| inline_frame_sniffer (const struct frame_unwind *self, |
| const frame_info_ptr &this_frame, |
| void **this_cache) |
| { |
| CORE_ADDR this_pc; |
| const struct block *frame_block, *cur_block; |
| int depth; |
| frame_info_ptr next_frame; |
| struct inline_state *state = find_inline_frame_state (inferior_thread ()); |
| |
| this_pc = get_frame_address_in_block (this_frame); |
| frame_block = block_for_pc (this_pc); |
| if (frame_block == NULL) |
| return 0; |
| |
| /* Calculate DEPTH, the number of inlined functions at this |
| location. */ |
| depth = 0; |
| cur_block = frame_block; |
| while (cur_block->superblock ()) |
| { |
| if (cur_block->inlined_p ()) |
| depth++; |
| else if (cur_block->function () != NULL) |
| break; |
| |
| cur_block = cur_block->superblock (); |
| } |
| |
| /* Check how many inlined functions already have frames. */ |
| for (next_frame = get_next_frame (this_frame); |
| next_frame && get_frame_type (next_frame) == INLINE_FRAME; |
| next_frame = get_next_frame (next_frame)) |
| { |
| gdb_assert (depth > 0); |
| depth--; |
| } |
| |
| /* If this is the topmost frame, or all frames above us are inlined, |
| then check whether we were requested to skip some frames (so they |
| can be stepped into later). */ |
| if (state != NULL && state->skipped_frames > 0 && next_frame == NULL) |
| { |
| gdb_assert (depth >= state->skipped_frames); |
| depth -= state->skipped_frames; |
| } |
| |
| /* If all the inlined functions here already have frames, then pass |
| to the normal unwinder for this PC. */ |
| if (depth == 0) |
| return 0; |
| |
| /* If the next frame is an inlined function, but not the outermost, then |
| we are the next outer. If it is not an inlined function, then we |
| are the innermost inlined function of a different real frame. */ |
| return 1; |
| } |
| |
| const struct frame_unwind inline_frame_unwind = { |
| "inline", |
| INLINE_FRAME, |
| default_frame_unwind_stop_reason, |
| inline_frame_this_id, |
| inline_frame_prev_register, |
| NULL, |
| inline_frame_sniffer |
| }; |
| |
| /* Return non-zero if BLOCK, an inlined function block containing PC, |
| has a group of contiguous instructions starting at PC (but not |
| before it). */ |
| |
| static int |
| block_starting_point_at (CORE_ADDR pc, const struct block *block) |
| { |
| const struct blockvector *bv; |
| const struct block *new_block; |
| |
| bv = blockvector_for_pc (pc, NULL); |
| if (bv->map () == nullptr) |
| return 0; |
| |
| new_block = (const struct block *) bv->map ()->find (pc - 1); |
| if (new_block == NULL) |
| return 1; |
| |
| if (new_block == block || block->contains (new_block)) |
| return 0; |
| |
| /* The immediately preceding address belongs to a different block, |
| which is not a child of this one. Treat this as an entrance into |
| BLOCK. */ |
| return 1; |
| } |
| |
| /* Loop over the stop chain and determine if execution stopped in an |
| inlined frame because of a breakpoint with a user-specified location |
| set at FRAME_SYMBOL. */ |
| |
| static bool |
| stopped_by_user_bp_inline_frame (const symbol *frame_symbol, |
| bpstat *stop_chain) |
| { |
| for (bpstat *s = stop_chain; s != nullptr; s = s->next) |
| { |
| struct breakpoint *bpt = s->breakpoint_at; |
| |
| if (bpt != NULL |
| && (user_breakpoint_p (bpt) || bpt->type == bp_until)) |
| { |
| bp_location *loc = s->bp_location_at.get (); |
| enum bp_loc_type t = loc->loc_type; |
| |
| if (t == bp_loc_software_breakpoint |
| || t == bp_loc_hardware_breakpoint) |
| { |
| /* If the location has a function symbol, check whether |
| the frame was for that inlined function. If it has |
| no function symbol, then assume it is. I.e., default |
| to presenting the stop at the innermost inline |
| function. */ |
| if (loc->symbol == nullptr |
| || frame_symbol == loc->symbol) |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /* Return a list of all the inline function symbols that start at THIS_PC |
| and the symbol for the function which contains all of the inline |
| functions. |
| |
| The function symbols are ordered such that the most inner function is |
| first. |
| |
| The returned list can be empty if there are no function at THIS_PC. Or |
| the returned list may have only a single entry if there are no inline |
| functions starting at THIS_PC. */ |
| |
| static std::vector<const symbol *> |
| gather_inline_frames (CORE_ADDR this_pc) |
| { |
| /* Build the list of inline frames starting at THIS_PC. After the loop, |
| CUR_BLOCK is expected to point at the first function symbol (inlined or |
| not) "containing" the inline frames starting at THIS_PC. */ |
| const block *cur_block = block_for_pc (this_pc); |
| if (cur_block == nullptr) |
| return {}; |
| |
| std::vector<const symbol *> function_symbols; |
| while (cur_block != nullptr) |
| { |
| if (cur_block->inlined_p ()) |
| { |
| gdb_assert (cur_block->function () != nullptr); |
| |
| /* See comments in inline_frame_this_id about this use |
| of BLOCK_ENTRY_PC. */ |
| if (cur_block->entry_pc () == this_pc |
| || block_starting_point_at (this_pc, cur_block)) |
| function_symbols.push_back (cur_block->function ()); |
| else |
| break; |
| } |
| else if (cur_block->function () != nullptr) |
| break; |
| |
| cur_block = cur_block->superblock (); |
| } |
| |
| /* If we have a code region for which we have no function blocks, |
| possibly due to bad debug, or possibly just when some debug |
| information has been stripped, then we can end up in a situation where |
| there are global and static blocks for an address, but no function |
| blocks. In this case the early return above will not trigger as we |
| will find the static block for THIS_PC, but in the loop above we will |
| fail to find any function blocks (inline or non-inline) and so |
| CUR_BLOCK will eventually become NULL. If this happens then |
| FUNCTION_SYMBOLS must be empty (as we found no function blocks). |
| |
| Otherwise, if we did find a function block, then we should only leave |
| the above loop when CUR_BLOCK is pointing to a non-inline function |
| that possibly contains some inline functions, or CUR_BLOCK should |
| point to an inline function that doesn't start at THIS_PC. */ |
| if (cur_block != nullptr) |
| { |
| gdb_assert (cur_block->function () != nullptr); |
| function_symbols.push_back (cur_block->function ()); |
| } |
| else |
| gdb_assert (function_symbols.empty ()); |
| |
| return function_symbols; |
| } |
| |
| /* See inline-frame.h. */ |
| |
| void |
| skip_inline_frames (thread_info *thread, bpstat *stop_chain) |
| { |
| gdb_assert (find_inline_frame_state (thread) == nullptr); |
| |
| CORE_ADDR this_pc = get_frame_pc (get_current_frame ()); |
| |
| std::vector<const symbol *> function_symbols |
| = gather_inline_frames (this_pc); |
| |
| /* Figure out how many of the inlined frames to skip. Do not skip an |
| inlined frame (and its callers) if execution stopped because of a user |
| breakpoint for this specific function. |
| |
| By default, skip all the found inlined frames. |
| |
| The last entry in FUNCTION_SYMBOLS is special, this is the function |
| which contains all of the inlined functions, we never skip this. */ |
| int skipped_frames = 0; |
| |
| for (const auto sym : function_symbols) |
| { |
| if (stopped_by_user_bp_inline_frame (sym, stop_chain) |
| || sym == function_symbols.back ()) |
| break; |
| |
| ++skipped_frames; |
| } |
| |
| if (skipped_frames > 0) |
| reinit_frame_cache (); |
| |
| inline_states.emplace_back (thread, skipped_frames, this_pc, |
| std::move (function_symbols)); |
| } |
| |
| /* Step into an inlined function by unhiding it. */ |
| |
| void |
| step_into_inline_frame (thread_info *thread) |
| { |
| inline_state *state = find_inline_frame_state (thread); |
| |
| gdb_assert (state != NULL && state->skipped_frames > 0); |
| state->skipped_frames--; |
| reinit_frame_cache (); |
| } |
| |
| /* Return the number of hidden functions inlined into the current |
| frame. */ |
| |
| int |
| inline_skipped_frames (thread_info *thread) |
| { |
| inline_state *state = find_inline_frame_state (thread); |
| |
| if (state == NULL) |
| return 0; |
| else |
| return state->skipped_frames; |
| } |
| |
| /* If one or more inlined functions are hidden, return the symbol for |
| the function inlined into the current frame. */ |
| |
| const symbol * |
| inline_skipped_symbol (thread_info *thread) |
| { |
| inline_state *state = find_inline_frame_state (thread); |
| gdb_assert (state != NULL); |
| |
| /* This should only be called when we are skipping at least one frame, |
| hence FUNCTION_SYMBOLS will contain more than one entry (the last |
| entry is the "outer" containing function). |
| |
| As we initialise SKIPPED_FRAMES at the same time as we build |
| FUNCTION_SYMBOLS it should be true that SKIPPED_FRAMES never indexes |
| outside of the FUNCTION_SYMBOLS vector. */ |
| gdb_assert (state->function_symbols.size () > 1); |
| gdb_assert (state->skipped_frames > 0); |
| gdb_assert (state->skipped_frames < state->function_symbols.size ()); |
| return state->function_symbols[state->skipped_frames - 1]; |
| } |
| |
| /* Return the number of functions inlined into THIS_FRAME. Some of |
| the callees may not have associated frames (see |
| skip_inline_frames). */ |
| |
| int |
| frame_inlined_callees (const frame_info_ptr &this_frame) |
| { |
| frame_info_ptr next_frame; |
| int inline_count = 0; |
| |
| /* First count how many inlined functions at this PC have frames |
| above FRAME (are inlined into FRAME). */ |
| for (next_frame = get_next_frame (this_frame); |
| next_frame && get_frame_type (next_frame) == INLINE_FRAME; |
| next_frame = get_next_frame (next_frame)) |
| inline_count++; |
| |
| /* Simulate some most-inner inlined frames which were suppressed, so |
| they can be stepped into later. If we are unwinding already |
| outer frames from some non-inlined frame this does not apply. */ |
| if (next_frame == NULL) |
| inline_count += inline_skipped_frames (inferior_thread ()); |
| |
| return inline_count; |
| } |
| |
| /* The 'maint info inline-frames' command. Takes an optional address |
| expression and displays inline frames that start at the given address, |
| or at the address of the current thread if no address is given. */ |
| |
| static void |
| maintenance_info_inline_frames (const char *arg, int from_tty) |
| { |
| std::optional<std::vector<const symbol *>> local_function_symbols; |
| std::vector<const symbol *> *function_symbols; |
| int skipped_frames; |
| CORE_ADDR addr; |
| |
| if (arg == nullptr) |
| { |
| /* With no argument then the user wants to know about the current |
| inline frame information. This information is cached per-thread |
| and can be updated as the user steps between inline functions at |
| the current address. */ |
| |
| if (inferior_ptid == null_ptid) |
| error (_("no inferior thread")); |
| |
| thread_info *thread = inferior_thread (); |
| auto it = std::find_if (inline_states.begin (), inline_states.end (), |
| [thread] (const inline_state &istate) |
| { |
| return thread == istate.thread; |
| }); |
| |
| /* Stopped threads always have cached inline_state information. */ |
| gdb_assert (it != inline_states.end ()); |
| |
| gdb_printf (_("Cached inline state information for thread %s.\n"), |
| print_thread_id (thread)); |
| |
| function_symbols = &it->function_symbols; |
| skipped_frames = it->skipped_frames; |
| addr = it->saved_pc; |
| } |
| else |
| { |
| /* If there is an argument then parse it as an address, the user is |
| asking about inline functions that start at the given address. */ |
| |
| addr = parse_and_eval_address (arg); |
| local_function_symbols.emplace (gather_inline_frames (addr)); |
| |
| function_symbols = &(local_function_symbols.value ()); |
| skipped_frames = function_symbols->size () - 1; |
| } |
| |
| /* The address we're analysing. */ |
| gdb_printf (_("program counter = %ps\n"), |
| styled_string (address_style.style (), |
| core_addr_to_string_nz (addr))); |
| |
| gdb_printf (_("skipped frames = %d\n"), skipped_frames); |
| |
| /* Print the full list of function symbols in STATE. Highlight the |
| current function as indicated by the skipped frames counter. */ |
| for (size_t i = 0; i < function_symbols->size (); ++i) |
| gdb_printf (_("%c %ps\n"), |
| (i == skipped_frames ? '>' : ' '), |
| styled_string (function_name_style.style (), |
| (*function_symbols)[i]->print_name ())); |
| } |
| |
| |
| |
| void _initialize_inline_frame (); |
| void |
| _initialize_inline_frame () |
| { |
| add_cmd ("inline-frames", class_maintenance, maintenance_info_inline_frames, |
| _("\ |
| Display inline frame information for current thread.\n\ |
| \n\ |
| Usage:\n\ |
| \n\ |
| maintenance info inline-frames [ADDRESS]\n\ |
| \n\ |
| With no ADDRESS show all inline frames starting at the current program\n\ |
| counter address. When ADDRESS is given, list all inline frames starting\n\ |
| at ADDRESS.\n\ |
| \n\ |
| The last frame listed might not start at ADDRESS, this is the frame that\n\ |
| contains the other inline frames."), |
| &maintenanceinfolist); |
| } |