| # Copyright 2020-2025 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 defining a conditional breakpoint that applies to multiple |
| # locations with different contexts (e.g. different set of local vars). |
| |
| standard_testfile .cc |
| |
| set flags {} |
| lappend flags debug |
| lappend flags c++ |
| |
| if {[build_executable "failed to prepare" ${binfile} ${srcfile} $flags]} { |
| return |
| } |
| |
| set warning "warning: failed to validate condition" |
| set fill "\[^\r\n\]*" |
| |
| # Location indices are determined by their address, which depends on |
| # how the compiler emits the code. We keep a map from the location |
| # index to the location context (i.e. A, Base, or C), so that we can |
| # write tests without hard-coding location indices. |
| set loc_name(1) "" |
| set loc_name(2) "" |
| set loc_name(3) "" |
| # And, for convenience, a reverse map from the name to the index. |
| set loc_index(A) 0 |
| set loc_index(Base) 0 |
| set loc_index(C) 0 |
| |
| # Find breakpoint location contexts. This is called before any of the |
| # tests are run and captures the order in which GDB will create the |
| # breakpoint locations. |
| # |
| # This whole test script does rely on GDB always creating the b/p |
| # locations in the same order, but that's true right now, and doesn't |
| # seem likely to change in the near future. |
| |
| proc find_location_contexts { } { |
| global loc_name loc_index fill |
| global decimal hex gdb_prompt binfile |
| |
| clean_restart |
| gdb_load $binfile |
| |
| if {![runto_main]} { |
| return |
| } |
| |
| gdb_breakpoint func |
| set bpnum [get_integer_valueof "\$bpnum" "*UNKNOWN*"] |
| |
| gdb_test_multiple "info breakpoint $bpnum" "find_location_indices" { |
| -re "\r\n$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" { |
| exp_continue |
| } |
| -re "^${bpnum}\.($decimal) ${fill} ${hex} in (A|Base|C)::func${fill}\r\n" { |
| set index $expect_out(1,string) |
| set name $expect_out(2,string) |
| set loc_name($index) $name |
| set loc_index($name) $index |
| exp_continue |
| } |
| -re "$gdb_prompt $" { |
| verbose -log "Loc names: $loc_name(1), $loc_name(2), $loc_name(3)" |
| gdb_assert { ![string equal $loc_name(1) $loc_name(2)] } |
| gdb_assert { ![string equal $loc_name(1) $loc_name(3)] } |
| gdb_assert { ![string equal $loc_name(2) $loc_name(3)] } |
| gdb_assert { [string length $loc_name(1)] > 0 } |
| gdb_assert { [string length $loc_name(2)] > 0 } |
| gdb_assert { [string length $loc_name(3)] > 0 } |
| } |
| } |
| } |
| |
| # Test the breakpoint location enabled states. STATES is a list of |
| # location states. We assume STATES to contain the state for A, Base, |
| # and C, and in this order. E.g. {N* y n} for 'N*' at A::func, 'y' at |
| # B::func, and 'n' at C::func, respectively. |
| |
| proc check_bp_locations {bpnum states cond {msg ""}} { |
| global loc_name fill loc_states |
| |
| # Map location names to location states. |
| set loc_states(A) [lindex $states 0] |
| set loc_states(Base) [lindex $states 1] |
| set loc_states(C) [lindex $states 2] |
| |
| if {$cond == ""} { |
| set cond_info "" |
| } else { |
| set bp_hit_info "${fill}(\r\n${fill}breakpoint already hit 1 time)?" |
| set cond_info "\r\n${fill}stop only if ${cond}${bp_hit_info}" |
| } |
| |
| set expected [multi_line \ |
| "Num${fill}" \ |
| "${bpnum}${fill}breakpoint${fill}keep y${fill}MULTIPLE${fill}${cond_info}" \ |
| "${bpnum}.1${fill} $loc_states($loc_name(1)) ${fill}" \ |
| "${bpnum}.2${fill} $loc_states($loc_name(2)) ${fill}" \ |
| "${bpnum}.3${fill} $loc_states($loc_name(3)) ${fill}"] |
| |
| if {[lsearch $states N*] >= 0} { |
| append expected "\r\n\\(\\*\\): Breakpoint condition is invalid at this location." |
| } |
| |
| gdb_test "info break $bpnum" $expected "check bp $bpnum $msg" |
| } |
| |
| # Scenario 1: Define breakpoints conditionally, using the "break N if |
| # cond" syntax. Run the program, check that we hit those locations |
| # only. |
| # |
| # When START_BEFORE is true we create the breakpoints after running to |
| # main. When START_BEFORE is false we create the breakpoints after |
| # starting GDB, but before running to main. |
| |
| proc_with_prefix scenario_1 { start_before } { |
| global warning decimal fill bkptno_num_re binfile |
| |
| clean_restart |
| gdb_load $binfile |
| |
| if { $start_before } { |
| if {![runto_main temporary]} { |
| return |
| } |
| } |
| |
| # Define the conditional breakpoints. Two locations (Base::func |
| # and C::func) should be disabled. We do not test location |
| # indices strictly at this moment, because we don't know them, |
| # yet. We have strict location index tests below. |
| gdb_test "break func if aaa == 10" \ |
| [multi_line \ |
| "${warning} at location $decimal, disabling:" \ |
| " No symbol \"aaa\" in current context." \ |
| "${warning} at location $decimal, disabling:" \ |
| " No symbol \"aaa\" in current context." \ |
| "Breakpoint $decimal at $fill .3 locations."] \ |
| "define bp with condition aaa == 10" |
| set bpnum1 [get_integer_valueof "\$bpnum" 0 "get bpnum1"] |
| |
| gdb_test "break func if ccc == 30" \ |
| [multi_line \ |
| ".*${warning} at location $decimal, disabling:" \ |
| " No symbol \"ccc\" in current context." \ |
| ".*${warning} at location $decimal, disabling:" \ |
| " No symbol \"ccc\" in current context." \ |
| ".*Breakpoint $decimal at $fill .3 locations."] \ |
| "define bp with condition ccc == 30" |
| set bpnum2 [get_integer_valueof "\$bpnum" 0 "get bpnum2"] |
| |
| with_test_prefix "after creating b/p" { |
| check_bp_locations $bpnum1 {y N* N*} "aaa == 10" |
| check_bp_locations $bpnum2 {N* N* y} "ccc == 30" |
| } |
| |
| if { !$start_before } { |
| if {![runto_main temporary no-delete-breakpoints]} { |
| return |
| } |
| } |
| |
| # Check our conditional breakpoints. |
| gdb_test "continue" ".*Breakpoint $bkptno_num_re, A::func .*" \ |
| "run until A::func" |
| gdb_test "print aaa" " = 10" |
| |
| gdb_test "continue" "Continuing.*Breakpoint $bkptno_num_re, C::func .*" \ |
| "run until C::func" |
| gdb_test "print ccc" " = 30" |
| |
| # No more hits! |
| gdb_continue_to_end |
| |
| with_test_prefix "after run" { |
| check_bp_locations $bpnum1 {y N* N*} "aaa == 10" |
| check_bp_locations $bpnum2 {N* N* y} "ccc == 30" |
| } |
| } |
| |
| # Assuming GDB is already started, create two breakpoints and define |
| # the conditions separately. |
| # |
| # BPNUM1_NAME and BPNUM2_NAME contain the name of two variables in the |
| # parent contents. The breakpoint numbers of the two created |
| # breakpoints are placed into these variables in the parent content. |
| |
| proc setup_bps { bpnum1_name bpnum2_name } { |
| global warning loc_index |
| |
| upvar $bpnum1_name bpnum1 |
| upvar $bpnum2_name bpnum2 |
| |
| # Define the breakpoints. |
| gdb_breakpoint "func" |
| set bpnum1 [get_integer_valueof "\$bpnum" 0 "get bpnum1"] |
| |
| gdb_breakpoint "func" |
| set bpnum2 [get_integer_valueof "\$bpnum" 0 "get bpnum2"] |
| |
| # Defining a condition on 'aaa' disables 2 locations. |
| set locs [lsort -integer "$loc_index(Base) $loc_index(C)"] |
| gdb_test "cond $bpnum1 aaa == 10" \ |
| [multi_line \ |
| "$warning at location ${bpnum1}.[lindex $locs 0], disabling:" \ |
| " No symbol \"aaa\" in current context." \ |
| "$warning at location ${bpnum1}.[lindex $locs 1], disabling:" \ |
| " No symbol \"aaa\" in current context."] |
| |
| # Defining a condition on 'c' disables 2 locations. |
| set locs [lsort -integer "$loc_index(Base) $loc_index(A)"] |
| gdb_test "cond $bpnum2 ccc == 30" \ |
| [multi_line \ |
| "$warning at location ${bpnum2}.[lindex $locs 0], disabling:" \ |
| " No symbol \"ccc\" in current context." \ |
| "$warning at location ${bpnum2}.[lindex $locs 1], disabling:" \ |
| " No symbol \"ccc\" in current context."] |
| } |
| |
| # Scenario 2: Define breakpoints unconditionally, and then define |
| # conditions using the "cond N <cond>" syntax. Expect that the |
| # locations where <cond> is not evaluatable are disabled. Run the |
| # program, check that we hit the enabled locations only. |
| # |
| # When START_BEFORE is true we create the breakpoints after running to |
| # main. When START_BEFORE is false we create the breakpoints after |
| # starting GDB, but before running to main. |
| |
| proc_with_prefix scenario_2 { start_before } { |
| global binfile bkptno_num_re |
| |
| clean_restart |
| gdb_load $binfile |
| |
| if { $start_before } { |
| if {![runto_main temporary]} { |
| return |
| } |
| } |
| |
| setup_bps bpnum1 bpnum2 |
| |
| with_test_prefix "after creating b/p" { |
| check_bp_locations $bpnum1 {y N* N*} "aaa == 10" |
| check_bp_locations $bpnum2 {N* N* y} "ccc == 30" |
| } |
| |
| if { !$start_before } { |
| if {![runto_main temporary no-delete-breakpoints]} { |
| return |
| } |
| } |
| |
| # Check that we hit enabled locations only. |
| gdb_test "continue" ".*Breakpoint $bkptno_num_re, A::func .*" \ |
| "run until A::func" |
| gdb_test "print aaa" " = 10" |
| |
| gdb_test "continue" "Continuing.*Breakpoint $bkptno_num_re, C::func .*" \ |
| "run until C::func" |
| gdb_test "print ccc" " = 30" |
| |
| # No more hits! |
| gdb_continue_to_end |
| |
| with_test_prefix "after run" { |
| check_bp_locations $bpnum1 {y N* N*} "aaa == 10" |
| check_bp_locations $bpnum2 {N* N* y} "ccc == 30" |
| } |
| } |
| |
| # Scenario 3: Apply misc. checks on the already-defined breakpoints. |
| # |
| # When START_BEFORE is true we create the breakpoints after running to |
| # main. When START_BEFORE is false we create the breakpoints after |
| # starting GDB, but before running to main. |
| |
| proc_with_prefix scenario_3 { start_before } { |
| global binfile bkptno_num_re loc_index warning |
| |
| clean_restart |
| gdb_load $binfile |
| |
| if { $start_before } { |
| if {![runto_main temporary]} { |
| return |
| } |
| } |
| |
| setup_bps bpnum1 bpnum2 |
| |
| set locs [lsort -integer "$loc_index(Base) $loc_index(A)"] |
| gdb_test "cond $bpnum1 ccc == 30" \ |
| [multi_line \ |
| "${warning} at location ${bpnum1}.[lindex $locs 0], disabling:" \ |
| " No symbol \"ccc\" in current context." \ |
| "${warning} at location ${bpnum1}.[lindex $locs 1], disabling:" \ |
| " No symbol \"ccc\" in current context." \ |
| "Breakpoint ${bpnum1}'s condition is now valid at location $loc_index(C), enabling."] \ |
| "change the condition of bp 1" |
| check_bp_locations $bpnum1 {N* N* y} "ccc == 30" "after changing the condition" |
| |
| gdb_test "cond $bpnum1" \ |
| [multi_line \ |
| "Breakpoint ${bpnum1}'s condition is now valid at location [lindex $locs 0], enabling." \ |
| "Breakpoint ${bpnum1}'s condition is now valid at location [lindex $locs 1], enabling." \ |
| "Breakpoint ${bpnum1} now unconditional."] \ |
| "reset the condition of bp 1" |
| check_bp_locations $bpnum1 {y y y} "" "after resetting the condition" |
| |
| gdb_test_no_output "disable ${bpnum2}.$loc_index(A)" |
| check_bp_locations $bpnum2 {N* N* y} "ccc == 30" "after disabling loc for A" |
| |
| gdb_test "cond $bpnum2" ".*" "reset the condition of bp 2" |
| check_bp_locations $bpnum2 {n y y} "" "loc for A should remain disabled" |
| |
| gdb_test_no_output "disable ${bpnum2}.$loc_index(C)" |
| check_bp_locations $bpnum2 {n y n} "" "after disabling loc for C" |
| |
| gdb_test "cond $bpnum2 ccc == 30" \ |
| [multi_line \ |
| "${warning} at location ${bpnum2}.$loc_index(Base), disabling:" \ |
| " No symbol \"ccc\" in current context."] \ |
| "re-define a condition" |
| check_bp_locations $bpnum2 {N* N* n} "ccc == 30" "loc for C should remain disabled" |
| |
| gdb_test "enable ${bpnum2}.$loc_index(Base)" \ |
| "Breakpoint ${bpnum2}'s condition is invalid at location $loc_index(Base), cannot enable." \ |
| "reject enabling a location that is disabled-by-cond" |
| check_bp_locations $bpnum2 {N* N* n} "ccc == 30" "after enable attempt" |
| |
| gdb_test "cond $bpnum2 garbage" \ |
| "No symbol \"garbage\" in current context." \ |
| "reject condition if bad for all locations" |
| |
| gdb_test_no_output "delete $bpnum1" |
| |
| # Do not use runto_main, it deletes all breakpoints. |
| if { !$start_before } { |
| if {![runto_main temporary no-delete-breakpoints]} { |
| return |
| } |
| } |
| |
| # The second BP's locations are all disabled. No more hits! |
| gdb_continue_to_end |
| } |
| |
| # Scenario 4: Test the '-force'/'-force-condition' flag. |
| # |
| # When START_BEFORE is true we create the breakpoints after running to |
| # main. When START_BEFORE is false we create the breakpoints after |
| # starting GDB, but before running to main, in fact we don't even |
| # bother with a run to main in this case. |
| |
| proc_with_prefix scenario_4 { start_before } { |
| global binfile bkptno_num_re loc_index warning |
| |
| clean_restart |
| gdb_load $binfile |
| |
| if { $start_before } { |
| if {![runto_main temporary]} { |
| return |
| } |
| } |
| |
| gdb_breakpoint "func" |
| # Pick a condition that is invalid at every location. |
| set bpnum1 [get_integer_valueof "\$bpnum" 0 "get bpnum1"] |
| gdb_test "cond -force $bpnum1 foo" \ |
| [multi_line \ |
| "${warning} at location ${bpnum1}.1, disabling:" \ |
| " No symbol \"foo\" in current context." \ |
| "${warning} at location ${bpnum1}.2, disabling:" \ |
| " No symbol \"foo\" in current context." \ |
| "${warning} at location ${bpnum1}.3, disabling:" \ |
| " No symbol \"foo\" in current context."] \ |
| "force the condition of bp 1" |
| check_bp_locations $bpnum1 {N* N* N*} "foo" "after forcing the condition" |
| |
| # Now with the 'break' command. |
| gdb_breakpoint "func -force-condition if baz" |
| set bpnum2 [get_integer_valueof "\$bpnum" 0 "get bpnum2"] |
| check_bp_locations $bpnum2 {N* N* N*} "baz" "set using the break command" |
| } |
| |
| # Some initial setup. Needed for most of the different scenarios |
| # below. |
| find_location_contexts |
| |
| # Now run all of the separate scenarios. |
| foreach_with_prefix start_before { true false } { |
| scenario_1 $start_before |
| scenario_2 $start_before |
| scenario_3 $start_before |
| scenario_4 $start_before |
| } |