diff options
-rw-r--r-- | gdb/ChangeLog | 36 | ||||
-rw-r--r-- | gdb/NEWS | 11 | ||||
-rw-r--r-- | gdb/breakpoint.c | 2 | ||||
-rw-r--r-- | gdb/doc/ChangeLog | 6 | ||||
-rw-r--r-- | gdb/doc/gdb.texinfo | 11 | ||||
-rw-r--r-- | gdb/gdbthread.h | 8 | ||||
-rw-r--r-- | gdb/infcall.c | 2 | ||||
-rw-r--r-- | gdb/infcmd.c | 64 | ||||
-rw-r--r-- | gdb/infrun.c | 93 | ||||
-rw-r--r-- | gdb/infrun.h | 8 | ||||
-rw-r--r-- | gdb/linux-nat.c | 3 | ||||
-rw-r--r-- | gdb/mi/mi-main.c | 2 | ||||
-rw-r--r-- | gdb/remote.c | 3 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 9 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/signal-command-handle-nopass.c | 49 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/signal-command-handle-nopass.exp | 78 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/signal-command-multiple-signals-pending.c | 98 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/signal-command-multiple-signals-pending.exp | 166 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/signal-delivered-right-thread.c | 61 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/signal-delivered-right-thread.exp | 85 |
20 files changed, 716 insertions, 79 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 11cdbf8..dd41478 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,39 @@ +2014-07-25 Pedro Alves <palves@redhat.com> + + * NEWS: Mention signal passing and "signal" command changes. + * gdbthread.h (struct thread_suspend_state) <stop_signal>: Extend + comment. + * breakpoint.c (until_break_command): Adjust clear_proceed_status + call. + * infcall.c (run_inferior_call): Adjust clear_proceed_status call. + * infcmd.c (proceed_thread_callback, continue_1, step_once) + (jump_command): Adjust clear_proceed_status call. + (signal_command): Warn if other thread that are resumed have + signals that will be delivered. Adjust clear_proceed_status call. + (until_next_command, finish_command) + (proceed_after_attach_callback, attach_command_post_wait) + (attach_command): Adjust clear_proceed_status call. + * infrun.c (proceed_after_vfork_done): Likewise. + (proceed_after_attach_callback): Adjust comment. + (clear_proceed_status_thread): Clear stop_signal if not in pass + state. + (clear_proceed_status_callback): Delete. + (clear_proceed_status): New 'step' parameter. Only clear the + proceed status of threads the command being prepared is about to + resume. + (proceed): If passed in an explicit signal, override stop_signal + with it. Don't pass the last stop signal to the thread we're + resuming. + (init_wait_for_inferior): Adjust clear_proceed_status call. + (switch_back_to_stepped_thread): Clear the signal if it should not + be passed. + * infrun.h (clear_proceed_status): New 'step' parameter. + (user_visible_resume_ptid): Add comment. + * linux-nat.c (linux_nat_resume_callback): Don't check whether the + signal is in pass state. + * remote.c (append_pending_thread_resumptions): Likewise. + * mi/mi-main.c (proceed_thread): Adjust clear_proceed_status call. + 2014-07-25 Tom Tromey <tromey@redhat.com> * target.h (target_stopped_data_address) @@ -3,6 +3,17 @@ *** Changes since GDB 7.8 +* 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 + always) deliver the signal to the thread that happens to be current + at resume time. + +* Conversely, the "signal" command now consistently delivers the + requested signal to the current thread. GDB now asks for + confirmation if the program had stopped for a signal and the user + switched threads meanwhile. + *** Changes in GDB 7.8 * New command line options diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 1c6070f..262e992 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -11668,7 +11668,7 @@ until_break_command (char *arg, int from_tty, int anywhere) int thread; struct thread_info *tp; - clear_proceed_status (); + clear_proceed_status (0); /* Set a breakpoint where the user wants it and at return from this function. */ diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index 67998ca..41de328 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,9 @@ +2014-07-25 Pedro Alves <palves@redhat.com> + Eli Zaretskii <eliz@gnu.org> + + * gdb.texinfo (Signaling) <signal command>: Explain what happens + with multi-threaded programs. + 2014-06-27 Yao Qi <yao@codesourcery.com> * gdb.texinfo (Maintenance Commands): Update the output of diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 8101b87..32f709a 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -16546,7 +16546,7 @@ detail. @table @code @kindex signal @item signal @var{signal} -Resume execution where your program stopped, but immediately give it the +Resume execution where your program is stopped, but immediately give it the signal @var{signal}. 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. @@ -16557,6 +16557,15 @@ a signal and would ordinarily see the signal when resumed with the @code{continue} command; @samp{signal 0} causes it to resume without a signal. +@emph{Note:} When resuming a multi-threaded program, @var{signal} is +delivered to the currently selected thread, not the thread that last +reported a stop. This includes the situation where a thread was +stopped due to a signal. So if you want to continue execution +suppressing the signal that stopped a thread, you should select that +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 diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index 9ef74cd..522b674 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -135,7 +135,13 @@ struct thread_control_state struct thread_suspend_state { - /* Last signal that the inferior received (why it stopped). */ + /* Last signal that the inferior received (why it stopped). When + the thread is resumed, this signal is delivered. Note: the + target should not check whether the signal is in pass state, + because the signal may have been explicitly passed with the + "signal" command, which overrides "handle nopass". If the signal + should be suppressed, the core will take care of clearing this + before the target is resumed. */ enum gdb_signal stop_signal; }; diff --git a/gdb/infcall.c b/gdb/infcall.c index a9b1ceb..5c65bb5 100644 --- a/gdb/infcall.c +++ b/gdb/infcall.c @@ -396,7 +396,7 @@ run_inferior_call (struct thread_info *call_thread, CORE_ADDR real_pc) call_thread->control.in_infcall = 1; - clear_proceed_status (); + clear_proceed_status (0); disable_watchpoints_before_interactive_call_start (); diff --git a/gdb/infcmd.c b/gdb/infcmd.c index 021a587..5eb092b8 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -679,7 +679,7 @@ proceed_thread_callback (struct thread_info *thread, void *arg) return 0; switch_to_thread (thread->ptid); - clear_proceed_status (); + clear_proceed_status (0); proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT, 0); return 0; } @@ -745,7 +745,7 @@ continue_1 (int all_threads) { ensure_valid_thread (); ensure_not_running (); - clear_proceed_status (); + clear_proceed_status (0); proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT, 0); } } @@ -1013,7 +1013,7 @@ step_once (int skip_subroutines, int single_inst, int count, int thread) THREAD is set. */ struct thread_info *tp = inferior_thread (); - clear_proceed_status (); + clear_proceed_status (!skip_subroutines); set_step_frame (); if (!single_inst) @@ -1186,7 +1186,7 @@ jump_command (char *arg, int from_tty) printf_filtered (".\n"); } - clear_proceed_status (); + clear_proceed_status (0); proceed (addr, GDB_SIGNAL_0, 0); } @@ -1245,6 +1245,50 @@ signal_command (char *signum_exp, int from_tty) oursig = gdb_signal_from_command (num); } + /* Look for threads other than the current that this command ends up + resuming too (due to schedlock off), and warn if they'll get a + signal delivered. "signal 0" is used to suppress a previous + signal, but if the current thread is no longer the one that got + the signal, then the user is potentially suppressing the signal + of the wrong thread. */ + if (!non_stop) + { + struct thread_info *tp; + ptid_t resume_ptid; + int must_confirm = 0; + + /* This indicates what will be resumed. Either a single thread, + a whole process, or all threads of all processes. */ + resume_ptid = user_visible_resume_ptid (0); + + ALL_NON_EXITED_THREADS (tp) + { + if (ptid_equal (tp->ptid, inferior_ptid)) + continue; + if (!ptid_match (tp->ptid, resume_ptid)) + continue; + + if (tp->suspend.stop_signal != GDB_SIGNAL_0 + && signal_pass_state (tp->suspend.stop_signal)) + { + if (!must_confirm) + printf_unfiltered (_("Note:\n")); + printf_unfiltered (_(" Thread %d previously stopped with signal %s, %s.\n"), + tp->num, + gdb_signal_to_name (tp->suspend.stop_signal), + gdb_signal_to_string (tp->suspend.stop_signal)); + must_confirm = 1; + } + } + + if (must_confirm + && !query (_("Continuing thread %d (the current thread) with specified signal will\n" + "still deliver the signals noted above to their respective threads.\n" + "Continue anyway? "), + inferior_thread ()->num)) + error (_("Not confirmed.")); + } + if (from_tty) { if (oursig == GDB_SIGNAL_0) @@ -1254,7 +1298,7 @@ signal_command (char *signum_exp, int from_tty) gdb_signal_to_name (oursig)); } - clear_proceed_status (); + clear_proceed_status (0); proceed ((CORE_ADDR) -1, oursig, 0); } @@ -1295,7 +1339,7 @@ until_next_command (int from_tty) int thread = tp->num; struct cleanup *old_chain; - clear_proceed_status (); + clear_proceed_status (0); set_step_frame (); frame = get_current_frame (); @@ -1686,7 +1730,7 @@ finish_command (char *arg, int from_tty) if (frame == 0) error (_("\"finish\" not meaningful in the outermost frame.")); - clear_proceed_status (); + clear_proceed_status (0); /* Finishing from an inline frame is completely different. We don't try to show the "return value" - no way to locate it. So we do @@ -2298,7 +2342,7 @@ proceed_after_attach_callback (struct thread_info *thread, && thread->suspend.stop_signal == GDB_SIGNAL_0) { switch_to_thread (thread->ptid); - clear_proceed_status (); + clear_proceed_status (0); proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT, 0); } @@ -2395,7 +2439,7 @@ attach_command_post_wait (char *args, int from_tty, int async_exec) { if (inferior_thread ()->suspend.stop_signal == GDB_SIGNAL_0) { - clear_proceed_status (); + clear_proceed_status (0); proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT, 0); } } @@ -2514,7 +2558,7 @@ attach_command (char *args, int from_tty) /* Set up execution context to know that we should return from wait_for_inferior as soon as the target reports a stop. */ init_wait_for_inferior (); - clear_proceed_status (); + clear_proceed_status (0); if (non_stop) { diff --git a/gdb/infrun.c b/gdb/infrun.c index 3c58ff2..33aa674 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -623,7 +623,7 @@ proceed_after_vfork_done (struct thread_info *thread, target_pid_to_str (thread->ptid)); switch_to_thread (thread->ptid); - clear_proceed_status (); + clear_proceed_status (0); proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT, 0); } @@ -1721,15 +1721,6 @@ maybe_software_singlestep (struct gdbarch *gdbarch, CORE_ADDR pc) return hw_step; } -/* Return a ptid representing the set of threads that we will proceed, - in the perspective of the user/frontend. We may actually resume - fewer threads at first, e.g., if a thread is stopped at a - breakpoint that needs stepping-off, but that should not be visible - to the user/frontend, and neither should the frontend/user be - allowed to proceed any of the threads that happen to be stopped for - internal run control handling, if a previous command wanted them - resumed. */ - ptid_t user_visible_resume_ptid (int step) { @@ -1757,6 +1748,12 @@ user_visible_resume_ptid (int step) resume_ptid = inferior_ptid; } + /* We may actually resume fewer threads at first, e.g., if a thread + is stopped at a breakpoint that needs stepping-off, but that + should not be visible to the user/frontend, and neither should + the frontend/user be allowed to proceed any of the threads that + happen to be stopped for internal run control handling, if a + previous command wanted them resumed. */ return resume_ptid; } @@ -2032,6 +2029,11 @@ clear_proceed_status_thread (struct thread_info *tp) "infrun: clear_proceed_status_thread (%s)\n", target_pid_to_str (tp->ptid)); + /* If this signal should not be seen by program, give it zero. + Used for debugging signals. */ + if (!signal_pass_state (tp->suspend.stop_signal)) + tp->suspend.stop_signal = GDB_SIGNAL_0; + tp->control.trap_expected = 0; tp->control.step_range_start = 0; tp->control.step_range_end = 0; @@ -2051,26 +2053,24 @@ clear_proceed_status_thread (struct thread_info *tp) bpstat_clear (&tp->control.stop_bpstat); } -static int -clear_proceed_status_callback (struct thread_info *tp, void *data) -{ - if (is_exited (tp->ptid)) - return 0; - - clear_proceed_status_thread (tp); - return 0; -} - void -clear_proceed_status (void) +clear_proceed_status (int step) { if (!non_stop) { - /* In all-stop mode, delete the per-thread status of all - threads, even if inferior_ptid is null_ptid, there may be - threads on the list. E.g., we may be launching a new - process, while selecting the executable. */ - iterate_over_threads (clear_proceed_status_callback, NULL); + struct thread_info *tp; + ptid_t resume_ptid; + + resume_ptid = user_visible_resume_ptid (step); + + /* In all-stop mode, delete the per-thread status of all threads + we're about to resume, implicitly and explicitly. */ + ALL_NON_EXITED_THREADS (tp) + { + if (!ptid_match (tp->ptid, resume_ptid)) + continue; + clear_proceed_status_thread (tp); + } } if (!ptid_equal (inferior_ptid, null_ptid)) @@ -2252,6 +2252,9 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal, int step) regcache_write_pc (regcache, addr); } + if (siggnal != GDB_SIGNAL_DEFAULT) + tp->suspend.stop_signal = siggnal; + /* Record the interpreter that issued the execution command that caused this thread to resume. If the top level interpreter is MI/async, and the execution command was a CLI command @@ -2318,38 +2321,6 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal, int step) tp->control.trap_expected = tp->stepping_over_breakpoint; - if (!non_stop) - { - /* Pass the last stop signal to the thread we're resuming, - irrespective of whether the current thread is the thread that - got the last event or not. This was historically GDB's - behaviour before keeping a stop_signal per thread. */ - - struct thread_info *last_thread; - ptid_t last_ptid; - struct target_waitstatus last_status; - - get_last_target_status (&last_ptid, &last_status); - if (!ptid_equal (inferior_ptid, last_ptid) - && !ptid_equal (last_ptid, null_ptid) - && !ptid_equal (last_ptid, minus_one_ptid)) - { - last_thread = find_thread_ptid (last_ptid); - if (last_thread) - { - tp->suspend.stop_signal = last_thread->suspend.stop_signal; - last_thread->suspend.stop_signal = GDB_SIGNAL_0; - } - } - } - - if (siggnal != GDB_SIGNAL_DEFAULT) - tp->suspend.stop_signal = siggnal; - /* If this signal should not be seen by program, - give it zero. Used for debugging signals. */ - else if (!signal_program[tp->suspend.stop_signal]) - tp->suspend.stop_signal = GDB_SIGNAL_0; - annotate_starting (); /* Make sure that output from GDB appears before output from the @@ -2443,7 +2414,7 @@ init_wait_for_inferior (void) breakpoint_init_inferior (inf_starting); - clear_proceed_status (); + clear_proceed_status (0); target_last_wait_ptid = minus_one_ptid; @@ -5190,6 +5161,10 @@ switch_back_to_stepped_thread (struct execution_control_state *ecs) what keep_going does as well, if we call it. */ ecs->event_thread->control.trap_expected = 0; + /* Likewise, clear the signal if it should not be passed. */ + if (!signal_program[ecs->event_thread->suspend.stop_signal]) + ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0; + /* If scheduler locking applies even if not stepping, there's no need to walk over threads. Above we've checked whether the current thread is stepping. If some other thread not the diff --git a/gdb/infrun.h b/gdb/infrun.h index 66612a8..35b2246 100644 --- a/gdb/infrun.h +++ b/gdb/infrun.h @@ -83,7 +83,11 @@ extern struct regcache *stop_registers; extern void start_remote (int from_tty); -extern void clear_proceed_status (void); +/* Clear out all variables saying what to do when inferior is + continued or stepped. First do this, then set the ones you want, + then call `proceed'. STEP indicates whether we're preparing for a + step/stepi command. */ +extern void clear_proceed_status (int step); extern void proceed (CORE_ADDR, enum gdb_signal, int); @@ -91,6 +95,8 @@ extern void proceed (CORE_ADDR, enum gdb_signal, int); Normally, use `proceed', which handles a lot of bookkeeping. */ extern void resume (int, enum gdb_signal); +/* Return a ptid representing the set of threads that we will proceed, + in the perspective of the user/frontend. */ extern ptid_t user_visible_resume_ptid (int step); extern void wait_for_inferior (void); diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 7db1b3d..8d4251f 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -1694,8 +1694,7 @@ linux_nat_resume_callback (struct lwp_info *lp, void *except) thread = find_thread_ptid (lp->ptid); if (thread != NULL) { - if (signal_pass_state (thread->suspend.stop_signal)) - signo = thread->suspend.stop_signal; + signo = thread->suspend.stop_signal; thread->suspend.stop_signal = GDB_SIGNAL_0; } } diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c index 96dfc71..26389f1 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -253,7 +253,7 @@ proceed_thread (struct thread_info *thread, int pid) return; switch_to_thread (thread->ptid); - clear_proceed_status (); + clear_proceed_status (0); proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT, 0); } diff --git a/gdb/remote.c b/gdb/remote.c index 972c0ff..3a05bfd 100644 --- a/gdb/remote.c +++ b/gdb/remote.c @@ -4643,8 +4643,7 @@ append_pending_thread_resumptions (char *p, char *endp, ptid_t ptid) ALL_NON_EXITED_THREADS (thread) if (ptid_match (thread->ptid, ptid) && !ptid_equal (inferior_ptid, thread->ptid) - && thread->suspend.stop_signal != GDB_SIGNAL_0 - && signal_pass_state (thread->suspend.stop_signal)) + && thread->suspend.stop_signal != GDB_SIGNAL_0) { p = append_resumption (p, endp, thread->ptid, 0, thread->suspend.stop_signal); diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index bd8cf44..e088b9b 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,5 +1,14 @@ 2014-07-25 Pedro Alves <palves@redhat.com> + * gdb.threads/signal-command-handle-nopass.c: New file. + * gdb.threads/signal-command-handle-nopass.exp: New file. + * gdb.threads/signal-command-multiple-signals-pending.c: New file. + * gdb.threads/signal-command-multiple-signals-pending.exp: New file. + * gdb.threads/signal-delivered-right-thread.c: New file. + * gdb.threads/signal-delivered-right-thread.exp: New file. + +2014-07-25 Pedro Alves <palves@redhat.com> + * gdb.base/double-prompt-target-event-error.exp (cancel_pagination_in_target_event): Remove '-notransfer <return>' match. diff --git a/gdb/testsuite/gdb.threads/signal-command-handle-nopass.c b/gdb/testsuite/gdb.threads/signal-command-handle-nopass.c new file mode 100644 index 0000000..abca2f9 --- /dev/null +++ b/gdb/testsuite/gdb.threads/signal-command-handle-nopass.c @@ -0,0 +1,49 @@ +/* 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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <pthread.h> +#include <signal.h> + +void +handler (int sig) +{ +} + +void * +thread_function (void *arg) +{ + volatile unsigned int i = 1; + + while (i != 0) + usleep (1); +} + +int +main (void) +{ + pthread_t child_thread; + int i; + + signal (SIGUSR1, handler); + pthread_create (&child_thread, NULL, thread_function, NULL); + pthread_join (child_thread, NULL); + + return 0; +} diff --git a/gdb/testsuite/gdb.threads/signal-command-handle-nopass.exp b/gdb/testsuite/gdb.threads/signal-command-handle-nopass.exp new file mode 100644 index 0000000..3ea9cf8 --- /dev/null +++ b/gdb/testsuite/gdb.threads/signal-command-handle-nopass.exp @@ -0,0 +1,78 @@ +# 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/>. */ + +# Test that an explicit "signal FOO" delivers FOO even if "handle" for +# that same signal is set to "nopass". Also make sure the signal is +# delivered to the right thread, even if GDB has to step over a +# breakpoint in some other thread first. + +if [target_info exists gdb,nosignals] { + verbose "Skipping ${testfile}.exp because of nosignals." + return -1 +} + +standard_testfile + +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \ + executable { debug }] != "" } { + return -1 +} + +# Run the test proper. STEP_OVER indicates whether we leave in place +# a breakpoint that needs to be stepped over when we explicitly +# request a signal be delivered with the "signal" command. + +proc test { step_over } { + global srcfile binfile + + with_test_prefix "step-over $step_over" { + clean_restart ${binfile} + + if ![runto_main] then { + fail "Can't run to main" + return 0 + } + + gdb_test "handle SIGUSR1 stop print nopass" + + gdb_test "b thread_function" "Breakpoint .* at .*$srcfile.*" + gdb_test "continue" "thread_function.*" "stopped in thread" + + # Thread 2 is stopped at a breakpoint. If we leave the + # breakpoint in place, GDB needs to move thread 2 past the + # breakpoint before delivering the signal to thread 1. We + # want to be sure that GDB doesn't mistakenly deliver the + # signal to thread 1 while doing that. + if { $step_over == "no" } { + delete_breakpoints + } + + gdb_test "break handler" "Breakpoint .* at .*$srcfile.*" + + gdb_test "thread 1" "Switching to thread 1.*" + + set pattern "\\\* 1\[ \t\]+Thread.*" + + gdb_test "info threads" $pattern "thread 1 selected" + + gdb_test "signal SIGUSR1" "handler .*" + + gdb_test "info threads" $pattern "thread 1 got the signal" + } +} + +foreach stepover {"yes" "no"} { + test $stepover +} diff --git a/gdb/testsuite/gdb.threads/signal-command-multiple-signals-pending.c b/gdb/testsuite/gdb.threads/signal-command-multiple-signals-pending.c new file mode 100644 index 0000000..2fc5f53 --- /dev/null +++ b/gdb/testsuite/gdb.threads/signal-command-multiple-signals-pending.c @@ -0,0 +1,98 @@ +/* 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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <pthread.h> +#include <signal.h> + +pthread_barrier_t barrier; +sig_atomic_t got_sigusr1; +sig_atomic_t got_sigusr2; + +void +handler_sigusr1 (int sig) +{ + got_sigusr1 = 1; +} + +void +handler_sigusr2 (int sig) +{ + got_sigusr2 = 1; +} + +void * +thread_function (void *arg) +{ + volatile unsigned int count = 1; + + pthread_barrier_wait (&barrier); + + while (count++ != 0) + { + if (got_sigusr1 && got_sigusr2) + break; + usleep (1); + } +} + +void +all_threads_started (void) +{ +} + +void +all_threads_signalled (void) +{ +} + +void +end (void) +{ +} + +int +main (void) +{ + pthread_t child_thread[2]; + int i; + + signal (SIGUSR1, handler_sigusr1); + signal (SIGUSR2, handler_sigusr2); + + pthread_barrier_init (&barrier, NULL, 3); + + for (i = 0; i < 2; i++) + pthread_create (&child_thread[i], NULL, thread_function, NULL); + + pthread_barrier_wait (&barrier); + + all_threads_started (); + + pthread_kill (child_thread[0], SIGUSR1); + pthread_kill (child_thread[1], SIGUSR2); + + all_threads_signalled (); + + for (i = 0; i < 2; i++) + pthread_join (child_thread[i], NULL); + + end (); + return 0; +} diff --git a/gdb/testsuite/gdb.threads/signal-command-multiple-signals-pending.exp b/gdb/testsuite/gdb.threads/signal-command-multiple-signals-pending.exp new file mode 100644 index 0000000..b5ec00a --- /dev/null +++ b/gdb/testsuite/gdb.threads/signal-command-multiple-signals-pending.exp @@ -0,0 +1,166 @@ +# 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/>. */ + +# Test that "signal FOO" behaves correctly when we have multiple +# threads that have stopped for a signal. + +if [target_info exists gdb,nosignals] { + verbose "Skipping ${testfile}.exp because of nosignals." + return -1 +} + +standard_testfile + +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \ + executable { debug }] != "" } { + return -1 +} + +# Run the test proper. SCHEDLOCK indicates which variant (around +# scheduler-locking) of the test to perform. + +proc test { schedlock } { + global srcfile binfile + + with_test_prefix "schedlock $schedlock" { + clean_restart ${binfile} + + if ![runto_main] then { + fail "Can't run to main" + return 0 + } + + gdb_test "handle SIGUSR1 stop print pass" + gdb_test "handle SIGUSR2 stop print pass" + + gdb_test "break all_threads_started" "Breakpoint .* at .*$srcfile.*" + gdb_test "continue" "all_threads_started.*" + + # Using schedlock, let the main thread queue a signal for each + # non-main thread. + gdb_test_no_output "set scheduler-locking on" + + gdb_test "break all_threads_signalled" "Breakpoint .* at .*$srcfile.*" + gdb_test "continue" "all_threads_signalled.*" + + gdb_test "info threads" "\\\* 1\[ \t\]+Thread.*" "thread 1 selected" + + # With schedlock still enabled, let each thread report its + # signal. + + gdb_test "thread 3" "Switching to thread 3.*" + gdb_test "continue" "Program received signal SIGUSR2.*" "stop with SIGUSR2" + gdb_test "thread 2" "Switching to thread 2.*" + gdb_test "continue" "Program received signal SIGUSR1.*" "stop with SIGUSR1" + + gdb_test "break handler_sigusr1" "Breakpoint .* at .*$srcfile.*" + gdb_test "break handler_sigusr2" "Breakpoint .* at .*$srcfile.*" + + set handler_re "Breakpoint .*, handler_sigusr. \\(sig=.*\\) at .*" + + # Now test the "signal" command with either scheduler locking + # enabled or disabled. + + if { $schedlock == "off" } { + # With scheduler locking off, switch to the main thread + # and issue "signal 0". "signal 0" should then warn that + # two threads have signals that will be delivered. When + # we let the command proceed, a signal should be + # delivered, and thus the corresponding breakpoint in the + # signal handler should trigger. + + gdb_test_no_output "set scheduler-locking off" + gdb_test "thread 1" "Switching to thread 1.*" + + set queried 0 + set test "signal command queries" + gdb_test_multiple "signal 0" $test { + -re "stopped with.*stopped with.*stopped with.*Continue anyway.*y or n. $" { + fail "$test (too many threads noted)" + set queried 1 + } + -re "stopped with signal SIGUSR.*\r\nContinuing .*still deliver .*Continue anyway.*y or n. $" { + pass $test + set queried 1 + } + -re "Continue anyway.*y or n. $" { + fail "$test (no threads noted)" + set queried 1 + } + } + + # Continuing should stop in one of the signal handlers. + # Which thread runs first is not determinate. + if {$queried} { + gdb_test "y" "$handler_re" "one signal delivered" + } + + # Continuing a second time should stop in the other + # handler. + with_test_prefix "second signal" { + gdb_test "continue" "$handler_re" "signal delivered" + } + } else { + # With scheduler locking on, stay with thread 2 selected, + # and try to deliver its signal explicitly. The "signal" + # command should then warn that one other thread has a + # signal that will be delivered. When we let the command + # proceed, the current thread's signal should be + # delivered, and thus the corresponding breakpoint in the + # signal handler should trigger. + gdb_test "signal SIGUSR1" \ + "Breakpoint .*, handler_sigusr1 \\(sig=.*\\) at .*" \ + "signal command does not query, signal delivered" + + with_test_prefix "second signal" { + # The other thread had stopped for a signal too, and + # it wasn't resumed yet. Disabling schedlock and + # trying "signal 0" from the main thread should warn + # again. + gdb_test_no_output "set scheduler-locking off" + + set queried 0 + set test "signal command queries" + gdb_test_multiple "signal 0" $test { + -re "stopped with.*stopped with.*Continue anyway.*y or n. $" { + fail "$test (too many threads noted)" + set queried 1 + } + -re "stopped with signal SIGUSR.*\r\nContinuing .*still deliver .*Continue anyway.*y or n. $" { + pass $test + set queried 1 + } + -re "Continue anyway.*y or n. $" { + fail "$test (no threads noted)" + set queried 1 + } + } + + if {$queried} { + gdb_test "y" "Breakpoint .*, handler_sigusr2 \\(sig=.*\\) at .*" "signal delivered" + } + } + } + + # Both threads got their signal. Continuing again should + # neither intercept nor deliver any other signal. + gdb_test "b end" "Breakpoint .* at .*$srcfile.*" + gdb_test "continue" "end .*" "no more signals" + } +} + +foreach schedlock {"off" "on"} { + test $schedlock +} diff --git a/gdb/testsuite/gdb.threads/signal-delivered-right-thread.c b/gdb/testsuite/gdb.threads/signal-delivered-right-thread.c new file mode 100644 index 0000000..66b68a8 --- /dev/null +++ b/gdb/testsuite/gdb.threads/signal-delivered-right-thread.c @@ -0,0 +1,61 @@ +/* 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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <pthread.h> +#include <signal.h> + +pthread_barrier_t barrier; + +void +handler (int sig) +{ +} + +void * +thread_function (void *arg) +{ + pthread_barrier_wait (&barrier); + + while (1) + usleep (1); +} + +int +main (void) +{ + pthread_t child_thread[2]; + int i; + + signal (SIGUSR1, handler); + + pthread_barrier_init (&barrier, NULL, 3); + + for (i = 0; i < 2; i++) + pthread_create (&child_thread[i], NULL, thread_function, NULL); + + pthread_barrier_wait (&barrier); + + pthread_kill (child_thread[0], SIGUSR1); + + for (i = 0; i < 2; i++) + pthread_join (child_thread[i], NULL); + + return 0; +} diff --git a/gdb/testsuite/gdb.threads/signal-delivered-right-thread.exp b/gdb/testsuite/gdb.threads/signal-delivered-right-thread.exp new file mode 100644 index 0000000..4243495 --- /dev/null +++ b/gdb/testsuite/gdb.threads/signal-delivered-right-thread.exp @@ -0,0 +1,85 @@ +# 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/>. */ + +if [target_info exists gdb,nosignals] { + verbose "Skipping ${testfile}.exp because of nosignals." + return -1 +} + +standard_testfile + +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \ + executable { debug }] != "" } { + return -1 +} + +# Run test proper. COMMAND indicates whether to resume the inferior +# with "signal 0" or "continue". + +proc test { command } { + global srcfile binfile + + with_test_prefix "$command" { + clean_restart ${binfile} + + if ![runto_main] then { + fail "Can't run to main" + return 0 + } + + gdb_test "handle SIGUSR1 stop print pass" + + gdb_test "continue" "Program received signal SIGUSR1.*" "stop with SIGUSR1" + + set pattern "\\\* 2\[ \t\]+Thread.*" + + gdb_test "info threads" $pattern "thread 2 intercepted signal" + + gdb_test "break handler" "Breakpoint .* at .*$srcfile.*" + + gdb_test "thread 1" "Switching to thread 1.*" + + if { $command == "continue" } { + gdb_test "continue" "handler .*" + } elseif { $command == "signal 0" } { + set queried 0 + set test "signal 0 queries" + gdb_test_multiple "signal 0" $test { + -re "stopped with.*stopped with.*Continue anyway.*y or n. $" { + fail "$test (multiple threads noted)" + set queried 1 + } + -re "stopped with signal SIGUSR1.*\r\nContinuing .*still deliver .*Continue anyway.*y or n. $" { + pass $test + set queried 1 + } + -re "Continue anyway.*y or n. $" { + fail "$test (no threads noted)" + set queried 1 + } + } + + if {$queried} { + gdb_test "y" "handler .*" "signal is delivered" + } + } + + gdb_test "info threads" $pattern "thread 2 got the signal" + } +} + +foreach command {"continue" "signal 0"} { + test $command +} |