summaryrefslogtreecommitdiff
path: root/gdb/inline-frame.c
diff options
context:
space:
mode:
authorDaniel Jacobowitz <dan@debian.org>2009-06-28 00:20:21 +0000
committerDaniel Jacobowitz <dan@debian.org>2009-06-28 00:20:21 +0000
commit089afc895b5bcfd814658c61a910ea386120c570 (patch)
tree3ff2e79f0f5a341f7086be856691dd066a2309f5 /gdb/inline-frame.c
parent78a5fe1b7d107c963fae6dc5beaf01a83c1d0cac (diff)
downloadgdb-089afc895b5bcfd814658c61a910ea386120c570.tar.gz
gdb/
* NEWS: Document inlined function support. * Makefile.in (SFILES): Add inline-frame.c. (COMMON_OBS): Add inline-frame.o. * block.c (contained_in): Rewrite to use lexical nesting. (block_linkage_function): Skip inlined function blocks. (block_inlined_p): New. * block.h (struct block): Update comment. (block_inlined_p): New prototype. * blockframe.c (get_frame_block): Handle inlined functions. (get_frame_function): Do not use block_linkage_function. (block_innermost_frame): Use get_frame_block and contained_in. * breakpoint.c (watchpoint_check): Remove extra reinit_frame_cache. Skip over inlined functions. Simplify epilogue check. (bpstat_check_breakpoint_conditions): Use get_stack_frame_id. Update comments. (set_momentary_breakpoint): Only accept non-inlined frames. (watch_command_1): Use frame_unwind_caller_pc and frame_unwind_caller_id instead of get_prev_frame. (until_break_command): Likewise. Use get_stack_frame_id. * buildsym.c (end_symtab): Set SYMBOL_SYMTAB for block functions. * dwarf2loc.c (dwarf_expr_frame_base): Use block_linkage_function. * dwarf2read.c (process_die): Handle DW_TAG_inlined_subroutine. (read_func_scope, new_symbol): Likewise. Handle arguments specially for inlined functions without call site information. (inherit_abstract_dies): Allow tag mismatch for inlined subroutines. (die_specification): Treat DW_AT_abstract_origin as a specification. (read_type_die): Handle DW_TAG_inlined_subroutine. * frame-unwind.c (frame_unwind_init): Add inline_frame_unwind. * frame.c (fprint_frame_id): Print inline depth. (fprint_frame_type): Handle INLINE_FRAME and SENTINEL_FRAME. (skip_inlined_frames, get_stack_frame_id): New. (frame_unwind_caller_id): Use skip_inlined_frames. (frame_id_inlined_p): New. (frame_id_eq): Make the logic match the comments. Add inline_depth check. (frame_id_inner): Handle inlined functions. (frame_unwind_pc): New function, copied from frame_unwind_caller_pc. (frame_unwind_caller_pc): Use skip_inlined_frames and frame_unwind_pc. (get_prev_frame_1): Check for inline frames. Split out frame allocation to get_prev_frame_raw. (get_prev_frame_raw): New function. (get_prev_frame): Handle inline frames. (get_frame_pc): Use frame_unwind_pc. (get_frame_address_in_block): Skip inlined frames on both sides. (pc_notcurrent): Delete. (find_frame_sal): Rewrite to handle inline call sites. Use get_frame_address_in_block. (deprecated_update_frame_pc_hack): Make static. * frame.h: Update comments. (struct frame_id): Add inline_depth. (enum frame_type): Add INLINE_FRAME. (frame_id_inlined_p, get_stack_frame_id): New prototypes. * gdbthread.h (struct thread_info): Add step_stack_frame_id field. * infcmd.c (set_step_frame): New function. (step_once): Use set_step_frame. Handle inlined functions. (until_next_command): Use set_step_frame. (finish_backward), finish_forward): Use get_stack_frame_id. (finish_command): Support inlined functions. * inferior.h (set_step_info): New prototype. * infrun.c (RESUME_ALL): Use minus_one_ptid. (clear_proceed_status): Clear step_stack_frame_id. (init_wait_for_inferior): Call clear_inline_frame_state. (init_execution_control_state): Make static. (set_step_info): New function. (init_thread_stepping_state): Do not set the symtab or line here. (stepped_in_from): New function. (handle_inferior_event): Handle inlined functions. Use set_step_info. (insert_step_resume_breakpoint_at_frame): Use get_stack_frame_id. (struct inferior_status): Add step_stack_frame_id. (save_inferior_status, restore_inferior_status): Save and restore step_stack_frame_id. * inline-frame.c, inline-frame.h: New files. * minsyms.c (prim_record_minimal_symbol_and_info): Use XCALLOC. * regcache.c (regcache_write_pc): Call reinit_frame_cache. * s390-tdep.c (s390_prologue_frame_unwind_cache): Handle INLINE_FRAME. * stack.c (frame_show_address): New. (print_frame_info, print_frame): Use it. (find_frame_funname): Use get_frame_function. Handle inlined blocks. (frame_info): Mark inlined functions. (backtrace_command_1): Use get_current_user_frame. (print_frame_local_vars, print_frame_label_vars): Update comments. (return_command): Refuse inlined functions. * symtab.c (lookup_symbol_aux_local): Stop at inlined function boundaries. (find_function_start_sal): Avoid inlined functions. (completion_list_add_fields): New function. (default_make_symbol_completion_list): Use it. Use block_static_block and block_global_block. Check for inlined functions. (skip_prologue_using_sal): Avoid line number comparison across inlining. * symtab.h (struct symbol): Add is_inlined. (SYMBOL_INLINED): New. * target.c (target_resume): Call clear_inline_frame_state. * valops.c (value_of_variable): Check block_inlined_p. gdb/doc/ * gdb.texinfo (Debugging Optimized Code): New chapter. (Compiling for Debugging): Reference it. Move some text to the new section. gdb/testsuite/ * gdb.base/break.exp: Add an XFAIL for gcc/36748. * gdb.cp/annota2.exp: Accept frames-invalid in more places. * gdb.opt/Makefile.in (EXECUTABLES): Update. * gdb.opt/clobbered-registers-O2.exp: Update to GPL v3. * gdb.opt/inline-bt.c, gdb.opt/inline-bt.exp, gdb.opt/inline-cmds.c, gdb.opt/inline-cmds.exp, gdb.opt/inline-locals.c, gdb.opt/inline-locals.exp, gdb.opt/inline-markers.c: New files. * lib/gdb.exp (skip_inline_frame_tests): New function. (skip_inline_var_tests): New function.
Diffstat (limited to 'gdb/inline-frame.c')
-rw-r--r--gdb/inline-frame.c394
1 files changed, 394 insertions, 0 deletions
diff --git a/gdb/inline-frame.c b/gdb/inline-frame.c
new file mode 100644
index 00000000000..731d3adcaaf
--- /dev/null
+++ b/gdb/inline-frame.c
@@ -0,0 +1,394 @@
+/* Inline frame unwinder for GDB.
+
+ Copyright (C) 2008 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 "defs.h"
+#include "addrmap.h"
+#include "block.h"
+#include "frame-unwind.h"
+#include "inferior.h"
+#include "symtab.h"
+#include "vec.h"
+
+#include "gdb_assert.h"
+
+/* 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
+{
+ /* The thread this data relates to. It should be a currently
+ stopped thread; we assume thread IDs never change while the
+ thread is stopped. */
+ ptid_t ptid;
+
+ /* The number of inlined functions we are skipping. Each of these
+ functions can be stepped in to. */
+ int skipped_frames;
+
+ /* Only valid if SKIPPED_FRAMES is non-zero. This is the PC used
+ when calculating SKIPPED_FRAMES; 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;
+
+ /* Only valid if SKIPPED_FRAMES is non-zero. This is the symbol
+ of the outermost skipped inline function. It's used to find the
+ call site of the current frame. */
+ struct symbol *skipped_symbol;
+};
+
+typedef struct inline_state inline_state_s;
+DEF_VEC_O(inline_state_s);
+
+static VEC(inline_state_s) *inline_states;
+
+/* Locate saved inlined frame state for PTID, if it exists. */
+
+static struct inline_state *
+find_inline_frame_state (ptid_t ptid)
+{
+ struct inline_state *state;
+ int ix;
+
+ for (ix = 0; VEC_iterate (inline_state_s, inline_states, ix, state); ix++)
+ {
+ if (ptid_equal (state->ptid, ptid))
+ return state;
+ }
+
+ return NULL;
+}
+
+/* Allocate saved inlined frame state for PTID. */
+
+static struct inline_state *
+allocate_inline_frame_state (ptid_t ptid)
+{
+ struct inline_state *state;
+
+ state = VEC_safe_push (inline_state_s, inline_states, NULL);
+ memset (state, 0, sizeof (*state));
+ state->ptid = ptid;
+
+ return state;
+}
+
+/* Forget about any hidden inlined functions in PTID, which is new or
+ about to be resumed. PTID may be minus_one_ptid (all processes)
+ or a PID (all threads in this process). */
+
+void
+clear_inline_frame_state (ptid_t ptid)
+{
+ struct inline_state *state;
+ int ix;
+
+ if (ptid_equal (ptid, minus_one_ptid))
+ {
+ VEC_free (inline_state_s, inline_states);
+ return;
+ }
+
+ if (ptid_is_pid (ptid))
+ {
+ VEC (inline_state_s) *new_states = NULL;
+ int pid = ptid_get_pid (ptid);
+ for (ix = 0; VEC_iterate (inline_state_s, inline_states, ix, state); ix++)
+ if (pid != ptid_get_pid (state->ptid))
+ VEC_safe_push (inline_state_s, new_states, state);
+ VEC_free (inline_state_s, inline_states);
+ inline_states = new_states;
+ return;
+ }
+
+ for (ix = 0; VEC_iterate (inline_state_s, inline_states, ix, state); ix++)
+ if (ptid_equal (state->ptid, ptid))
+ {
+ VEC_unordered_remove (inline_state_s, inline_states, ix);
+ return;
+ }
+}
+
+static void
+inline_frame_this_id (struct frame_info *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.
+ 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. */
+ *this_id = get_frame_id (get_prev_frame (this_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 = BLOCK_START (SYMBOL_BLOCK_VALUE (func));
+ (*this_id).inline_depth++;
+}
+
+static struct value *
+inline_frame_prev_register (struct frame_info *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,
+ struct frame_info *this_frame,
+ void **this_cache)
+{
+ CORE_ADDR this_pc;
+ struct block *frame_block, *cur_block;
+ int depth;
+ struct frame_info *next_frame;
+ struct inline_state *state = find_inline_frame_state (inferior_ptid);
+
+ 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 (BLOCK_SUPERBLOCK (cur_block))
+ {
+ if (block_inlined_p (cur_block))
+ depth++;
+
+ cur_block = BLOCK_SUPERBLOCK (cur_block);
+ }
+
+ /* 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)
+ {
+ if (this_pc != state->saved_pc)
+ state->skipped_frames = 0;
+ else
+ {
+ 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_unwinder = {
+ INLINE_FRAME,
+ inline_frame_this_id,
+ inline_frame_prev_register,
+ NULL,
+ inline_frame_sniffer
+};
+
+const struct frame_unwind *const inline_frame_unwind = &inline_frame_unwinder;
+
+/* 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, struct block *block)
+{
+ struct blockvector *bv;
+ struct block *new_block;
+
+ bv = blockvector_for_pc (pc, NULL);
+ if (BLOCKVECTOR_MAP (bv) == NULL)
+ return 0;
+
+ new_block = addrmap_find (BLOCKVECTOR_MAP (bv), pc - 1);
+ if (new_block == NULL)
+ return 1;
+
+ if (new_block == block || contained_in (new_block, block))
+ return 0;
+
+ /* The immediately preceeding address belongs to a different block,
+ which is not a child of this one. Treat this as an entrance into
+ BLOCK. */
+ return 1;
+}
+
+/* Skip all inlined functions whose call sites are at the current PC.
+ Frames for the hidden functions will not appear in the backtrace until the
+ user steps into them. */
+
+void
+skip_inline_frames (ptid_t ptid)
+{
+ CORE_ADDR this_pc;
+ struct block *frame_block, *cur_block;
+ struct symbol *last_sym = NULL;
+ int skip_count = 0;
+ struct inline_state *state;
+
+ /* This function is called right after reinitializing the frame
+ cache. We try not to do more unwinding than absolutely
+ necessary, for performance. */
+ this_pc = get_frame_pc (get_current_frame ());
+ frame_block = block_for_pc (this_pc);
+
+ if (frame_block != NULL)
+ {
+ cur_block = frame_block;
+ while (BLOCK_SUPERBLOCK (cur_block))
+ {
+ if (block_inlined_p (cur_block))
+ {
+ /* See comments in inline_frame_this_id about this use
+ of BLOCK_START. */
+ if (BLOCK_START (cur_block) == this_pc
+ || block_starting_point_at (this_pc, cur_block))
+ {
+ skip_count++;
+ last_sym = BLOCK_FUNCTION (cur_block);
+ }
+ else
+ break;
+ }
+ cur_block = BLOCK_SUPERBLOCK (cur_block);
+ }
+ }
+
+ gdb_assert (find_inline_frame_state (ptid) == NULL);
+ state = allocate_inline_frame_state (ptid);
+ state->skipped_frames = skip_count;
+ state->saved_pc = this_pc;
+ state->skipped_symbol = last_sym;
+
+ if (skip_count != 0)
+ reinit_frame_cache ();
+}
+
+/* Step into an inlined function by unhiding it. */
+
+void
+step_into_inline_frame (ptid_t ptid)
+{
+ struct inline_state *state = find_inline_frame_state (ptid);
+
+ 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 (ptid_t ptid)
+{
+ struct inline_state *state = find_inline_frame_state (ptid);
+
+ 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. */
+
+struct symbol *
+inline_skipped_symbol (ptid_t ptid)
+{
+ struct inline_state *state = find_inline_frame_state (ptid);
+
+ gdb_assert (state != NULL);
+ return state->skipped_symbol;
+}
+
+/* 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 (struct frame_info *this_frame)
+{
+ struct frame_info *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_ptid);
+
+ return inline_count;
+}