diff options
Diffstat (limited to 'gdb/testsuite/gdb.replay')
-rw-r--r-- | gdb/testsuite/gdb.replay/connect.exp | 10 | ||||
-rw-r--r-- | gdb/testsuite/gdb.replay/fetch-exec-and-args.c | 34 | ||||
-rw-r--r-- | gdb/testsuite/gdb.replay/fetch-exec-and-args.exp | 146 | ||||
-rw-r--r-- | gdb/testsuite/gdb.replay/missing-thread.c | 61 | ||||
-rw-r--r-- | gdb/testsuite/gdb.replay/missing-thread.exp | 237 |
5 files changed, 482 insertions, 6 deletions
diff --git a/gdb/testsuite/gdb.replay/connect.exp b/gdb/testsuite/gdb.replay/connect.exp index 5790d38..f52f209 100644 --- a/gdb/testsuite/gdb.replay/connect.exp +++ b/gdb/testsuite/gdb.replay/connect.exp @@ -13,7 +13,7 @@ # 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. +# Starts a communication with gdbserver 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 @@ -70,9 +70,8 @@ proc_with_prefix record_initial_logfile {} { # 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 + clean_restart $::testfile # Make sure we're disconnected, in case we're testing with an # extended-remote board, therefore already connected. gdb_test "disconnect" ".*" @@ -97,7 +96,6 @@ proc_with_prefix replay_without_error {} { # 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 @@ -105,9 +103,9 @@ proc_with_prefix replay_with_mustreplyempty_error {} { # Modify the log file by changing the *response* to # the vMustReplayEmty packet to an error. - update_log $remotelog $output_file "vMustReplyEmpty" $newline + update_log $remotelog $output_file "vMustReplyEmpty" $newline true - clean_restart $binfile + clean_restart $::testfile # Make sure we're disconnected, in case we're testing with an # extended-remote board, therefore already connected. gdb_test "disconnect" ".*" diff --git a/gdb/testsuite/gdb.replay/fetch-exec-and-args.c b/gdb/testsuite/gdb.replay/fetch-exec-and-args.c new file mode 100644 index 0000000..3dc01a2 --- /dev/null +++ b/gdb/testsuite/gdb.replay/fetch-exec-and-args.c @@ -0,0 +1,34 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2023-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/>. */ + +/* Simple test, do some work with the arguments so GDB has a chance to + break and check that the arguments are correct. */ + +volatile int global_counter; + +int +main (int argc, char *argv[]) +{ + int i; + + global_counter = 0; /* Break here. */ + + for (i = 0; i < argc; ++i) + argv[i] = (char *) 0; + + return 0; +} diff --git a/gdb/testsuite/gdb.replay/fetch-exec-and-args.exp b/gdb/testsuite/gdb.replay/fetch-exec-and-args.exp new file mode 100644 index 0000000..c4fcba7 --- /dev/null +++ b/gdb/testsuite/gdb.replay/fetch-exec-and-args.exp @@ -0,0 +1,146 @@ +# Copyright 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 gdbserver 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 { [build_executable "failed to prepare" $testfile $srcfile] } { + return +} + +# Connect to gdbserver and run to a breakpoint in main. Record the +# remotelogfile into the REMOTELOG. +proc_with_prefix record_initial_logfile { remotelog } { + clean_restart + + # 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. + set res [gdbserver_start "" "$::binfile a b c"] + set gdbserver_protocol [lindex $res 0] + set gdbserver_gdbport [lindex $res 1] + + 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 connect to gdbserver)" + return + } + + # If we're connecting as 'remote' then we can't use 'runto'. + gdb_breakpoint [gdb_get_line_number "Break here"] + gdb_continue_to_breakpoint "continuing to breakpoint" + + gdb_test "show remote exec-file" \ + "The remote exec-file is \"[string_to_regexp $::binfile]\"\\." + gdb_test "show args" \ + "Argument list to give program being debugged when it is started is \"a b c\"\\." +} + +proc update_log_file { src dst } { + # Get the reply to the qExecAndArgs packet. + set reply [get_reply_line $src "qExecAndArgs"] + + # Remove the program name from the reply. + regsub "S;\[^;\]+;" $reply "S;;" reply + + # Remove the leading "r $" as this is added back by update_log. + regsub "^r \\\$" $reply "" reply + + # Write the new reply line into the modified log file. + update_log $src $dst "qExecAndArgs" $reply false +} + +# Rerun REMOTELOG using gdbreplay. The log file has been modified so +# that the qExecAndArgs packet reply no longer includes the program +# name, this will look like 'S;;args;'. As a result, GDB should no +# longer overwrite and existing remote exec-file setting. +# +# When REMOTE_EXEC is true set a remote exec-file value and check this +# is retained after connecting to gdbreplay. When REMOTE_EXEC is +# false, don't set a remote exec-file value, check that GDB shows the +# remote exec-file as unset after connecting. +proc_with_prefix replay_with_update_logfile { remotelog remote_exec } { + clean_restart + + # 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" + + # If requested, set a remote exec-file. + if { $remote_exec } { + set fake_remote_exec "/xxx/yyy/zzz" + gdb_test_no_output "set remote exec-file $fake_remote_exec" + set exec_file_re \ + "The remote exec-file is \"[string_to_regexp $fake_remote_exec]\"\\." + } else { + set exec_file_re \ + "The remote exec-file is unset, the default remote executable will be used\\." + } + + # Start gdbreplay. + set res [gdbreplay_start $remotelog] + set gdbserver_protocol [lindex $res 0] + set gdbserver_gdbport [lindex $res 1] + + # Connect to gdbreplay. + gdb_assert {[gdb_target_cmd $gdbserver_protocol $gdbserver_gdbport] == 0} \ + "connect to gdbreplay" + + # Same breakpoint and continue as when we recorded the log. + gdb_breakpoint [gdb_get_line_number "Break here"] + gdb_continue_to_breakpoint "continuing to breakpoint" + + # Inspect GDB's state. With the modified logfile the executable + # was passed back as an empty string, indicating that the + # executable should not be changed. + gdb_test "show remote exec-file" \ + $exec_file_re + gdb_test "show args" \ + "Argument list to give program being debugged when it is started is \"a b c\"\\." +} + +# The replay log is placed in 'replay.log'. +set remotelog [standard_output_file replay.log] +set remotelog_modified [standard_output_file replay-modified.log] + +record_initial_logfile $remotelog + +update_log_file $remotelog $remotelog_modified + +foreach_with_prefix set_remote_exec { true false } { + replay_with_update_logfile $remotelog_modified $set_remote_exec +} diff --git a/gdb/testsuite/gdb.replay/missing-thread.c b/gdb/testsuite/gdb.replay/missing-thread.c new file mode 100644 index 0000000..8edb240 --- /dev/null +++ b/gdb/testsuite/gdb.replay/missing-thread.c @@ -0,0 +1,61 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 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/>. */ + +#include <pthread.h> +#include <assert.h> +#include <signal.h> +#include <stdio.h> +#include <unistd.h> +#include <stdbool.h> + +pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t g_condvar = PTHREAD_COND_INITIALIZER; + +void * +worker_function (void *arg) +{ + printf ("In worker, about to notify\n"); + pthread_cond_signal (&g_condvar); + + while (true) + sleep(1); + + return NULL; +} + +int +main() +{ + pthread_t my_thread; + + int result = pthread_create (&my_thread, NULL, worker_function, NULL); + assert (result == 0); + + pthread_mutex_lock (&g_mutex); + pthread_cond_wait (&g_condvar, &g_mutex); + + printf ("In main, have been woken.\n"); + pthread_mutex_unlock (&g_mutex); + + result = pthread_kill (my_thread, SIGTRAP); + assert (result == 0); + + result = pthread_join (my_thread, NULL); + assert (result == 0); + + return 0; +} diff --git a/gdb/testsuite/gdb.replay/missing-thread.exp b/gdb/testsuite/gdb.replay/missing-thread.exp new file mode 100644 index 0000000..6ee2e4c --- /dev/null +++ b/gdb/testsuite/gdb.replay/missing-thread.exp @@ -0,0 +1,237 @@ +# Copyright 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/>. */ + +# This test confirms how GDB handles a badly behaving remote target. The +# remote target reports a stop event (signal delivery), then, as GDB is +# processing the stop it syncs the thread list with the remote. +# +# The badly behaving remote target was dropping the signaled thread from the +# thread list at this point, that is, the thread appeared to exit before an +# exit event had been sent to (and seen by) GDB. +# +# At one point this was causing an assertion failed. GDB would try to +# process the signal stop event, and to do this would try to read some +# registers. Reading registers requires a regcache, and GDB will only +# create a regcache for a non-exited thread. + +load_lib gdbserver-support.exp +load_lib gdbreplay-support.exp + +require allow_gdbserver_tests +require has_gdbreplay + +standard_testfile + +if { [build_executable "failed to build exec" $testfile $srcfile {debug pthreads}] } { + return -1 +} + +# Start the inferior and record a remote log for our interaction with it. +# All we do is start the inferior and wait for thread 2 to receive a signal. +# Check that GDB correctly shows the signal as received. LOG_FILENAME is +# where we should write the remote log. +proc_with_prefix record_initial_logfile { log_filename } { + clean_restart $::testfile + + # 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] + + gdb_test_no_output "set remotelogfile $log_filename" \ + "setup remotelogfile" + + # Connect to gdbserver. + if {![gdb_target_cmd $gdbserver_protocol $gdbserver_gdbport] == 0} { + unsupported "$testfile (couldn't start gdbserver)" + return + } + + gdb_breakpoint main + gdb_continue_to_breakpoint "continuing to main" + + gdb_test "continue" \ + "Thread $::decimal \[^\r\n\]+ received signal SIGTRAP, .*" + + gdb_test "disconnect" ".*" \ + "disconnect after seeing signal" +} + +# Copy the remote log from IN_FILENAME to OUT_FILENAME, but modify one +# particular line. +# +# The line to be modified is the last <threads>...</threads> line, this is +# the reply from the remote that indicates the thread list. It is expected +# that the thread list will contain two threads. +# +# When DROP_BOTH is true then both threads will be removed from the modified +# line. Otherwise, only the second thread is removed. +proc update_replay_log { in_filename out_filename drop_both } { + # Read IN_FILENAME into a list. + set fd [open $in_filename] + set data [read $fd] + close $fd + set lines [split $data "\n"] + + # Find the last line in LINES that contains the <threads> list. + set idx -1 + for { set i 0 } { $i < [llength $lines] } { incr i } { + if { [regexp "^r.*<threads>.*</threads>" [lindex $lines $i]] } { + set idx $i + } + } + + # Modify the line by dropping the second thread. This does assume + # the thread order as seen in the <threads>...</threads> list, but + # this seems stable for now. + set line [lindex $lines $idx] + set fixed_log false + if {[regexp "^(r .*<threads>\\\\n)(<thread id.*/>\\\\n)(<thread id.*/>\\\\n)(</threads>.*)$" $line \ + match part1 part2 part3 part4]} { + if { $drop_both } { + set line $part1$part4 + } else { + set line $part1$part2$part4 + } + set lines [lreplace $lines $idx $idx $line] + set fixed_log true + } + + # Write all the lines to OUT_FILENAME + set fd [open $out_filename "w"] + foreach l $lines { + puts $fd $l + } + close $fd + + # Did we manage to update the log file? + return $fixed_log +} + +# Replay the test process using REMOTE_LOG as the logfile to replay. If +# EXPECT_ERROR is true then after the final 'continue' we expect GDB to give +# an error as the required thread is missing. When EXPECT_ERROR is false +# then we expect the test to complete as normal. NON_STOP is eithe 'on' or +# 'off' and indicates GDBs non-stop mode. +proc_with_prefix replay_with_log { remote_log expect_error non_stop } { + clean_restart $::testfile + + # 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 $remote_log] + set gdbserver_protocol [lindex $res 0] + set gdbserver_gdbport [lindex $res 1] + + # Connect to gdbserver. + if {![gdb_target_cmd $gdbserver_protocol $gdbserver_gdbport] == 0} { + fail "couldn't connect to gdbreplay" + return + } + + gdb_breakpoint main + gdb_continue_to_breakpoint "continuing to main" + + if { $expect_error } { + set expected_output \ + [list \ + "\\\[Thread \[^\r\n\]+ exited\\\]" \ + "warning: command aborted, Thread \[^\r\n\]+ unexpectedly exited after signal stop event"] + + if { !$non_stop } { + lappend expected_output "\\\[Switching to Thread \[^\r\n\]+\\\]" + } + + gdb_test "continue" [multi_line {*}$expected_output] + } else { + # This is the original behaviour, we see this when running + # with the unmodified log. + gdb_test "continue" \ + "Thread ${::decimal}(?: \[^\r\n\]+)? received signal SIGTRAP, .*" + } + + gdb_test "disconnect" ".*" \ + "disconnect after seeing signal" +} + +# Run the complete test cycle; generate an initial log file, modify the log +# file, then check that GDB correctly handles replaying the modified log +# file. +# +# NON_STOP is either 'on' or 'off' and indicates GDB's non-stop mode. +proc run_test { non_stop } { + if { $non_stop } { + set suffix "-ns" + } else { + set suffix "" + } + + # The replay log is placed in 'replay.log'. + set remote_log [standard_output_file replay${suffix}.log] + set missing_1_log [standard_output_file replay-missing-1${suffix}.log] + set missing_2_log [standard_output_file replay-missing-2${suffix}.log] + + record_initial_logfile $remote_log + + if { ![update_replay_log $remote_log $missing_1_log false] } { + fail "couldn't update remote replay log (drop 1 case)" + } + + if { ![update_replay_log $remote_log $missing_2_log true] } { + fail "couldn't update remote replay log (drop 2 case)" + } + + with_test_prefix "with unmodified log" { + # Replay with the unmodified log. This confirms that we can replay this + # scenario correctly. + replay_with_log $remote_log false $non_stop + } + + with_test_prefix "missing 1 thread log" { + # Now replay with the modified log, this time the thread that receives + # the event should be missing from the thread list, GDB will give an + # error when the inferior stops. + replay_with_log $missing_1_log true $non_stop + } + + with_test_prefix "missing 2 threads log" { + # When we drop both threads from the <threads> reply, GDB doesn't + # actually remove both threads from the inferior; an inferior must + # always have at least one thread. So in this case, as the primary + # thread is first, GDB drops this, then retains the second thread, which + # is the one we're stopping in, and so, we don't expect to see the error + # in this case. + replay_with_log $missing_2_log false $non_stop + } +} + +# Run the test twice, with non-stop on and off. +foreach_with_prefix non_stop { on off } { + save_vars { ::GDBFLAGS } { + append ::GDBFLAGS " -ex \"set non-stop $non_stop\"" + run_test $non_stop + } +} |