diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 3dca399276..389000f188 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,17 @@ +2007-01-08 Daniel Jacobowitz + + * linux-nat.c (struct simple_pid_list): Add status. + (add_to_pid_list): Record the PID's status. + (linux_record_stopped_pid): Likewise. Make static. + (pull_pid_from_list): Return the saved status. + (linux_nat_handle_extended): Deleted. + (linux_handle_extended_wait): Combine with linux_nat_handle_extended. + Make static. Handle non-SIGSTOP for a new thread's first signal. + (flush_callback): Handle unexpected pending signals. + (linux_nat_wait): Update calls to changed functions. + * linux-nat.h (linux_record_stopped_pid, linux_handle_extended_wait): + Remove prototypes for newly static functions. + 2007-01-08 Ulrich Weigand * gdbarch.sh (value_from_register): New gdbarch function. diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index fedb179630..601ea3222e 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -113,6 +113,7 @@ static int linux_parent_pid; struct simple_pid_list { int pid; + int status; struct simple_pid_list *next; }; struct simple_pid_list *stopped_pids; @@ -131,16 +132,17 @@ static int linux_supports_tracevforkdone_flag = -1; /* Trivial list manipulation functions to keep track of a list of new stopped processes. */ static void -add_to_pid_list (struct simple_pid_list **listp, int pid) +add_to_pid_list (struct simple_pid_list **listp, int pid, int status) { struct simple_pid_list *new_pid = xmalloc (sizeof (struct simple_pid_list)); new_pid->pid = pid; + new_pid->status = status; new_pid->next = *listp; *listp = new_pid; } static int -pull_pid_from_list (struct simple_pid_list **listp, int pid) +pull_pid_from_list (struct simple_pid_list **listp, int pid, int *status) { struct simple_pid_list **p; @@ -148,6 +150,7 @@ pull_pid_from_list (struct simple_pid_list **listp, int pid) if ((*p)->pid == pid) { struct simple_pid_list *next = (*p)->next; + *status = (*p)->status; xfree (*p); *p = next; return 1; @@ -155,10 +158,10 @@ pull_pid_from_list (struct simple_pid_list **listp, int pid) return 0; } -void -linux_record_stopped_pid (int pid) +static void +linux_record_stopped_pid (int pid, int status) { - add_to_pid_list (&stopped_pids, pid); + add_to_pid_list (&stopped_pids, pid, status); } @@ -516,69 +519,6 @@ child_follow_fork (struct target_ops *ops, int follow_child) return 0; } -ptid_t -linux_handle_extended_wait (int pid, int status, - struct target_waitstatus *ourstatus) -{ - int event = status >> 16; - - if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK - || event == PTRACE_EVENT_CLONE) - { - unsigned long new_pid; - int ret; - - ptrace (PTRACE_GETEVENTMSG, pid, 0, &new_pid); - - /* If we haven't already seen the new PID stop, wait for it now. */ - if (! pull_pid_from_list (&stopped_pids, new_pid)) - { - /* The new child has a pending SIGSTOP. We can't affect it until it - hits the SIGSTOP, but we're already attached. */ - ret = my_waitpid (new_pid, &status, - (event == PTRACE_EVENT_CLONE) ? __WCLONE : 0); - if (ret == -1) - perror_with_name (_("waiting for new child")); - else if (ret != new_pid) - internal_error (__FILE__, __LINE__, - _("wait returned unexpected PID %d"), ret); - else if (!WIFSTOPPED (status) || WSTOPSIG (status) != SIGSTOP) - internal_error (__FILE__, __LINE__, - _("wait returned unexpected status 0x%x"), status); - } - - if (event == PTRACE_EVENT_FORK) - ourstatus->kind = TARGET_WAITKIND_FORKED; - else if (event == PTRACE_EVENT_VFORK) - ourstatus->kind = TARGET_WAITKIND_VFORKED; - else - ourstatus->kind = TARGET_WAITKIND_SPURIOUS; - - ourstatus->value.related_pid = new_pid; - return inferior_ptid; - } - - if (event == PTRACE_EVENT_EXEC) - { - ourstatus->kind = TARGET_WAITKIND_EXECD; - ourstatus->value.execd_pathname - = xstrdup (child_pid_to_exec_file (pid)); - - if (linux_parent_pid) - { - detach_breakpoints (linux_parent_pid); - ptrace (PTRACE_DETACH, linux_parent_pid, 0, 0); - - linux_parent_pid = 0; - } - - return inferior_ptid; - } - - internal_error (__FILE__, __LINE__, - _("unknown ptrace event %d"), event); -} - void child_insert_fork_catchpoint (int pid) @@ -1293,44 +1233,113 @@ kill_lwp (int lwpid, int signo) return kill (lwpid, signo); } -/* Handle a GNU/Linux extended wait response. Most of the work we - just pass off to linux_handle_extended_wait, but if it reports a - clone event we need to add the new LWP to our list (and not report - the trap to higher layers). This function returns non-zero if - the event should be ignored and we should wait again. If STOPPING - is true, the new LWP remains stopped, otherwise it is continued. */ +/* Handle a GNU/Linux extended wait response. If we see a clone + event, we need to add the new LWP to our list (and not report the + trap to higher layers). This function returns non-zero if the + event should be ignored and we should wait again. If STOPPING is + true, the new LWP remains stopped, otherwise it is continued. */ static int -linux_nat_handle_extended (struct lwp_info *lp, int status, int stopping) +linux_handle_extended_wait (struct lwp_info *lp, int status, + int stopping) { - linux_handle_extended_wait (GET_LWP (lp->ptid), status, - &lp->waitstatus); + int pid = GET_LWP (lp->ptid); + struct target_waitstatus *ourstatus = &lp->waitstatus; + struct lwp_info *new_lp = NULL; + int event = status >> 16; - /* TARGET_WAITKIND_SPURIOUS is used to indicate clone events. */ - if (lp->waitstatus.kind == TARGET_WAITKIND_SPURIOUS) + if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK + || event == PTRACE_EVENT_CLONE) { - struct lwp_info *new_lp; - new_lp = add_lwp (BUILD_LWP (lp->waitstatus.value.related_pid, - GET_PID (inferior_ptid))); - new_lp->cloned = 1; + unsigned long new_pid; + int ret; - if (stopping) - new_lp->stopped = 1; + ptrace (PTRACE_GETEVENTMSG, pid, 0, &new_pid); + + /* If we haven't already seen the new PID stop, wait for it now. */ + if (! pull_pid_from_list (&stopped_pids, new_pid, &status)) + { + /* The new child has a pending SIGSTOP. We can't affect it until it + hits the SIGSTOP, but we're already attached. */ + ret = my_waitpid (new_pid, &status, + (event == PTRACE_EVENT_CLONE) ? __WCLONE : 0); + if (ret == -1) + perror_with_name (_("waiting for new child")); + else if (ret != new_pid) + internal_error (__FILE__, __LINE__, + _("wait returned unexpected PID %d"), ret); + else if (!WIFSTOPPED (status)) + internal_error (__FILE__, __LINE__, + _("wait returned unexpected status 0x%x"), status); + } + + ourstatus->value.related_pid = new_pid; + + if (event == PTRACE_EVENT_FORK) + ourstatus->kind = TARGET_WAITKIND_FORKED; + else if (event == PTRACE_EVENT_VFORK) + ourstatus->kind = TARGET_WAITKIND_VFORKED; else - ptrace (PTRACE_CONT, lp->waitstatus.value.related_pid, 0, 0); + { + ourstatus->kind = TARGET_WAITKIND_IGNORE; + new_lp = add_lwp (BUILD_LWP (new_pid, GET_PID (inferior_ptid))); + new_lp->cloned = 1; - lp->waitstatus.kind = TARGET_WAITKIND_IGNORE; + if (WSTOPSIG (status) != SIGSTOP) + { + /* This can happen if someone starts sending signals to + the new thread before it gets a chance to run, which + have a lower number than SIGSTOP (e.g. SIGUSR1). + This is an unlikely case, and harder to handle for + fork / vfork than for clone, so we do not try - but + we handle it for clone events here. We'll send + the other signal on to the thread below. */ - if (debug_linux_nat) - fprintf_unfiltered (gdb_stdlog, - "LLHE: Got clone event from LWP %ld, resuming\n", - GET_LWP (lp->ptid)); - ptrace (PTRACE_CONT, GET_LWP (lp->ptid), 0, 0); + new_lp->signalled = 1; + } + else + status = 0; - return 1; + if (stopping) + new_lp->stopped = 1; + else + { + new_lp->resumed = 1; + ptrace (PTRACE_CONT, lp->waitstatus.value.related_pid, 0, + status ? WSTOPSIG (status) : 0); + } + + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, + "LHEW: Got clone event from LWP %ld, resuming\n", + GET_LWP (lp->ptid)); + ptrace (PTRACE_CONT, GET_LWP (lp->ptid), 0, 0); + + return 1; + } + + return 0; } - return 0; + if (event == PTRACE_EVENT_EXEC) + { + ourstatus->kind = TARGET_WAITKIND_EXECD; + ourstatus->value.execd_pathname + = xstrdup (child_pid_to_exec_file (pid)); + + if (linux_parent_pid) + { + detach_breakpoints (linux_parent_pid); + ptrace (PTRACE_DETACH, linux_parent_pid, 0, 0); + + linux_parent_pid = 0; + } + + return 0; + } + + internal_error (__FILE__, __LINE__, + _("unknown ptrace event %d"), event); } /* Wait for LP to stop. Returns the wait status, or 0 if the LWP has @@ -1401,7 +1410,7 @@ wait_lwp (struct lwp_info *lp) fprintf_unfiltered (gdb_stdlog, "WL: Handling extended status 0x%06x\n", status); - if (linux_nat_handle_extended (lp, status, 1)) + if (linux_handle_extended_wait (lp, status, 1)) return wait_lwp (lp); } @@ -1641,7 +1650,15 @@ flush_callback (struct lwp_info *lp, void *data) lp->status = 0; } - while (linux_nat_has_pending (GET_LWP (lp->ptid), &pending, flush_mask)) + /* While there is a pending signal we would like to flush, continue + the inferior and collect another signal. But if there's already + a saved status that we don't want to flush, we can't resume the + inferior - if it stopped for some other reason we wouldn't have + anywhere to save the new status. In that case, we must leave the + signal unflushed (and possibly generate an extra SIGINT stop). + That's much less bad than losing a signal. */ + while (lp->status == 0 + && linux_nat_has_pending (GET_LWP (lp->ptid), &pending, flush_mask)) { int ret; @@ -1995,7 +2012,7 @@ retry: from waitpid before or after the event is. */ if (WIFSTOPPED (status) && !lp) { - linux_record_stopped_pid (lwpid); + linux_record_stopped_pid (lwpid, status); status = 0; continue; } @@ -2046,7 +2063,7 @@ retry: fprintf_unfiltered (gdb_stdlog, "LLW: Handling extended status 0x%06x\n", status); - if (linux_nat_handle_extended (lp, status, 0)) + if (linux_handle_extended_wait (lp, status, 0)) { status = 0; continue; diff --git a/gdb/linux-nat.h b/gdb/linux-nat.h index d3f4f3974b..3e2c6f4158 100644 --- a/gdb/linux-nat.h +++ b/gdb/linux-nat.h @@ -75,10 +75,7 @@ void thread_db_init (struct target_ops *); void linux_proc_pending_signals (int pid, sigset_t *pending, sigset_t *blocked, sigset_t *ignored); /* linux-nat functions for handling fork events. */ -extern void linux_record_stopped_pid (int pid); extern void linux_enable_event_reporting (ptid_t ptid); -extern ptid_t linux_handle_extended_wait (int pid, int status, - struct target_waitstatus *ourstatus); extern int lin_lwp_attach_lwp (ptid_t ptid, int verbose); diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 945bfe5fd1..b7120a3b4d 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,4 +1,8 @@ -2006-02-05 Joel Brobecker +2007-01-08 Daniel Jacobowitz + + * gdb.threads/sigthread.c, gdb.threads/sigthread.exp: New. + +2007-01-05 Joel Brobecker * gdb.base/nofield.c: New file. * gdb.base/nofield.exp: New testcase. diff --git a/gdb/testsuite/gdb.threads/sigthread.c b/gdb/testsuite/gdb.threads/sigthread.c new file mode 100644 index 0000000000..81ebd67831 --- /dev/null +++ b/gdb/testsuite/gdb.threads/sigthread.c @@ -0,0 +1,67 @@ +/* Test case for C-c sent to threads with pending signals. Before I + even get there, creating a thread and sending it a signal before it + has a chance to run leads to an internal error in GDB. We need to + record that there's a pending SIGSTOP, so that we'll ignore it + later, and pass the current signal back to the thread. + + The fork/vfork case has similar trouble but that's even harder + to get around. We may need to send a SIGCONT to cancel out the + SIGSTOP. Different kernels may do different things if the thread + is stopped by ptrace and sent a SIGSTOP. */ + +#include +#include +#include +#include +#include + +/* Loop long enough for GDB to send a few signals of its own, but + don't hang around eating CPU forever if something goes wrong during + testing. */ +#define NSIGS 1000000 + +void +handler (int sig) +{ + ; +} + +pthread_t main_thread; +pthread_t child_thread, child_thread_two; + +void * +child_two (void *arg) +{ + int i; + + for (i = 0; i < NSIGS; i++) + pthread_kill (child_thread, SIGUSR1); +} + +void * +thread_function (void *arg) +{ + int i; + + for (i = 0; i < NSIGS; i++) + pthread_kill (child_thread_two, SIGUSR2); +} + +int main() +{ + int i; + + signal (SIGUSR1, handler); + signal (SIGUSR2, handler); + + main_thread = pthread_self (); + pthread_create (&child_thread, NULL, thread_function, NULL); + pthread_create (&child_thread_two, NULL, child_two, NULL); + + for (i = 0; i < NSIGS; i++) + pthread_kill (child_thread_two, SIGUSR1); + + pthread_join (child_thread, NULL); + + exit (0); +} diff --git a/gdb/testsuite/gdb.threads/sigthread.exp b/gdb/testsuite/gdb.threads/sigthread.exp new file mode 100644 index 0000000000..43e1c53611 --- /dev/null +++ b/gdb/testsuite/gdb.threads/sigthread.exp @@ -0,0 +1,56 @@ +# sigthread.exp -- Expect script to test thread and signal interaction +# Copyright (C) 2007 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +set testfile sigthread +set srcfile ${testfile}.c +set binfile ${objdir}/${subdir}/${testfile} + +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \ + executable { debug }] != "" } { + return -1 +} + +gdb_exit +gdb_start +gdb_reinitialize_dir $srcdir/$subdir +gdb_load ${binfile} + +if ![runto_main] then { + fail "Can't run to main" + return 0 +} + +gdb_test "handle SIGUSR1 nostop noprint pass" +gdb_test "handle SIGUSR2 nostop noprint pass" + +send_gdb "continue\n" +gdb_expect { + -re "Continuing" { + pass "continue" + } + timeout { + fail "continue (timeout)" + } +} + +# For this to work we must be sure to consume the "Continuing." +# message first, or GDB's signal handler may not be in place. +after 500 {send_gdb "\003"} + +# Make sure we do not get an internal error from hitting Control-C +# while many signals are flying back and forth. +gdb_test "" "Program received signal SIGINT.*" "stop with control-c"