[v3,3/3] Fix range end handling of inlined subroutines
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-arm |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 |
success
|
Test passed
|
Commit Message
Currently there is a problem when debugging
optimized code when the inferior stops at an inline
sub-range end PC. It is unclear if that location
is from the inline function or from the calling
function. Therefore the call stack is often
wrong.
This patch detects the "weak" line table entries
which are likely part of the previous inline block,
and if we have such a location, it assumes the
location belongs to the previous block.
Additionally it may happen that the infrun machinery
steps from one inline range to another inline range
of the same inline function. That can look like
jumping back and forth from the calling program
to the inline function, while really the inline
function just jumps from a hot to a cold section
of the code, i.e. error handling.
Additionally it may happen that one inline sub-range
is empty or the inline is completely empty. But
filtering that information away is not the right
solution, since although there is no actual code
from the inline, it is still possible that variables
from an inline function can be inspected here.
The issue with the empty ranges is also discussed here:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94474
Conceptually this patch uses a heuristic to work around
a deficiency in the dwarf-4 and dwarf-5 rnglists structure.
There should be a location view number for each inline
sub-range begin PC and end PC, similar to the DW_AT_GNU_entry_view
which is the location view for the inline entry point.
---
gdb/block.c | 15 +-
gdb/dwarf2/read.c | 77 ++-----
gdb/infcmd.c | 3 +-
gdb/infrun.c | 30 ++-
gdb/symtab.c | 17 +-
gdb/testsuite/gdb.base/empty-inline.c | 39 ++++
gdb/testsuite/gdb.base/empty-inline.exp | 56 +++++
gdb/testsuite/gdb.cp/empty-inline.cc | 33 +++
gdb/testsuite/gdb.cp/empty-inline.exp | 50 +++++
gdb/testsuite/gdb.cp/step-and-next-inline.cc | 6 +-
gdb/testsuite/gdb.cp/step-and-next-inline.exp | 194 +++++++-----------
11 files changed, 322 insertions(+), 198 deletions(-)
create mode 100644 gdb/testsuite/gdb.base/empty-inline.c
create mode 100644 gdb/testsuite/gdb.base/empty-inline.exp
create mode 100644 gdb/testsuite/gdb.cp/empty-inline.cc
create mode 100644 gdb/testsuite/gdb.cp/empty-inline.exp
Comments
On 7/5/24 6:18 AM, Bernd Edlinger wrote:
> Currently there is a problem when debugging
> optimized code when the inferior stops at an inline
> sub-range end PC. It is unclear if that location
> is from the inline function or from the calling
> function. Therefore the call stack is often
> wrong.
>
> This patch detects the "weak" line table entries
> which are likely part of the previous inline block,
> and if we have such a location, it assumes the
> location belongs to the previous block.
>
> Additionally it may happen that the infrun machinery
> steps from one inline range to another inline range
> of the same inline function. That can look like
> jumping back and forth from the calling program
> to the inline function, while really the inline
> function just jumps from a hot to a cold section
> of the code, i.e. error handling.
>
> Additionally it may happen that one inline sub-range
> is empty or the inline is completely empty. But
> filtering that information away is not the right
> solution, since although there is no actual code
> from the inline, it is still possible that variables
> from an inline function can be inspected here.
Hi! Thanks for working on this.
As I said on my reply to v2, I'd like to see the commits split
differently. Some of my questions here may be redundant or obvious when
I have less concurrent changes to keep in my head.
>
> The issue with the empty ranges is also discussed here:
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94474
>
> Conceptually this patch uses a heuristic to work around
> a deficiency in the dwarf-4 and dwarf-5 rnglists structure.
> There should be a location view number for each inline
> sub-range begin PC and end PC, similar to the DW_AT_GNU_entry_view
> which is the location view for the inline entry point.
> ---
> gdb/block.c | 15 +-
> gdb/dwarf2/read.c | 77 ++-----
> gdb/infcmd.c | 3 +-
> gdb/infrun.c | 30 ++-
> gdb/symtab.c | 17 +-
> gdb/testsuite/gdb.base/empty-inline.c | 39 ++++
> gdb/testsuite/gdb.base/empty-inline.exp | 56 +++++
> gdb/testsuite/gdb.cp/empty-inline.cc | 33 +++
> gdb/testsuite/gdb.cp/empty-inline.exp | 50 +++++
> gdb/testsuite/gdb.cp/step-and-next-inline.cc | 6 +-
> gdb/testsuite/gdb.cp/step-and-next-inline.exp | 194 +++++++-----------
> 11 files changed, 322 insertions(+), 198 deletions(-)
> create mode 100644 gdb/testsuite/gdb.base/empty-inline.c
> create mode 100644 gdb/testsuite/gdb.base/empty-inline.exp
> create mode 100644 gdb/testsuite/gdb.cp/empty-inline.cc
> create mode 100644 gdb/testsuite/gdb.cp/empty-inline.exp
>
> diff --git a/gdb/block.c b/gdb/block.c
> index 511689c9738..d9c4435c9e2 100644
> --- a/gdb/block.c
> +++ b/gdb/block.c
> @@ -197,7 +197,20 @@ blockvector_for_pc_sect (CORE_ADDR pc, struct obj_section *section,
> return NULL;
>
> if (pblock)
> - *pblock = b;
> + {
> + struct symtab_and_line sal = find_pc_sect_line (pc, section, 0);
> + if (sal.line != 0 && sal.pc == pc && sal.is_weak)
> + {
> + const struct block *b2 = find_block_in_blockvector (bl, pc - 1);
> + const struct block *b0 = b;
> + while (b0->superblock () && !b0->function ())
> + b0 = b0->superblock ();
> + if (b0->contains (b2))
> + b = b2;
> + }
> + *pblock = b;
> + }
> +
> return bl;
> }
>
> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> index 60fd8b45eb5..e40679611fe 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -10733,10 +10733,6 @@ dwarf2_rnglists_process (unsigned offset, struct dwarf2_cu *cu,
> return false;
> }
>
> - /* Empty range entries have no effect. */
> - if (range_beginning == range_end)
> - continue;
> -
> /* Only DW_RLE_offset_pair needs the base address added. */
> if (rlet == DW_RLE_offset_pair)
> {
> @@ -10855,10 +10851,6 @@ dwarf2_ranges_process (unsigned offset, struct dwarf2_cu *cu, dwarf_tag tag,
> return 0;
> }
>
> - /* Empty range entries have no effect. */
> - if (range_beginning == range_end)
> - continue;
> -
> range_beginning = (unrelocated_addr) ((CORE_ADDR) range_beginning
> + (CORE_ADDR) *base);
> range_end = (unrelocated_addr) ((CORE_ADDR) range_end
> @@ -11080,8 +11072,8 @@ dwarf2_get_pc_bounds (struct die_info *die, unrelocated_addr *lowpc,
> if (ret == PC_BOUNDS_NOT_PRESENT || ret == PC_BOUNDS_INVALID)
> return ret;
>
> - /* partial_die_info::read has also the strict LOW < HIGH requirement. */
> - if (high <= low)
> + /* partial_die_info::read has also the same low < high requirement. */
We use scream case to indicate variable names, so there's no need for
this change.
> + if (low > high || (low == high && die->tag != DW_TAG_inlined_subroutine))
> return PC_BOUNDS_INVALID;
>
> /* When using the GNU linker, .gnu.linkonce. sections are used to
> @@ -18027,21 +18019,9 @@ class lnp_state_machine
>
> /* Additional bits of state we need to track. */
>
> - /* The last file that we called dwarf2_start_subfile for.
> - This is only used for TLLs. */
> - unsigned int m_last_file = 0;
> /* The last file a line number was recorded for. */
> struct subfile *m_last_subfile = NULL;
>
> - /* The address of the last line entry. */
> - unrelocated_addr m_last_address;
> -
> - /* Set to true when a previous line at the same address (using
> - m_last_address) had LEF_IS_STMT set in m_flags. This is reset to false
> - when a line entry at a new address (m_address different to
> - m_last_address) is processed. */
> - bool m_stmt_at_address = false;
> -
> /* When true, record the lines we decode. */
> bool m_currently_recording_lines = true;
>
> @@ -18199,7 +18179,8 @@ dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
>
> static void
> dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
> - unrelocated_addr address, struct dwarf2_cu *cu)
> + unrelocated_addr address, struct dwarf2_cu *cu,
> + bool end_sequence)
> {
> if (subfile == NULL)
> return;
> @@ -18212,7 +18193,8 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
> paddress (gdbarch, (CORE_ADDR) address));
> }
>
> - dwarf_record_line_1 (gdbarch, subfile, 0, address, LEF_IS_STMT, cu);
> + dwarf_record_line_1 (gdbarch, subfile, end_sequence ? 0 : -1, address,
> + LEF_IS_STMT, cu);
> }
>
> void
> @@ -18240,38 +18222,17 @@ lnp_state_machine::record_line (bool end_sequence)
> /* For now we ignore lines not starting on an instruction boundary.
> But not when processing end_sequence for compatibility with the
> previous version of the code. */
> - else if (m_op_index == 0 || end_sequence)
> - {
> - /* When we switch files we insert an end maker in the first file,
> - switch to the second file and add a new line entry. The
> - problem is that the end marker inserted in the first file will
> - discard any previous line entries at the same address. If the
> - line entries in the first file are marked as is-stmt, while
> - the new line in the second file is non-stmt, then this means
> - the end marker will discard is-stmt lines so we can have a
> - non-stmt line. This means that there are less addresses at
> - which the user can insert a breakpoint.
> -
> - To improve this we track the last address in m_last_address,
> - and whether we have seen an is-stmt at this address. Then
> - when switching files, if we have seen a stmt at the current
> - address, and we are switching to create a non-stmt line, then
> - discard the new line. */
> - bool file_changed
> - = m_last_subfile != m_cu->get_builder ()->get_current_subfile ();
> - bool ignore_this_line
> - = ((file_changed && !end_sequence && m_last_address == m_address
> - && ((m_flags & LEF_IS_STMT) == 0)
> - && m_stmt_at_address)
> - || (!end_sequence && m_line == 0));
Could you explain why you think its safe to remove this logic of adding
fake entries to the line table to avoid missing out on some valid
breakpoint locations?
I can imagine this could cause issues with the is_stmt logic, but I
would think the way forward is to have some more check on
ignore_this_line, otherwise we're improving the situation on one side
but making it somewhat worse on another. Have you tried tweaking this
logic to accommodate for the is_weak flag?
> -
> - if ((file_changed && !ignore_this_line) || end_sequence)
> + else if ((m_op_index == 0 && m_line != 0) || end_sequence)
> + {
> + if (m_last_subfile != m_cu->get_builder ()->get_current_subfile ()
> + || end_sequence)
> {
> dwarf_finish_line (m_gdbarch, m_last_subfile, m_address,
> - m_currently_recording_lines ? m_cu : nullptr);
> + m_currently_recording_lines ? m_cu : nullptr,
> + end_sequence || (m_flags & LEF_IS_STMT) != 0);
> }
>
> - if (!end_sequence && !ignore_this_line)
> + if (!end_sequence)
> {
> linetable_entry_flags lte_flags = m_flags;
> if (producer_is_codewarrior (m_cu))
> @@ -18291,15 +18252,6 @@ lnp_state_machine::record_line (bool end_sequence)
> m_last_line = m_line;
> }
> }
> -
> - /* Track whether we have seen any IS_STMT true at m_address in case we
> - have multiple line table entries all at m_address. */
> - if (m_last_address != m_address)
> - {
> - m_stmt_at_address = false;
> - m_last_address = m_address;
> - }
> - m_stmt_at_address |= (m_flags & LEF_IS_STMT) != 0;
> }
>
> lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
> @@ -18313,8 +18265,7 @@ lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
> This is currently used by MIPS code,
> cf. `mips_adjust_dwarf2_line'. */
> m_address ((unrelocated_addr) gdbarch_adjust_dwarf2_line (arch, 0, 0)),
> - m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0),
> - m_last_address (m_address)
> + m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0)
> {
> }
>
> diff --git a/gdb/infcmd.c b/gdb/infcmd.c
> index 71514d5ba66..7598c9e3032 100644
> --- a/gdb/infcmd.c
> +++ b/gdb/infcmd.c
> @@ -996,7 +996,8 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
> if (sym->aclass () == LOC_BLOCK)
> {
> const block *block = sym->value_block ();
> - if (block->end () < tp->control.step_range_end)
> + if (block->end () < tp->control.step_range_end
> + && block->end () > tp->control.step_range_start)
> tp->control.step_range_end = block->end ();
> }
> }
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index 1f32a63ad54..097b42c4e92 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -8114,6 +8114,31 @@ process_event_stop_test (struct execution_control_state *ecs)
> return;
> }
>
> + /* Handle the case when subroutines have multiple ranges.
> + When we step from one part to the next part of the same subroutine,
> + all subroutine levels are skipped again which begin here.
> + Compensate for this by removing all skipped subroutines,
> + which were already executing from the user's perspective. */
> +
> + if (get_stack_frame_id (frame)
> + == ecs->event_thread->control.step_stack_frame_id
> + && inline_skipped_frames (ecs->event_thread)
> + && ecs->event_thread->control.step_frame_id.artificial_depth > 0
> + && ecs->event_thread->control.step_frame_id.code_addr_p)
> + {
> + const struct block *prev, *curr;
> + int depth = 0;
> + prev = block_for_pc (ecs->event_thread->control.step_frame_id.code_addr);
> + curr = block_for_pc (ecs->event_thread->stop_pc ());
> + while (curr && curr->inlined_p () && !curr->contains (prev))
> + {
> + depth ++;
> + curr = curr->superblock ();
> + }
> + while (inline_skipped_frames (ecs->event_thread) > depth)
> + step_into_inline_frame (ecs->event_thread);
> + }
> +
> /* Look for "calls" to inlined functions, part one. If the inline
> frame machinery detected some skipped call sites, we have entered
> a new inline function. */
> @@ -8172,6 +8197,8 @@ process_event_stop_test (struct execution_control_state *ecs)
> infrun_debug_printf ("stepping through inlined function");
>
> if (ecs->event_thread->control.step_over_calls == STEP_OVER_ALL
> + || ecs->event_thread->stop_pc () != stop_pc_sal.pc
> + || !stop_pc_sal.is_stmt
> || inline_frame_is_marked_for_skip (false, ecs->event_thread))
> keep_going (ecs);
> else
> @@ -8220,7 +8247,8 @@ process_event_stop_test (struct execution_control_state *ecs)
> end_stepping_range (ecs);
> return;
> }
> - else if (*curr_frame_id == original_frame_id)
> + else if (get_stack_frame_id (frame)
> + == ecs->event_thread->control.step_stack_frame_id)
> {
> /* We are not at the start of a statement, and we have not changed
> frame.
> diff --git a/gdb/symtab.c b/gdb/symtab.c
> index 41d71beec2a..957fe971d3c 100644
> --- a/gdb/symtab.c
> +++ b/gdb/symtab.c
> @@ -3290,7 +3290,10 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
> 0) instead of a real line. */
>
> if (prev && prev->line
> - && (!best || prev->unrelocated_pc () > best->unrelocated_pc ()))
> + && (!best || prev->unrelocated_pc () > best->unrelocated_pc ()
> + || (prev->unrelocated_pc () == best->unrelocated_pc ()
> + && (best->pc (objfile) == pc
> + ? !best->is_stmt : best->is_weak))))
> {
> best = prev;
> best_symtab = iter_s;
> @@ -3309,7 +3312,7 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
> && (tmp - 1)->unrelocated_pc () == tmp->unrelocated_pc ()
> && (tmp - 1)->line != 0 && !tmp->is_stmt)
> --tmp;
> - if (tmp->is_stmt)
> + if (tmp->is_stmt && (tmp->pc (objfile) == pc || !tmp->is_weak))
> best = tmp;
> }
>
> @@ -3333,18 +3336,14 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
> We used to return alt->line - 1 here, but that could be
> anywhere; if we don't have line number info for this PC,
> don't make some up. */
> - val.pc = pc;
> - }
> - else if (best->line == 0)
> - {
> - /* If our best fit is in a range of PC's for which no line
> - number info is available (line number is zero) then we didn't
> - find any valid line information. */
Why did you remove the logic handling not finding any line information?
Surely it is still possible that we can't find debug information for
other reasons.
> + if (notcurrent)
> + pc++;
> val.pc = pc;
> }
> else
> {
> val.is_stmt = best->is_stmt;
> + val.is_weak = best->is_weak;
> val.symtab = best_symtab;
> val.line = best->line;
> val.pc = best->pc (objfile);
> diff --git a/gdb/testsuite/gdb.base/empty-inline.c b/gdb/testsuite/gdb.base/empty-inline.c
> new file mode 100644
> index 00000000000..4ecb3ff14a3
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/empty-inline.c
> @@ -0,0 +1,39 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 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/>. */
> +
> +static int test0 (void)
> +{
> + asm (""); /* line 20 */
> + return 1; /* line 21 */
In general, I'd like if these "tag" comments were more informative than
a line. In here, you could use things like:
"empty asm inlined function"
"return statement inlined function"
And similar informative comments in this .c file and in the other
proposed test.
> +}
> +
> +int __attribute__((noinline, noclone))
> +#ifdef __CET__
> + __attribute__((nocf_check))
> +#endif
> +test1 (int x)
> +{
> + asm ("");
> + return x+1; /* line 31 */
> +}
> +
> +int
> +main()
> +{ test1 (test0 ()); /* line 36 */
> + test1 (test0 ()); /* line 37 */
> + return 0; /* line 38 */
> +}
> diff --git a/gdb/testsuite/gdb.base/empty-inline.exp b/gdb/testsuite/gdb.base/empty-inline.exp
> new file mode 100644
> index 00000000000..0d9cfe922cf
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/empty-inline.exp
> @@ -0,0 +1,56 @@
> +# Copyright 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/>.
> +
> +standard_testfile .c
> +
> +if [get_compiler_info] {
> + return -1
> +}
> +
> +if { [test_compiler_info gcc*] && ![supports_statement_frontiers] } {
It would be nice to have an "untested" statement here. I was testing
with clang and (the next test) just silently didn't run and took me a
moment to understand why.
> + return -1
> +}
> +
> +global srcfile testfile
> +
> +set options {debug nowarnings optimize=-O2}
> +if { [supports_statement_frontiers] } {
> + lappend options additional_flags=-gstatement-frontiers
> +}
> +
> +if { [prepare_for_testing "failed to prepare" $binfile \
> + $srcfile $options] } {
> + return -1
> +}
> +
> +if ![runto_main] {
> + return
> +}
> +
> +gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:36.*" "in main"
> +gdb_test_multiple "step" "step into test0" {
> + -re ".*test0.*${srcfile}:20.*$::gdb_prompt $" {
> + gdb_test "step" ".*line 21.*" $gdb_test_name
> + }
> + -re ".*test0.*${srcfile}:21.*$::gdb_prompt $" {
> + pass $gdb_test_name
> + }
If I use clang to run this test, I still get a failure here. Clang's
line table is completely different to gcc's, and so isn't affected by
your patch at all, printing line 36 on the main function instead of line
20, and there is no entry for line 21. Considering this, I'd suggest
checking for clang at the start and skipping the test (with an UNTESTED).
> +}
> +gdb_test "frame 1" "\\s*\\#1\\s+main.*${srcfile}:36.*" "frame1"
> +gdb_test "step" ".*test1.*${srcfile}:31.*" "step into test1"
> +gdb_test "frame 1" "\\s*\\#1.*in main.*${srcfile}:36.*" "frame2"
> +gdb_test "step" ".*main.*${srcfile}:37.*" "step back to main"
> +gdb_test "next" ".*return 0;.*" "step over test0+1"
> +gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:38.*" "in main again"
> diff --git a/gdb/testsuite/gdb.cp/empty-inline.cc b/gdb/testsuite/gdb.cp/empty-inline.cc
> new file mode 100644
> index 00000000000..a960d5f7ec0
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/empty-inline.cc
> @@ -0,0 +1,33 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 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/>. */
> +
> +/* PR 25987 */
> +struct MyClass;
> +struct ptr {
> + MyClass* get() { return t; } /* line 21 */
> + MyClass* t;
> +};
> +struct MyClass { void call(); };
> +void MyClass::call() {
> + *(volatile char*)-1 = 1; /* line 26 */
> +}
> +static void intermediate(ptr p) {
> + p.get()->call(); /* line 29 */
> +}
> +int main() {
> + intermediate(ptr{new MyClass});
> +}
> diff --git a/gdb/testsuite/gdb.cp/empty-inline.exp b/gdb/testsuite/gdb.cp/empty-inline.exp
> new file mode 100644
> index 00000000000..7848c963f59
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/empty-inline.exp
> @@ -0,0 +1,50 @@
> +# Copyright 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/>.
> +
> +# PR 25987
> +standard_testfile .cc
> +
> +if {![supports_statement_frontiers]} {
Similarly here, it would be nice to, one, emit an untested statement,
and two, filter clang out even if it eventually supports statement
frontiers.
On 7/19/24 20:48, Guinevere Larsen wrote:
> On 7/5/24 6:18 AM, Bernd Edlinger wrote:
>> Currently there is a problem when debugging
>> optimized code when the inferior stops at an inline
>> sub-range end PC. It is unclear if that location
>> is from the inline function or from the calling
>> function. Therefore the call stack is often
>> wrong.
>>
>> This patch detects the "weak" line table entries
>> which are likely part of the previous inline block,
>> and if we have such a location, it assumes the
>> location belongs to the previous block.
>>
>> Additionally it may happen that the infrun machinery
>> steps from one inline range to another inline range
>> of the same inline function. That can look like
>> jumping back and forth from the calling program
>> to the inline function, while really the inline
>> function just jumps from a hot to a cold section
>> of the code, i.e. error handling.
>>
>> Additionally it may happen that one inline sub-range
>> is empty or the inline is completely empty. But
>> filtering that information away is not the right
>> solution, since although there is no actual code
>> from the inline, it is still possible that variables
>> from an inline function can be inspected here.
>
> Hi! Thanks for working on this.
>
> As I said on my reply to v2, I'd like to see the commits split differently. Some of my questions here may be redundant or obvious when I have less concurrent changes to keep in my head.
>
Not sure If I can do that, but I will of course give it a try,
it will certainly take a while so please be patient.
>>
>> The issue with the empty ranges is also discussed here:
>> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94474
>>
>> Conceptually this patch uses a heuristic to work around
>> a deficiency in the dwarf-4 and dwarf-5 rnglists structure.
>> There should be a location view number for each inline
>> sub-range begin PC and end PC, similar to the DW_AT_GNU_entry_view
>> which is the location view for the inline entry point.
>> ---
>> gdb/block.c | 15 +-
>> gdb/dwarf2/read.c | 77 ++-----
>> gdb/infcmd.c | 3 +-
>> gdb/infrun.c | 30 ++-
>> gdb/symtab.c | 17 +-
>> gdb/testsuite/gdb.base/empty-inline.c | 39 ++++
>> gdb/testsuite/gdb.base/empty-inline.exp | 56 +++++
>> gdb/testsuite/gdb.cp/empty-inline.cc | 33 +++
>> gdb/testsuite/gdb.cp/empty-inline.exp | 50 +++++
>> gdb/testsuite/gdb.cp/step-and-next-inline.cc | 6 +-
>> gdb/testsuite/gdb.cp/step-and-next-inline.exp | 194 +++++++-----------
>> 11 files changed, 322 insertions(+), 198 deletions(-)
>> create mode 100644 gdb/testsuite/gdb.base/empty-inline.c
>> create mode 100644 gdb/testsuite/gdb.base/empty-inline.exp
>> create mode 100644 gdb/testsuite/gdb.cp/empty-inline.cc
>> create mode 100644 gdb/testsuite/gdb.cp/empty-inline.exp
>>
>> diff --git a/gdb/block.c b/gdb/block.c
>> index 511689c9738..d9c4435c9e2 100644
>> --- a/gdb/block.c
>> +++ b/gdb/block.c
>> @@ -197,7 +197,20 @@ blockvector_for_pc_sect (CORE_ADDR pc, struct obj_section *section,
>> return NULL;
>> if (pblock)
>> - *pblock = b;
>> + {
>> + struct symtab_and_line sal = find_pc_sect_line (pc, section, 0);
>> + if (sal.line != 0 && sal.pc == pc && sal.is_weak)
>> + {
>> + const struct block *b2 = find_block_in_blockvector (bl, pc - 1);
>> + const struct block *b0 = b;
>> + while (b0->superblock () && !b0->function ())
>> + b0 = b0->superblock ();
>> + if (b0->contains (b2))
>> + b = b2;
>> + }
>> + *pblock = b;
>> + }
>> +
>> return bl;
>> }
>> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
>> index 60fd8b45eb5..e40679611fe 100644
>> --- a/gdb/dwarf2/read.c
>> +++ b/gdb/dwarf2/read.c
>> @@ -10733,10 +10733,6 @@ dwarf2_rnglists_process (unsigned offset, struct dwarf2_cu *cu,
>> return false;
>> }
>> - /* Empty range entries have no effect. */
>> - if (range_beginning == range_end)
>> - continue;
>> -
>> /* Only DW_RLE_offset_pair needs the base address added. */
>> if (rlet == DW_RLE_offset_pair)
>> {
>> @@ -10855,10 +10851,6 @@ dwarf2_ranges_process (unsigned offset, struct dwarf2_cu *cu, dwarf_tag tag,
>> return 0;
>> }
>> - /* Empty range entries have no effect. */
>> - if (range_beginning == range_end)
>> - continue;
>> -
>> range_beginning = (unrelocated_addr) ((CORE_ADDR) range_beginning
>> + (CORE_ADDR) *base);
>> range_end = (unrelocated_addr) ((CORE_ADDR) range_end
>> @@ -11080,8 +11072,8 @@ dwarf2_get_pc_bounds (struct die_info *die, unrelocated_addr *lowpc,
>> if (ret == PC_BOUNDS_NOT_PRESENT || ret == PC_BOUNDS_INVALID)
>> return ret;
>> - /* partial_die_info::read has also the strict LOW < HIGH requirement. */
>> - if (high <= low)
>> + /* partial_die_info::read has also the same low < high requirement. */
> We use scream case to indicate variable names, so there's no need for this change.
>> + if (low > high || (low == high && die->tag != DW_TAG_inlined_subroutine))
>> return PC_BOUNDS_INVALID;
>> /* When using the GNU linker, .gnu.linkonce. sections are used to
>> @@ -18027,21 +18019,9 @@ class lnp_state_machine
>> /* Additional bits of state we need to track. */
>> - /* The last file that we called dwarf2_start_subfile for.
>> - This is only used for TLLs. */
>> - unsigned int m_last_file = 0;
>> /* The last file a line number was recorded for. */
>> struct subfile *m_last_subfile = NULL;
>> - /* The address of the last line entry. */
>> - unrelocated_addr m_last_address;
>> -
>> - /* Set to true when a previous line at the same address (using
>> - m_last_address) had LEF_IS_STMT set in m_flags. This is reset to false
>> - when a line entry at a new address (m_address different to
>> - m_last_address) is processed. */
>> - bool m_stmt_at_address = false;
>> -
>> /* When true, record the lines we decode. */
>> bool m_currently_recording_lines = true;
>> @@ -18199,7 +18179,8 @@ dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
>> static void
>> dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
>> - unrelocated_addr address, struct dwarf2_cu *cu)
>> + unrelocated_addr address, struct dwarf2_cu *cu,
>> + bool end_sequence)
>> {
>> if (subfile == NULL)
>> return;
>> @@ -18212,7 +18193,8 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
>> paddress (gdbarch, (CORE_ADDR) address));
>> }
>> - dwarf_record_line_1 (gdbarch, subfile, 0, address, LEF_IS_STMT, cu);
>> + dwarf_record_line_1 (gdbarch, subfile, end_sequence ? 0 : -1, address,
>> + LEF_IS_STMT, cu);
>> }
>> void
>> @@ -18240,38 +18222,17 @@ lnp_state_machine::record_line (bool end_sequence)
>> /* For now we ignore lines not starting on an instruction boundary.
>> But not when processing end_sequence for compatibility with the
>> previous version of the code. */
>> - else if (m_op_index == 0 || end_sequence)
>> - {
>> - /* When we switch files we insert an end maker in the first file,
>> - switch to the second file and add a new line entry. The
>> - problem is that the end marker inserted in the first file will
>> - discard any previous line entries at the same address. If the
>> - line entries in the first file are marked as is-stmt, while
>> - the new line in the second file is non-stmt, then this means
>> - the end marker will discard is-stmt lines so we can have a
>> - non-stmt line. This means that there are less addresses at
>> - which the user can insert a breakpoint.
>> -
>> - To improve this we track the last address in m_last_address,
>> - and whether we have seen an is-stmt at this address. Then
>> - when switching files, if we have seen a stmt at the current
>> - address, and we are switching to create a non-stmt line, then
>> - discard the new line. */
>> - bool file_changed
>> - = m_last_subfile != m_cu->get_builder ()->get_current_subfile ();
>> - bool ignore_this_line
>> - = ((file_changed && !end_sequence && m_last_address == m_address
>> - && ((m_flags & LEF_IS_STMT) == 0)
>> - && m_stmt_at_address)
>> - || (!end_sequence && m_line == 0));
>
> Could you explain why you think its safe to remove this logic of adding fake entries to the line table to avoid missing out on some valid breakpoint locations?
>
> I can imagine this could cause issues with the is_stmt logic, but I would think the way forward is to have some more check on ignore_this_line, otherwise we're improving the situation on one side but making it somewhat worse on another. Have you tried tweaking this logic to accommodate for the is_weak flag?
>
I actually do not want to ignore any line, instead they
may become weak line table entries, if necessary.
So instead of simply skipping the call to dwarf_finish_line
and the following even more important dwarf_record_line_1,
the logic in dwarf_finish_line is changed to be non-destructive
if the added last parameter is false, thus the ignore_this_line is
morphed into "end_sequence || (m_flags & LEF_IS_STMT) != 0"
at the call to dwarf_finish_line below.
>> -
>> - if ((file_changed && !ignore_this_line) || end_sequence)
>> + else if ((m_op_index == 0 && m_line != 0) || end_sequence)
>> + {
>> + if (m_last_subfile != m_cu->get_builder ()->get_current_subfile ()
>> + || end_sequence)
>> {
>> dwarf_finish_line (m_gdbarch, m_last_subfile, m_address,
>> - m_currently_recording_lines ? m_cu : nullptr);
>> + m_currently_recording_lines ? m_cu : nullptr,
>> + end_sequence || (m_flags & LEF_IS_STMT) != 0);
>> }
>> - if (!end_sequence && !ignore_this_line)
>> + if (!end_sequence)
>> {
>> linetable_entry_flags lte_flags = m_flags;
>> if (producer_is_codewarrior (m_cu))
>> @@ -18291,15 +18252,6 @@ lnp_state_machine::record_line (bool end_sequence)
>> m_last_line = m_line;
>> }
>> }
>> -
>> - /* Track whether we have seen any IS_STMT true at m_address in case we
>> - have multiple line table entries all at m_address. */
>> - if (m_last_address != m_address)
>> - {
>> - m_stmt_at_address = false;
>> - m_last_address = m_address;
>> - }
>> - m_stmt_at_address |= (m_flags & LEF_IS_STMT) != 0;
>> }
>> lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
>> @@ -18313,8 +18265,7 @@ lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
>> This is currently used by MIPS code,
>> cf. `mips_adjust_dwarf2_line'. */
>> m_address ((unrelocated_addr) gdbarch_adjust_dwarf2_line (arch, 0, 0)),
>> - m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0),
>> - m_last_address (m_address)
>> + m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0)
>> {
>> }
>> diff --git a/gdb/infcmd.c b/gdb/infcmd.c
>> index 71514d5ba66..7598c9e3032 100644
>> --- a/gdb/infcmd.c
>> +++ b/gdb/infcmd.c
>> @@ -996,7 +996,8 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
>> if (sym->aclass () == LOC_BLOCK)
>> {
>> const block *block = sym->value_block ();
>> - if (block->end () < tp->control.step_range_end)
>> + if (block->end () < tp->control.step_range_end
>> + && block->end () > tp->control.step_range_start)
>> tp->control.step_range_end = block->end ();
>> }
>> }
>> diff --git a/gdb/infrun.c b/gdb/infrun.c
>> index 1f32a63ad54..097b42c4e92 100644
>> --- a/gdb/infrun.c
>> +++ b/gdb/infrun.c
>> @@ -8114,6 +8114,31 @@ process_event_stop_test (struct execution_control_state *ecs)
>> return;
>> }
>> + /* Handle the case when subroutines have multiple ranges.
>> + When we step from one part to the next part of the same subroutine,
>> + all subroutine levels are skipped again which begin here.
>> + Compensate for this by removing all skipped subroutines,
>> + which were already executing from the user's perspective. */
>> +
>> + if (get_stack_frame_id (frame)
>> + == ecs->event_thread->control.step_stack_frame_id
>> + && inline_skipped_frames (ecs->event_thread)
>> + && ecs->event_thread->control.step_frame_id.artificial_depth > 0
>> + && ecs->event_thread->control.step_frame_id.code_addr_p)
>> + {
>> + const struct block *prev, *curr;
>> + int depth = 0;
>> + prev = block_for_pc (ecs->event_thread->control.step_frame_id.code_addr);
>> + curr = block_for_pc (ecs->event_thread->stop_pc ());
>> + while (curr && curr->inlined_p () && !curr->contains (prev))
>> + {
>> + depth ++;
>> + curr = curr->superblock ();
>> + }
>> + while (inline_skipped_frames (ecs->event_thread) > depth)
>> + step_into_inline_frame (ecs->event_thread);
>> + }
>> +
>> /* Look for "calls" to inlined functions, part one. If the inline
>> frame machinery detected some skipped call sites, we have entered
>> a new inline function. */
>> @@ -8172,6 +8197,8 @@ process_event_stop_test (struct execution_control_state *ecs)
>> infrun_debug_printf ("stepping through inlined function");
>> if (ecs->event_thread->control.step_over_calls == STEP_OVER_ALL
>> + || ecs->event_thread->stop_pc () != stop_pc_sal.pc
>> + || !stop_pc_sal.is_stmt
>> || inline_frame_is_marked_for_skip (false, ecs->event_thread))
>> keep_going (ecs);
>> else
>> @@ -8220,7 +8247,8 @@ process_event_stop_test (struct execution_control_state *ecs)
>> end_stepping_range (ecs);
>> return;
>> }
>> - else if (*curr_frame_id == original_frame_id)
>> + else if (get_stack_frame_id (frame)
>> + == ecs->event_thread->control.step_stack_frame_id)
>> {
>> /* We are not at the start of a statement, and we have not changed
>> frame.
>> diff --git a/gdb/symtab.c b/gdb/symtab.c
>> index 41d71beec2a..957fe971d3c 100644
>> --- a/gdb/symtab.c
>> +++ b/gdb/symtab.c
>> @@ -3290,7 +3290,10 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
>> 0) instead of a real line. */
>> if (prev && prev->line
>> - && (!best || prev->unrelocated_pc () > best->unrelocated_pc ()))
>> + && (!best || prev->unrelocated_pc () > best->unrelocated_pc ()
>> + || (prev->unrelocated_pc () == best->unrelocated_pc ()
>> + && (best->pc (objfile) == pc
>> + ? !best->is_stmt : best->is_weak))))
>> {
>> best = prev;
>> best_symtab = iter_s;
>> @@ -3309,7 +3312,7 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
>> && (tmp - 1)->unrelocated_pc () == tmp->unrelocated_pc ()
>> && (tmp - 1)->line != 0 && !tmp->is_stmt)
>> --tmp;
>> - if (tmp->is_stmt)
>> + if (tmp->is_stmt && (tmp->pc (objfile) == pc || !tmp->is_weak))
>> best = tmp;
>> }
>> @@ -3333,18 +3336,14 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
>> We used to return alt->line - 1 here, but that could be
>> anywhere; if we don't have line number info for this PC,
>> don't make some up. */
>> - val.pc = pc;
>> - }
>> - else if (best->line == 0)
>> - {
>> - /* If our best fit is in a range of PC's for which no line
>> - number info is available (line number is zero) then we didn't
>> - find any valid line information. */
> Why did you remove the logic handling not finding any line information? Surely it is still possible that we can't find debug information for other reasons.
>> + if (notcurrent)
>> + pc++;
>> val.pc = pc;
>> }
>> else
>> {
>> val.is_stmt = best->is_stmt;
>> + val.is_weak = best->is_weak;
>> val.symtab = best_symtab;
>> val.line = best->line;
>> val.pc = best->pc (objfile);
>> diff --git a/gdb/testsuite/gdb.base/empty-inline.c b/gdb/testsuite/gdb.base/empty-inline.c
>> new file mode 100644
>> index 00000000000..4ecb3ff14a3
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.base/empty-inline.c
>> @@ -0,0 +1,39 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> + Copyright 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/>. */
>> +
>> +static int test0 (void)
>> +{
>> + asm (""); /* line 20 */
>> + return 1; /* line 21 */
>
> In general, I'd like if these "tag" comments were more informative than a line. In here, you could use things like:
>
> "empty asm inlined function"
> "return statement inlined function"
>
> And similar informative comments in this .c file and in the other proposed test.
>
I just have them here, because the line-numbers are checked in the empty-inline.exp file,
so I do not forget that when the line numbers change that the empty-inline.exp file needs
to be adjusted.
>> +}
>> +
>> +int __attribute__((noinline, noclone))
>> +#ifdef __CET__
>> + __attribute__((nocf_check))
>> +#endif
>> +test1 (int x)
>> +{
>> + asm ("");
>> + return x+1; /* line 31 */
>> +}
>> +
>> +int
>> +main()
>> +{ test1 (test0 ()); /* line 36 */
>> + test1 (test0 ()); /* line 37 */
>> + return 0; /* line 38 */
>> +}
>> diff --git a/gdb/testsuite/gdb.base/empty-inline.exp b/gdb/testsuite/gdb.base/empty-inline.exp
>> new file mode 100644
>> index 00000000000..0d9cfe922cf
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.base/empty-inline.exp
>> @@ -0,0 +1,56 @@
>> +# Copyright 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/>.
>> +
>> +standard_testfile .c
>> +
>> +if [get_compiler_info] {
>> + return -1
>> +}
>> +
>> +if { [test_compiler_info gcc*] && ![supports_statement_frontiers] } {
> It would be nice to have an "untested" statement here. I was testing with clang and (the next test) just silently didn't run and took me a moment to understand why.
>> + return -1
>> +}
>> +
>> +global srcfile testfile
>> +
>> +set options {debug nowarnings optimize=-O2}
>> +if { [supports_statement_frontiers] } {
>> + lappend options additional_flags=-gstatement-frontiers
>> +}
>> +
>> +if { [prepare_for_testing "failed to prepare" $binfile \
>> + $srcfile $options] } {
>> + return -1
>> +}
>> +
>> +if ![runto_main] {
>> + return
>> +}
>> +
>> +gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:36.*" "in main"
>> +gdb_test_multiple "step" "step into test0" {
>> + -re ".*test0.*${srcfile}:20.*$::gdb_prompt $" {
>> + gdb_test "step" ".*line 21.*" $gdb_test_name
>> + }
>> + -re ".*test0.*${srcfile}:21.*$::gdb_prompt $" {
>> + pass $gdb_test_name
>> + }
>
> If I use clang to run this test, I still get a failure here. Clang's line table is completely different to gcc's, and so isn't affected by your patch at all, printing line 36 on the main function instead of line 20, and there is no entry for line 21. Considering this, I'd suggest checking for clang at the start and skipping the test (with an UNTESTED).
>
Ah, okay, thanks for testing this.
>> +}
>> +gdb_test "frame 1" "\\s*\\#1\\s+main.*${srcfile}:36.*" "frame1"
>> +gdb_test "step" ".*test1.*${srcfile}:31.*" "step into test1"
>> +gdb_test "frame 1" "\\s*\\#1.*in main.*${srcfile}:36.*" "frame2"
>> +gdb_test "step" ".*main.*${srcfile}:37.*" "step back to main"
>> +gdb_test "next" ".*return 0;.*" "step over test0+1"
>> +gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:38.*" "in main again"
>> diff --git a/gdb/testsuite/gdb.cp/empty-inline.cc b/gdb/testsuite/gdb.cp/empty-inline.cc
>> new file mode 100644
>> index 00000000000..a960d5f7ec0
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.cp/empty-inline.cc
>> @@ -0,0 +1,33 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> + Copyright 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/>. */
>> +
>> +/* PR 25987 */
>> +struct MyClass;
>> +struct ptr {
>> + MyClass* get() { return t; } /* line 21 */
>> + MyClass* t;
>> +};
>> +struct MyClass { void call(); };
>> +void MyClass::call() {
>> + *(volatile char*)-1 = 1; /* line 26 */
>> +}
>> +static void intermediate(ptr p) {
>> + p.get()->call(); /* line 29 */
>> +}
>> +int main() {
>> + intermediate(ptr{new MyClass});
>> +}
>> diff --git a/gdb/testsuite/gdb.cp/empty-inline.exp b/gdb/testsuite/gdb.cp/empty-inline.exp
>> new file mode 100644
>> index 00000000000..7848c963f59
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.cp/empty-inline.exp
>> @@ -0,0 +1,50 @@
>> +# Copyright 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/>.
>> +
>> +# PR 25987
>> +standard_testfile .cc
>> +
>> +if {![supports_statement_frontiers]} {
> Similarly here, it would be nice to, one, emit an untested statement, and two, filter clang out even if it eventually supports statement frontiers.
>
Okay, sure.
Thanks
Bernd.
Oops, sorry.
I've missed some comments in my last reply.
On 7/19/24 20:48, Guinevere Larsen wrote:
> On 7/5/24 6:18 AM, Bernd Edlinger wrote:
>> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
>> index 60fd8b45eb5..e40679611fe 100644
>> --- a/gdb/dwarf2/read.c
>> +++ b/gdb/dwarf2/read.c
>> @@ -10733,10 +10733,6 @@ dwarf2_rnglists_process (unsigned offset, struct dwarf2_cu *cu,
>> return false;
>> }
>> - /* Empty range entries have no effect. */
>> - if (range_beginning == range_end)
>> - continue;
>> -
>> /* Only DW_RLE_offset_pair needs the base address added. */
>> if (rlet == DW_RLE_offset_pair)
>> {
>> @@ -10855,10 +10851,6 @@ dwarf2_ranges_process (unsigned offset, struct dwarf2_cu *cu, dwarf_tag tag,
>> return 0;
>> }
>> - /* Empty range entries have no effect. */
>> - if (range_beginning == range_end)
>> - continue;
>> -
>> range_beginning = (unrelocated_addr) ((CORE_ADDR) range_beginning
>> + (CORE_ADDR) *base);
>> range_end = (unrelocated_addr) ((CORE_ADDR) range_end
>> @@ -11080,8 +11072,8 @@ dwarf2_get_pc_bounds (struct die_info *die, unrelocated_addr *lowpc,
>> if (ret == PC_BOUNDS_NOT_PRESENT || ret == PC_BOUNDS_INVALID)
>> return ret;
>> - /* partial_die_info::read has also the strict LOW < HIGH requirement. */
>> - if (high <= low)
>> + /* partial_die_info::read has also the same low < high requirement. */
> We use scream case to indicate variable names, so there's no need for this change.
Ah, good you mentioned this.
Actually the comment can go away completely, since there is no longer any partial_die_info,
since this commit apparently removed that function completely:
commit 6209cde4ddb85a991ed1dda6f143ef08b75558df
Author: Tom Tromey <tom@tromey.com>
Date: Sun May 30 08:00:19 2021 -0600
Delete DWARF psymtab code
This removes the DWARF psymtab reader.
>> @@ -3333,18 +3336,14 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
>> We used to return alt->line - 1 here, but that could be
>> anywhere; if we don't have line number info for this PC,
>> don't make some up. */
>> - val.pc = pc;
>> - }
>> - else if (best->line == 0)
>> - {
>> - /* If our best fit is in a range of PC's for which no line
>> - number info is available (line number is zero) then we didn't
>> - find any valid line information. */
> Why did you remove the logic handling not finding any line information? Surely it is still possible that we can't find debug information for other reasons.
This code is unreachable.
The only place where BEST is assigned to non-zero value,
is here:
if (prev && prev->line
&& (!best || prev->unrelocated_pc () > best->unrelocated_pc ()
|| (prev->unrelocated_pc () == best->unrelocated_pc ()
&& (best->pc (objfile) == pc
? !best->is_stmt : best->is_weak))))
{
best = prev;
and here:
if (!best->is_stmt)
{
const linetable_entry *tmp = best;
while (tmp > first
&& (tmp - 1)->unrelocated_pc () == tmp->unrelocated_pc ()
&& (tmp - 1)->line != 0 && !tmp->is_stmt)
--tmp;
if (tmp->is_stmt && (tmp->pc (objfile) == pc || !tmp->is_weak))
best = tmp;
so as you can see, in both places it is ensured that best->line != 0.
This is so since in 2003 when this commit
commit 083ae9356e21082ea18fc21d91a84bbaa7a76fc7
Author: Daniel Jacobowitz <drow@false.org>
Date: Mon Jan 13 21:59:53 2003 +0000
* symtab.c (find_pc_sect_line): Don't consider end-of-function
lines.
changed the if condition here:
- if (prev && (!best || prev->pc > best->pc))
+ if (prev && prev->line && (!best || prev->pc > best->pc))
{
best = prev;
best_symtab = s;
but forgot to remove the now no longer needed
else if (best->line == 0)
{
/* If our best fit is in a range of PC's for which no line
number info is available (line number is zero) then we didn't
find any valid line information. */
val.pc = pc;
}
Thanks
Bernd.
@@ -197,7 +197,20 @@ blockvector_for_pc_sect (CORE_ADDR pc, struct obj_section *section,
return NULL;
if (pblock)
- *pblock = b;
+ {
+ struct symtab_and_line sal = find_pc_sect_line (pc, section, 0);
+ if (sal.line != 0 && sal.pc == pc && sal.is_weak)
+ {
+ const struct block *b2 = find_block_in_blockvector (bl, pc - 1);
+ const struct block *b0 = b;
+ while (b0->superblock () && !b0->function ())
+ b0 = b0->superblock ();
+ if (b0->contains (b2))
+ b = b2;
+ }
+ *pblock = b;
+ }
+
return bl;
}
@@ -10733,10 +10733,6 @@ dwarf2_rnglists_process (unsigned offset, struct dwarf2_cu *cu,
return false;
}
- /* Empty range entries have no effect. */
- if (range_beginning == range_end)
- continue;
-
/* Only DW_RLE_offset_pair needs the base address added. */
if (rlet == DW_RLE_offset_pair)
{
@@ -10855,10 +10851,6 @@ dwarf2_ranges_process (unsigned offset, struct dwarf2_cu *cu, dwarf_tag tag,
return 0;
}
- /* Empty range entries have no effect. */
- if (range_beginning == range_end)
- continue;
-
range_beginning = (unrelocated_addr) ((CORE_ADDR) range_beginning
+ (CORE_ADDR) *base);
range_end = (unrelocated_addr) ((CORE_ADDR) range_end
@@ -11080,8 +11072,8 @@ dwarf2_get_pc_bounds (struct die_info *die, unrelocated_addr *lowpc,
if (ret == PC_BOUNDS_NOT_PRESENT || ret == PC_BOUNDS_INVALID)
return ret;
- /* partial_die_info::read has also the strict LOW < HIGH requirement. */
- if (high <= low)
+ /* partial_die_info::read has also the same low < high requirement. */
+ if (low > high || (low == high && die->tag != DW_TAG_inlined_subroutine))
return PC_BOUNDS_INVALID;
/* When using the GNU linker, .gnu.linkonce. sections are used to
@@ -18027,21 +18019,9 @@ class lnp_state_machine
/* Additional bits of state we need to track. */
- /* The last file that we called dwarf2_start_subfile for.
- This is only used for TLLs. */
- unsigned int m_last_file = 0;
/* The last file a line number was recorded for. */
struct subfile *m_last_subfile = NULL;
- /* The address of the last line entry. */
- unrelocated_addr m_last_address;
-
- /* Set to true when a previous line at the same address (using
- m_last_address) had LEF_IS_STMT set in m_flags. This is reset to false
- when a line entry at a new address (m_address different to
- m_last_address) is processed. */
- bool m_stmt_at_address = false;
-
/* When true, record the lines we decode. */
bool m_currently_recording_lines = true;
@@ -18199,7 +18179,8 @@ dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
static void
dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
- unrelocated_addr address, struct dwarf2_cu *cu)
+ unrelocated_addr address, struct dwarf2_cu *cu,
+ bool end_sequence)
{
if (subfile == NULL)
return;
@@ -18212,7 +18193,8 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
paddress (gdbarch, (CORE_ADDR) address));
}
- dwarf_record_line_1 (gdbarch, subfile, 0, address, LEF_IS_STMT, cu);
+ dwarf_record_line_1 (gdbarch, subfile, end_sequence ? 0 : -1, address,
+ LEF_IS_STMT, cu);
}
void
@@ -18240,38 +18222,17 @@ lnp_state_machine::record_line (bool end_sequence)
/* For now we ignore lines not starting on an instruction boundary.
But not when processing end_sequence for compatibility with the
previous version of the code. */
- else if (m_op_index == 0 || end_sequence)
- {
- /* When we switch files we insert an end maker in the first file,
- switch to the second file and add a new line entry. The
- problem is that the end marker inserted in the first file will
- discard any previous line entries at the same address. If the
- line entries in the first file are marked as is-stmt, while
- the new line in the second file is non-stmt, then this means
- the end marker will discard is-stmt lines so we can have a
- non-stmt line. This means that there are less addresses at
- which the user can insert a breakpoint.
-
- To improve this we track the last address in m_last_address,
- and whether we have seen an is-stmt at this address. Then
- when switching files, if we have seen a stmt at the current
- address, and we are switching to create a non-stmt line, then
- discard the new line. */
- bool file_changed
- = m_last_subfile != m_cu->get_builder ()->get_current_subfile ();
- bool ignore_this_line
- = ((file_changed && !end_sequence && m_last_address == m_address
- && ((m_flags & LEF_IS_STMT) == 0)
- && m_stmt_at_address)
- || (!end_sequence && m_line == 0));
-
- if ((file_changed && !ignore_this_line) || end_sequence)
+ else if ((m_op_index == 0 && m_line != 0) || end_sequence)
+ {
+ if (m_last_subfile != m_cu->get_builder ()->get_current_subfile ()
+ || end_sequence)
{
dwarf_finish_line (m_gdbarch, m_last_subfile, m_address,
- m_currently_recording_lines ? m_cu : nullptr);
+ m_currently_recording_lines ? m_cu : nullptr,
+ end_sequence || (m_flags & LEF_IS_STMT) != 0);
}
- if (!end_sequence && !ignore_this_line)
+ if (!end_sequence)
{
linetable_entry_flags lte_flags = m_flags;
if (producer_is_codewarrior (m_cu))
@@ -18291,15 +18252,6 @@ lnp_state_machine::record_line (bool end_sequence)
m_last_line = m_line;
}
}
-
- /* Track whether we have seen any IS_STMT true at m_address in case we
- have multiple line table entries all at m_address. */
- if (m_last_address != m_address)
- {
- m_stmt_at_address = false;
- m_last_address = m_address;
- }
- m_stmt_at_address |= (m_flags & LEF_IS_STMT) != 0;
}
lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
@@ -18313,8 +18265,7 @@ lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
This is currently used by MIPS code,
cf. `mips_adjust_dwarf2_line'. */
m_address ((unrelocated_addr) gdbarch_adjust_dwarf2_line (arch, 0, 0)),
- m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0),
- m_last_address (m_address)
+ m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0)
{
}
@@ -996,7 +996,8 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
if (sym->aclass () == LOC_BLOCK)
{
const block *block = sym->value_block ();
- if (block->end () < tp->control.step_range_end)
+ if (block->end () < tp->control.step_range_end
+ && block->end () > tp->control.step_range_start)
tp->control.step_range_end = block->end ();
}
}
@@ -8114,6 +8114,31 @@ process_event_stop_test (struct execution_control_state *ecs)
return;
}
+ /* Handle the case when subroutines have multiple ranges.
+ When we step from one part to the next part of the same subroutine,
+ all subroutine levels are skipped again which begin here.
+ Compensate for this by removing all skipped subroutines,
+ which were already executing from the user's perspective. */
+
+ if (get_stack_frame_id (frame)
+ == ecs->event_thread->control.step_stack_frame_id
+ && inline_skipped_frames (ecs->event_thread)
+ && ecs->event_thread->control.step_frame_id.artificial_depth > 0
+ && ecs->event_thread->control.step_frame_id.code_addr_p)
+ {
+ const struct block *prev, *curr;
+ int depth = 0;
+ prev = block_for_pc (ecs->event_thread->control.step_frame_id.code_addr);
+ curr = block_for_pc (ecs->event_thread->stop_pc ());
+ while (curr && curr->inlined_p () && !curr->contains (prev))
+ {
+ depth ++;
+ curr = curr->superblock ();
+ }
+ while (inline_skipped_frames (ecs->event_thread) > depth)
+ step_into_inline_frame (ecs->event_thread);
+ }
+
/* Look for "calls" to inlined functions, part one. If the inline
frame machinery detected some skipped call sites, we have entered
a new inline function. */
@@ -8172,6 +8197,8 @@ process_event_stop_test (struct execution_control_state *ecs)
infrun_debug_printf ("stepping through inlined function");
if (ecs->event_thread->control.step_over_calls == STEP_OVER_ALL
+ || ecs->event_thread->stop_pc () != stop_pc_sal.pc
+ || !stop_pc_sal.is_stmt
|| inline_frame_is_marked_for_skip (false, ecs->event_thread))
keep_going (ecs);
else
@@ -8220,7 +8247,8 @@ process_event_stop_test (struct execution_control_state *ecs)
end_stepping_range (ecs);
return;
}
- else if (*curr_frame_id == original_frame_id)
+ else if (get_stack_frame_id (frame)
+ == ecs->event_thread->control.step_stack_frame_id)
{
/* We are not at the start of a statement, and we have not changed
frame.
@@ -3290,7 +3290,10 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
0) instead of a real line. */
if (prev && prev->line
- && (!best || prev->unrelocated_pc () > best->unrelocated_pc ()))
+ && (!best || prev->unrelocated_pc () > best->unrelocated_pc ()
+ || (prev->unrelocated_pc () == best->unrelocated_pc ()
+ && (best->pc (objfile) == pc
+ ? !best->is_stmt : best->is_weak))))
{
best = prev;
best_symtab = iter_s;
@@ -3309,7 +3312,7 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
&& (tmp - 1)->unrelocated_pc () == tmp->unrelocated_pc ()
&& (tmp - 1)->line != 0 && !tmp->is_stmt)
--tmp;
- if (tmp->is_stmt)
+ if (tmp->is_stmt && (tmp->pc (objfile) == pc || !tmp->is_weak))
best = tmp;
}
@@ -3333,18 +3336,14 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
We used to return alt->line - 1 here, but that could be
anywhere; if we don't have line number info for this PC,
don't make some up. */
- val.pc = pc;
- }
- else if (best->line == 0)
- {
- /* If our best fit is in a range of PC's for which no line
- number info is available (line number is zero) then we didn't
- find any valid line information. */
+ if (notcurrent)
+ pc++;
val.pc = pc;
}
else
{
val.is_stmt = best->is_stmt;
+ val.is_weak = best->is_weak;
val.symtab = best_symtab;
val.line = best->line;
val.pc = best->pc (objfile);
new file mode 100644
@@ -0,0 +1,39 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 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/>. */
+
+static int test0 (void)
+{
+ asm (""); /* line 20 */
+ return 1; /* line 21 */
+}
+
+int __attribute__((noinline, noclone))
+#ifdef __CET__
+ __attribute__((nocf_check))
+#endif
+test1 (int x)
+{
+ asm ("");
+ return x+1; /* line 31 */
+}
+
+int
+main()
+{ test1 (test0 ()); /* line 36 */
+ test1 (test0 ()); /* line 37 */
+ return 0; /* line 38 */
+}
new file mode 100644
@@ -0,0 +1,56 @@
+# Copyright 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/>.
+
+standard_testfile .c
+
+if [get_compiler_info] {
+ return -1
+}
+
+if { [test_compiler_info gcc*] && ![supports_statement_frontiers] } {
+ return -1
+}
+
+global srcfile testfile
+
+set options {debug nowarnings optimize=-O2}
+if { [supports_statement_frontiers] } {
+ lappend options additional_flags=-gstatement-frontiers
+}
+
+if { [prepare_for_testing "failed to prepare" $binfile \
+ $srcfile $options] } {
+ return -1
+}
+
+if ![runto_main] {
+ return
+}
+
+gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:36.*" "in main"
+gdb_test_multiple "step" "step into test0" {
+ -re ".*test0.*${srcfile}:20.*$::gdb_prompt $" {
+ gdb_test "step" ".*line 21.*" $gdb_test_name
+ }
+ -re ".*test0.*${srcfile}:21.*$::gdb_prompt $" {
+ pass $gdb_test_name
+ }
+}
+gdb_test "frame 1" "\\s*\\#1\\s+main.*${srcfile}:36.*" "frame1"
+gdb_test "step" ".*test1.*${srcfile}:31.*" "step into test1"
+gdb_test "frame 1" "\\s*\\#1.*in main.*${srcfile}:36.*" "frame2"
+gdb_test "step" ".*main.*${srcfile}:37.*" "step back to main"
+gdb_test "next" ".*return 0;.*" "step over test0+1"
+gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:38.*" "in main again"
new file mode 100644
@@ -0,0 +1,33 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 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/>. */
+
+/* PR 25987 */
+struct MyClass;
+struct ptr {
+ MyClass* get() { return t; } /* line 21 */
+ MyClass* t;
+};
+struct MyClass { void call(); };
+void MyClass::call() {
+ *(volatile char*)-1 = 1; /* line 26 */
+}
+static void intermediate(ptr p) {
+ p.get()->call(); /* line 29 */
+}
+int main() {
+ intermediate(ptr{new MyClass});
+}
new file mode 100644
@@ -0,0 +1,50 @@
+# Copyright 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/>.
+
+# PR 25987
+standard_testfile .cc
+
+if {![supports_statement_frontiers]} {
+ return -1
+}
+
+set options {c++ debug nowarnings optimize=-Og}
+lappend options additional_flags=-gstatement-frontiers
+if { [prepare_for_testing "failed to prepare" $testfile \
+ $srcfile $options] } {
+ return -1
+}
+
+if ![runto_main] {
+ return
+}
+
+gdb_test "bt" "\\s*\\#0\\s+main.*" "in main"
+#break at the empty inline function ptr::get
+gdb_test "b get" ".*" "break at get"
+gdb_test "c" ".*" "continue to get"
+#call frame 1 is at line 29
+gdb_test "bt" [multi_line "\\s*\\#0\\s+ptr::get\[^\r\]*${srcfile}:21" \
+ "\\s*\\#1\\s+intermediate\[^\r\]*${srcfile}:29" \
+ ".*"] \
+ "at get"
+#print a local value here
+gdb_test "p t" ".*(\\\$1 = \\(MyClass \\*\\) 0x|value has been optimized out).*" "print t"
+gdb_test "c" ".*SIGSEGV.*" "continue to SIGSEGV"
+#call frame 1 is at line 29
+gdb_test "bt" [multi_line "\\s*\\#0\\s+\[^\r\]*MyClass::call\[^\r\]*${srcfile}:26" \
+ "\\s*\\#1\\s+0x\[^\r\]*intermediate\[^\r\]*${srcfile}:29" \
+ ".*"] \
+ "at call"
@@ -47,8 +47,7 @@ tree_check (tree *t, int i)
int __attribute__((noinline, noclone))
get_alias_set (tree *t)
-{
- if (t != NULL
+{ if (t != NULL
&& TREE_TYPE (t).z != 1
&& TREE_TYPE (t).z != 2
&& TREE_TYPE (t).z != 3)
@@ -60,7 +59,6 @@ tree xx;
int
main()
-{
- get_alias_set (&xx); /* Beginning of main */
+{ get_alias_set (&xx);
return 0;
} // main
@@ -15,7 +15,7 @@
standard_testfile .cc
-if {[test_compiler_info gcc*] && ![supports_statement_frontiers] } {
+if { [test_compiler_info gcc*] && ![supports_statement_frontiers] } {
return -1
}
@@ -24,13 +24,6 @@ if {[test_compiler_info gcc*] && ![supports_statement_frontiers] } {
proc do_test { use_header } {
global srcfile testfile
- if { $use_header } {
- # This test will not pass due to poor debug information
- # generated by GCC (at least upto 10.x). See
- # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94474
- return
- }
-
set options {c++ debug nowarnings optimize=-O2}
if { [supports_statement_frontiers] } {
lappend options additional_flags=-gstatement-frontiers
@@ -53,128 +46,29 @@ proc do_test { use_header } {
with_test_prefix $prefix {
- set main_location [gdb_get_line_number "Beginning of main" $srcfile]
-
- if ![runto $main_location qualified] {
+ if ![runto_main] {
return
}
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main"
- set line1 {\t\{}
- set line2 {\t if \(t != NULL}
- gdb_test_multiple "step" "step into get_alias_set" {
- -re -wrap $line1 {
- gdb_test "next" $line2 $gdb_test_name
- }
- -re -wrap $line2 {
- pass $gdb_test_name
- }
- }
+ gdb_test "step" ".*" "step into get_alias_set"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 1"
-
- # It's possible that this first failure (when not using a header
- # file) is GCC's fault, though the remaining failures would best
- # be fixed by adding location views support (though it could be
- # that some easier heuristic could be figured out). Still, it is
- # not certain that the first failure wouldn't also be fixed by
- # having location view support, so for now it is tagged as such.
- set have_kfail [expr [test_compiler_info gcc*] && !$use_header]
-
- set ok 1
- gdb_test_multiple "next" "next step 1" {
- -re -wrap "if \\(t->x != i\\)" {
- set ok 0
- send_gdb "next\n"
- exp_continue
- }
- -re -wrap ".*TREE_TYPE.* != 1" {
- if { $ok } {
- pass $gdb_test_name
- } else {
- if { $have_kfail } {
- setup_kfail "*-*-*" symtab/25507
- }
- fail $gdb_test_name
- }
- }
- }
+ gdb_test "next" ".*TREE_TYPE.*" "next step 1"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 2"
-
- set ok 1
- gdb_test_multiple "next" "next step 2" {
- -re -wrap "return x;" {
- set ok 0
- send_gdb "next\n"
- exp_continue
- }
- -re -wrap ".*TREE_TYPE.* != 2" {
- if { $ok } {
- pass $gdb_test_name
- } else {
- if { $have_kfail } {
- setup_kfail "*-*-*" symtab/25507
- }
- fail $gdb_test_name
- }
- }
- }
+ gdb_test "next" ".*TREE_TYPE.*" "next step 2"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 3"
-
- set ok 1
- gdb_test_multiple "next" "next step 3" {
- -re -wrap "return x;" {
- set ok 0
- send_gdb "next\n"
- exp_continue
- }
- -re -wrap ".*TREE_TYPE.* != 3\\)" {
- if { $ok } {
- pass $gdb_test_name
- } else {
- if { $have_kfail } {
- setup_kfail "*-*-*" symtab/25507
- }
- fail $gdb_test_name
- }
- }
- }
+ gdb_test "next" ".*TREE_TYPE.*" "next step 3"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 4"
-
- set ok 1
- gdb_test_multiple "next" "next step 4" {
- -re -wrap "(if \\(t != NULL|\} // get_alias_set)" {
- send_gdb "next\n"
- exp_continue
- }
- -re -wrap "return x;" {
- set ok 0
- send_gdb "next\n"
- exp_continue
- }
- -re -wrap "return 0.*" {
- if { $ok } {
- pass $gdb_test_name
- } else {
- if { $have_kfail } {
- setup_kfail "*-*-*" symtab/25507
- }
- fail $gdb_test_name
- }
- }
- }
+ gdb_test "next" ".*" "next step 4"
gdb_test "bt" \
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
"not in inline 5"
- if {!$use_header} {
- # With the debug from GCC 10.x (and earlier) GDB is currently
- # unable to successfully complete the following tests when we
- # are not using a header file.
- kfail symtab/25507 "stepping tests"
+ if ![test_compiler_info gcc*] {
return
}
@@ -194,22 +88,84 @@ proc do_test { use_header } {
gdb_test "step" ".*if \\(t->x != i\\).*" "step 2"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 1 pass 2"
- gdb_test "step" ".*TREE_TYPE.*" "step 3"
+ gdb_test "step" ".*return x.*" "step 3"
+ gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
+ "return from inline 1 pass 2"
+ gdb_test "step" ".*TREE_TYPE.*" "step 4"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 2 pass 2"
- gdb_test "step" ".*if \\(t->x != i\\).*" "step 4"
+ gdb_test "step" ".*if \\(t->x != i\\).*" "step 5"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 2 pass 2"
- gdb_test "step" ".*TREE_TYPE.*" "step 5"
+ gdb_test "step" ".*return x.*" "step 6"
+ gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
+ "return from inline 2 pass 2"
+ gdb_test "step" ".*TREE_TYPE.*" "step 7"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 3 pass 2"
- gdb_test "step" ".*if \\(t->x != i\\).*" "step 6"
+ gdb_test "step" ".*if \\(t->x != i\\).*" "step 8"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 3 pass 2"
- gdb_test "step" "return 0.*" "step 7"
+ gdb_test "step" ".*return x.*" "step 9"
+ gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
+ "return from inline 3 pass 2"
+ gdb_test "step" "return 0.*" "step 10"
gdb_test "bt" \
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
"not in inline 4 pass 2"
+
+ clean_restart ${executable}
+
+ if ![runto_main] {
+ return
+ }
+
+ gdb_test "bt" "\\s*\\#0\\s+main.*" "in main pass 3"
+ gdb_test "step" ".*" "step into get_alias_set pass 3"
+ gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+ "in get_alias_set pass 3"
+ gdb_test "step" ".*TREE_TYPE.*" "step 1 pass 3"
+ gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+ "not in inline 1 pass 3"
+ gdb_test "step" ".*if \\(t->x != i\\).*" "step 2 pass 3"
+ gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
+ "in inline 1 pass 3"
+ gdb_test_multiple "p t->x = 2" "change value pass 3" {
+ -re ".*value has been optimized out.*$::gdb_prompt $" {
+ gdb_test "p xx.x = 2" ".* = 2.*" $gdb_test_name
+ }
+ -re ".* = 2.*$::gdb_prompt $" {
+ pass $gdb_test_name
+ }
+ }
+ gdb_test "step" ".*abort.*" "step 3, pass 3"
+ gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
+ "abort from inline 1 pass 3"
+
+ clean_restart ${executable}
+
+ if ![runto_main] {
+ return
+ }
+
+ gdb_test "bt" "\\s*\\#0\\s+main.*" "in main pass 4"
+ gdb_test "skip tree_check" ".*" "skip tree_check pass 4"
+ gdb_test "step" ".*" "step into get_alias_set pass 4"
+ gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+ "in get_alias_set pass 4"
+ gdb_test "step" ".*TREE_TYPE.*" "step 1 pass 4"
+ gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+ "not in inline 1 pass 4"
+ gdb_test "step" ".*TREE_TYPE.*" "step 2 pass 4"
+ gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+ "not in inline 2 pass 4"
+ gdb_test "step" ".*TREE_TYPE.*" "step 3 pass 4"
+ gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+ "not in inline 3 pass 4"
+ gdb_test "step" "return 0.*" "step 4 pass 4"
+ gdb_test "bt" \
+ "\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
+ "not in inline 4 pass 4"
}
}