[RFC] Handle jumping back to first instruction in line
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 |
fail
|
Test failed
|
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 |
fail
|
Test failed
|
Commit Message
Consider test-case test.c:
...
1 int count{0};
2
3 bool f(int& w)
4 {
5 bool result = ++count != 42;
6 return result;
7 }
8
9 int main()
10 {
11 int n;
12 while (f(n))
13 ;
14 return n;
15 }
...
compiled with debuginfo:
...
$ g++ -g test.c
...
Using next to step out of function f also steps out of the loop:
...
$ gdb -q a.out -ex start -ex step
Reading symbols from a.out...
Temporary breakpoint 1 at 0x4004fb: file test.c, line 12.
Starting program: a.out
Temporary breakpoint 1, main () at test.c:12
12 while (f(n))
f (w=@0x7fffffffdc7c: 0) at test.c:5
5 bool result = ++count != 42;
(gdb) next
6 return result;
(gdb) next
7 }
(gdb) next
main () at test.c:14
14 return n;
(gdb)
...
To understand what happens, the line table for main is:
...
CU: test.c:
File name Line number Starting address View Stmt
test.c 10 0x4004f3 x
test.c 12 0x4004fb x
test.c 14 0x40050d x
test.c 15 0x400510 x
test.c - 0x400512
...
That info allows us to annotate main like so:
...
00000000004004f3 <main>:
line 10:
4004f3: 55 push %rbp
4004f4: 48 89 e5 mov %rsp,%rbp
4004f7: 48 83 ec 10 sub $0x10,%rsp
line 12:
4004fb: 48 8d 45 fc lea -0x4(%rbp),%rax
4004ff: 48 89 c7 mov %rax,%rdi
400502: e8 c0 ff ff ff call 4004c7 <_Z1fRi>
400507: 84 c0 test %al,%al
400509: 74 02 je 40050d <main+0x1a>
40050b: eb ee jmp 4004fb <main+0x8>
14:
40050d: 8b 45 fc mov -0x4(%rbp),%eax
15:
400510: c9 leave
400511: c3 ret
...
When stepping out of function f, ptrace single-stepping stops at pcs 0x400507,
0x400509, 0x40050b and 0x4004fb where it re-enters the loop.
The semantics of next prescribe that it "only stops at the first instruction
of a source line", which would suggest that gdb would stop there, but
apparently it doesn't.
Fix this by handling this case in process_event_stop_test, such that we get
instead:
...
(gdb) next
6 return result;
(gdb) next
7 }
(gdb) next
main () at test.c:12
12 while (f(n))
(gdb)
...
In three test-cases we rely on "while (1);" to hang when stepping into it,
which is no longer the case, so use "for (int i = 0; ; ++i)" instead.
Also a fix in gdbserver is needed, in case range stepping is on.
Tested on x86_64-linux, target board unix and native-gdbserver.
PR gdb/32000
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32000
---
gdb/infrun.c | 3 +
gdb/testsuite/gdb.dwarf2/dw2-insn-loop.c | 25 +++++
gdb/testsuite/gdb.dwarf2/dw2-insn-loop.exp | 91 +++++++++++++++++++
.../step-bg-decr-pc-switch-thread.c | 2 +-
.../step-bg-decr-pc-switch-thread.exp | 2 +-
.../step-over-lands-on-breakpoint.c | 2 +-
.../step-over-trips-on-watchpoint.c | 2 +-
gdbserver/linux-low.cc | 2 +-
8 files changed, 124 insertions(+), 5 deletions(-)
create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-insn-loop.c
create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-insn-loop.exp
base-commit: 85402ee9edae1be4a3efd53b14826ab6b1061ae4
Comments
On 7/23/24 14:21, Tom de Vries wrote:
> Consider test-case test.c:
> ...
> 1 int count{0};
> 2
> 3 bool f(int& w)
> 4 {
> 5 bool result = ++count != 42;
> 6 return result;
> 7 }
> 8
> 9 int main()
> 10 {
> 11 int n;
> 12 while (f(n))
> 13 ;
> 14 return n;
> 15 }
> ...
> compiled with debuginfo:
> ...
> $ g++ -g test.c
> ...
>
> Using next to step out of function f also steps out of the loop:
> ...
> $ gdb -q a.out -ex start -ex step
> Reading symbols from a.out...
> Temporary breakpoint 1 at 0x4004fb: file test.c, line 12.
> Starting program: a.out
>
> Temporary breakpoint 1, main () at test.c:12
> 12 while (f(n))
> f (w=@0x7fffffffdc7c: 0) at test.c:5
> 5 bool result = ++count != 42;
> (gdb) next
> 6 return result;
> (gdb) next
> 7 }
> (gdb) next
> main () at test.c:14
> 14 return n;
> (gdb)
> ...
>
> To understand what happens, the line table for main is:
> ...
> CU: test.c:
> File name Line number Starting address View Stmt
> test.c 10 0x4004f3 x
> test.c 12 0x4004fb x
> test.c 14 0x40050d x
> test.c 15 0x400510 x
> test.c - 0x400512
> ...
>
> That info allows us to annotate main like so:
> ...
> 00000000004004f3 <main>:
> line 10:
> 4004f3: 55 push %rbp
> 4004f4: 48 89 e5 mov %rsp,%rbp
> 4004f7: 48 83 ec 10 sub $0x10,%rsp
> line 12:
> 4004fb: 48 8d 45 fc lea -0x4(%rbp),%rax
> 4004ff: 48 89 c7 mov %rax,%rdi
> 400502: e8 c0 ff ff ff call 4004c7 <_Z1fRi>
> 400507: 84 c0 test %al,%al
> 400509: 74 02 je 40050d <main+0x1a>
> 40050b: eb ee jmp 4004fb <main+0x8>
> 14:
> 40050d: 8b 45 fc mov -0x4(%rbp),%eax
> 15:
> 400510: c9 leave
> 400511: c3 ret
> ...
>
> When stepping out of function f, ptrace single-stepping stops at pcs 0x400507,
> 0x400509, 0x40050b and 0x4004fb where it re-enters the loop.
>
> The semantics of next prescribe that it "only stops at the first instruction
> of a source line", which would suggest that gdb would stop there, but
> apparently it doesn't.
>
> Fix this by handling this case in process_event_stop_test, such that we get
> instead:
> ...
> (gdb) next
> 6 return result;
> (gdb) next
> 7 }
> (gdb) next
> main () at test.c:12
> 12 while (f(n))
> (gdb)
> ...
>
> In three test-cases we rely on "while (1);" to hang when stepping into it,
> which is no longer the case, so use "for (int i = 0; ; ++i)" instead.
>
I ran into one more such test-case on aarch64-linux, reported by the
linaro CI.
Submitted a v2 (
https://sourceware.org/pipermail/gdb-patches/2024-July/210730.html ).
Thanks,
- Tom
> Also a fix in gdbserver is needed, in case range stepping is on.
>
> Tested on x86_64-linux, target board unix and native-gdbserver.
>
> PR gdb/32000
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32000
> ---
> gdb/infrun.c | 3 +
> gdb/testsuite/gdb.dwarf2/dw2-insn-loop.c | 25 +++++
> gdb/testsuite/gdb.dwarf2/dw2-insn-loop.exp | 91 +++++++++++++++++++
> .../step-bg-decr-pc-switch-thread.c | 2 +-
> .../step-bg-decr-pc-switch-thread.exp | 2 +-
> .../step-over-lands-on-breakpoint.c | 2 +-
> .../step-over-trips-on-watchpoint.c | 2 +-
> gdbserver/linux-low.cc | 2 +-
> 8 files changed, 124 insertions(+), 5 deletions(-)
> create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-insn-loop.c
> create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-insn-loop.exp
>
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index 06b454bf78f..7bac4a8b9ba 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -7695,6 +7695,9 @@ process_event_stop_test (struct execution_control_state *ecs)
> && stop_pc != ecs->stop_func_start
> && execution_direction == EXEC_REVERSE)
> end_stepping_range (ecs);
> + else if (stop_pc == ecs->event_thread->control.step_range_start
> + && execution_direction == EXEC_FORWARD)
> + end_stepping_range (ecs);
> else
> keep_going (ecs);
>
> diff --git a/gdb/testsuite/gdb.dwarf2/dw2-insn-loop.c b/gdb/testsuite/gdb.dwarf2/dw2-insn-loop.c
> new file mode 100644
> index 00000000000..f5454cf5fc9
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/dw2-insn-loop.c
> @@ -0,0 +1,25 @@
> +/*
> + 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/>. */
> +
> +int
> +main (void)
> +{ /* main_start. */
> + loop_label:
> + asm ("main_label: .globl main_label");
> + goto loop_label;
> +
> + return 0;
> +}
> diff --git a/gdb/testsuite/gdb.dwarf2/dw2-insn-loop.exp b/gdb/testsuite/gdb.dwarf2/dw2-insn-loop.exp
> new file mode 100644
> index 00000000000..cab078d5f65
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/dw2-insn-loop.exp
> @@ -0,0 +1,91 @@
> +# 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/>.
> +
> +# Check that stepping over an insn containing a loop to the start of that insn
> +# stops at the start.
> +
> +load_lib dwarf.exp
> +
> +require dwarf2_support
> +
> +standard_testfile .c .S
> +
> +lassign [function_range main $srcdir/$subdir/$srcfile] \
> + main_start main_len
> +
> +set main_start_line [gdb_get_line_number "main_start."]
> +
> +set loop_label_line [gdb_get_line_number "loop_label:"]
> +
> +set asm_file [standard_output_file $srcfile2]
> +
> +Dwarf::assemble $asm_file {
> + declare_labels lines_unit
> +
> + cu {} {
> + DW_TAG_compile_unit {
> + {DW_AT_name $::srcfile}
> + {DW_AT_stmt_list $lines_unit DW_FORM_sec_offset}
> + } {
> + declare_labels int_type
> +
> + int_type: DW_TAG_base_type {
> + { DW_AT_byte_size 4 DW_FORM_sdata }
> + { DW_AT_encoding @DW_ATE_signed }
> + { DW_AT_name int }
> + }
> +
> + DW_TAG_subprogram {
> + { DW_AT_name main }
> + { DW_AT_low_pc $::main_start DW_FORM_addr }
> + { DW_AT_high_pc "$::main_start + $::main_len" DW_FORM_addr }
> + { type :$int_type }
> + }
> + }
> + }
> +
> + lines {} lines_unit {
> + file_name $::srcfile 1
> + program {
> + DW_LNE_set_address $::main_start
> + line $::main_start_line
> + DW_LNS_copy
> +
> + DW_LNE_set_address main_label
> + line $::loop_label_line
> + DW_LNS_copy
> +
> + DW_LNE_set_address "$::main_start + $::main_len"
> + DW_LNE_end_sequence
> + }
> + }
> +}
> +
> +if { [prepare_for_testing "failed to prepare" $testfile \
> + [list $srcfile $asm_file] {nodebug}] } {
> + return
> +}
> +
> +if {![runto_main]} {
> + return
> +}
> +
> +delete_breakpoints
> +
> +# Regression test for PR32000. This used to hang, but it should stop at the
> +# first insn of the line.
> +gdb_test "next" "loop_label:"
> diff --git a/gdb/testsuite/gdb.threads/step-bg-decr-pc-switch-thread.c b/gdb/testsuite/gdb.threads/step-bg-decr-pc-switch-thread.c
> index 71cbdc0f49a..0658d164352 100644
> --- a/gdb/testsuite/gdb.threads/step-bg-decr-pc-switch-thread.c
> +++ b/gdb/testsuite/gdb.threads/step-bg-decr-pc-switch-thread.c
> @@ -32,7 +32,7 @@ void *
> thread_function (void *arg)
> {
> NOP; /* set breakpoint here */
> - while (1);
> + for (int i = 0; ; ++i);
> }
>
> int
> diff --git a/gdb/testsuite/gdb.threads/step-bg-decr-pc-switch-thread.exp b/gdb/testsuite/gdb.threads/step-bg-decr-pc-switch-thread.exp
> index 7726c7f1d47..1a830e5945c 100644
> --- a/gdb/testsuite/gdb.threads/step-bg-decr-pc-switch-thread.exp
> +++ b/gdb/testsuite/gdb.threads/step-bg-decr-pc-switch-thread.exp
> @@ -48,7 +48,7 @@ gdb_breakpoint [gdb_get_line_number "set breakpoint here"]
> gdb_continue_to_breakpoint "run to nop breakpoint"
> gdb_test "info threads" " 1 .*\\\* 2 .*" "info threads shows all threads"
>
> -gdb_test "next" "while.*" "next over nop"
> +gdb_test "next" "for.*" "next over nop"
>
> gdb_test_no_output "next&" "next& over inf loop"
>
> diff --git a/gdb/testsuite/gdb.threads/step-over-lands-on-breakpoint.c b/gdb/testsuite/gdb.threads/step-over-lands-on-breakpoint.c
> index c467e512b31..a26ee67a6bf 100644
> --- a/gdb/testsuite/gdb.threads/step-over-lands-on-breakpoint.c
> +++ b/gdb/testsuite/gdb.threads/step-over-lands-on-breakpoint.c
> @@ -60,7 +60,7 @@ main ()
> done with displaced stepping on a target that is always in
> non-stop mode, as in that case GDB runs both threads
> simultaneously. */
> - while (1); /* set wait-thread breakpoint here */
> + for (int k = 0; ; k++); /* set wait-thread breakpoint here */
>
> pthread_join (child_thread, NULL);
>
> diff --git a/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c b/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c
> index 1266530a245..5e5ab171dc6 100644
> --- a/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c
> +++ b/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c
> @@ -62,7 +62,7 @@ main ()
> done with displaced stepping on a target that is always in
> non-stop mode, as in that case GDB runs both threads
> simultaneously. */
> - while (1); /* set wait-thread breakpoint here */
> + for (int k = 0; ; k++); /* set wait-thread breakpoint here */
>
> pthread_join (child_thread, NULL);
>
> diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
> index 266c7de8fb8..39fd3316315 100644
> --- a/gdbserver/linux-low.cc
> +++ b/gdbserver/linux-low.cc
> @@ -330,7 +330,7 @@ lwp_in_step_range (struct lwp_info *lwp)
> {
> CORE_ADDR pc = lwp->stop_pc;
>
> - return (pc >= lwp->step_range_start && pc < lwp->step_range_end);
> + return (pc > lwp->step_range_start && pc < lwp->step_range_end);
> }
>
> /* The event pipe registered as a waitable file in the event loop. */
>
> base-commit: 85402ee9edae1be4a3efd53b14826ab6b1061ae4
@@ -7695,6 +7695,9 @@ process_event_stop_test (struct execution_control_state *ecs)
&& stop_pc != ecs->stop_func_start
&& execution_direction == EXEC_REVERSE)
end_stepping_range (ecs);
+ else if (stop_pc == ecs->event_thread->control.step_range_start
+ && execution_direction == EXEC_FORWARD)
+ end_stepping_range (ecs);
else
keep_going (ecs);
new file mode 100644
@@ -0,0 +1,25 @@
+/*
+ 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/>. */
+
+int
+main (void)
+{ /* main_start. */
+ loop_label:
+ asm ("main_label: .globl main_label");
+ goto loop_label;
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,91 @@
+# 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/>.
+
+# Check that stepping over an insn containing a loop to the start of that insn
+# stops at the start.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+
+standard_testfile .c .S
+
+lassign [function_range main $srcdir/$subdir/$srcfile] \
+ main_start main_len
+
+set main_start_line [gdb_get_line_number "main_start."]
+
+set loop_label_line [gdb_get_line_number "loop_label:"]
+
+set asm_file [standard_output_file $srcfile2]
+
+Dwarf::assemble $asm_file {
+ declare_labels lines_unit
+
+ cu {} {
+ DW_TAG_compile_unit {
+ {DW_AT_name $::srcfile}
+ {DW_AT_stmt_list $lines_unit DW_FORM_sec_offset}
+ } {
+ declare_labels int_type
+
+ int_type: DW_TAG_base_type {
+ { DW_AT_byte_size 4 DW_FORM_sdata }
+ { DW_AT_encoding @DW_ATE_signed }
+ { DW_AT_name int }
+ }
+
+ DW_TAG_subprogram {
+ { DW_AT_name main }
+ { DW_AT_low_pc $::main_start DW_FORM_addr }
+ { DW_AT_high_pc "$::main_start + $::main_len" DW_FORM_addr }
+ { type :$int_type }
+ }
+ }
+ }
+
+ lines {} lines_unit {
+ file_name $::srcfile 1
+ program {
+ DW_LNE_set_address $::main_start
+ line $::main_start_line
+ DW_LNS_copy
+
+ DW_LNE_set_address main_label
+ line $::loop_label_line
+ DW_LNS_copy
+
+ DW_LNE_set_address "$::main_start + $::main_len"
+ DW_LNE_end_sequence
+ }
+ }
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile \
+ [list $srcfile $asm_file] {nodebug}] } {
+ return
+}
+
+if {![runto_main]} {
+ return
+}
+
+delete_breakpoints
+
+# Regression test for PR32000. This used to hang, but it should stop at the
+# first insn of the line.
+gdb_test "next" "loop_label:"
@@ -32,7 +32,7 @@ void *
thread_function (void *arg)
{
NOP; /* set breakpoint here */
- while (1);
+ for (int i = 0; ; ++i);
}
int
@@ -48,7 +48,7 @@ gdb_breakpoint [gdb_get_line_number "set breakpoint here"]
gdb_continue_to_breakpoint "run to nop breakpoint"
gdb_test "info threads" " 1 .*\\\* 2 .*" "info threads shows all threads"
-gdb_test "next" "while.*" "next over nop"
+gdb_test "next" "for.*" "next over nop"
gdb_test_no_output "next&" "next& over inf loop"
@@ -60,7 +60,7 @@ main ()
done with displaced stepping on a target that is always in
non-stop mode, as in that case GDB runs both threads
simultaneously. */
- while (1); /* set wait-thread breakpoint here */
+ for (int k = 0; ; k++); /* set wait-thread breakpoint here */
pthread_join (child_thread, NULL);
@@ -62,7 +62,7 @@ main ()
done with displaced stepping on a target that is always in
non-stop mode, as in that case GDB runs both threads
simultaneously. */
- while (1); /* set wait-thread breakpoint here */
+ for (int k = 0; ; k++); /* set wait-thread breakpoint here */
pthread_join (child_thread, NULL);
@@ -330,7 +330,7 @@ lwp_in_step_range (struct lwp_info *lwp)
{
CORE_ADDR pc = lwp->stop_pc;
- return (pc >= lwp->step_range_start && pc < lwp->step_range_end);
+ return (pc > lwp->step_range_start && pc < lwp->step_range_end);
}
/* The event pipe registered as a waitable file in the event loop. */