* config/i386/nm-linux.h (PREPARE_TO_PROCEED, ATTCH_LWP,
authorMark Kettenis <kettenis@gnu.org>
Sun, 3 Sep 2000 18:41:28 +0000 (18:41 +0000)
committerMark Kettenis <kettenis@gnu.org>
Sun, 3 Sep 2000 18:41:28 +0000 (18:41 +0000)
GET_THREAD_SIGNALS): New defines.
* config/i386/linux.mh (NATDEPFILES): Remove lin-thread.o and
linux-threads.o.  Add proc-service.o, thread-db.o and lin-lwp.o.
* proc-service.c: New file.
* thread-db.c: New file.
* lin-lwp.c: New file.

gdb/ChangeLog
gdb/config/i386/linux.mh
gdb/config/i386/nm-linux.h
gdb/lin-lwp.c [new file with mode: 0644]
gdb/proc-service.c [new file with mode: 0644]
gdb/thread-db.c [new file with mode: 0644]

index 4100ea9b9389c5fb02624c0b14d5c73a7d867116..a2b241ae104b944c75122196b220e9b57a58e428 100644 (file)
@@ -1,5 +1,13 @@
 2000-09-03  Mark Kettenis  <kettenis@gnu.org>
 
+       * config/i386/nm-linux.h (PREPARE_TO_PROCEED, ATTCH_LWP,
+       GET_THREAD_SIGNALS): New defines.
+       * config/i386/linux.mh (NATDEPFILES): Remove lin-thread.o and
+       linux-threads.o.  Add proc-service.o, thread-db.o and lin-lwp.o.
+       * proc-service.c: New file.
+       * thread-db.c: New file.
+       * lin-lwp.c: New file.
+
        * gdb_assert.h: New file.
 
        * gdb_thread_db.h [HAVE_THREAD_DB_H]: Include <thread_db.h>.
index daaab15999f4a23f4d7fd3d65332fb9e5a92e851..06c5e809a9334f50d46ec27b28a2ccf139970688 100644 (file)
@@ -5,7 +5,7 @@ XDEPFILES=
 
 NAT_FILE= nm-linux.h
 NATDEPFILES= infptrace.o solib.o inftarg.o fork-child.o corelow.o \
-       core-aout.o i386v-nat.o i386-linux-nat.o linux-thread.o lin-thread.o \
-       i387-nat.o
+       core-aout.o i386v-nat.o i386-linux-nat.o i387-nat.o \
+       proc-service.o thread-db.o lin-lwp.o
 
 LOADLIBES = -ldl -rdynamic
index f95c6fe5a79856f9b76d4ef4a74c7a79bfa59210..daebbb7ff7163a88ac28d6f5679a3c9425e565ea 100644 (file)
@@ -1,5 +1,5 @@
-/* Native support for GNU/Linux, for GDB, the GNU debugger.
-   Copyright (C) 1986, 1987, 1989, 1992, 1996, 1998, 2000
+/* Native support for Linux/x86.
+   Copyright 1986, 1987, 1989, 1992, 1996, 1998, 2000
    Free Software Foundation, Inc.
 
    This file is part of GDB.
@@ -79,4 +79,18 @@ extern CORE_ADDR i386_stopped_by_watchpoint (int);
 extern int i386_insert_watchpoint (int pid, CORE_ADDR addr, int len, int rw);
 extern int i386_remove_watchpoint (int pid, CORE_ADDR addr, int len);
 
-#endif /* #ifndef NM_LINUX_H */
+/* FIXME: kettenis/2000-09-03: This should be moved to ../nm-linux.h
+   once we have converted all Linux targets to use the new threads
+   stuff (without the #undef of course).  */
+
+extern int lin_lwp_prepare_to_proceed (void);
+#undef PREPARE_TO_PROCEED
+#define PREPARE_TO_PROCEED(select_it) lin_lwp_prepare_to_proceed ()
+
+extern void lin_lwp_attach_lwp (int pid, int verbose);
+#define ATTACH_LWP(pid, verbose) lin_lwp_attach_lwp ((pid), (verbose))
+
+extern void lin_thread_get_thread_signals (sigset_t *mask);
+#define GET_THREAD_SIGNALS(mask) lin_thread_get_thread_signals (mask)
+
+#endif /* nm_linux.h */
diff --git a/gdb/lin-lwp.c b/gdb/lin-lwp.c
new file mode 100644 (file)
index 0000000..3b0812e
--- /dev/null
@@ -0,0 +1,1048 @@
+/* Multi-threaded debugging support for Linux (LWP layer).
+   Copyright 2000 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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.  */
+
+#include "defs.h"
+
+#include "gdb_assert.h"
+#include <errno.h>
+#include <signal.h>
+#include <sys/ptrace.h>
+#include "gdb_wait.h"
+
+#include "gdbthread.h"
+#include "inferior.h"
+#include "target.h"
+
+#define DEBUG 1
+
+#if DEBUG
+extern const char *strsignal (int sig);
+#endif
+
+/* On Linux there are no real LWP's.  The closest thing to LWP's are
+   processes sharing the same VM space.  A multi-threaded process is
+   basically a group of such processes.  However, such a grouping is
+   almost entirely a user-space issue; the kernel doesn't enforce such
+   a grouping at all (this might change in the future).  In general,
+   we'll rely on the threads library (i.e. the LinuxThreads library)
+   to provide such a grouping.
+
+   It is perfectly well possible to write a multi-threaded application
+   without the assistance of a threads library, by using the clone
+   system call directly.  This module should be able to give some
+   rudimentary support for debugging such applications if developers
+   specify the CLONE_PTRACE flag in the clone system call, and are
+   using Linux 2.4 or above.
+
+   Note that there are some peculiarities in Linux that affect this
+   code:
+
+   - In general one should specify the __WCLONE flag to waitpid in
+     order to make it report events for any of the cloned processes.
+     However, if a cloned process has exited the exit status is only
+     reported if the __WCLONE flag is absent.
+
+   - When a traced, cloned process exits and is waited for by the
+     debugger, the kernel reassigns it to the origional parent and
+     keeps it around as a "zombie".  Somehow, the LinuxThreads library
+     doesn't notice this, which leads to the "zombie problem": When
+     debugged a multi-threaded process that spawns a lot of threads
+     will run out of processes, even if the threads exit, because the
+     "zombies" stay around.  */
+
+/* Structure describing a LWP.  */
+struct lwp_info
+{
+  /* The process id of the LWP.  This is a combination of the LWP id
+     and overall process id.  */
+  int pid;
+
+  /* Non-zero if we sent this LWP a SIGSTOP (but the LWP didn't report
+     it back yet).  */
+  int signalled;
+
+  /* Non-zero if this LWP is stopped.  */
+  int stopped;
+
+  /* If non-zero, a pending wait status.  */
+  int status;
+
+  /* Non-zero if we were stepping this LWP.  */
+  int step;
+
+  /* Next LWP in list.  */
+  struct lwp_info *next;
+};
+
+/* List of known LWPs.  */
+static struct lwp_info *lwp_list;
+
+/* Number of LWPs in the list.  */
+static int num_lwps;
+
+/* Non-zero if we're running in "threaded" mode.  */
+static int threaded;
+\f
+
+#ifndef TIDGET
+#define TIDGET(PID)            (((PID) & 0x7fffffff) >> 16)
+#define PIDGET(PID)            (((PID) & 0xffff))
+#define MERGEPID(PID, TID)     (((PID) & 0xffff) | ((TID) << 16))
+#endif
+
+#define THREAD_FLAG            0x80000000
+#define is_lwp(pid)            (((pid) & THREAD_FLAG) == 0 && TIDGET (pid))
+#define GET_LWP(pid)           TIDGET (pid)
+#define GET_PID(pid)           PIDGET (pid)
+#define BUILD_LWP(tid, pid)    MERGEPID (pid, tid)
+
+#define is_cloned(pid) (GET_LWP (pid) != GET_PID (pid))
+
+/* If the last reported event was a SIGTRAP, this variable is set to
+   the process id of the LWP/thread that got it.  */
+int trap_pid;
+\f
+
+/* This module's target-specific operations.  */
+static struct target_ops lin_lwp_ops;
+
+/* The standard child operations.  */
+extern struct target_ops child_ops;
+
+/* Signal mask for use with sigsuspend in lin_lwp_wait, initialized in
+   _initialize_lin_lwp.  */
+static sigset_t suspend_mask;
+\f
+
+/* Prototypes for local functions.  */
+static void lin_lwp_mourn_inferior (void);
+\f
+
+/* Initialize the list of LWPs.  */
+
+static void
+init_lwp_list (void)
+{
+  struct lwp_info *lp, *lpnext;
+
+  for (lp = lwp_list; lp; lp = lpnext)
+    {
+      lpnext = lp->next;
+      free (lp);
+    }
+
+  lwp_list = NULL;
+  num_lwps = 0;
+  threaded = 0;
+}
+
+/* Add the LWP specified by PID to the list.  If this causes the
+   number of LWPs to become larger than one, go into "threaded" mode.
+   Return a pointer to the structure describing the new LWP.  */
+
+static struct lwp_info *
+add_lwp (int pid)
+{
+  struct lwp_info *lp;
+
+  gdb_assert (is_lwp (pid));
+
+  lp = (struct lwp_info *) xmalloc (sizeof (struct lwp_info));
+
+  memset (lp, 0, sizeof (struct lwp_info));
+
+  lp->pid = pid;
+
+  lp->next = lwp_list;
+  lwp_list = lp;
+  if (++num_lwps > 1)
+    threaded = 1;
+
+  return lp;
+}
+
+/* Remove the LWP specified by PID from the list.  */
+
+static void
+delete_lwp (int pid)
+{
+  struct lwp_info *lp, *lpprev;
+
+  lpprev = NULL;
+
+  for (lp = lwp_list; lp; lpprev = lp, lp = lp->next)
+    if (lp->pid == pid)
+      break;
+
+  if (!lp)
+    return;
+
+  /* We don't go back to "non-threaded" mode if the number of threads
+     becomes less than two.  */
+  num_lwps--;
+
+  if (lpprev)
+    lpprev->next = lp->next;
+  else
+    lwp_list = lp->next;
+
+  free (lp);
+}
+
+/* Return a pointer to the structure describing the LWP corresponding
+   to PID.  If no corresponding LWP could be found, return NULL.  */
+
+static struct lwp_info *
+find_lwp_pid (int pid)
+{
+  struct lwp_info *lp;
+
+  if (is_lwp (pid))
+    pid = GET_LWP (pid);
+
+  for (lp = lwp_list; lp; lp = lp->next)
+    if (pid == GET_LWP (lp->pid))
+      return lp;
+
+  return NULL;
+}
+
+/* Call CALLBACK with its second argument set to DATA for every LWP in
+   the list.  If CALLBACK returns 1 for a particular LWP, return a
+   pointer to the structure describing that LWP immediately.
+   Otherwise return NULL.  */
+
+struct lwp_info *
+iterate_over_lwps (int (*callback) (struct lwp_info *, void *), void *data)
+{
+  struct lwp_info *lp;
+
+  for (lp = lwp_list; lp; lp = lp->next)
+    if ((*callback) (lp, data))
+      return lp;
+
+  return NULL;
+}
+\f
+
+/* Helper functions.  */
+
+static void
+restore_inferior_pid (void *arg)
+{
+  int *saved_pid_ptr = arg;
+  inferior_pid = *saved_pid_ptr;
+  free (arg);
+}
+
+static struct cleanup *
+save_inferior_pid (void)
+{
+  int *saved_pid_ptr;
+
+  saved_pid_ptr = xmalloc (sizeof (int));
+  *saved_pid_ptr = inferior_pid;
+  return make_cleanup (restore_inferior_pid, saved_pid_ptr);
+}
+\f
+
+/* Implementation of the PREPARE_TO_PROCEED hook for the Linux LWP layer.  */
+
+int
+lin_lwp_prepare_to_proceed (void)
+{
+  if (trap_pid && inferior_pid != trap_pid)
+    {
+      /* Switched over from TRAP_PID.  */
+      CORE_ADDR stop_pc = read_pc ();
+      CORE_ADDR trap_pc;
+
+      /* Avoid switching where it wouldn't do any good, i.e. if both
+         threads are at the same breakpoint.  */
+      trap_pc = read_pc_pid (trap_pid);
+      if (trap_pc != stop_pc && breakpoint_here_p (trap_pc))
+       {
+         /* User hasn't deleted the breakpoint.  Return non-zero, and
+             switch back to TRAP_PID.  */
+         inferior_pid = trap_pid;
+
+         /* FIXME: Is this stuff really necessary?  */
+         flush_cached_frames ();
+         registers_changed ();
+
+         return 1;
+       }
+    }
+
+  return 0;
+}
+\f
+
+#if 0
+static void
+lin_lwp_open (char *args, int from_tty)
+{
+  push_target (&lin_lwp_ops);
+}
+#endif
+
+/* Attach to the LWP specified by PID.  If VERBOSE is non-zero, print
+   a message telling the user that a new LWP has been added to the
+   process.  */
+
+void
+lin_lwp_attach_lwp (int pid, int verbose)
+{
+  struct lwp_info *lp;
+
+  gdb_assert (is_lwp (pid));
+
+  if (verbose)
+    printf_filtered ("[New %s]\n", target_pid_to_str (pid));
+
+  if (ptrace (PTRACE_ATTACH, GET_LWP (pid), 0, 0) < 0)
+    error ("Can't attach %s: %s", target_pid_to_str (pid), strerror (errno));
+
+  lp = add_lwp (pid);
+  lp->signalled = 1;
+}
+
+static void
+lin_lwp_attach (char *args, int from_tty)
+{
+  /* FIXME: We should probably accept a list of process id's, and
+     attach all of them.  */
+  error("Not implemented yet");
+}
+
+static void
+lin_lwp_detach (char *args, int from_tty)
+{
+  /* FIXME: Provide implementation when we implement lin_lwp_attach.  */
+  error ("Not implemented yet");
+}
+\f
+
+struct private_thread_info
+{
+  int lwpid;
+};
+
+/* Return non-zero if TP corresponds to the LWP specified by DATA
+   (which is assumed to be a pointer to a `struct lwp_info'.  */
+
+static int
+find_lwp_callback (struct thread_info *tp, void *data)
+{
+  struct lwp_info *lp = data;
+
+  if (tp->private->lwpid == GET_LWP (lp->pid))
+    return 1;
+
+  return 0;
+}
+
+/* Resume LP.  */
+
+static int
+resume_callback (struct lwp_info *lp, void *data)
+{
+  if (lp->stopped && lp->status == 0)
+    {
+      struct thread_info *tp;
+
+#if 1
+      /* FIXME: kettenis/2000-08-26: This should really be handled
+         properly by core GDB.  */
+
+      tp = find_thread_pid (lp->pid);
+      if (tp == NULL)
+       tp = iterate_over_threads (find_lwp_callback, lp);
+      gdb_assert (tp);
+
+      /* If we were previously stepping the thread, and now continue
+         the thread we must invalidate the stepping range.  However,
+         if there is a step_resume breakpoint for this thread, we must
+         preserve the stepping range to make it possible to continue
+         stepping once we hit it.  */
+      if (tp->step_range_end && tp->step_resume_breakpoint == NULL)
+       {
+         gdb_assert (lp->step);
+         tp->step_range_start = tp->step_range_end = 0;
+       }
+#endif
+
+      child_resume (GET_LWP (lp->pid), 0, TARGET_SIGNAL_0);
+      lp->stopped = 0;
+      lp->step = 0;
+    }
+
+  return 0;
+}
+
+static void
+lin_lwp_resume (int pid, int step, enum target_signal signo)
+{
+  struct lwp_info *lp;
+  int resume_all;
+
+  /* Apparently the interpretation of PID is dependent on STEP: If
+     STEP is non-zero, a specific PID means `step only this process
+     id'.  But if STEP is zero, then PID means `continue *all*
+     processes, but give the signal only to this one'.  */
+  resume_all = (pid == -1) || !step;
+
+  /* If PID is -1, it's the current inferior that should be
+     handled special.  */
+  if (pid == -1)
+    pid = inferior_pid;
+
+  lp = find_lwp_pid (pid);
+  if (lp)
+    {
+      pid = GET_LWP (lp->pid);
+
+      /* Mark LWP as not stopped to prevent it from being continued by
+        resume_callback.  */
+      lp->stopped = 0;
+
+      /* Remember if we're stepping.  */
+      lp->step = step;
+
+      /* If we have a pending wait status for this thread, there is no
+         point in resuming the process.  */
+      if (lp->status)
+       {
+         /* FIXME: What should we do if we are supposed to continue
+             this thread with a signal?  */
+         gdb_assert (signo == TARGET_SIGNAL_0);
+         return;
+       }
+    }
+
+  if (resume_all)
+    iterate_over_lwps (resume_callback, NULL);
+
+  child_resume (pid, step, signo);
+}
+\f
+
+/* Send a SIGSTOP to LP.  */
+
+static int
+stop_callback (struct lwp_info *lp, void *data)
+{
+  if (! lp->stopped && ! lp->signalled)
+    {
+      int ret;
+
+      ret = kill (GET_LWP (lp->pid), SIGSTOP);
+      gdb_assert (ret == 0);
+
+      lp->signalled = 1;
+      gdb_assert (lp->status == 0);
+    }
+
+  return 0;
+}
+
+/* Wait until LP is stopped.  */
+
+static int
+stop_wait_callback (struct lwp_info *lp, void *data)
+{
+  if (! lp->stopped && lp->signalled)
+    {
+      pid_t pid;
+      int status;
+
+      gdb_assert (lp->status == 0);
+
+      pid = waitpid (GET_LWP (lp->pid), &status,
+                    is_cloned (lp->pid) ? __WCLONE : 0);
+      if (pid == -1 && errno == ECHILD)
+       /* OK, the proccess has disappeared.  We'll catch the actual
+           exit event in lin_lwp_wait.  */
+       return 0;
+
+      gdb_assert (pid == GET_LWP (lp->pid));
+
+      if (WIFEXITED (status) || WIFSIGNALED (status))
+       {
+         gdb_assert (num_lwps > 1);
+         gdb_assert (! is_cloned (lp->pid));
+
+         gdb_assert (in_thread_list (lp->pid));
+         if (lp->pid != inferior_pid)
+           delete_thread (lp->pid);
+         printf_unfiltered ("[%s exited]\n",
+                            target_pid_to_str (lp->pid));
+
+         delete_lwp (lp->pid);
+         return 0;
+       }
+
+      gdb_assert (WIFSTOPPED (status));
+      lp->stopped = 1;
+
+      if (WSTOPSIG (status) != SIGSTOP)
+       {
+         if (WSTOPSIG (status) == SIGTRAP
+             && breakpoint_inserted_here_p (read_pc_pid (pid)
+                                            - DECR_PC_AFTER_BREAK))
+           {
+             /* If a LWP other than the LWP that we're reporting an
+                 event for has hit a GDB breakpoint (as opposed to
+                 some random trap signal), then just arrange for it to
+                 hit it again later.  We don't keep the SIGTRAP status
+                 and don't forward the SIGTRAP signal to the LWP.  We
+                 will handle the current event, eventually we will
+                 resume all LWPs, and this one will get its breakpoint
+                 trap again.
+
+                If we do not do this, then we run the risk that the
+                user will delete or disable the breakpoint, but the
+                thread will have already tripped on it.  */
+#if DEBUG
+             printf ("Tripped breakpoint at %lx in LWP %d"
+                     " while waiting for SIGSTOP.\n",
+                     (long) read_pc_pid (lp->pid), pid);
+#endif
+             /* Set the PC to before the trap.  */
+             if (DECR_PC_AFTER_BREAK)
+               write_pc_pid (read_pc_pid (pid) - DECR_PC_AFTER_BREAK, pid);
+           }
+         else
+           {
+#if DEBUG
+             printf ("Received %s in LWP %d while waiting for SIGSTOP.\n",
+                     strsignal (WSTOPSIG (status)), pid);
+#endif
+             /* The thread was stopped with a signal other than
+                SIGSTOP, and didn't accidentiliy trip a breakpoint.
+                Record the wait status.  */
+             lp->status = status;
+           }
+       }
+      else
+       {
+         /* We caught the SIGSTOP that we intended to catch, so
+             there's no SIGSTOP pending.  */
+         lp->signalled = 0;
+       }
+    }
+
+  return 0;
+}
+
+/* Return non-zero if LP has a wait status pending.  */
+
+static int
+status_callback (struct lwp_info *lp, void *data)
+{
+  return (lp->status != 0);
+}
+
+/* Return non-zero if LP isn't stopped.  */
+
+static int
+running_callback (struct lwp_info *lp, void *data)
+{
+  return (lp->stopped == 0);
+}
+
+static int
+lin_lwp_wait (int pid, struct target_waitstatus *ourstatus)
+{
+  struct lwp_info *lp = NULL;
+  int options = 0;
+  int status = 0;
+
+ retry:
+
+  /* First check if there is a LWP with a wait status pending.  */
+  if (pid == -1)
+    {
+      /* Any LWP will do.  */
+      lp = iterate_over_lwps (status_callback, NULL);
+      if (lp)
+       {
+#if DEBUG
+         printf ("Using pending wait status for LWP %d.\n",
+                 GET_LWP (lp->pid));
+#endif
+         status = lp->status;
+         lp->status = 0;
+       }
+
+      /* But if we don't fine one, we'll have to wait, and check both
+         cloned and uncloned processes.  We start with the cloned
+         processes.  */
+      options = __WCLONE | WNOHANG;
+    }
+  else if (is_lwp (pid))
+    {
+#if DEBUG
+      printf ("Waiting for specific LWP %d.\n", GET_LWP (pid));
+#endif
+      /* We have a specific LWP to check.  */
+      lp = find_lwp_pid (GET_LWP (pid));
+      gdb_assert (lp);
+      status = lp->status;
+      lp->status = 0;
+#if DEBUG
+      if (status)
+         printf ("Using pending wait status for LWP %d.\n",
+                 GET_LWP (lp->pid));
+#endif
+
+      /* If we have to wait, take into account whether PID is a cloned
+         process or not.  And we have to convert it to something that
+         the layer beneath us can understand.  */
+      options = is_cloned (lp->pid) ? __WCLONE : 0;
+      pid = GET_LWP (pid);
+    }
+
+  if (status && lp->signalled)
+    {
+      /* A pending SIGSTOP may interfere with the normal stream of
+        events.  In a typical case where interference is a problem,
+        we have a SIGSTOP signal pending for LWP A while
+        single-stepping it, encounter an event in LWP B, and take the
+        pending SIGSTOP while trying to stop LWP A.  After processing
+        the event in LWP B, LWP A is continued, and we'll never see
+        the SIGTRAP associated with the last time we were
+        single-stepping LWP A.  */
+
+      /* Resume the thread.  It should halt immediately returning the
+        pending SIGSTOP.  */
+      child_resume (GET_LWP (lp->pid), lp->step, TARGET_SIGNAL_0);
+      lp->stopped = 0;
+
+      /* This should catch the pending SIGSTOP.  */
+      stop_wait_callback (lp, NULL);
+    }
+
+  set_sigint_trap ();  /* Causes SIGINT to be passed on to the
+                          attached process. */
+  set_sigio_trap ();
+
+  while (status == 0)
+    {
+      pid_t lwpid;
+
+      lwpid = waitpid (pid, &status, options);
+      if (lwpid > 0)
+       {
+         gdb_assert (pid == -1 || lwpid == pid);
+
+         lp = find_lwp_pid (lwpid);
+         if (! lp)
+           {
+             lp = add_lwp (BUILD_LWP (lwpid, inferior_pid));
+             if (threaded)
+               {
+                 gdb_assert (WIFSTOPPED (status) && WSTOPSIG (status) == SIGSTOP);
+                 lp->signalled = 1;
+
+                 if (! in_thread_list (inferior_pid))
+                   {
+                     inferior_pid = BUILD_LWP (inferior_pid, inferior_pid);
+                     add_thread (inferior_pid);
+                   }
+
+                 add_thread (lp->pid);
+                 printf_unfiltered ("[New %s]\n",
+                                    target_pid_to_str (lp->pid));
+               }
+           }
+
+         /* Make sure we don't report a TARGET_WAITKIND_EXITED or
+             TARGET_WAITKIND_SIGNALLED event if there are still LWP's
+             left in the process.  */
+         if ((WIFEXITED (status) || WIFSIGNALED (status)) && num_lwps > 1)
+           {
+             if (in_thread_list (lp->pid))
+               {
+                 /* Core GDB cannot deal with us deeting the current
+                     thread.  */
+                 if (lp->pid != inferior_pid)
+                   delete_thread (lp->pid);
+                 printf_unfiltered ("[%s exited]\n",
+                                    target_pid_to_str (lp->pid));
+               }
+#if DEBUG
+             printf ("%s exited.\n", target_pid_to_str (lp->pid));
+#endif
+             delete_lwp (lp->pid);
+
+             /* Make sure there is at least one thread running.  */
+             gdb_assert (iterate_over_lwps (running_callback, NULL));
+
+             /* Discard the event.  */
+             status = 0;
+             continue;
+           }
+
+         /* Make sure we don't report a SIGSTOP that we sent
+             ourselves in an attempt to stop an LWP.  */
+         if (lp->signalled && WIFSTOPPED (status)
+             && WSTOPSIG (status) == SIGSTOP)
+           {
+#if DEBUG
+             printf ("Delayed SIGSTOP caught for %s.\n",
+                     target_pid_to_str (lp->pid));
+#endif
+             /* This is a delayed SIGSTOP.  */
+             lp->signalled = 0;
+
+             child_resume (GET_LWP (lp->pid), lp->step, TARGET_SIGNAL_0);
+             lp->stopped = 0;
+
+             /* Discard the event.  */
+             status = 0;
+             continue;
+           }
+
+         break;
+       }
+
+      if (pid == -1)
+       {
+         /* Alternate between checking cloned and uncloned processes.  */
+         options ^= __WCLONE;
+
+         /* And suspend every time we have checked both.  */
+         if (options & __WCLONE)
+           sigsuspend (&suspend_mask);
+       }
+
+      /* We shouldn't end up here unless we want to try again.  */
+      gdb_assert (status == 0);
+    }
+
+  clear_sigio_trap ();
+  clear_sigint_trap ();
+
+  gdb_assert (lp);
+
+  /* Don't report signals that GDB isn't interested in, such as
+     signals that are neither printed nor stopped upon.  Stopping all
+     threads can be a bit time-consuming so if we want decent
+     performance with heavily multi-threaded programs, especially when
+     they're using a high frequency timer, we'd better avoid it if we
+     can.  */
+
+  if (WIFSTOPPED (status))
+    {
+      int signo = target_signal_from_host (WSTOPSIG (status));
+
+      if (signal_stop_state (signo) == 0
+         && signal_print_state (signo) == 0
+         && signal_pass_state (signo) == 1)
+       {
+         child_resume (GET_LWP (lp->pid), lp->step, signo);
+         lp->stopped = 0;
+         status = 0;
+         goto retry;
+       }
+    }
+
+  /* This LWP is stopped now.  */
+  lp->stopped = 1;
+
+  /* Now stop all other LWP's ...  */
+  iterate_over_lwps (stop_callback, NULL);
+
+  /* ... and wait until all of them have reported back that they're no
+     longer running.  */
+  iterate_over_lwps (stop_wait_callback, NULL);
+
+  /* If we're not running in "threaded" mode, we'll report the bare
+     process id.  */
+
+  if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP)
+    trap_pid = (threaded ? lp->pid : GET_LWP (lp->pid));
+  else
+    trap_pid = 0;
+
+  store_waitstatus (ourstatus, status);
+  return (threaded ? lp->pid : GET_LWP (lp->pid));
+}
+
+static int
+kill_callback (struct lwp_info *lp, void *data)
+{
+  ptrace (PTRACE_KILL, GET_LWP (lp->pid), 0, 0);
+  return 0;
+}
+
+static int
+kill_wait_callback (struct lwp_info *lp, void *data)
+{
+  pid_t pid;
+
+  /* We must make sure that there are no pending events (delayed
+     SIGSTOPs, pending SIGTRAPs, etc.) to make sure the current
+     program doesn't interfere with any following debugging session.  */
+
+  /* For cloned processes we must check both with __WCLONE and
+     without, since the exit status of a cloned process isn't reported
+     with __WCLONE.  */
+  if (is_cloned (lp->pid))
+    {
+      do
+       {
+         pid = waitpid (GET_LWP (lp->pid), NULL, __WCLONE);
+       }
+      while (pid == GET_LWP (lp->pid));
+
+      gdb_assert (pid == -1 && errno == ECHILD);
+    }
+
+  do
+    {
+      pid = waitpid (GET_LWP (lp->pid), NULL, 0);
+    }
+  while (pid == GET_LWP (lp->pid));
+
+  gdb_assert (pid == -1 && errno == ECHILD);
+  return 0;
+}
+
+static void
+lin_lwp_kill (void)
+{
+  /* Kill all LWP's ...  */
+  iterate_over_lwps (kill_callback, NULL);
+
+  /* ... and wait until we've flushed all events.  */
+  iterate_over_lwps (kill_wait_callback, NULL);
+
+  target_mourn_inferior ();
+}
+
+static void
+lin_lwp_create_inferior (char *exec_file, char *allargs, char **env)
+{
+  struct target_ops *target_beneath;
+
+  init_lwp_list ();
+
+#if 0
+  target_beneath = find_target_beneath (&lin_lwp_ops);
+#else
+  target_beneath = &child_ops;
+#endif
+  target_beneath->to_create_inferior (exec_file, allargs, env);
+}
+
+static void  
+lin_lwp_mourn_inferior (void)
+{
+  struct target_ops *target_beneath;
+
+  init_lwp_list ();
+
+  trap_pid = 0;
+
+#if 0
+  target_beneath = find_target_beneath (&lin_lwp_ops);
+#else
+  target_beneath = &child_ops;
+#endif
+  target_beneath->to_mourn_inferior ();
+}
+
+static void
+lin_lwp_fetch_registers (int regno)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+
+  if (is_lwp (inferior_pid))
+    inferior_pid = GET_LWP (inferior_pid);
+
+  fetch_inferior_registers (regno);
+
+  do_cleanups (old_chain);
+}
+
+static void
+lin_lwp_store_registers (int regno)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+
+  if (is_lwp (inferior_pid))
+    inferior_pid = GET_LWP (inferior_pid);
+
+  store_inferior_registers (regno);
+
+  do_cleanups (old_chain);
+}
+
+static int
+lin_lwp_xfer_memory (CORE_ADDR memaddr, char *myaddr, int len, int write,
+                    struct target_ops *target)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+  int xfer;
+
+  if (is_lwp (inferior_pid))
+    inferior_pid = GET_LWP (inferior_pid);
+
+  xfer = child_xfer_memory (memaddr, myaddr, len, write, target);
+
+  do_cleanups (old_chain);
+  return xfer;
+}
+
+static int
+lin_lwp_thread_alive (int pid)
+{
+  gdb_assert (is_lwp (pid));
+
+  errno = 0;
+  ptrace (PTRACE_PEEKUSER, GET_LWP (pid), 0, 0);
+  if (errno)
+    return 0;
+
+  return 1;
+}
+
+static char *
+lin_lwp_pid_to_str (int pid)
+{
+  static char buf[64];
+
+  if (is_lwp (pid))
+    {
+      snprintf (buf, sizeof (buf), "LWP %d", GET_LWP (pid));
+      return buf;
+    }
+
+  return normal_pid_to_str (pid);
+}
+
+static void
+init_lin_lwp_ops (void)
+{
+#if 0
+  lin_lwp_ops.to_open = lin_lwp_open;
+#endif
+  lin_lwp_ops.to_shortname = "lwp-layer";
+  lin_lwp_ops.to_longname = "lwp-layer";
+  lin_lwp_ops.to_doc = "Low level threads support (LWP layer)";
+  lin_lwp_ops.to_attach = lin_lwp_attach;
+  lin_lwp_ops.to_detach = lin_lwp_detach;
+  lin_lwp_ops.to_resume = lin_lwp_resume;
+  lin_lwp_ops.to_wait = lin_lwp_wait;
+  lin_lwp_ops.to_fetch_registers = lin_lwp_fetch_registers;
+  lin_lwp_ops.to_store_registers = lin_lwp_store_registers;
+  lin_lwp_ops.to_xfer_memory = lin_lwp_xfer_memory;
+  lin_lwp_ops.to_kill = lin_lwp_kill;
+  lin_lwp_ops.to_create_inferior = lin_lwp_create_inferior;
+  lin_lwp_ops.to_mourn_inferior = lin_lwp_mourn_inferior;
+  lin_lwp_ops.to_thread_alive = lin_lwp_thread_alive;
+  lin_lwp_ops.to_pid_to_str = lin_lwp_pid_to_str;
+  lin_lwp_ops.to_stratum = thread_stratum;
+  lin_lwp_ops.to_has_thread_control = tc_schedlock;
+  lin_lwp_ops.to_magic = OPS_MAGIC;
+}
+
+static void
+sigchld_handler (int signo)
+{
+  /* Do nothing.  The only reason for this handler is that it allows
+     us to use sigsuspend in lin_lwp_wait above to wait for the
+     arrival of a SIGCHLD.  */
+}
+
+void
+_initialize_lin_lwp (void)
+{
+  struct sigaction action;
+  sigset_t mask;
+
+  extern void thread_db_init (struct target_ops *);
+
+  init_lin_lwp_ops ();
+  add_target (&lin_lwp_ops);
+  thread_db_init (&lin_lwp_ops);
+
+  action.sa_handler = sigchld_handler;
+  sigemptyset (&action.sa_mask);
+  action.sa_flags = 0;
+  sigaction (SIGCHLD, &action, NULL);
+
+  /* We block SIGCHLD throughout this code ...  */
+  sigemptyset (&mask);
+  sigaddset (&mask, SIGCHLD);
+  sigprocmask (SIG_BLOCK, &mask, &suspend_mask);
+
+  /* ... except during a sigsuspend.  */
+  sigdelset (&suspend_mask, SIGCHLD);
+}
+\f
+
+/* FIXME: kettenis/2000-08-26: The stuff on this page is specific to
+   the LinuxThreads library and therefore doesn't really belong here.  */
+
+/* Read variable NAME in the target and return its value if found.
+   Otherwise return zero.  It is assumed that the type of the variable
+   is `int'.  */
+
+static int
+get_signo (const char *name)
+{
+  struct minimal_symbol *ms;
+  int signo;
+
+  ms = lookup_minimal_symbol (name, NULL, NULL);
+  if (ms == NULL)
+    return 0;
+
+  if (target_read_memory (SYMBOL_VALUE_ADDRESS (ms), (char *) &signo,
+                         sizeof (signo)) != 0)
+    return 0;
+
+  return signo;
+}
+
+/* Return the set of signals used by the threads library in *SET.  */
+
+void
+lin_thread_get_thread_signals (sigset_t *set)
+{
+  int restart;
+  int cancel;
+
+  sigemptyset (set);
+
+  restart = get_signo ("__pthread_sig_restart");
+  if (restart == 0)
+    return;
+
+  cancel = get_signo ("__pthread_sig_cancel");
+  if (cancel == 0)
+    return;
+
+  sigaddset (set, restart);
+  sigaddset (set, cancel);
+}
diff --git a/gdb/proc-service.c b/gdb/proc-service.c
new file mode 100644 (file)
index 0000000..b69207e
--- /dev/null
@@ -0,0 +1,333 @@
+/* <proc_service.h> implementation.
+   Copyright 1999, 2000 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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.  */
+
+#include "defs.h"
+
+#include "gdb_proc_service.h"
+#include <sys/procfs.h>
+
+#include "inferior.h"
+#include "symtab.h"
+#include "target.h"
+
+/* Prototypes for supply_gregset etc.  */
+#include "gregset.h"
+\f
+
+/* Fix-up some broken systems.  */
+
+/* The prototypes in <proc_service.h> are slightly different on older
+   systems.  Compensate for the discrepancies.  */
+
+#ifdef PROC_SERVICE_IS_OLD
+typedef const struct ps_prochandle *gdb_ps_prochandle_t;
+typedef char *gdb_ps_read_buf_t;
+typedef char *gdb_ps_write_buf_t;
+typedef int gdb_ps_size_t;
+#else
+typedef struct ps_prochandle *gdb_ps_prochandle_t;
+typedef void *gdb_ps_read_buf_t;
+typedef const void *gdb_ps_write_buf_t;
+typedef size_t gdb_ps_size_t;
+#endif
+\f
+
+/* Building process ids.  */
+
+#ifndef MERGEPID
+#define MERGEPID(PID, TID)     (((PID) & 0xffff) | ((TID) << 16))
+#endif
+
+#define BUILD_LWP(tid, pid)    MERGEPID (pid, tid)
+\f
+
+/* Helper functions.  */
+
+static void
+restore_inferior_pid (void *arg)
+{
+  int *saved_pid_ptr = arg;
+  inferior_pid = *saved_pid_ptr;
+  free (arg);
+}
+
+static struct cleanup *
+save_inferior_pid (void)
+{
+  int *saved_pid_ptr;
+
+  saved_pid_ptr = xmalloc (sizeof (int));
+  *saved_pid_ptr = inferior_pid;
+  return make_cleanup (restore_inferior_pid, saved_pid_ptr);
+}
+
+/* Transfer LEN bytes of memory between BUF and address ADDR in the
+   process specified by PH.  If WRITE, transfer them to the process,
+   else transfer them from the process.  Returns PS_OK for success,
+   PS_ERR on failure.
+
+   This is a helper function for ps_pdread, ps_pdwrite, ps_ptread and
+   ps_ptwrite.  */
+
+static ps_err_e
+ps_xfer_memory (const struct ps_prochandle *ph, paddr_t addr,
+               char *buf, size_t len, int write)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+  int ret;
+
+  inferior_pid = ph->pid;
+
+  if (write)
+    ret = target_write_memory (addr, buf, len);
+  else
+    ret = target_read_memory (addr, buf, len);
+
+  do_cleanups (old_chain);
+
+  return (ret == 0 ? PS_OK : PS_ERR);
+}
+\f
+
+/* Stop the target process PH.  */
+
+ps_err_e
+ps_pstop (gdb_ps_prochandle_t ph)
+{
+  /* The process is always stopped when under control of GDB.  */
+  return PS_OK;
+}
+
+/* Resume the target process PH.  */
+
+ps_err_e
+ps_pcontinue (gdb_ps_prochandle_t ph)
+{
+  /* Pretend we did successfully continue the process.  GDB will take
+     care of it later on.  */
+  return PS_OK;
+}
+
+/* Stop the lightweight process LWPID within the target process PH.  */
+
+ps_err_e
+ps_lstop (gdb_ps_prochandle_t ph, lwpid_t lwpid)
+{
+  /* All lightweight processes are stopped when under control of GDB.  */
+  return PS_OK;
+}
+
+/* Resume the lightweight process (LWP) LWPID within the target
+   process PH.  */
+
+ps_err_e
+ps_lcontinue (gdb_ps_prochandle_t ph, lwpid_t lwpid)
+{
+  /* Pretend we did successfully continue LWPID.  GDB will take care
+     of it later on.  */
+  return PS_OK;
+}
+
+/* Get the size of the architecture-dependent extra state registers
+   for LWP LWPID within the target process PH and return it in
+   *XREGSIZE.  */
+
+ps_err_e
+ps_lgetxregsize (gdb_ps_prochandle_t ph, lwpid_t lwpid, int *xregsize)
+{
+  /* FIXME: Not supported yet.  */
+  return PS_OK;
+}
+
+/* Get the extra state registers of LWP LWPID within the target
+   process PH and store them in XREGSET.  */
+
+ps_err_e
+ps_lgetxregs (gdb_ps_prochandle_t ph, lwpid_t lwpid, caddr_t xregset)
+{
+  /* FIXME: Not supported yet.  */
+  return PS_OK;
+}
+
+/* Set the extra state registers of LWP LWPID within the target
+   process PH from XREGSET.  */
+
+ps_err_e
+ps_lsetxregs (gdb_ps_prochandle_t ph, lwpid_t lwpid, caddr_t xregset)
+{
+  /* FIXME: Not supported yet.  */
+  return PS_OK;
+}
+
+/* Log (additional) diognostic information.  */
+
+void
+ps_plog (const char *fmt, ...)
+{
+  va_list args;
+
+  va_start (args, fmt);
+  vfprintf_filtered (gdb_stderr, fmt, args);
+}
+
+/* Search for the symbol named NAME within the object named OBJ within
+   the target process PH.  If the symbol is found the address of the
+   symbol is stored in SYM_ADDR.  */
+
+ps_err_e
+ps_pglobal_lookup (gdb_ps_prochandle_t ph, const char *obj,
+                  const char *name, paddr_t *sym_addr)
+{
+  struct minimal_symbol *ms;
+
+  /* FIXME: kettenis/2000-09-03: What should we do with OBJ?  */
+  ms = lookup_minimal_symbol (name, NULL, NULL);
+  if (ms == NULL)
+    return PS_NOSYM;
+
+  *sym_addr = SYMBOL_VALUE_ADDRESS (ms);
+  return PS_OK;
+}
+
+/* Read SIZE bytes from the target process PH at address ADDR and copy
+   them into BUF.  */
+
+ps_err_e
+ps_pdread (gdb_ps_prochandle_t ph, paddr_t addr,
+          gdb_ps_read_buf_t buf, gdb_ps_size_t size)
+{
+  return ps_xfer_memory (ph, addr, buf, size, 0);
+}
+
+/* Write SIZE bytes from BUF into the target process PH at address ADDR.  */
+
+ps_err_e
+ps_pdwrite (gdb_ps_prochandle_t ph, paddr_t addr,
+           gdb_ps_write_buf_t buf, gdb_ps_size_t size)
+{
+  return ps_xfer_memory (ph, addr, (char *) buf, size, 1);
+}
+
+/* Read SIZE bytes from the target process PH at address ADDR and copy
+   them into BUF.  */
+
+ps_err_e
+ps_ptread (gdb_ps_prochandle_t ph, paddr_t addr,
+          gdb_ps_read_buf_t buf, gdb_ps_size_t size)
+{
+  return ps_xfer_memory (ph, addr, buf, size, 0);
+}
+
+/* Write SIZE bytes from BUF into the target process PH at address ADDR.  */
+
+ps_err_e
+ps_ptwrite (gdb_ps_prochandle_t ph, paddr_t addr,
+           gdb_ps_write_buf_t buf, gdb_ps_size_t size)
+{
+  return ps_xfer_memory (ph, addr, (char *) buf, size, 1);
+}
+
+/* Get the general registers of LWP LWPID within the target process PH
+   and store them in GREGSET.  */
+
+ps_err_e
+ps_lgetregs (gdb_ps_prochandle_t ph, lwpid_t lwpid, prgregset_t gregset)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+
+  inferior_pid = BUILD_LWP (lwpid, ph->pid);
+
+  target_fetch_registers (-1);
+  fill_gregset ((gdb_gregset_t *) gregset, -1);
+
+  do_cleanups (old_chain);
+  return PS_OK;
+}
+
+/* Set the general registers of LWP LWPID within the target process PH
+   from GREGSET.  */
+
+ps_err_e
+ps_lsetregs (gdb_ps_prochandle_t ph, lwpid_t lwpid, const prgregset_t gregset)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+
+  inferior_pid = BUILD_LWP (lwpid, ph->pid);
+
+  /* FIXME: We should really make supply_gregset const-correct.  */
+  supply_gregset ((gdb_gregset_t *) gregset);
+  target_store_registers (-1);
+
+  do_cleanups (old_chain);
+  return PS_OK;
+}
+
+/* Get the floating-point registers of LWP LWPID within the target
+   process PH and store them in FPREGSET.  */
+
+ps_err_e
+ps_lgetfpregs (gdb_ps_prochandle_t ph, lwpid_t lwpid,
+              gdb_prfpregset_t *fpregset)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+
+  inferior_pid = BUILD_LWP (lwpid, ph->pid);
+
+  target_fetch_registers (-1);
+  fill_fpregset ((gdb_fpregset_t *) fpregset, -1);
+
+  do_cleanups (old_chain);
+  return PS_OK;
+}
+
+/* Set the floating-point registers of LWP LWPID within the target
+   process PH from FPREGSET.  */
+
+ps_err_e
+ps_lsetfpregs (gdb_ps_prochandle_t ph, lwpid_t lwpid,
+              const gdb_prfpregset_t *fpregset)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+
+  inferior_pid = BUILD_LWP (lwpid, ph->pid);
+
+  /* FIXME: We should really make supply_fpregset const-correct.  */
+  supply_fpregset ((gdb_fpregset_t *) fpregset);
+  target_store_registers (-1);
+
+  do_cleanups (old_chain);
+  return PS_OK;
+}
+
+/* Return overall process id of the target PH.
+   Special for Linux -- not used on Solaris.  */
+
+pid_t
+ps_getpid (gdb_ps_prochandle_t ph)
+{
+  return ph->pid;
+}
+
+void
+_initialize_proc_service (void)
+{
+  /* This function solely exists to make sure this module is linked
+     into the final binary.  */
+}
diff --git a/gdb/thread-db.c b/gdb/thread-db.c
new file mode 100644 (file)
index 0000000..7c3c0ae
--- /dev/null
@@ -0,0 +1,992 @@
+/* libthread_db assisted debugging support, generic parts.
+   Copyright 1999, 2000 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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.  */
+
+#include "defs.h"
+
+#include "gdb_assert.h"
+#include <dlfcn.h>
+#include "gdb_proc_service.h"
+#include "gdb_thread_db.h"
+
+#include "gdbthread.h"
+#include "inferior.h"
+#include "target.h"
+
+#ifndef LIBTHREAD_DB_SO
+#define LIBTHREAD_DB_SO "libthread_db.so.1"
+#endif
+
+/* If we're running on Linux, we must explicitly attach to any new threads.  */
+
+/* FIXME: There is certainly some room for improvements:
+   - Cache LWP ids.
+   - Bypass libthread_db when fetching or storing registers for
+   threads bound to a LWP.  */
+
+/* This module's target vector.  */
+static struct target_ops thread_db_ops;
+
+/* The target vector that we call for things this module can't handle.  */
+static struct target_ops *target_beneath;
+
+/* Pointer to the next function on the objfile event chain.  */
+static void (*target_new_objfile_chain) (struct objfile *objfile);
+
+/* Non-zero if we're using this module's target vector.  */
+static int using_thread_db;
+
+/* Non-zero if we have determined the signals used by the threads
+   library.  */
+static int thread_signals;
+static sigset_t thread_stop_set;
+static sigset_t thread_print_set;
+
+/* Structure that identifies the child process for the
+   <proc_service.h> interface.  */
+static struct ps_prochandle proc_handle;
+
+/* Connection to the libthread_db library.  */
+static td_thragent_t *thread_agent;
+
+/* Pointers to the libthread_db functions.  */
+
+static td_err_e (*td_init_p) (void);
+
+static td_err_e (*td_ta_new_p) (struct ps_prochandle *ps, td_thragent_t **ta);
+static td_err_e (*td_ta_map_id2thr_p) (const td_thragent_t *ta, thread_t pt,
+                                      td_thrhandle_t *__th);
+static td_err_e (*td_ta_map_lwp2thr_p) (const td_thragent_t *ta, lwpid_t lwpid,
+                                       td_thrhandle_t *th);
+static td_err_e (*td_ta_thr_iter_p) (const td_thragent_t *ta,
+                                    td_thr_iter_f *callback,
+                                    void *cbdata_p, td_thr_state_e state,
+                                    int ti_pri, sigset_t *ti_sigmask_p,
+                                    unsigned int ti_user_flags);
+static td_err_e (*td_ta_event_addr_p) (const td_thragent_t *ta,
+                                      td_event_e event, td_notify_t *ptr);
+static td_err_e (*td_ta_set_event_p) (const td_thragent_t *ta,
+                                     td_thr_events_t *event);
+static td_err_e (*td_ta_event_getmsg_p) (const td_thragent_t *ta,
+                                        td_event_msg_t *msg);
+
+static td_err_e (*td_thr_validate_p) (const td_thrhandle_t *th);
+static td_err_e (*td_thr_get_info_p) (const td_thrhandle_t *th,
+                                     td_thrinfo_t *infop);
+static td_err_e (*td_thr_getfpregs_p) (const td_thrhandle_t *th,
+                                      gdb_prfpregset_t *regset);
+static td_err_e (*td_thr_getgregs_p) (const td_thrhandle_t *th,
+                                     prgregset_t gregs);
+static td_err_e (*td_thr_setfpregs_p) (const td_thrhandle_t *th,
+                                      const gdb_prfpregset_t *fpregs);
+static td_err_e (*td_thr_setgregs_p) (const td_thrhandle_t *th,
+                                     prgregset_t gregs);
+static td_err_e (*td_thr_event_enable_p) (const td_thrhandle_t *th, int event);
+
+/* Location of the thread creation event breakpoint.  The code at this
+   location in the child process will be called by the pthread library
+   whenever a new thread is created.  By setting a special breakpoint
+   at this location, GDB can detect when a new thread is created.  We
+   obtain this location via the td_ta_event_addr call.  */
+static CORE_ADDR td_create_bp_addr;
+
+/* Location of the thread death event breakpoint.  */
+static CORE_ADDR td_death_bp_addr;
+
+/* Prototypes for local functions.  */
+static int find_new_threads_callback (const td_thrhandle_t *th_p, void *data);
+\f
+
+/* Building process ids.  */
+
+#ifndef TIDGET
+#define TIDGET(PID)            (((PID) & 0x7fffffff) >> 16)
+#define PIDGET(PID)            (((PID) & 0xffff))
+#define MERGEPID(PID, TID)     (((PID) & 0xffff) | ((TID) << 16))
+#endif
+
+#define THREAD_FLAG            0x80000000
+
+#define is_lwp(pid)            (((pid) & THREAD_FLAG) == 0 && TIDGET (pid))
+#define is_thread(pid)         ((pid) & THREAD_FLAG)
+
+#define GET_PID(pid)           PIDGET (pid)
+#define GET_LWP(pid)           TIDGET (pid)
+#define GET_THREAD(pid)                TIDGET (pid)
+
+#define BUILD_LWP(tid, pid)    MERGEPID (pid, tid)
+#define BUILD_THREAD(tid, pid) (MERGEPID (pid, tid) | THREAD_FLAG)
+\f
+
+struct private_thread_info
+{
+  /* Cached LWP id.  Must come first, see lin-lwp.c.  */
+  lwpid_t lwpid;
+};
+
+\f
+/* Helper functions.  */
+
+static void
+restore_inferior_pid (void *arg)
+{
+  int *saved_pid_ptr = arg;
+  inferior_pid = *saved_pid_ptr;
+  free (arg);
+}
+
+static struct cleanup *
+save_inferior_pid (void)
+{
+  int *saved_pid_ptr;
+
+  saved_pid_ptr = xmalloc (sizeof (int));
+  *saved_pid_ptr = inferior_pid;
+  return make_cleanup (restore_inferior_pid, saved_pid_ptr);
+}
+\f
+
+static char *
+thread_db_err_str (td_err_e err)
+{
+  static char buf[64];
+
+  switch (err)
+    {
+    case TD_OK:
+      return "generic 'call succeeded'";
+    case TD_ERR:
+      return "generic error";
+    case TD_NOTHR:
+      return "no thread to satisfy query";
+    case TD_NOSV:
+      return "no sync handle to satisfy query";
+    case TD_NOLWP:
+      return "no LWP to satisfy query";
+    case TD_BADPH:
+      return "invalid process handle";
+    case TD_BADTH:
+      return "invalid thread handle";
+    case TD_BADSH:
+      return "invalid synchronization handle";
+    case TD_BADTA:
+      return "invalid thread agent";
+    case TD_BADKEY:
+      return "invalid key";
+    case TD_NOMSG:
+      return "no event message for getmsg";
+    case TD_NOFPREGS:
+      return "FPU register set not available";
+    case TD_NOLIBTHREAD:
+      return "application not linked with libthread";
+    case TD_NOEVENT:
+      return "requested event is not supported";
+    case TD_NOCAPAB:
+      return "capability not available";
+    case TD_DBERR:
+      return "debugger service failed";
+    case TD_NOAPLIC:
+      return "operation not applicable to";
+    case TD_NOTSD:
+      return "no thread-specific data for this thread";
+    case TD_MALLOC:
+      return "malloc failed";
+    case TD_PARTIALREG:
+      return "only part of register set was written/read";
+    case TD_NOXREGS:
+      return "X register set not available for this thread";
+    default:
+      snprintf (buf, sizeof (buf), "unknown thread_db error '%d'", err);
+      return buf;
+    }
+}
+
+static char *
+thread_db_state_str (td_thr_state_e state)
+{
+  static char buf[64];
+
+  switch (state)
+    {
+    case TD_THR_STOPPED:
+      return "stopped by debugger";
+    case TD_THR_RUN:
+      return "runnable";
+    case TD_THR_ACTIVE:
+      return "active";
+    case TD_THR_ZOMBIE:
+      return "zombie";
+    case TD_THR_SLEEP:
+      return "sleeping";
+    case TD_THR_STOPPED_ASLEEP:
+      return "stopped by debugger AND blocked";
+    default:
+      snprintf (buf, sizeof (buf), "unknown thread_db state %d", state);
+      return buf;
+    }
+}
+\f
+
+/* Convert between user-level thread ids and LWP ids.  */
+
+static int
+thread_from_lwp (int pid)
+{
+  td_thrinfo_t ti;
+  td_thrhandle_t th;
+  td_err_e err;
+
+  if (GET_LWP (pid) == 0)
+    pid = BUILD_LWP (pid, pid);
+
+  gdb_assert (is_lwp (pid));
+
+  err = td_ta_map_lwp2thr_p (thread_agent, GET_LWP (pid), &th);
+  if (err != TD_OK)
+    error ("Cannot find user-level thread for LWP %d: %s",
+          GET_LWP (pid), thread_db_err_str (err));
+
+  err = td_thr_get_info_p (&th, &ti);
+  if (err != TD_OK)
+    error ("Cannot get thread info: %s", thread_db_err_str (err));
+
+  return BUILD_THREAD (ti.ti_tid, GET_PID (pid));
+}
+
+static int
+lwp_from_thread (int pid)
+{
+  td_thrinfo_t ti;
+  td_thrhandle_t th;
+  td_err_e err;
+
+  if (! is_thread (pid))
+    return pid;
+
+  err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (pid), &th);
+  if (err != TD_OK)
+    error ("Cannot find thread %ld: %s",
+          (long) GET_THREAD (pid), thread_db_err_str (err));
+
+  err = td_thr_get_info_p (&th, &ti);
+  if (err != TD_OK)
+    error ("Cannot get thread info: %s", thread_db_err_str (err));
+
+  return BUILD_LWP (ti.ti_lid, GET_PID (pid));
+}
+\f
+
+void
+thread_db_init (struct target_ops *target)
+{
+  target_beneath = target;
+}
+
+static int
+thread_db_load (void)
+{
+  void *handle;
+  td_err_e err;
+
+  handle = dlopen (LIBTHREAD_DB_SO, RTLD_NOW);
+  if (handle == NULL)
+    return 0;
+
+  /* Initialize pointers to the dynamic library functions we will use.
+     Essential functions first.  */
+
+  td_init_p = dlsym (handle, "td_init");
+  if (td_init_p == NULL)
+    return 0;
+
+  td_ta_new_p = dlsym (handle, "td_ta_new");
+  if (td_ta_new_p == NULL)
+    return 0;
+
+  td_ta_map_id2thr_p = dlsym (handle, "td_ta_map_id2thr");
+  if (td_ta_map_id2thr_p == NULL)
+    return 0;
+
+  td_ta_map_lwp2thr_p = dlsym (handle, "td_ta_map_lwp2thr");
+  if (td_ta_map_lwp2thr_p == NULL)
+    return 0;
+
+  td_ta_thr_iter_p = dlsym (handle, "td_ta_thr_iter");
+  if (td_ta_thr_iter_p == NULL)
+    return 0;
+
+  td_thr_validate_p = dlsym (handle, "td_thr_validate");
+  if (td_thr_validate_p == NULL)
+    return 0;
+
+  td_thr_get_info_p = dlsym (handle, "td_thr_get_info");
+  if (td_thr_get_info_p == NULL)
+    return 0;
+
+  td_thr_getfpregs_p = dlsym (handle, "td_thr_getfpregs");
+  if (td_thr_getfpregs_p == NULL)
+    return 0;
+
+  td_thr_getgregs_p = dlsym (handle, "td_thr_getgregs");
+  if (td_thr_getgregs_p == NULL)
+    return 0;
+
+  td_thr_setfpregs_p = dlsym (handle, "td_thr_setfpregs");
+  if (td_thr_setfpregs_p == NULL)
+    return 0;
+
+  td_thr_setgregs_p = dlsym (handle, "td_thr_setgregs");
+  if (td_thr_setgregs_p == NULL)
+    return 0;
+
+  /* Initialize the library.  */
+  err = td_init_p ();
+  if (err != TD_OK)
+    {
+      warning ("Cannot initialize libthread_db: %s", thread_db_err_str (err));
+      return 0;
+    }
+
+  /* These are not essential.  */
+  td_ta_event_addr_p = dlsym (handle, "td_ta_event_addr");
+  td_ta_set_event_p = dlsym (handle, "td_ta_set_event");
+  td_ta_event_getmsg_p = dlsym (handle, "td_ta_event_getmsg");
+  td_thr_event_enable_p = dlsym (handle, "td_thr_event_enable");
+
+  return 1;
+}
+
+static void
+enable_thread_event_reporting (void)
+{
+  td_thr_events_t events;
+  td_notify_t notify;
+  td_err_e err;
+
+  /* We cannot use the thread event reporting facility if these
+     functions aren't available.  */
+  if (td_ta_event_addr_p == NULL || td_ta_set_event_p == NULL
+      || td_ta_event_getmsg_p == NULL || td_thr_event_enable_p == NULL)
+    return;
+
+  /* Set the process wide mask saying which events we're interested in.  */
+  td_event_emptyset (&events);
+  td_event_addset (&events, TD_CREATE);
+#if 0
+  /* 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);
+#endif
+
+  err = td_ta_set_event_p (thread_agent, &events);
+  if (err != TD_OK)
+    {
+      warning ("Unable to set global thread event mask: %s",
+              thread_db_err_str (err));
+      return;
+    }
+
+  /* Delete previous thread event breakpoints, if any.  */
+  remove_thread_event_breakpoints ();
+
+  /* Get address for thread creation breakpoint.  */
+  err = td_ta_event_addr_p (thread_agent, TD_CREATE, &notify);
+  if (err != TD_OK)
+    {
+      warning ("Unable to get location for thread creation breakpoint: %s",
+              thread_db_err_str (err));
+      return;
+    }
+
+  /* Set up the breakpoint.  */
+  td_create_bp_addr = (CORE_ADDR) notify.u.bptaddr;
+  create_thread_event_breakpoint (td_create_bp_addr);
+
+  /* Get address for thread death breakpoint.  */
+  err = td_ta_event_addr_p (thread_agent, TD_DEATH, &notify);
+  if (err != TD_OK)
+    {
+      warning ("Unable to get location for thread creation breakpoint: %s",
+              thread_db_err_str (err));
+      return;
+    }
+
+  /* Set up the breakpoint.  */
+  td_death_bp_addr = (CORE_ADDR) notify.u.bptaddr;
+  create_thread_event_breakpoint (td_death_bp_addr);
+}
+
+static void
+disable_thread_event_reporting (void)
+{
+  td_thr_events_t events;
+
+  /* Set the process wide mask saying we aren't interested in any
+     events anymore.  */
+  td_event_emptyset (&events);
+  td_ta_set_event_p (thread_agent, &events);
+
+  /* Delete thread event breakpoints, if any.  */
+  remove_thread_event_breakpoints ();
+  td_create_bp_addr = 0;
+  td_death_bp_addr = 0;
+}
+
+static void
+check_thread_signals (void)
+{
+#ifdef GET_THREAD_SIGNALS
+  if (! thread_signals)
+    {
+      sigset_t mask;
+      int i;
+
+      GET_THREAD_SIGNALS (&mask);
+      sigemptyset (&thread_stop_set);
+      sigemptyset (&thread_print_set);
+
+      for (i = 0; i < NSIG; i++)
+       {
+         if (sigismember (&mask, i))
+           {
+             if (signal_stop_update (target_signal_from_host (i), 0))
+               sigaddset (&thread_stop_set, i);
+             if (signal_print_update (target_signal_from_host (i), 0))
+               sigaddset (&thread_print_set, i);
+             thread_signals = 1;
+           }
+       }
+    }
+#endif
+}
+
+static void
+disable_thread_signals (void)
+{
+#ifdef GET_THREAD_SIGNALS
+  if (thread_signals)
+    {
+      int i;
+
+      for (i = 0; i < NSIG; i++)
+       {
+         if (sigismember (&thread_stop_set, i))
+           signal_stop_update (target_signal_from_host (i), 1);
+         if (sigismember (&thread_print_set, i))
+           signal_print_update (target_signal_from_host (i), 1);
+       }
+
+      thread_signals = 0;
+    }
+#endif
+}
+
+static void
+thread_db_push_target (void)
+{
+  using_thread_db = 1;
+
+  /* Push this target vector.  */
+  push_target (&thread_db_ops);
+
+  enable_thread_event_reporting ();
+}
+
+static void
+thread_db_unpush_target (void)
+{
+  /* Unpush this target vector.  */
+  unpush_target (&thread_db_ops);
+
+  using_thread_db = 0;
+}
+
+static void
+thread_db_new_objfile (struct objfile *objfile)
+{
+  td_err_e err;
+
+  if (using_thread_db)
+    /* Nothing to do.  The thread library was already detected and the
+       target vector was already activated.  */
+    goto quit;
+
+  if (objfile == NULL)
+    /* Un-interesting object file.  */
+    goto quit;
+
+  /* Initialize the structure that identifies the child process.  */
+  proc_handle.pid = GET_PID (inferior_pid);
+
+  /* Now attempt to open a connection to the thread library running in
+     the child process.  */
+  err = td_ta_new_p (&proc_handle, &thread_agent);
+  switch (err)
+    {
+    case TD_NOLIBTHREAD:
+      /* No thread library found in the child process, probably
+         because the child process isn't running yet.  */
+      break;
+
+    case TD_OK:
+      /* The thread library was detected in the child; we go live now!  */
+      thread_db_push_target ();
+
+      /* Find all user-space threads.  */
+      err = td_ta_thr_iter_p (thread_agent, find_new_threads_callback,
+                             &inferior_pid, TD_THR_ANY_STATE,
+                             TD_THR_LOWEST_PRIORITY, TD_SIGNO_MASK,
+                             TD_THR_ANY_USER_FLAGS);
+      if (err != TD_OK)
+       error ("Finding new threads failed: %s", thread_db_err_str (err));
+      break;
+
+    default:
+      warning ("Cannot initialize thread debugging library: %s",
+              thread_db_err_str (err));
+      break;
+    }
+
+ quit:
+  if (target_new_objfile_chain)
+    target_new_objfile_chain (objfile);
+}
+
+static void
+attach_thread (int pid, const td_thrhandle_t *th_p,
+              const td_thrinfo_t *ti_p, int verbose)
+{
+  struct thread_info *tp;
+  td_err_e err;
+
+  check_thread_signals ();
+
+  if (verbose)
+    printf_unfiltered ("[New %s]\n", target_pid_to_str (pid));
+
+  /* Add the thread to GDB's thread list.  */
+  tp = add_thread (pid);
+  tp->private = xmalloc (sizeof (struct private_thread_info));
+  tp->private->lwpid = ti_p->ti_lid;
+
+  /* Under Linux, we have to attach to each and every thread.  */
+#ifdef ATTACH_LWP
+  if (ti_p->ti_lid != GET_PID (pid))
+    ATTACH_LWP (BUILD_LWP (ti_p->ti_lid, GET_PID (pid)), 0);
+#endif
+
+  /* Enable thread event reporting for this thread.  */
+  err = td_thr_event_enable_p (th_p, 1);
+  if (err != TD_OK)
+    error ("Cannot enable thread event reporting for %s: %s",
+          target_pid_to_str (pid), thread_db_err_str (err));
+}
+
+static void
+detach_thread (int pid, int verbose)
+{
+  if (verbose)
+    printf_unfiltered ("[%s exited]\n", target_pid_to_str (pid));
+}
+
+static void
+thread_db_detach (char *args, int from_tty)
+{
+  disable_thread_event_reporting ();
+  thread_db_unpush_target ();
+
+  target_beneath->to_detach (args, from_tty);
+}
+
+static void
+thread_db_resume (int pid, int step, enum target_signal signo)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+
+  if (pid == -1)
+    inferior_pid = lwp_from_thread (inferior_pid);
+  else if (is_thread (pid))
+    pid = lwp_from_thread (pid);
+
+  target_beneath->to_resume (pid, step, signo);
+
+  do_cleanups (old_chain);
+}
+
+/* Check if PID is currently stopped at the location of a thread event
+   breakpoint location.  If it is, read the event message and act upon
+   the event.  */
+
+static void
+check_event (int pid)
+{
+  td_event_msg_t msg;
+  td_thrinfo_t ti;
+  td_err_e err;
+  CORE_ADDR stop_pc;
+
+  /* Bail out early if we're not at a thread event breakpoint.  */
+  stop_pc = read_pc_pid (pid) - DECR_PC_AFTER_BREAK;
+  if (stop_pc != td_create_bp_addr && stop_pc != td_death_bp_addr)
+    return;
+
+  err = td_ta_event_getmsg_p (thread_agent, &msg);
+  if (err != TD_OK)
+    {
+      if (err == TD_NOMSG)
+       return;
+
+      error ("Cannot get thread event message: %s", thread_db_err_str (err));
+    }
+
+  err = td_thr_get_info_p (msg.th_p, &ti);
+  if (err != TD_OK)
+    error ("Cannot get thread info: %s", thread_db_err_str (err));
+
+  pid = BUILD_THREAD (ti.ti_tid, GET_PID (pid));
+
+  switch (msg.event)
+    {
+    case TD_CREATE:
+#if 0
+      /* FIXME: kettenis/2000-08-26: Since we use td_ta_event_getmsg,
+         there is no guarantee that the breakpoint will match the
+         event.  Should we use td_thr_event_getmsg instead?  */
+
+      if (stop_pc != td_create_bp_addr)
+       error ("Thread creation event doesn't match breakpoint.");
+#endif
+
+      if (in_thread_list (pid))
+       error ("Spurious thread creation event.");
+
+      attach_thread (pid, msg.th_p, &ti, 1);
+      return;
+
+    case TD_DEATH:
+#if 0
+      /* FIXME: See TD_CREATE.  */
+
+      if (stop_pc != td_death_bp_addr)
+       error ("Thread death event doesn't match breakpoint.");
+#endif
+
+      if (! in_thread_list (pid))
+       error ("Spurious thread death event.");
+
+      detach_thread (pid, 1);
+      return;
+
+    default:
+      error ("Spurious thread event.");
+    }
+}
+
+static int
+thread_db_wait (int pid, struct target_waitstatus *ourstatus)
+{
+  extern int trap_pid;
+
+  if (pid != -1 && is_thread (pid))
+    pid = lwp_from_thread (pid);
+
+  pid = target_beneath->to_wait (pid, ourstatus);
+
+  if (ourstatus->kind == TARGET_WAITKIND_EXITED)
+    return -1;
+
+  if (ourstatus->kind == TARGET_WAITKIND_STOPPED
+      && ourstatus->value.sig == TARGET_SIGNAL_TRAP)
+    /* Check for a thread event.  */
+    check_event (pid);
+
+  if (trap_pid)
+    trap_pid = thread_from_lwp (trap_pid);
+
+  return thread_from_lwp (pid);
+}
+
+static int
+thread_db_xfer_memory (CORE_ADDR memaddr, char *myaddr, int len, int write,
+                      struct target_ops *target)
+{
+  struct cleanup *old_chain = save_inferior_pid ();
+  int xfer;
+
+  if (is_thread (inferior_pid))
+    {
+      /* FIXME: This seems to be necessary to make sure breakpoints
+         are removed.  */
+      if (! target_thread_alive (inferior_pid))
+       inferior_pid = GET_PID (inferior_pid);
+      else
+       inferior_pid = lwp_from_thread (inferior_pid);
+    }
+
+  xfer = target_beneath->to_xfer_memory (memaddr, myaddr, len, write, target);
+
+  do_cleanups (old_chain);
+  return xfer;
+}
+
+static void
+thread_db_fetch_registers (int regno)
+{
+  td_thrhandle_t th;
+  prgregset_t gregset;
+  gdb_prfpregset_t fpregset;
+  td_err_e err;
+
+  if (! is_thread (inferior_pid))
+    {
+      /* Pass the request to the target beneath us.  */
+      target_beneath->to_fetch_registers (regno);
+      return;
+    }
+
+  err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (inferior_pid), &th);
+  if (err != TD_OK)
+    error ("Cannot find thread %ld: %s",
+          (long) GET_THREAD (inferior_pid), thread_db_err_str (err));
+
+  err = td_thr_getgregs_p (&th, gregset);
+  if (err != TD_OK)
+    error ("Cannot fetch general-purpose registers for thread %ld: %s",
+          (long) GET_THREAD (inferior_pid), thread_db_err_str (err));
+
+  err = td_thr_getfpregs_p (&th, &fpregset);
+  if (err != TD_OK)
+    error ("Cannot get floating-point registers for thread %ld: %s",
+          (long) GET_THREAD (inferior_pid), thread_db_err_str (err));
+
+  /* Note that we must call supply_gregset after calling the thread_db
+     routines because the thread_db routines call ps_lgetgregs and
+     friends which clobber GDB's register cache.  */
+  supply_gregset ((gdb_gregset_t *) gregset);
+  supply_fpregset (&fpregset);
+}
+
+static void
+thread_db_store_registers (int regno)
+{
+  td_thrhandle_t th;
+  prgregset_t gregset;
+  gdb_prfpregset_t fpregset;
+  td_err_e err;
+
+  if (! is_thread (inferior_pid))
+    {
+      /* Pass the request to the target beneath us.  */
+      target_beneath->to_store_registers (regno);
+      return;
+    }
+
+  err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (inferior_pid), &th);
+  if (err != TD_OK)
+    error ("Cannot find thread %ld: %s",
+          (long) GET_THREAD (inferior_pid), thread_db_err_str (err));
+
+  if (regno != -1)
+    {
+      char raw[MAX_REGISTER_RAW_SIZE];
+
+      read_register_gen (regno, raw);
+      thread_db_fetch_registers (-1);
+      supply_register (regno, raw);
+    }
+
+  fill_gregset ((gdb_gregset_t *) gregset, -1);
+  fill_fpregset (&fpregset, -1);
+
+  err = td_thr_setgregs_p (&th, gregset);
+  if (err != TD_OK)
+    error ("Cannot store general-purpose registers for thread %ld: %s",
+          (long) GET_THREAD (inferior_pid), thread_db_err_str (err));
+  err = td_thr_setfpregs_p (&th, &fpregset);
+  if (err != TD_OK)
+    error ("Cannot store floating-point registers  for thread %ld: %s",
+          (long) GET_THREAD (inferior_pid), thread_db_err_str (err));
+}
+
+static void
+thread_db_kill (void)
+{
+  target_beneath->to_kill ();
+}
+
+static void
+thread_db_create_inferior (char *exec_file, char *allargs, char **env)
+{
+  /* We never want to actually create the inferior!  If this is ever
+     called, it means we were on the target stack when the user said
+     "run".  But we don't want to be on the new inferior's target
+     stack until the libthread_db connection is ready to be made.  So
+     we unpush ourselves from the stack, and then invoke
+     find_default_create_inferior, which will invoke the appropriate
+     process_stratum target to do the create.  */
+
+  thread_db_unpush_target ();
+
+  find_default_create_inferior (exec_file, allargs, env);
+}
+
+static void
+thread_db_mourn_inferior (void)
+{
+  remove_thread_event_breakpoints ();
+  thread_db_unpush_target ();
+
+  target_beneath->to_mourn_inferior ();
+}
+
+static int
+thread_db_thread_alive (int pid)
+{
+  if (is_thread (pid))
+    {
+      td_thrhandle_t th;
+      td_err_e err;
+
+      err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (pid), &th);
+      if (err != TD_OK)
+       return 0;
+
+      err = td_thr_validate_p (&th);
+      if (err != TD_OK)
+       return 0;
+
+      return 1;
+    }
+
+  if (target_beneath->to_thread_alive)
+    return target_beneath->to_thread_alive (pid);
+
+  return 0;
+}
+
+static int
+find_new_threads_callback (const td_thrhandle_t *th_p, void *data)
+{
+  td_thrinfo_t ti;
+  td_err_e err;
+  int pid;
+
+  err = td_thr_get_info_p (th_p, &ti);
+  if (err != TD_OK)
+    error ("Cannot get thread info: %s", thread_db_err_str (err));
+
+  pid = BUILD_THREAD (ti.ti_tid, GET_PID (inferior_pid));
+
+  if (! in_thread_list (pid))
+    attach_thread (pid, th_p, &ti, 1);
+
+  return 0;
+}
+
+static void
+thread_db_find_new_threads (void)
+{
+  td_err_e err;
+
+  /* Iterate over all user-space threads to discover new threads.  */
+  err = td_ta_thr_iter_p (thread_agent, find_new_threads_callback, NULL,
+                         TD_THR_ANY_STATE, TD_THR_LOWEST_PRIORITY,
+                         TD_SIGNO_MASK, TD_THR_ANY_USER_FLAGS);
+  if (err != TD_OK)
+    error ("Cannot find new threads: %s", thread_db_err_str (err));
+}
+
+static char *
+thread_db_pid_to_str (int pid)
+{
+  if (is_thread (pid))
+    {
+      static char buf[64];
+      td_thrhandle_t th;
+      td_thrinfo_t ti;
+      td_err_e err;
+
+      err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (pid), &th);
+      if (err != TD_OK)
+       error ("Cannot find thread %ld: %s",
+              (long) GET_THREAD (pid), thread_db_err_str (err));
+
+      err = td_thr_get_info_p (&th, &ti);
+      if (err != TD_OK)
+       error ("Cannot get thread info for thread %ld: %s",
+              (long) GET_THREAD (pid), thread_db_err_str (err));
+
+      if (ti.ti_state == TD_THR_ACTIVE && ti.ti_lid != 0)
+       {
+         snprintf (buf, sizeof (buf), "Thread %ld (LWP %d)",
+                   (long) ti.ti_tid, ti.ti_lid);
+       }
+      else
+       {
+         snprintf (buf, sizeof (buf), "Thread %ld (%s)",
+                   (long) ti.ti_tid, thread_db_state_str (ti.ti_state));
+       }
+
+      return buf;
+    }
+
+  if (target_beneath->to_pid_to_str (pid))
+    return target_beneath->to_pid_to_str (pid);
+
+  return normal_pid_to_str (pid);
+}
+
+static void
+init_thread_db_ops (void)
+{
+  thread_db_ops.to_shortname = "multi-thread";
+  thread_db_ops.to_longname = "multi-threaded child process.";
+  thread_db_ops.to_doc = "Threads and pthreads support.";
+  thread_db_ops.to_detach = thread_db_detach;
+  thread_db_ops.to_resume = thread_db_resume;
+  thread_db_ops.to_wait = thread_db_wait;
+  thread_db_ops.to_fetch_registers = thread_db_fetch_registers;
+  thread_db_ops.to_store_registers = thread_db_store_registers;
+  thread_db_ops.to_xfer_memory = thread_db_xfer_memory;
+  thread_db_ops.to_kill = thread_db_kill;
+  thread_db_ops.to_create_inferior = thread_db_create_inferior;
+  thread_db_ops.to_mourn_inferior = thread_db_mourn_inferior;
+  thread_db_ops.to_thread_alive = thread_db_thread_alive;
+  thread_db_ops.to_find_new_threads = thread_db_find_new_threads;
+  thread_db_ops.to_pid_to_str = thread_db_pid_to_str;
+  thread_db_ops.to_stratum = thread_stratum;
+  thread_db_ops.to_has_thread_control = tc_schedlock;
+  thread_db_ops.to_magic = OPS_MAGIC;
+}
+
+void
+_initialize_thread_db (void)
+{
+  /* Only initialize the module if we can load libthread_db.  */
+  if (thread_db_load ())
+    {
+      init_thread_db_ops ();
+      add_target (&thread_db_ops);
+
+      /* Add ourselves to objfile event chain.  */
+      target_new_objfile_chain = target_new_objfile_hook;
+      target_new_objfile_hook = thread_db_new_objfile;
+    }
+}