| # Copyright 2014-2021 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/>. |
| |
| # This file is part of the gdb testsuite |
| |
| # Test relies on checking gdb debug output. Do not run if gdb debug is |
| # enabled as any debug will be redirected to the log. |
| if [gdb_debug_enabled] { |
| untested "debug is enabled" |
| return 0 |
| } |
| |
| standard_testfile |
| |
| if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } { |
| return -1 |
| } |
| |
| if ![runto_main] { |
| return -1 |
| } |
| |
| # Delete all target-supplied memory regions. |
| delete_memory_regions |
| |
| delete_breakpoints |
| |
| # Probe for hardware stepping. |
| |
| proc probe_target_hardware_step {} { |
| global gdb_prompt |
| |
| set hw_step 0 |
| |
| gdb_test_no_output "set debug target 1" |
| set test "probe target hardware step" |
| gdb_test_multiple "si" $test { |
| -re "resume \\(\[^\r\n\]+, step, .*$gdb_prompt $" { |
| set hw_step 1 |
| pass $test |
| } |
| -re "$gdb_prompt $" { |
| pass $test |
| } |
| } |
| gdb_test "set debug target 0" "->log_command.*\\).*" |
| return $hw_step |
| } |
| |
| # Get the bounds of a function, and write them to FUNC_LO (inclusive), |
| # FUNC_HI (exclusive). Return true on success and false on failure. |
| proc get_function_bounds {function func_lo func_hi} { |
| global gdb_prompt |
| global hex decimal |
| |
| upvar $func_lo lo |
| upvar $func_hi hi |
| |
| set lo "" |
| set size "" |
| |
| set test "get lo address of $function" |
| gdb_test_multiple "disassemble $function" $test { |
| -re "($hex) .*$hex <\\+($decimal)>:\[^\r\n\]+\r\nEnd of assembler dump\.\r\n$gdb_prompt $" { |
| set lo $expect_out(1,string) |
| set size $expect_out(2,string) |
| pass $test |
| } |
| } |
| |
| if { $lo == "" || $size == "" } { |
| return false |
| } |
| |
| # Account for the size of the last instruction. |
| set test "get hi address of $function" |
| gdb_test_multiple "x/2i $function+$size" $test { |
| -re ".*$hex <$function\\+$size>:\[^\r\n\]+\r\n\[ \]+($hex).*\.\r\n$gdb_prompt $" { |
| set hi $expect_out(1,string) |
| pass $test |
| } |
| } |
| |
| if { $hi == "" } { |
| return false |
| } |
| |
| # Remove unnecessary leading 0's (0x00000ADDR => 0xADDR) so we can |
| # easily do matches. Disassemble includes leading zeros, while |
| # x/i doesn't. |
| regsub -all "0x0\+" $lo "0x" lo |
| regsub -all "0x0\+" $hi "0x" hi |
| |
| return true |
| } |
| |
| # Get the address where the thread is currently stopped. |
| proc get_curr_insn {} { |
| global gdb_prompt |
| global hex |
| |
| set pc "" |
| set test "get current insn" |
| gdb_test_multiple "p /x \$pc" $test { |
| -re " = ($hex)\r\n$gdb_prompt $" { |
| set pc $expect_out(1,string) |
| pass $test |
| } |
| } |
| |
| return $pc |
| } |
| |
| # Get the address of where a single-step should land. |
| proc get_next_insn {} { |
| global gdb_prompt |
| global hex |
| |
| set next "" |
| set test "get next insn" |
| gdb_test_multiple "x/2i \$pc" $test { |
| -re "$hex .*:\[^\r\n\]+\r\n\[ \]+($hex).*\.\r\n$gdb_prompt $" { |
| set next $expect_out(1,string) |
| pass $test |
| } |
| } |
| |
| return $next |
| } |
| |
| set hw_step [probe_target_hardware_step] |
| |
| if ![get_function_bounds "main" main_lo main_hi] { |
| # Can't do the following tests if main's bounds are unknown. |
| return -1 |
| } |
| |
| # Manually create a read-only memory region that covers 'main'. |
| gdb_test_no_output "mem $main_lo $main_hi ro" \ |
| "create read-only mem region covering main" |
| |
| # So that we don't fail inserting breakpoints on addresses outside |
| # main, like the internal event breakpoints. |
| gdb_test_no_output "set mem inaccessible-by-default off" |
| |
| # So we get an immediate warning/error without needing to resume the |
| # target. |
| gdb_test_no_output "set breakpoint always-inserted on" |
| |
| # Disable the automatic fallback to HW breakpoints. We want a |
| # software breakpoint to be attempted, and to fail. |
| gdb_test_no_output "set breakpoint auto-hw off" |
| |
| # Confirm manual writes to the read-only memory region fail. |
| gdb_test "p /x *(char *) $main_lo = 1" \ |
| "Cannot access memory at address $main_lo" \ |
| "writing to read-only memory fails" |
| |
| # Ensure that inserting a software breakpoint in a known-read-only |
| # region fails. |
| gdb_test "break *$main_lo" \ |
| "Cannot insert breakpoint .*Cannot set software breakpoint at read-only address $main_lo.*" \ |
| "inserting software breakpoint in read-only memory fails" |
| |
| delete_breakpoints |
| |
| set supports_hbreak 0 |
| set test "probe hbreak support" |
| gdb_test_multiple "hbreak *$main_lo" $test { |
| -re "You may have requested too many.*$gdb_prompt $" { |
| pass "$test (no support)" |
| } |
| -re "No hardware breakpoint support.*$gdb_prompt $" { |
| pass "$test (no support)" |
| } |
| -re "$gdb_prompt $" { |
| pass "$test (support)" |
| set supports_hbreak 1 |
| } |
| } |
| |
| delete_breakpoints |
| |
| # Check that the "auto-hw on/off" setting affects single-step |
| # breakpoints as expected, by stepping through the read-only region. |
| # If the target does hardware stepping, we won't exercise that aspect, |
| # but we should be able to step through the region without seeing the |
| # hardware breakpoint or read-only address errors. |
| proc test_single_step { always_inserted auto_hw } { |
| global gdb_prompt |
| global decimal |
| global hex |
| global supports_hbreak |
| global hw_step |
| |
| gdb_test_no_output "set breakpoint always-inserted $always_inserted" |
| gdb_test_no_output "set breakpoint auto-hw $auto_hw" |
| |
| # Get the address of the current instruction so we know where SI is |
| # starting from. |
| set curr_insn [get_curr_insn] |
| |
| # Get the address of the next instruction so we know where SI should |
| # land. |
| set next_insn [get_next_insn] |
| |
| set test "step in ro region" |
| gdb_test_multiple "si" $test { |
| -re "Could not insert hardware breakpoints.*$gdb_prompt $" { |
| gdb_assert {!$hw_step && $auto_hw == "on" && !$supports_hbreak} \ |
| "$test (cannot insert hw break)" |
| } |
| -re "Cannot set software breakpoint at read-only address $next_insn.*$gdb_prompt $" { |
| gdb_assert {!$hw_step && $auto_hw == "off"} \ |
| "$test (cannot insert sw break)" |
| } |
| -re "^si\r\nNote: automatically using hardware breakpoints for read-only addresses\.\r\n\(\?\:${hex}\[ \t\]\)\?${decimal}\[ \t\]+i = 0;\r\n$gdb_prompt $" { |
| gdb_assert {!$hw_step && $auto_hw == "on" && $supports_hbreak} \ |
| "$test (auto-hw)" |
| } |
| -re "^si\r\n\(\?\:${hex}\[ \t\]\)\?${decimal}\[ \t\]+i = 0;\r\n$gdb_prompt $" { |
| gdb_assert {$hw_step || ($auto_hw == "on" && $supports_hbreak)} \ |
| "$test (no error)" |
| } |
| } |
| |
| gdb_test "maint info breakpoints 0" \ |
| "No breakpoint or watchpoint matching '0'\." \ |
| "single-step breakpoint is not left behind" |
| |
| # Confirm the thread really advanced. |
| if {$hw_step || ($auto_hw == "on" && $supports_hbreak)} { |
| gdb_test "p /x \$pc" " = $next_insn" "thread advanced" |
| } else { |
| gdb_test "p /x \$pc" " = $curr_insn" "thread did not advance" |
| } |
| } |
| |
| foreach always_inserted {"off" "on"} { |
| foreach auto_hw {"off" "on"} { |
| with_test_prefix "always-inserted $always_inserted: auto-hw $auto_hw" { |
| test_single_step $always_inserted $auto_hw |
| } |
| } |
| } |