diff options
author | Pedro Alves <pedro@palves.net> | 2020-08-22 21:37:26 +0100 |
---|---|---|
committer | Pedro Alves <pedro@palves.net> | 2020-08-22 21:37:26 +0100 |
commit | 7dfecca0d25adb7874cbbe6f7211ad93a02e7ca9 (patch) | |
tree | 74378324dcc7770d5cc8ec249578d5356eb71fae | |
parent | e4e53e546c76aa3ac386ad4816ee34069eddb54e (diff) | |
download | fsf-binutils-gdb-users/palves/advance-until-fixes.zip fsf-binutils-gdb-users/palves/advance-until-fixes.tar.gz fsf-binutils-gdb-users/palves/advance-until-fixes.tar.bz2 |
Fix advance/until and multiple locations (PR gdb/26524)users/palves/advance-until-fixes
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 <MULTIPLE>
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 <location>"
is really just syntactic sugar for "tbreak <location>;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) <location_breakpoint,
caller_breakpoint>: Delete fields.
<breakpoints>: New field.
<until_break_fsm>: 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.
-rw-r--r-- | gdb/breakpoint.c | 77 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/advance-until-multiple-locations.cc | 61 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/advance-until-multiple-locations.exp | 142 |
3 files changed, 239 insertions, 41 deletions
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 977599d..4f94a0a 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -10950,20 +10950,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 the destination location. */ + std::vector<breakpoint_up> breakpoints; until_break_fsm (struct interp *cmd_interp, int thread, - breakpoint_up &&location_breakpoint, - breakpoint_up &&caller_breakpoint) + std::vector<breakpoint_up> &&breakpoints) : thread_fsm (cmd_interp), thread (thread), - location_breakpoint (std::move (location_breakpoint)), - caller_breakpoint (std::move (caller_breakpoint)) + breakpoints (std::move (breakpoints)) { } @@ -10978,12 +10973,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; } @@ -10995,8 +10991,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); } @@ -11034,16 +11029,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; @@ -11060,7 +11051,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<breakpoint_up> breakpoints; gdb::optional<delete_longjmp_breakpoint_cleanup> lj_deleter; @@ -11072,10 +11063,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); @@ -11084,21 +11076,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/gdb.base/advance-until-multiple-locations.cc b/gdb/testsuite/gdb.base/advance-until-multiple-locations.cc new file mode 100644 index 0000000..a904938 --- /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 <http://www.gnu.org/licenses/>. */ + +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 0000000..a6a1de6 --- /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 <http://www.gnu.org/licenses/>. */ + +# 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 |