* 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.
This commit is contained in:
Daniel Jacobowitz 2007-01-08 21:09:47 +00:00
parent 9acbedc0c0
commit 3d799a9542
6 changed files with 257 additions and 102 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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);

View File

@ -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.

View File

@ -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);
}

View File

@ -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"