* configure.in: Check for <gnu/libc-version.h>.
* configure: Regenerate.
* config.in: Regenerate.
* linux-nat.h: Include "target.h". Add waitstatus field to
struct lwp_info.
* lin-lwp.c (add_lwp): Initialize waitstatus.kind.
(lin_lwp_attach_lwp): Don't attach to LWPs we have already attached
to.
(lin_lwp_handle_extended): New function. Handle clone events.
(wait_lwp): Use lin_lwp_handle_extended. Update comment about
thread exit events.
(child_wait): Handle clone events.
(lin_lwp_wait: Use lin_lwp_handle_extended and handle clone events.
* linux-nat.c (linux_enable_event_reporting): Turn on
PTRACE_O_TRACECLONE.
(linux_handle_extended_wait): Handle clone events.
* thread-db.c: Include <gnu/libc-version.h>.
(struct private_thread_info): Add dying flag.
(enable_thread_event_reporting): Enable TD_DEATH for glibc 2.2 and
higher.
(attach_thread): Update comments. Handle dying threads.
(detach_thread): Set the dying flag.
(check_event): Always call attach_thread.
+2004-03-29 Daniel Jacobowitz <drow@mvista.com>
+
+ * Makefile.in (linux_nat_h): Update dependencies.
+ * configure.in: Check for <gnu/libc-version.h>.
+ * configure: Regenerate.
+ * config.in: Regenerate.
+ * linux-nat.h: Include "target.h". Add waitstatus field to
+ struct lwp_info.
+ * lin-lwp.c (add_lwp): Initialize waitstatus.kind.
+ (lin_lwp_attach_lwp): Don't attach to LWPs we have already attached
+ to.
+ (lin_lwp_handle_extended): New function. Handle clone events.
+ (wait_lwp): Use lin_lwp_handle_extended. Update comment about
+ thread exit events.
+ (child_wait): Handle clone events.
+ (lin_lwp_wait: Use lin_lwp_handle_extended and handle clone events.
+ * linux-nat.c (linux_enable_event_reporting): Turn on
+ PTRACE_O_TRACECLONE.
+ (linux_handle_extended_wait): Handle clone events.
+ * thread-db.c: Include <gnu/libc-version.h>.
+ (struct private_thread_info): Add dying flag.
+ (enable_thread_event_reporting): Enable TD_DEATH for glibc 2.2 and
+ higher.
+ (attach_thread): Update comments. Handle dying threads.
+ (detach_thread): Set the dying flag.
+ (check_event): Always call attach_thread.
+
2004-03-29 Daniel Jacobowitz <drow@mvista.com>
* mips-tdep.c (mips_pdr_data): New.
language_h = language.h
libunwind_frame_h = libunwind-frame.h $(libunwind_h)
linespec_h = linespec.h
-linux_nat_h = linux-nat.h
+linux_nat_h = linux-nat.h $(target_h)
m2_lang_h = m2-lang.h
m68k_tdep_h = m68k-tdep.h
macroexp_h = macroexp.h
/* Define if you have the <dirent.h> header file. */
#undef HAVE_DIRENT_H
+/* Define if you have the <gnu/libc-version.h> header file. */
+#undef HAVE_GNU_LIBC_VERSION_H
+
/* Define if you have the <libunwind-ia64.h> header file. */
#undef HAVE_LIBUNWIND_IA64_H
fi
done
-for ac_hdr in proc_service.h thread_db.h
+for ac_hdr in proc_service.h thread_db.h gnu/libc-version.h
do
ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
AC_CHECK_HEADERS(machine/reg.h)
AC_CHECK_HEADERS(nlist.h)
AC_CHECK_HEADERS(poll.h sys/poll.h)
-AC_CHECK_HEADERS(proc_service.h thread_db.h)
+AC_CHECK_HEADERS(proc_service.h thread_db.h gnu/libc-version.h)
AC_CHECK_HEADERS(stddef.h)
AC_CHECK_HEADERS(stdlib.h)
AC_CHECK_HEADERS(stdint.h)
/* Multi-threaded debugging support for GNU/Linux (LWP layer).
- Copyright 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
+ Copyright 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
This file is part of GDB.
memset (lp, 0, sizeof (struct lwp_info));
+ lp->waitstatus.kind = TARGET_WAITKIND_IGNORE;
+
lp->ptid = ptid;
lp->next = lwp_list;
void
lin_lwp_attach_lwp (ptid_t ptid, int verbose)
{
- struct lwp_info *lp;
+ struct lwp_info *lp, *found_lp;
gdb_assert (is_lwp (ptid));
if (verbose)
printf_filtered ("[New %s]\n", target_pid_to_str (ptid));
- lp = find_lwp_pid (ptid);
+ found_lp = lp = find_lwp_pid (ptid);
if (lp == NULL)
lp = add_lwp (ptid);
- /* We assume that we're already attached to any LWP that has an
- id equal to the overall process id. */
- if (GET_LWP (ptid) != GET_PID (ptid))
+ /* We assume that we're already attached to any LWP that has an id
+ equal to the overall process id, and to any LWP that is already
+ in our list of LWPs. If we're not seeing exit events from threads
+ and we've had PID wraparound since we last tried to stop all threads,
+ this assumption might be wrong; fortunately, this is very unlikely
+ to happen. */
+ if (GET_LWP (ptid) != GET_PID (ptid) && found_lp == NULL)
{
pid_t pid;
int status;
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. */
+
+static int
+lin_lwp_handle_extended (struct lwp_info *lp, int status)
+{
+ linux_handle_extended_wait (GET_LWP (lp->ptid), status,
+ &lp->waitstatus);
+
+ /* TARGET_WAITKIND_SPURIOUS is used to indicate clone events. */
+ if (lp->waitstatus.kind == TARGET_WAITKIND_SPURIOUS)
+ {
+ struct lwp_info *new_lp;
+ new_lp = add_lwp (BUILD_LWP (lp->waitstatus.value.related_pid,
+ GET_PID (inferior_ptid)));
+ new_lp->cloned = 1;
+ new_lp->stopped = 1;
+
+ lp->waitstatus.kind = TARGET_WAITKIND_IGNORE;
+
+ if (debug_lin_lwp)
+ 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);
+
+ return 1;
+ }
+
+ return 0;
+}
+
/* Wait for LP to stop. Returns the wait status, or 0 if the LWP has
exited. */
pid = waitpid (GET_LWP (lp->ptid), &status, __WCLONE);
if (pid == -1 && errno == ECHILD)
{
- /* The thread has previously exited. We need to delete it now
- because in the case of NPTL threads, there won't be an
- exit event unless it is the main thread. */
+ /* The thread has previously exited. We need to delete it
+ now because, for some vendor 2.4 kernels with NPTL
+ support backported, there won't be an exit event unless
+ it is the main thread. 2.6 kernels will report an exit
+ event for each thread that exits, as expected. */
thread_dead = 1;
if (debug_lin_lwp)
fprintf_unfiltered (gdb_stdlog, "WL: %s vanished.\n",
gdb_assert (WIFSTOPPED (status));
+ /* Handle GNU/Linux's extended waitstatus for trace events. */
+ if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0)
+ {
+ if (debug_lin_lwp)
+ fprintf_unfiltered (gdb_stdlog,
+ "WL: Handling extended status 0x%06x\n",
+ status);
+ if (lin_lwp_handle_extended (lp, status))
+ return wait_lwp (lp);
+ }
+
return status;
}
int status;
pid_t pid;
+ ourstatus->kind = TARGET_WAITKIND_IGNORE;
+
do
{
set_sigint_trap (); /* Causes SIGINT to be passed on to the
save_errno = EINTR;
}
+ /* Handle GNU/Linux's extended waitstatus for trace events. */
+ if (pid != -1 && WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP
+ && status >> 16 != 0)
+ {
+ linux_handle_extended_wait (pid, status, ourstatus);
+
+ /* If we see a clone event, detach the child, and don't
+ report the event. It would be nice to offer some way to
+ switch into a non-thread-db based threaded mode at this
+ point. */
+ if (ourstatus->kind == TARGET_WAITKIND_SPURIOUS)
+ {
+ ptrace (PTRACE_DETACH, ourstatus->value.related_pid, 0, 0);
+ ourstatus->kind = TARGET_WAITKIND_IGNORE;
+ pid = -1;
+ save_errno = EINTR;
+ }
+ }
+
clear_sigio_trap ();
clear_sigint_trap ();
}
return minus_one_ptid;
}
- /* Handle GNU/Linux's extended waitstatus for trace events. */
- if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0)
- return linux_handle_extended_wait (pid, status, ourstatus);
+ if (ourstatus->kind == TARGET_WAITKIND_IGNORE)
+ store_waitstatus (ourstatus, status);
- store_waitstatus (ourstatus, status);
return pid_to_ptid (pid);
}
}
}
+ /* Handle GNU/Linux's extended waitstatus for trace events. */
+ if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0)
+ {
+ if (debug_lin_lwp)
+ fprintf_unfiltered (gdb_stdlog,
+ "LLW: Handling extended status 0x%06x\n",
+ status);
+ if (lin_lwp_handle_extended (lp, status))
+ {
+ status = 0;
+ continue;
+ }
+ }
+
/* Check if the thread has exited. */
if ((WIFEXITED (status) || WIFSIGNALED (status)) && num_lwps > 1)
{
else
trap_ptid = null_ptid;
- /* Handle GNU/Linux's extended waitstatus for trace events. */
- if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0)
+ if (lp->waitstatus.kind != TARGET_WAITKIND_IGNORE)
{
- linux_handle_extended_wait (GET_LWP (lp->ptid), status, ourstatus);
- return trap_ptid;
+ *ourstatus = lp->waitstatus;
+ lp->waitstatus.kind = TARGET_WAITKIND_IGNORE;
}
+ else
+ store_waitstatus (ourstatus, status);
- store_waitstatus (ourstatus, status);
return (threaded ? lp->ptid : pid_to_ptid (GET_LWP (lp->ptid)));
}
/* GNU/Linux native-dependent code common to multiple platforms.
- Copyright (C) 2003 Free Software Foundation, Inc.
+ Copyright (C) 2003, 2004 Free Software Foundation, Inc.
This file is part of GDB.
if (! linux_supports_tracefork ())
return;
- options = PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXEC;
+ options = PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXEC
+ | PTRACE_O_TRACECLONE;
if (linux_supports_tracevforkdone ())
options |= PTRACE_O_TRACEVFORKDONE;
{
int event = status >> 16;
- if (event == PTRACE_EVENT_CLONE)
- internal_error (__FILE__, __LINE__,
- "unexpected clone event");
-
- if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
+ if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK
+ || event == PTRACE_EVENT_CLONE)
{
unsigned long new_pid;
int ret;
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.
-
- It won't be a clone (we didn't ask for clones in the event mask)
- so we can just call waitpid and wait for the SIGSTOP. */
+ hits the SIGSTOP, but we're already attached. */
do {
- ret = waitpid (new_pid, &status, 0);
+ ret = waitpid (new_pid, &status,
+ (event == PTRACE_EVENT_CLONE) ? __WCLONE : 0);
} while (ret == -1 && errno == EINTR);
if (ret == -1)
perror_with_name ("waiting for new child");
"wait returned unexpected status 0x%x", status);
}
- ourstatus->kind = (event == PTRACE_EVENT_FORK)
- ? TARGET_WAITKIND_FORKED : TARGET_WAITKIND_VFORKED;
+ 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;
}
/* Native debugging support for GNU/Linux (LWP layer).
- Copyright 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
+ Copyright 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
This file is part of GDB.
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */
+#include "target.h"
+
/* Structure describing an LWP. */
struct lwp_info
/* Non-zero if we were stepping this LWP. */
int step;
+ /* If WAITSTATUS->KIND != TARGET_WAITKIND_SPURIOUS, the waitstatus
+ for this LWP's last event. This may correspond to STATUS above,
+ or to a local variable in lin_lwp_wait. */
+ struct target_waitstatus waitstatus;
+
/* Next LWP in list. */
struct lwp_info *next;
};
system". */
struct mem_attrib;
struct target_ops;
-struct target_waitstatus;
extern int linux_proc_xfer_memory (CORE_ADDR addr, char *myaddr, int len,
int write, struct mem_attrib *attrib,
/* libthread_db assisted debugging support, generic parts.
- Copyright 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
+ Copyright 1999, 2000, 2001, 2003, 2004 Free Software Foundation, Inc.
This file is part of GDB.
#include "regcache.h"
#include "solib-svr4.h"
+#ifdef HAVE_GNU_LIBC_VERSION_H
+#include <gnu/libc-version.h>
+#endif
+
#ifndef LIBTHREAD_DB_SO
#define LIBTHREAD_DB_SO "libthread_db.so.1"
#endif
static void thread_db_find_new_threads (void);
static void attach_thread (ptid_t ptid, const td_thrhandle_t *th_p,
const td_thrinfo_t *ti_p, int verbose);
+static void detach_thread (ptid_t ptid, int verbose);
\f
/* Building process ids. */
struct private_thread_info
{
+ /* Flag set when we see a TD_DEATH event for this thread. */
+ unsigned int dying:1;
+
/* Cached thread state. */
unsigned int th_valid:1;
unsigned int ti_valid:1;
td_thr_events_t events;
td_notify_t notify;
td_err_e err;
+#ifdef HAVE_GNU_LIBC_VERSION_H
+ const char *libc_version;
+ int libc_major, libc_minor;
+#endif
/* We cannot use the thread event reporting facility if these
functions aren't available. */
/* Set the process wide mask saying which events we're interested in. */
td_event_emptyset (&events);
td_event_addset (&events, TD_CREATE);
-#if 0
+
+#ifdef HAVE_GNU_LIBC_VERSION_H
/* FIXME: kettenis/2000-04-23: The event reporting facility is
broken for TD_DEATH events in glibc 2.1.3, so don't enable it for
now. */
- td_event_addset (&events, TD_DEATH);
+ libc_version = gnu_get_libc_version ();
+ if (sscanf (libc_version, "%d.%d", &libc_major, &libc_minor) == 2
+ && (libc_major > 2 || (libc_major == 2 && libc_minor > 1)))
#endif
+ td_event_addset (&events, TD_DEATH);
err = td_ta_set_event_p (thread_agent, &events);
if (err != TD_OK)
target_new_objfile_chain (objfile);
}
+/* Attach to a new thread. This function is called when we receive a
+ TD_CREATE event or when we iterate over all threads and find one
+ that wasn't already in our list. */
+
static void
attach_thread (ptid_t ptid, const td_thrhandle_t *th_p,
const td_thrinfo_t *ti_p, int verbose)
struct thread_info *tp;
td_err_e err;
+ /* If we're being called after a TD_CREATE event, we may already
+ know about this thread. There are two ways this can happen. We
+ may have iterated over all threads between the thread creation
+ and the TD_CREATE event, for instance when the user has issued
+ the `info threads' command before the SIGTRAP for hitting the
+ thread creation breakpoint was reported. Alternatively, the
+ thread may have exited and a new one been created with the same
+ thread ID. In the first case we don't need to do anything; in
+ the second case we should discard information about the dead
+ thread and attach to the new one. */
+ if (in_thread_list (ptid))
+ {
+ tp = find_thread_pid (ptid);
+ gdb_assert (tp != NULL);
+
+ if (!tp->private->dying)
+ return;
+
+ delete_thread (ptid);
+ }
+
check_thread_signals ();
/* Add the thread to GDB's thread list. */
static void
detach_thread (ptid_t ptid, int verbose)
{
+ struct thread_info *thread_info;
+
if (verbose)
printf_unfiltered ("[%s exited]\n", target_pid_to_str (ptid));
+
+ /* Don't delete the thread now, because it still reports as active
+ until it has executed a few instructions after the event
+ breakpoint - if we deleted it now, "info threads" would cause us
+ to re-attach to it. Just mark it as having had a TD_DEATH
+ event. This means that we won't delete it from our thread list
+ until we notice that it's dead (via prune_threads), or until
+ something re-uses its thread ID. */
+ thread_info = find_thread_pid (ptid);
+ gdb_assert (thread_info != NULL);
+ thread_info->private->dying = 1;
}
static void
switch (msg.event)
{
case TD_CREATE:
-
- /* We may already know about this thread, for instance when the
- user has issued the `info threads' command before the SIGTRAP
- for hitting the thread creation breakpoint was reported. */
- if (!in_thread_list (ptid))
- attach_thread (ptid, msg.th_p, &ti, 1);
+ /* Call attach_thread whether or not we already know about a
+ thread with this thread ID. */
+ attach_thread (ptid, msg.th_p, &ti, 1);
break;