New command queue-signal.
authorDoug Evans <xdje42@gmail.com>
Sun, 14 Sep 2014 04:44:00 +0000 (21:44 -0700)
committerDoug Evans <xdje42@gmail.com>
Sun, 14 Sep 2014 04:44:00 +0000 (21:44 -0700)
If I want to change the signalled state of multiple threads
it's a bit cumbersome to do with the "signal" command.
What you really want is a way to set the signal state of the
desired threads and then just do "continue".

This patch adds a new command, queue-signal, to accomplish this.
Basically "signal N" == "queue-signal N" + "continue".
That's not precisely true in that "signal" can be used to inject
any signal, including signals set to "nopass"; whereas "queue-signal"
just queues the signal as if the thread stopped because of it.
"nopass" handling is done when the thread is resumed which
"queue-signal" doesn't do.

One could add extra complexity to allow queue-signal to be used to
deliver "nopass" signals like the "signal" command.  I have no current
need for it so in the interests of incremental complexity, I have
left such support out and just have the code flag an error if one
tries to queue a nopass signal.

gdb/ChangeLog:

* NEWS: Mention new "queue-signal" command.
* infcmd.c (queue_signal_command): New function.
(_initialize_infcmd): Add new queue-signal command.

gdb/doc/ChangeLog:

* gdb.texinfo (Signaling): Document new queue-signal command.

gdb/testsuite/ChangeLog:

* gdb.threads/queue-signal.c: New file.
* gdb.threads/queue-signal.exp: New file.

gdb/ChangeLog
gdb/NEWS
gdb/doc/ChangeLog
gdb/doc/gdb.texinfo
gdb/infcmd.c
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.threads/queue-signal.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/queue-signal.exp [new file with mode: 0644]

index 6c442eb69a5547039da3f42edf1ff8e50e6ca15d..908d6a39186549a5f6d0e920aba6030b0077628c 100644 (file)
@@ -1,3 +1,9 @@
+2014-09-13  Doug Evans  <xdje42@gmail.com>
+
+       * NEWS: Mention new "queue-signal" command.
+       * infcmd.c (queue_signal_command): New function.
+       (_initialize_infcmd): Add new queue-signal command.
+
 2014-09-13  Doug Evans  <xdje42@gmail.com>
 
        * linux-nat.c (wait_lwp): Add debugging printf.
index 3bb1c742aa1fc5dc9b5d5e8840c4d4ee0373c5d5..d0a6ea557deb26ff2009d9677708ad1686ac0a86 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
   ** $_any_caller_is(name [, number_of_frames])
   ** $_any_caller_matches(regexp [, number_of_frames])
 
+* New commands
+
+queue-signal signal-name-or-number
+  Queue a signal to be delivered to the thread when it is resumed.
+
 * On resume, GDB now always passes the signal the program had stopped
   for to the thread the signal was sent to, even if the user changed
   threads before resuming.  Previously GDB would often (but not
index ddc11f0e7452d511a310a8b32dcce6787dcb620d..98ff38b5a69a19191696711e51df2bcef4182567 100644 (file)
@@ -1,3 +1,7 @@
+2014-09-13  Doug Evans  <xdje42@gmail.com>
+
+       * gdb.texinfo (Signaling): Document new queue-signal command.
+
 2014-09-06  Doug Evans  <xdje42@gmail.com>
 
        PR 15276
index facbd16cf674c5245ebe38e5e8c433a5f4912b67..037806f579727c9a848ff5da34544b7d3165db75 100644 (file)
@@ -16630,17 +16630,38 @@ same thread before issuing the @samp{signal 0} command.  If you issue
 the @samp{signal 0} command with another thread as the selected one,
 @value{GDBN} detects that and asks for confirmation.
 
-@code{signal} does not repeat when you press @key{RET} a second time
-after executing the command.
-@end table
-@c @end group
-
 Invoking the @code{signal} command is not the same as invoking the
 @code{kill} utility from the shell.  Sending a signal with @code{kill}
 causes @value{GDBN} to decide what to do with the signal depending on
 the signal handling tables (@pxref{Signals}).  The @code{signal} command
 passes the signal directly to your program.
 
+@code{signal} does not repeat when you press @key{RET} a second time
+after executing the command.
+
+@kindex queue-signal
+@item queue-signal @var{signal}
+Queue @var{signal} to be delivered immediately to the current thread
+when execution of the thread resumes.  The @var{signal} can be the name or
+the number of a signal.  For example, on many systems @code{signal 2} and
+@code{signal SIGINT} are both ways of sending an interrupt signal.
+The handling of the signal must be set to pass the signal to the program,
+otherwise @value{GDBN} will report an error.
+You can control the handling of signals from @value{GDBN} with the
+@code{handle} command (@pxref{Signals}).
+
+Alternatively, if @var{signal} is zero, any currently queued signal
+for the current thread is discarded and when execution resumes no signal
+will be delivered.  This is useful when your program stopped on account
+of a signal and would ordinarily see the signal when resumed with the
+@code{continue} command.
+
+This command differs from the @code{signal} command in that the signal
+is just queued, execution is not resumed.  And @code{queue-signal} cannot
+be used to pass a signal whose handling state has been set to @code{nopass}
+(@pxref{Signals}).
+@end table
+@c @end group
 
 @node Returning
 @section Returning from a Function
index 46a2b0ecf5f697df39cfc3c7aed3c522ceacc94d..178bd7328908cd894fda314b2e2983c3cb8d0f95 100644 (file)
@@ -1300,6 +1300,46 @@ signal_command (char *signum_exp, int from_tty)
   proceed ((CORE_ADDR) -1, oursig, 0);
 }
 
+/* Queue a signal to be delivered to the current thread.  */
+
+static void
+queue_signal_command (char *signum_exp, int from_tty)
+{
+  enum gdb_signal oursig;
+  struct thread_info *tp;
+
+  ERROR_NO_INFERIOR;
+  ensure_not_tfind_mode ();
+  ensure_valid_thread ();
+  ensure_not_running ();
+
+  if (signum_exp == NULL)
+    error_no_arg (_("signal number"));
+
+  /* It would be even slicker to make signal names be valid expressions,
+     (the type could be "enum $signal" or some such), then the user could
+     assign them to convenience variables.  */
+  oursig = gdb_signal_from_name (signum_exp);
+
+  if (oursig == GDB_SIGNAL_UNKNOWN)
+    {
+      /* No, try numeric.  */
+      int num = parse_and_eval_long (signum_exp);
+
+      if (num == 0)
+       oursig = GDB_SIGNAL_0;
+      else
+       oursig = gdb_signal_from_command (num);
+    }
+
+  if (oursig != GDB_SIGNAL_0
+      && !signal_pass_state (oursig))
+    error (_("Signal handling set to not pass this signal to the program."));
+
+  tp = inferior_thread ();
+  tp->suspend.stop_signal = oursig;
+}
+
 /* Continuation args to be passed to the "until" command
    continuation.  */
 struct until_next_continuation_args
@@ -3008,7 +3048,24 @@ The SIGNAL argument is processed the same as the handle command.\n\
 \n\
 An argument of \"0\" means continue the program without sending it a signal.\n\
 This is useful in cases where the program stopped because of a signal,\n\
-and you want to resume the program while discarding the signal."));
+and you want to resume the program while discarding the signal.\n\
+\n\
+In a multi-threaded program the signal is delivered to, or discarded from,\n\
+the current thread only."));
+  set_cmd_completer (c, signal_completer);
+
+  c = add_com ("queue-signal", class_run, queue_signal_command, _("\
+Queue a signal to be delivered to the current thread when it is resumed.\n\
+Usage: queue-signal SIGNAL\n\
+The SIGNAL argument is processed the same as the handle command.\n\
+It is an error if the handling state of SIGNAL is \"nopass\".\n\
+\n\
+An argument of \"0\" means remove any currently queued signal from\n\
+the current thread.  This is useful in cases where the program stopped\n\
+because of a signal, and you want to resume it while discarding the signal.\n\
+\n\
+In a multi-threaded program the signal is queued with, or discarded from,\n\
+the current thread only."));
   set_cmd_completer (c, signal_completer);
 
   add_com ("stepi", class_run, stepi_command, _("\
index c53fae47254f335cce2c59210b9374689e36f068..c1feb31baa0205fc9e6101ac9adf0114c2157818 100644 (file)
@@ -1,3 +1,8 @@
+2014-09-13  Doug Evans  <xdje42@gmail.com>
+
+       * gdb.threads/queue-signal.c: New file.
+       * gdb.threads/queue-signal.exp: New file.
+
 2014-09-13  Doug Evans  <xdje42@gmail.com>
 
        * lib/gdb.exp (gdb_prompt): Add comment and change initial value to
diff --git a/gdb/testsuite/gdb.threads/queue-signal.c b/gdb/testsuite/gdb.threads/queue-signal.c
new file mode 100644 (file)
index 0000000..147f2f7
--- /dev/null
@@ -0,0 +1,102 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2014 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 <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Used to individually advance each thread to the desired stopping point.  */
+int ready;
+
+sig_atomic_t sigusr1_received;
+sig_atomic_t sigusr2_received;
+sig_atomic_t sigabrt_received;
+
+static void
+sigusr1_handler (int sig)
+{
+  sigusr1_received = 1;
+}
+
+static void
+sigusr2_handler (int sig)
+{
+  sigusr2_received = 1;
+}
+
+static void
+sigabrt_handler (int sig)
+{
+  sigabrt_received = 1;
+}
+
+static void *
+sigusr1_thread_function (void *unused)
+{
+  while (!ready)
+    usleep (100);
+  pthread_kill (pthread_self (), SIGUSR1);
+}
+
+static void *
+sigusr2_thread_function (void *unused)
+{
+  while (!ready)
+    usleep (100);
+  /* pthread_kill (pthread_self (), SIGUSR2); - manually injected by gdb */
+}
+
+static void
+all_threads_running (void)
+{
+  while (!ready)
+    usleep (100);
+}
+
+static void
+all_threads_done (void)
+{
+}
+
+int
+main ()
+{
+  pthread_t sigusr1_thread, sigusr2_thread;
+
+  /* Protect against running forever.  */
+  alarm (60);
+
+  signal (SIGUSR1, sigusr1_handler);
+  signal (SIGUSR2, sigusr2_handler);
+  signal (SIGABRT, sigabrt_handler);
+
+  /* Don't let any thread advance past initialization.  */
+  ready = 0;
+
+  pthread_create (&sigusr1_thread, NULL, sigusr1_thread_function, NULL);
+  pthread_create (&sigusr2_thread, NULL, sigusr2_thread_function, NULL);
+  all_threads_running ();
+
+  pthread_kill (pthread_self (), SIGABRT);
+
+  pthread_join (sigusr1_thread, NULL);
+  pthread_join (sigusr2_thread, NULL);
+  all_threads_done ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/queue-signal.exp b/gdb/testsuite/gdb.threads/queue-signal.exp
new file mode 100644 (file)
index 0000000..207073d
--- /dev/null
@@ -0,0 +1,91 @@
+# Copyright (C) 2014 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/>.
+
+standard_testfile
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \
+        executable { debug }] != "" } {
+    return -1
+}
+
+clean_restart ${binfile}
+
+if ![runto_main] {
+   fail "Can't run to main"
+   return 0
+}
+
+gdb_test "handle SIGUSR1 stop print pass"
+gdb_test "handle SIGUSR2 stop print pass"
+gdb_test "handle SIGABRT stop print pass"
+
+gdb_breakpoint "all_threads_running"
+gdb_continue_to_breakpoint "all_threads_running"
+
+# Find out which of threads 2,3 are for sigusr1,2.
+set sigusr1_thread 0
+set sigusr2_thread 0
+gdb_test "thread 2"
+gdb_test_multiple "bt" "determine thread functions" {
+    -re "sigusr1.*$gdb_prompt $" {
+       set sigusr1_thread 2
+       set sigusr2_thread 3
+    }
+    -re "sigusr2.*$gdb_prompt $" {
+       set sigusr1_thread 3
+       set sigusr2_thread 2
+    }
+}
+
+# No point in continuing if we couldn't figure out which thread is which.
+if { $sigusr1_thread == 0 } {
+    # FAIL already recorded.
+    return 0
+}
+
+# Advance each thread to where we want them one at a time.
+gdb_test_no_output "set scheduler-locking on"
+gdb_test_no_output "set var ready = 1"
+
+# Thread sigusr1_thread gets a SIGUSR1 which we leave alone.
+gdb_test "thread $sigusr1_thread" ""
+gdb_test "continue" "SIGUSR1.*"
+
+# Inject SIGUSR2 into thread sigusr2_thread.
+gdb_test "thread $sigusr2_thread" ""
+gdb_test_no_output "queue-signal SIGUSR2"
+
+# The main thread gets SIGABRT which we then throw away.
+gdb_test "thread 1" ""
+gdb_test "continue" "SIGABRT.*"
+gdb_test_no_output "queue-signal 0"
+
+# Now let every thread run.
+gdb_test_no_output "set scheduler-locking off"
+
+gdb_breakpoint "all_threads_done"
+gdb_continue_to_breakpoint "all_threads_done"
+
+# Verify SIGUSR1, SIGUSR2 were received, and SIGABRT was discarded.
+gdb_test "p sigusr1_received" "= 1"
+gdb_test "p sigusr2_received" "= 1"
+gdb_test "p sigabrt_received" "= 0"
+
+# Before we finish up verify the queueing of nopass signals flags an error.
+gdb_test "queue-signal SIGINT" \
+  "Signal handling set to not pass this signal to the program."
+
+# Verify program is able to finish.
+gdb_continue_to_end