aboutsummaryrefslogtreecommitdiff
path: root/gdb
diff options
context:
space:
mode:
authorDaniel Jacobowitz <drow@false.org>2007-01-08 21:09:47 +0000
committerDaniel Jacobowitz <drow@false.org>2007-01-08 21:09:47 +0000
commit3d799a954241be52d355c2cf9da71da8e90b0c6a (patch)
tree61387a43b74a21649956d6f2021ce55e28a0f8ec /gdb
parent9acbedc0c0f0d09cddb633a090ceec409f57f87a (diff)
downloadfsf-binutils-gdb-3d799a954241be52d355c2cf9da71da8e90b0c6a.zip
fsf-binutils-gdb-3d799a954241be52d355c2cf9da71da8e90b0c6a.tar.gz
fsf-binutils-gdb-3d799a954241be52d355c2cf9da71da8e90b0c6a.tar.bz2
* 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. * gdb.threads/sigthread.c, gdb.threads/sigthread.exp: New.
Diffstat (limited to 'gdb')
-rw-r--r--gdb/ChangeLog14
-rw-r--r--gdb/linux-nat.c213
-rw-r--r--gdb/linux-nat.h3
-rw-r--r--gdb/testsuite/ChangeLog6
-rw-r--r--gdb/testsuite/gdb.threads/sigthread.c67
-rw-r--r--gdb/testsuite/gdb.threads/sigthread.exp56
6 files changed, 257 insertions, 102 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 3dca399..389000f 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,17 @@
+2007-01-08 Daniel Jacobowitz <dan@codesourcery.com>
+
+ * 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 <uweigand@de.ibm.com>
* gdbarch.sh (value_from_register): New gdbarch function.
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index fedb179..601ea32 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;
+
+ ptrace (PTRACE_GETEVENTMSG, pid, 0, &new_pid);
- if (stopping)
- new_lp->stopped = 1;
+ /* 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. */
+
+ new_lp->signalled = 1;
+ }
+ else
+ status = 0;
- 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);
+ if (stopping)
+ new_lp->stopped = 1;
+ else
+ {
+ new_lp->resumed = 1;
+ ptrace (PTRACE_CONT, lp->waitstatus.value.related_pid, 0,
+ status ? WSTOPSIG (status) : 0);
+ }
- return 1;
+ 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 d3f4f39..3e2c6f4 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 945bfe5..b7120a3 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,4 +1,8 @@
-2006-02-05 Joel Brobecker <brobecker@adacore.com>
+2007-01-08 Daniel Jacobowitz <dan@codesourcery.com>
+
+ * gdb.threads/sigthread.c, gdb.threads/sigthread.exp: New.
+
+2007-01-05 Joel Brobecker <brobecker@adacore.com>
* 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 0000000..81ebd67
--- /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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <signal.h>
+
+/* 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 0000000..43e1c53
--- /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"