gdb: Fix bps on inline callsites stopping in wrong frame
Checks
Context |
Check |
Description |
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_gdb_build--master-arm |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_gdb_check--master-arm |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 |
success
|
Testing passed
|
Commit Message
Breakpoints on inline callsite lines were sometimes stopping in the
wrong frame. This would happen when the callsite line contains no
instructions. In this case GCC (but not Clang) will emit a line for the
callsite which means that the user can set a breakpoint there. However,
as the callsite contains no instructions it will be at the start of the
inline function, and thus when getting the block for this PC GDB will
get a block inside the inline function rather than in the caller
function. This caused the breakpoint's symbol to be set to that of the
innermost function. Instead the breakpoint's symbol should be set to
the caller. This will then cause skip_inline_frames to correctly hide
the inline frame leaving us at the callsite when we hit the breakpoint.
This is achieved by modifying create_sals_line_offset. This processes
linespecs created from user input into sals which are then used to
create breakpoints. For every sal this function finds we check if it
matches an inline callsite line at its PC. If it does we modify its
block to the superblock of the called function's block. This results in
the sal using the function symbol of the callsite rather than that of
the innermost function.
Note that GDB will currently only keep an empty callsite line if it is
in the same source file as the inline function. When this is the case
then the user can create a breakpoint at the callsite line, otherwise an
attempt to create a breakpoint there will cause gdb to create a
breakpoint at a different line.
---
gdb/blockframe.c | 26 +++
gdb/inline-frame.c | 14 +-
gdb/inline-frame.h | 3 +
gdb/linespec.c | 19 ++-
gdb/symtab.h | 8 +
.../gdb.dwarf2/dw2-inline-callsite-break.c | 43 +++++
.../gdb.dwarf2/dw2-inline-callsite-break.exp | 151 ++++++++++++++++++
7 files changed, 257 insertions(+), 7 deletions(-)
create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-inline-callsite-break.c
create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-inline-callsite-break.exp
Comments
On 25/01/2024 15:53, Toby Lloyd Davies wrote:
> Breakpoints on inline callsite lines were sometimes stopping in the
> wrong frame. This would happen when the callsite line contains no
> instructions. In this case GCC (but not Clang) will emit a line for the
> callsite which means that the user can set a breakpoint there. However,
> as the callsite contains no instructions it will be at the start of the
> inline function, and thus when getting the block for this PC GDB will
> get a block inside the inline function rather than in the caller
> function. This caused the breakpoint's symbol to be set to that of the
> innermost function. Instead the breakpoint's symbol should be set to
> the caller. This will then cause skip_inline_frames to correctly hide
> the inline frame leaving us at the callsite when we hit the breakpoint.
>
> This is achieved by modifying create_sals_line_offset. This processes
> linespecs created from user input into sals which are then used to
> create breakpoints. For every sal this function finds we check if it
> matches an inline callsite line at its PC. If it does we modify its
> block to the superblock of the called function's block. This results in
> the sal using the function symbol of the callsite rather than that of
> the innermost function.
>
> Note that GDB will currently only keep an empty callsite line if it is
> in the same source file as the inline function. When this is the case
> then the user can create a breakpoint at the callsite line, otherwise an
> attempt to create a breakpoint there will cause gdb to create a
> breakpoint at a different line.
Hi!
I don't understand much of the code in this patch, but it does fix the
issue you mentioned.
Tested-By: Guinevere Larsen <blarsen@redhat.com>
@@ -477,3 +477,29 @@ block_innermost_frame (const struct block *block)
return NULL;
}
+
+const struct block *
+find_sal_inline_callsite_block (const struct block *bl,
+ const struct symtab_and_line *sal)
+{
+ const block *cur_block = bl;
+ while (cur_block->superblock () != nullptr)
+ {
+ if (cur_block-> inlined_p ())
+ {
+ if (inline_block_entry_point_at_pc (bl, sal->pc))
+ {
+ if (sal->line == cur_block->function ()->line ()
+ && sal->symtab == cur_block->function ()->symtab ())
+ return cur_block;
+ }
+ else
+ break;
+ }
+ else if (cur_block->function () != nullptr)
+ break;
+
+ cur_block = cur_block->superblock ();
+ }
+ return nullptr;
+}
@@ -339,6 +339,15 @@ stopped_by_user_bp_inline_frame (const block *frame_block, bpstat *stop_chain)
/* See inline-frame.h. */
+bool
+inline_block_entry_point_at_pc (const struct block *bl, CORE_ADDR pc)
+{
+ /* See comments in inline_frame_this_id about this use of BLOCK_ENTRY_PC. */
+ return bl->entry_pc () == pc || block_starting_point_at (pc, bl);
+}
+
+/* See inline-frame.h. */
+
void
skip_inline_frames (thread_info *thread, bpstat *stop_chain)
{
@@ -359,10 +368,7 @@ skip_inline_frames (thread_info *thread, bpstat *stop_chain)
{
if (cur_block->inlined_p ())
{
- /* 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))
+ if (inline_block_entry_point_at_pc (cur_block, this_pc))
{
/* Do not skip the inlined frame if execution
stopped in an inlined frame because of a user
@@ -70,4 +70,7 @@ struct symbol *inline_skipped_symbol (thread_info *thread);
int frame_inlined_callees (frame_info_ptr this_frame);
+/* Return true if PC is at an entry point to inline block BL. */
+bool inline_block_entry_point_at_pc (const struct block *bl, CORE_ADDR pc);
+
#endif /* !defined (INLINE_FRAME_H) */
@@ -2129,9 +2129,22 @@ create_sals_line_offset (struct linespec_state *self,
for (i = 0; i < intermediate_results.size (); ++i)
if (filter[i])
{
- struct symbol *sym = (blocks[i]
- ? blocks[i]->containing_function ()
- : NULL);
+ if (blocks[i] != nullptr)
+ {
+ /* Check if this line matches any inline callsite at this pc. */
+ const block *inline_block
+ = find_sal_inline_callsite_block (blocks[i],
+ &intermediate_results[i]);
+ if (inline_block != nullptr)
+ {
+ /* Use the superblock of the inline block.
+ block_containing_function will then give us the calling
+ function. */
+ blocks[i] = inline_block->superblock ();
+ }
+ }
+ struct symbol *sym
+ = (blocks[i] ? blocks[i]->containing_function () : nullptr);
if (self->funfirstline)
skip_prologue_sal (&intermediate_results[i]);
@@ -2261,6 +2261,14 @@ extern bool find_function_entry_range_from_pc (CORE_ADDR pc,
CORE_ADDR *address,
CORE_ADDR *endaddr);
+/* Find if SAL is an inline callsite for block BL or any of its superblocks.
+ If it is return the inline block it is an inline callsite for. Otherwise
+ return nullptr. */
+
+extern const struct block *
+find_sal_inline_callsite_block (const struct block *bl,
+ const struct symtab_and_line *sal);
+
/* Return the type of a function with its first instruction exactly at
the PC address. Return NULL otherwise. */
new file mode 100644
@@ -0,0 +1,43 @@
+/* Copyright 2019-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/>. */
+
+/* This test relies on both foo and bar being inlined into main. */
+
+volatile int global_var;
+
+static inline int __attribute__ ((always_inline))
+bar ()
+{ /* bar prologue */
+ asm ("bar_label: .globl bar_label");
+ return global_var; /* bar return global_var */
+} /* bar end */
+
+static inline int __attribute__ ((always_inline))
+foo ()
+{ /* foo prologue */
+ return bar (); /* foo call bar */
+} /* foo end */
+
+int
+main ()
+{ /* main prologue */
+ int ans;
+ asm ("main_label: .globl main_label");
+ global_var = 0; /* main set global_var */
+ asm ("main_label2: .globl main_label2");
+ ans = foo (); /* main call foo */
+ asm ("main_label3: .globl main_label3");
+ return ans;
+} /* main end */
new file mode 100644
@@ -0,0 +1,151 @@
+# Copyright 2019-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 that breakpoints on empty inline callsite lines stop in the
+# correct frame.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+if {![dwarf2_support]} {
+ verbose "Skipping $gdb_test_file_name."
+ return 0
+}
+
+# The .c files use __attribute__.
+if ![is_c_compiler_gcc] {
+ verbose "Skipping $gdb_test_file_name."
+ return 0
+}
+
+standard_testfile .c .S
+
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+ global srcdir subdir srcfile srcfile2
+ declare_labels ranges_label lines_label foo_prog bar_prog
+
+ lassign [function_range main [list ${srcdir}/${subdir}/$srcfile]] \
+ main_start main_len
+ set main_end "$main_start + $main_len"
+
+ set foo_call_line [gdb_get_line_number "main call foo"]
+ set bar_call_line [gdb_get_line_number "foo call bar"]
+
+ cu {} {
+ compile_unit {
+ {language @DW_LANG_C}
+ {name dw2-inline-callsite-break.c}
+ {low_pc 0 addr}
+ {stmt_list ${lines_label} DW_FORM_sec_offset}
+ {ranges ${ranges_label} DW_FORM_sec_offset}
+ } {
+ foo_prog: subprogram {
+ {name foo}
+ {inline 3 data1}
+ }
+ bar_prog: subprogram {
+ {name bar}
+ {inline 3 data1}
+ }
+ subprogram {
+ {external 1 flag}
+ {name main}
+ {low_pc $main_start addr}
+ {high_pc "$main_start + $main_len" addr}
+ } {
+ inlined_subroutine {
+ {abstract_origin %$foo_prog}
+ {low_pc main_label2 addr}
+ {high_pc main_label3 addr}
+ {call_file 1 data1}
+ {call_line $foo_call_line data1}
+ } {
+ inlined_subroutine {
+ {abstract_origin %$bar_prog}
+ {low_pc main_label2 addr}
+ {high_pc main_label3 addr}
+ {call_file 1 data1}
+ {call_line $bar_call_line data1}
+ }
+ }
+ }
+ }
+ }
+
+ lines {version 2} lines_label {
+ include_dir "${srcdir}/${subdir}"
+ file_name "$srcfile" 1
+
+ program {
+ DW_LNE_set_address $main_start
+ line [gdb_get_line_number "main prologue"]
+ DW_LNS_copy
+
+ DW_LNE_set_address main_label
+ line [gdb_get_line_number "main set global_var"]
+ DW_LNS_copy
+
+ DW_LNE_set_address main_label2
+ line [gdb_get_line_number "main call foo"]
+ DW_LNS_copy
+
+ DW_LNE_set_address main_label2
+ line [gdb_get_line_number "foo call bar"]
+ DW_LNS_copy
+
+ DW_LNE_set_address bar_label
+ line [gdb_get_line_number "bar return global_var"]
+ DW_LNS_copy
+
+ DW_LNE_set_address $main_end
+ DW_LNE_end_sequence
+ }
+ }
+
+ ranges {is_64 [is_64_target]} {
+ ranges_label: sequence {
+ range ${main_start} ${main_end}
+ }
+ }
+}
+
+if { [prepare_for_testing "failed to prepare" ${testfile} \
+ [list $srcfile $asm_file] {nodebug}] } {
+ return -1
+}
+
+if ![runto_main] {
+ return -1
+}
+
+set bp_location [gdb_get_line_number "main call foo"]
+gdb_test "break $bp_location" ".*Breakpoint.*" \
+ "set breakpoint on outer callsite line"
+
+gdb_test "continue" ".*Breakpoint.*main call foo.*" \
+ "continue to outer callsite breakpoint"
+
+delete_breakpoints
+if ![runto_main] {
+ return -1
+}
+
+set bp_location [gdb_get_line_number "foo call bar"]
+gdb_test "break $bp_location" ".*Breakpoint.*" \
+ "set breakpoint on inner callsite line"
+
+gdb_test "continue" ".*Breakpoint.*foo call bar.*" \
+ "continue to inner callsite breakpoint"