From df63178325a5705a609bff94a349162bbb839895 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Thu, 27 Aug 2020 21:03:53 +0100 Subject: [PATCH] Fix advance/until and multiple locations (PR gdb/26524) If you do "advance LINESPEC", and LINESPEC expands to more than one location, GDB just errors out: if (sals.size () != 1) error (_("Couldn't get information on specified line.")); For example, advancing to a line in an inlined function, inlined three times: (gdb) b 21 Breakpoint 1 at 0x55555555516f: advance.cc:21. (3 locations) (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 1.1 y 0x000055555555516f in inline_func at advance.cc:21 1.2 y 0x000055555555517e in inline_func at advance.cc:21 1.3 y 0x000055555555518d in inline_func at advance.cc:21 (gdb) advance 21 Couldn't get information on specified line. (gdb) Similar issue with the "until" command, as it shares the implementation with "advance". Since, as the comment in gdb.base/advance.exp says, "advance " is really just syntactic sugar for "tbreak ;continue", fix this by making GDB insert a breakpoint at all the resolved locations. A new testcase is included, which exercises both "advance" and "until", in two different cases expanding to multiple locations: - inlined functions - C++ overloads This also exercises the inline frames issue fixed by the previous patch. gdb/ChangeLog: PR gdb/26524 * breakpoint.c (until_break_fsm) : Delete fields. : New field. : Adjust to save a breakpoint vector instead of two individual breakpoints. (until_break_fsm::should_stop): Loop over breakpoints in the breakpoint vector. (until_break_fsm::clean_up): Adjust to clear the breakpoints vector. (until_break_command): Handle location expanding into multiple sals. gdb/testsuite/ChangeLog: PR gdb/26523 PR gdb/26524 * gdb.base/advance-until-multiple-locations.cc: New. * gdb.base/advance-until-multiple-locations.exp: New. --- gdb/ChangeLog | 15 ++ gdb/breakpoint.c | 77 +++++----- gdb/testsuite/ChangeLog | 7 + .../advance-until-multiple-locations.cc | 61 ++++++++ .../advance-until-multiple-locations.exp | 142 ++++++++++++++++++ 5 files changed, 261 insertions(+), 41 deletions(-) create mode 100644 gdb/testsuite/gdb.base/advance-until-multiple-locations.cc create mode 100644 gdb/testsuite/gdb.base/advance-until-multiple-locations.exp diff --git a/gdb/ChangeLog b/gdb/ChangeLog index d9cb0b17643..0a1b55fb69a 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,18 @@ +2020-08-27 Pedro Alves + + PR gdb/26524 + * breakpoint.c (until_break_fsm) : Delete fields. + : New field. + : Adjust to save a breakpoint vector instead of + two individual breakpoints. + (until_break_fsm::should_stop): Loop over breakpoints in the + breakpoint vector. + (until_break_fsm::clean_up): Adjust to clear the breakpoints + vector. + (until_break_command): Handle location expanding into multiple + sals. + 2020-08-27 Pedro Alves PR gdb/26523 diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index ef8e54f6344..8f75618bc9d 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -10939,20 +10939,15 @@ struct until_break_fsm : public thread_fsm /* The thread that was current when the command was executed. */ int thread; - /* The breakpoint set at the destination location. */ - breakpoint_up location_breakpoint; - - /* Breakpoint set at the return address in the caller frame. May be - NULL. */ - breakpoint_up caller_breakpoint; + /* The breakpoint set at the return address in the caller frame, + plus breakpoints at all the destination locations. */ + std::vector breakpoints; until_break_fsm (struct interp *cmd_interp, int thread, - breakpoint_up &&location_breakpoint, - breakpoint_up &&caller_breakpoint) + std::vector &&breakpoints) : thread_fsm (cmd_interp), thread (thread), - location_breakpoint (std::move (location_breakpoint)), - caller_breakpoint (std::move (caller_breakpoint)) + breakpoints (std::move (breakpoints)) { } @@ -10967,12 +10962,13 @@ struct until_break_fsm : public thread_fsm bool until_break_fsm::should_stop (struct thread_info *tp) { - if (bpstat_find_breakpoint (tp->control.stop_bpstat, - location_breakpoint.get ()) != NULL - || (caller_breakpoint != NULL - && bpstat_find_breakpoint (tp->control.stop_bpstat, - caller_breakpoint.get ()) != NULL)) - set_finished (); + for (const breakpoint_up &bp : breakpoints) + if (bpstat_find_breakpoint (tp->control.stop_bpstat, + bp.get ()) != NULL) + { + set_finished (); + break; + } return true; } @@ -10984,8 +10980,7 @@ void until_break_fsm::clean_up (struct thread_info *) { /* Clean up our temporary breakpoints. */ - location_breakpoint.reset (); - caller_breakpoint.reset (); + breakpoints.clear (); delete_longjmp_breakpoint (thread); } @@ -11023,16 +11018,12 @@ until_break_command (const char *arg, int from_tty, int anywhere) : decode_line_1 (location.get (), DECODE_LINE_FUNFIRSTLINE, NULL, NULL, 0)); - if (sals.size () != 1) + if (sals.empty ()) error (_("Couldn't get information on specified line.")); - symtab_and_line &sal = sals[0]; - if (*arg) error (_("Junk at end of arguments.")); - resolve_sal_pc (&sal); - tp = inferior_thread (); thread = tp->global_num; @@ -11049,7 +11040,7 @@ until_break_command (const char *arg, int from_tty, int anywhere) /* Keep within the current frame, or in frames called by the current one. */ - breakpoint_up caller_breakpoint; + std::vector breakpoints; gdb::optional lj_deleter; @@ -11061,10 +11052,11 @@ until_break_command (const char *arg, int from_tty, int anywhere) sal2 = find_pc_line (frame_unwind_caller_pc (frame), 0); sal2.pc = frame_unwind_caller_pc (frame); caller_gdbarch = frame_unwind_caller_arch (frame); - caller_breakpoint = set_momentary_breakpoint (caller_gdbarch, - sal2, - caller_frame_id, - bp_until); + + breakpoint_up caller_breakpoint + = set_momentary_breakpoint (caller_gdbarch, sal2, + caller_frame_id, bp_until); + breakpoints.emplace_back (std::move (caller_breakpoint)); set_longjmp_breakpoint (tp, caller_frame_id); lj_deleter.emplace (thread); @@ -11073,21 +11065,24 @@ until_break_command (const char *arg, int from_tty, int anywhere) /* set_momentary_breakpoint could invalidate FRAME. */ frame = NULL; - breakpoint_up location_breakpoint; - if (anywhere) - /* If the user told us to continue until a specified location, - we don't specify a frame at which we need to stop. */ - location_breakpoint = set_momentary_breakpoint (frame_gdbarch, sal, - null_frame_id, bp_until); - else - /* Otherwise, specify the selected frame, because we want to stop - only at the very same frame. */ - location_breakpoint = set_momentary_breakpoint (frame_gdbarch, sal, - stack_frame_id, bp_until); + /* If the user told us to continue until a specified location, we + don't specify a frame at which we need to stop. Otherwise, + specify the selected frame, because we want to stop only at the + very same frame. */ + frame_id stop_frame_id = anywhere ? null_frame_id : stack_frame_id; + + for (symtab_and_line &sal : sals) + { + resolve_sal_pc (&sal); + + breakpoint_up location_breakpoint + = set_momentary_breakpoint (frame_gdbarch, sal, + stop_frame_id, bp_until); + breakpoints.emplace_back (std::move (location_breakpoint)); + } tp->thread_fsm = new until_break_fsm (command_interp (), tp->global_num, - std::move (location_breakpoint), - std::move (caller_breakpoint)); + std::move (breakpoints)); if (lj_deleter) lj_deleter->release (); diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 25835139b36..6ce2b71b746 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,10 @@ +2020-08-27 Pedro Alves + + PR gdb/26523 + PR gdb/26524 + * gdb.base/advance-until-multiple-locations.cc: New. + * gdb.base/advance-until-multiple-locations.exp: New. + 2020-08-27 Simon Marchi * gdb.dwarf2/dw2-reg-undefined.exp: Use multi_line. diff --git a/gdb/testsuite/gdb.base/advance-until-multiple-locations.cc b/gdb/testsuite/gdb.base/advance-until-multiple-locations.cc new file mode 100644 index 00000000000..a90493805fa --- /dev/null +++ b/gdb/testsuite/gdb.base/advance-until-multiple-locations.cc @@ -0,0 +1,61 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2020 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 . */ + +static inline int __attribute__ ((always_inline)) +inline_func (int i) +{ + i++; /* multiple locations here */ + return i; +} + +int global = 0; + +void +ovld_func () +{ + global = 1; +} + +void +ovld_func (int) +{ + global = 2; +} + +/* This is a separate function so that we can test that "until" stops + at the caller. */ + +int +test () +{ + int i = 0; + + i = inline_func (i); + i = inline_func (i); + i = inline_func (i); + + ovld_func (); + ovld_func (0); + + return 0; +} + +int +main () +{ + return test (); +} diff --git a/gdb/testsuite/gdb.base/advance-until-multiple-locations.exp b/gdb/testsuite/gdb.base/advance-until-multiple-locations.exp new file mode 100644 index 00000000000..a6a1de6653d --- /dev/null +++ b/gdb/testsuite/gdb.base/advance-until-multiple-locations.exp @@ -0,0 +1,142 @@ +# Copyright 2020 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 . */ + +# Test 'advance/until LINESPEC' where LINESPEC expands to multiple +# locations. + +standard_testfile .cc + +if { [skip_cplus_tests] } { continue } + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \ + {debug c++}] } { + return -1 +} + +set lineno [gdb_get_line_number "multiple locations here"] + +# advance/until to an inlined line number, which has been inlined +# multiple times, when the program is stopped at the same inlined +# function. +proc_with_prefix until_advance_lineno_from_inlined {cmd} { + global lineno + + if ![runto test] { + fail "can't run to test" + return + } + + gdb_breakpoint $lineno + gdb_continue_to_breakpoint "break here" + + set lineno2 [expr $lineno + 1] + + gdb_test "$cmd $lineno2" \ + "inline_func .* at .*:$lineno2.*return i.*" \ + "$cmd line number" +} + +# advance/until to a line number, which has been inlined multiple +# times, when the program is stopped at a non-inlined function. + +proc_with_prefix until_advance_lineno_from_non_inlined {cmd} { + global lineno + + if ![runto test] { + fail "can't run to test" + return + } + + gdb_test "$cmd $lineno" \ + "inline_func .* at .*:$lineno.*multiple locations here.*" \ + "$cmd line number" +} + +# Test advancing to an inlined function, which has been inlined +# multiple times. + +proc_with_prefix until_advance_inline_func {cmd} { + global lineno + + if ![runto test] { + fail "can't run to test" + return + } + + gdb_test "$cmd inline_func" \ + "inline_func .* at .*:$lineno.*multiple locations here.*" +} + +# Test advancing to an overloaded function, which is another form of a +# linespec expanding to multiple locations. GDB will stop at the +# first overload called. + +proc_with_prefix advance_overload {} { + global lineno + + if ![runto test] { + fail "can't run to test" + return + } + + # Test that advance stops at the first overload called by the + # program. + + gdb_test "advance ovld_func" \ + "ovld_func .* at .*global = 1.*" \ + "first advance stops at ovld_func()" + + # Now test that advance also stops at the other overload called by + # the program. + + # Need to issue the advance twice, because advance also stops upon + # exit from the current stack frame. + gdb_test "advance ovld_func" \ + "ovld_func \\(0\\);.*" \ + "second advance stops at caller" + + gdb_test "advance ovld_func" \ + "ovld_func .* at .*global = 2.*" \ + "third advance stops at ovld_func(int)" +} + +# Test "until" to an overloaded function, which is another form of a +# linespec expanding to multiple locations. Unlike "advance", "until" +# only stops if still in the same frame. Since the overloaded +# function is a different frame, the program should stop at the caller +# frame instead. + +proc_with_prefix until_overload {} { + global lineno + + if ![runto test] { + fail "can't run to test" + return + } + + # ovld_func is a different frame, so it shouldn't cause a stop. + # Instead, the program should stop at the caller frame. + gdb_test "until ovld_func" \ + "main .* at .*return test \\(\\);.*" +} + +foreach_with_prefix cmd {"until" "advance"} { + until_advance_lineno_from_inlined $cmd + until_advance_lineno_from_non_inlined $cmd + until_advance_inline_func $cmd +} + +advance_overload +until_overload -- 2.30.2