aboutsummaryrefslogtreecommitdiff
path: root/gdb
diff options
context:
space:
mode:
authorAlexandra Hájková <ahajkova@redhat.com>2024-10-23 14:18:43 +0200
committerAlexandra Hájková <ahajkova@redhat.com>2025-01-30 20:37:12 +0100
commit202655d42a51c2de665b023710a4bfc76b6f5db7 (patch)
tree87ae11042fa5c4569848ac107fb425d1d69bc301 /gdb
parentf77f3d6d9cef650cf1c3aaf51d91eb9af9cd026d (diff)
downloadgdb-202655d42a51c2de665b023710a4bfc76b6f5db7.zip
gdb-202655d42a51c2de665b023710a4bfc76b6f5db7.tar.gz
gdb-202655d42a51c2de665b023710a4bfc76b6f5db7.tar.bz2
gdb: add first gdbreplay test, connect.exp
When the changes on the remote protocol are made, we want to test all the corner cases to prevent regressions. Currently it can be tricky to simulate some corner case conditions that would expose possible regressions. When I want to add or change the remote protocol packet, I need to hack gdbserver to send a corrupted packet or an error to make sure GDB is able to handle such a case. This test makes it easy to send a corruped packet or an error message to GDB using the gdbreplay tool and check GDB deals with it as we expect it to. This test starts a communication with gdbsever setting the remotelog file. Then, it modifies the remotelog with update_log proc, injects an error message instead of the expected replay to the vMustReplyEmpty packet in order to test GDB reacts to the error response properly. After the remotelog modification, this test restarts GDB and starts communication with gdbreply instead of the gdbserver using the remotelog. Add a lib/gdbreplay-support.exp. update_log proc matches lines from GDB to gdbserver in a remotelogfile. Once a match is found then the custom line is used to build a replacement line to send from gdbserver to GDB. Approved-By: Andrew Burgess <aburgess@redhat.com>
Diffstat (limited to 'gdb')
-rw-r--r--gdb/testsuite/gdb.replay/connect.c22
-rw-r--r--gdb/testsuite/gdb.replay/connect.exp129
-rw-r--r--gdb/testsuite/lib/gdbreplay-support.exp143
3 files changed, 294 insertions, 0 deletions
diff --git a/gdb/testsuite/gdb.replay/connect.c b/gdb/testsuite/gdb.replay/connect.c
new file mode 100644
index 0000000..dc4f40e
--- /dev/null
+++ b/gdb/testsuite/gdb.replay/connect.c
@@ -0,0 +1,22 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2024-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/>. */
+
+int
+main (int argc, char **argv)
+{
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.replay/connect.exp b/gdb/testsuite/gdb.replay/connect.exp
new file mode 100644
index 0000000..5790d38
--- /dev/null
+++ b/gdb/testsuite/gdb.replay/connect.exp
@@ -0,0 +1,129 @@
+# Copyright 2024-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/>. */
+#
+# Starts a communication with gdbsever setting the remotelog file.
+# Modifies the remotelog with update_log proc, injects an error message
+# instead of the expected replay to the vMustReplyEmpty packet in order
+# to test GDB reacts to the error response properly. After the remotelog
+# modification, this test restarts GDB and starts communication with gdbreply
+# instead of the gdbserver using the remotelog.
+
+load_lib gdbserver-support.exp
+load_lib gdbreplay-support.exp
+
+require allow_gdbserver_tests
+require has_gdbreplay
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+ return -1
+}
+
+# Connect to gdbserver and run to a breakpoint in main. Record the
+# remotelogfile into the global REMOTELOG.
+proc_with_prefix record_initial_logfile {} {
+ global binfile
+ global remotelog
+ # Make sure we're disconnected, in case we're testing with an
+ # extended-remote board, therefore already connected.
+ gdb_test "disconnect" ".*"
+
+ gdb_test_no_output "set sysroot" \
+ "setting sysroot before starting gdbserver"
+
+ # Start gdbserver like:
+ # gdbserver :PORT ....
+ set res [gdbserver_start "" $binfile]
+ set gdbserver_protocol [lindex $res 0]
+ set gdbserver_gdbport [lindex $res 1]
+
+ # The replay log is placed in 'replay.log'.
+ set remotelog [standard_output_file replay.log]
+
+ gdb_test_no_output "set remotelogfile $remotelog" \
+ "setup remotelogfile"
+
+ # Connect to gdbserver.
+ if {![gdb_target_cmd $gdbserver_protocol $gdbserver_gdbport] == 0} {
+ unsupported "$testfile (couldn't start gdbserver)"
+ return
+ }
+
+ # If we're connecting as 'remote' then we can't use 'runto'.
+ gdb_breakpoint main
+ gdb_continue_to_breakpoint "continuing to main"
+}
+
+# Connect to gdbreply using the global REMOTELOG. Runs to a breakpoint
+# in main.
+proc_with_prefix replay_without_error {} {
+ global binfile
+ global remotelog
+ clean_restart $binfile
+ # Make sure we're disconnected, in case we're testing with an
+ # extended-remote board, therefore already connected.
+ gdb_test "disconnect" ".*"
+
+ gdb_test_no_output "set sysroot" "setting sysroot"
+
+ set res [gdbreplay_start $remotelog]
+ set gdbserver_protocol [lindex $res 0]
+ set gdbserver_gdbport [lindex $res 1]
+
+ # Connect to gdbreplay.
+ if {![gdb_target_cmd $gdbserver_protocol $gdbserver_gdbport] == 0} {
+ unsupported "$testfile (couldn't start gdbreplay)"
+ return
+ }
+ gdb_breakpoint main
+ gdb_continue_to_breakpoint "continue to main"
+}
+
+# Create a modified copy of global REMOTELOG, returning an error for the
+# vMustReplyEmpty packet. Then use gdbreplay to play back the modified
+# copy of REMOTELOG. Attempt to connect to the remote and expect to see
+# the error reported by GDB.
+proc_with_prefix replay_with_mustreplyempty_error {} {
+ global binfile
+ global remotelog
+ global testfile
+ set newline E.errtext
+ set output_file [standard_output_file ${testfile}_out.log]
+
+ # Modify the log file by changing the *response* to
+ # the vMustReplayEmty packet to an error.
+ update_log $remotelog $output_file "vMustReplyEmpty" $newline
+
+ clean_restart $binfile
+ # Make sure we're disconnected, in case we're testing with an
+ # extended-remote board, therefore already connected.
+ gdb_test "disconnect" ".*"
+
+ gdb_test_no_output "set sysroot"
+
+ set res [gdbreplay_start $output_file]
+ set gdbserver_protocol [lindex $res 0]
+ set gdbserver_gdbport [lindex $res 1]
+
+ # Connect to gdbreplay.
+ gdb_assert {[gdb_target_cmd_ext $gdbserver_protocol $gdbserver_gdbport \
+ ".*Remote replied unexpectedly to 'vMustReplyEmpty': E.errtext"] == 0} \
+ "remote replied with an error"
+}
+
+record_initial_logfile
+replay_without_error
+replay_with_mustreplyempty_error
diff --git a/gdb/testsuite/lib/gdbreplay-support.exp b/gdb/testsuite/lib/gdbreplay-support.exp
new file mode 100644
index 0000000..fc4dc52
--- /dev/null
+++ b/gdb/testsuite/lib/gdbreplay-support.exp
@@ -0,0 +1,143 @@
+# Copyright 2024-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/>.
+
+# We're going to reuse some helper function from the gdbserver library.
+load_lib gdbserver-support.exp
+
+if {![info exists GDBREPLAY]} {
+ set GDBREPLAY [findfile $base_dir/../../gdbserver/gdbreplay]
+}
+
+global GDBREPLAY
+verbose "using GDBREPLAY = $GDBREPLAY" 2
+
+# Check is non empty and we're running on the host.
+proc has_gdbreplay {} {
+ global GDBREPLAY
+ if {$GDBREPLAY == ""} {
+ return false
+ }
+
+ # We currently rely on running gdbreplay on the same machine as
+ # GDB.
+ if {[is_remote target]} {
+ return false
+ }
+
+ return true
+}
+
+# Write the command line used to invocate gdbserver to the cmd file.
+
+proc gdbreplay_write_cmd_file { cmdline } {
+ set logfile [standard_output_file_with_gdb_instance gdbreplay.cmd]
+ set cmd_file [open $logfile w]
+ puts $cmd_file $cmdline
+ catch "close $cmd_file"
+}
+
+# Start gdbreplay using REMOTELOG as the log file. Return a list of
+# two elements, the protocol and the hostname:port string. This
+# result list has the same format as gdbserver_start.
+
+proc gdbreplay_start { remotelog } {
+ # Port id -- either specified in baseboard file, or managed here.
+ set portnum [get_portnum]
+
+ # Extract the protocol
+ if [target_info exists gdb_protocol] {
+ set protocol [target_info gdb_protocol]
+ } else {
+ set protocol "remote"
+ }
+
+ # Loop till we find a free port.
+ while 1 {
+ # Fire off the debug agent.
+ set gdbreplay_command "$::GDBREPLAY $remotelog localhost:$portnum"
+
+ gdbreplay_write_cmd_file $gdbreplay_command
+
+ global gdbreplay_spawn_id
+ set gdbreplay_spawn_id [remote_spawn target $gdbreplay_command]
+
+ # Wait for the server to open its TCP socket, so that GDB can connect.
+ expect {
+ -i $gdbreplay_spawn_id
+ -timeout 120
+ -notransfer
+ -re "Replay logfile using" { }
+ -re "Can't (bind address|listen on socket): Address already in use\\.\r\n" {
+ verbose -log "Port $portnum is already in use."
+ set other_portnum [get_portnum]
+ if { $other_portnum != $portnum } {
+ # Bump the port number to avoid the conflict.
+ wait -i $expect_out(spawn_id)
+ set portnum $other_portnum
+ continue
+ }
+ }
+ -re ".*: cannot resolve name: .*\r\n" {
+ error "gdbserver cannot resolve name."
+ }
+ -re "Can't open socket: Address family not supported by protocol." {
+ return {}
+ }
+ timeout {
+ error "Timeout waiting for gdbreplay response."
+ }
+ }
+ break
+ }
+
+ return [list $protocol "localhost:$portnum"]
+}
+
+# MATCH_REGEXP matches lines from GDB to gdbserver. Once a match is
+# found then NEWLINE is used to build a replacement line to send from
+# gdbserver to GDB.
+#
+# Examples of MATCH_REGEXP: "vMustReplyEmpty"
+#
+# Example usage:
+#
+# update_log $logname "${logname}.updated" "vMustReplyEmpty" "E.failed"
+
+proc update_log { filename_in filename_out match_regexp newline } {
+ set fh_in [open $filename_in r]
+ set fh_out [open $filename_out w]
+
+ while { [gets $fh_in line] >= 0 } {
+ # Print the line to the file.
+ puts $fh_out $line
+ if { [regexp $match_regexp $line] } {
+ # print out NEWLINE.
+ puts $fh_out "r +\$${newline}"
+
+ # Don't truncate the file, otherwise gdbreplay will
+ # close the connection early and this might impact
+ # what GDB does. We want GDB to get a chance to
+ # process the error.
+ puts $fh_out "c q"
+ puts $fh_out "w \$qTStatus#49"
+ puts $fh_out "End of log"
+
+ break
+ }
+ }
+
+ close $fh_out
+ close $fh_in
+}