This is the mail archive of the gdb-patches@sources.redhat.com mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

[PATCH] New Linux threads support


Here is a preliminary patch for the Linux threads support I promised
Kevin.  I'll be working this weekend on fixing up things a bit such
that checking this in won't disrupt the non-x86 Linux ports, and
adding the appropriate ChangeLog entries.

Mark


Index: gdb_proc_service.h
===================================================================
RCS file: /cvs/src/src/gdb/gdb_proc_service.h,v
retrieving revision 1.2
diff -u -p -r1.2 gdb_proc_service.h
--- gdb_proc_service.h	2000/02/16 13:43:34	1.2
+++ gdb_proc_service.h	2000/09/02 13:00:06
@@ -1,19 +1,51 @@
-typedef enum {
-  PS_OK,          /* generic "call succeeded" */
-  PS_ERR,         /* generic. */
-  PS_BADPID,      /* bad process handle */
-  PS_BADLID,      /* bad lwp identifier */
-  PS_BADADDR,     /* bad address */
-  PS_NOSYM,       /* p_lookup() could not find given symbol */
-        PS_NOFREGS
-  /*
-   * FPU register set not available for given
-   * lwp
-   */
-}       ps_err_e;
+/* <proc_service.h> replacement for systems that don't have it.
+   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.  */
+
+#ifndef GDB_PROC_SERVICE_H
+#define GDB_PROC_SERVICE_H
+
+#include <sys/types.h>
+
+#ifdef HAVE_PROC_SERVICE_H
+#include <proc_service.h>
+#else
+
+#ifdef HAVE_SYS_PROCFS_H
+#include <sys/procfs.h>
+#endif
+
+#include "gregset.h"
+
+typedef enum
+{
+  PS_OK,			/* Success.  */
+  PS_ERR,			/* Generic error.  */
+  PS_BADPID,			/* Bad process handle.  */
+  PS_BADLID,			/* Bad LWP id.  */
+  PS_BADADDR,			/* Bad address.  */
+  PS_NOSYM,			/* Symbol not found.  */
+  PS_NOFREGS			/* FPU register set not available.  */
+} ps_err_e;
+
 #ifndef HAVE_LWPID_T
-typedef unsigned int  lwpid_t;
+typedef unsigned int lwpid_t;
 #endif
 
 typedef unsigned long paddr_t;
@@ -23,11 +55,33 @@ typedef unsigned long psaddr_t;
 #endif
 
 #ifndef HAVE_PRGREGSET_T
-typedef gregset_t  prgregset_t;		/* BOGUS BOGUS BOGUS */
+typedef gdb_gregset_t prgregset_t;
 #endif
 
 #ifndef HAVE_PRFPREGSET_T
-typedef fpregset_t prfpregset_t;	/* BOGUS BOGUS BOGUS */
+typedef gdb_fpregset_t prfpregset_t;
+#endif
+
+#endif /* HAVE_PROC_SERVICE_H */
+
+/* Fix-up some broken systems.  */
+
+/* Unfortunately glibc 2.1.3 was released with a broken prfpregset_t
+   type.  We let configure check for this lossage, and make
+   appropriate typedefs here.  */
+
+#ifdef PRFPREGSET_T_BROKEN
+typedef gdb_fpregset_t gdb_prfpregset_t;
+#else
+typedef prfpregset_t gdb_prfpregset_t;
 #endif
+
+/* Structure that identifies the target process.  */
+struct ps_prochandle
+{
+  /* The process id is all we need.  */
+  pid_t pid;
+};
+
+#endif /* gdb_proc_service.h */
 
-struct ps_prochandle;		/* user defined. */
Index: gdb_thread_db.h
===================================================================
RCS file: /cvs/src/src/gdb/gdb_thread_db.h,v
retrieving revision 1.1.1.1
diff -u -p -r1.1.1.1 gdb_thread_db.h
--- gdb_thread_db.h	1999/12/22 21:45:05	1.1.1.1
+++ gdb_thread_db.h	2000/09/02 13:00:06
@@ -1,3 +1,7 @@
+#ifdef HAVE_THREAD_DB_H
+#include <thread_db.h>
+#else
+
 /* Copyright (C) 1999 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
@@ -25,33 +29,33 @@
 #include <pthread.h>
 #include <stdint.h>
 #include <sys/types.h>
-/*#include <sys/ucontext.h>*/
+#include <sys/procfs.h>
 
 
 /* Error codes of the library.  */
 typedef enum
 {
-  TD_OK,	/* No error.  */
-  TD_ERR,	/* No further specified error.  */
-  TD_NOTHR,	/* No matching thread found.  */
-  TD_NOSV,	/* No matching synchronization handle found.  */
-  TD_NOLWP,	/* No matching light-weighted process found.  */
-  TD_BADPH,	/* Invalid process handle.  */
-  TD_BADTH,	/* Invalid thread handle.  */
-  TD_BADSH,	/* Invalid synchronization handle.  */
-  TD_BADTA,	/* Invalid thread agent.  */
-  TD_BADKEY,	/* Invalid key.  */
-  TD_NOMSG,	/* No event available.  */
-  TD_NOFPREGS,	/* No floating-point register content available.  */
-  TD_NOLIBTHREAD,	/* Application not linked with thread library.  */
-  TD_NOEVENT,	/* Requested event is not supported.  */
-  TD_NOCAPAB,	/* Capability not available.  */
-  TD_DBERR,	/* Internal debug library error.  */
-  TD_NOAPLIC,	/* Operation is not applicable.  */
-  TD_NOTSD,	/* No thread-specific data available.  */
-  TD_MALLOC,	/* Out of memory.  */
-  TD_PARTIALREG,/* Not entire register set was read or written.  */
-  TD_NOXREGS	/* X register set not available for given thread.  */
+  TD_OK,	  /* No error.  */
+  TD_ERR,	  /* No further specified error.  */
+  TD_NOTHR,	  /* No matching thread found.  */
+  TD_NOSV,	  /* No matching synchronization handle found.  */
+  TD_NOLWP,	  /* No matching light-weighted process found.  */
+  TD_BADPH,	  /* Invalid process handle.  */
+  TD_BADTH,	  /* Invalid thread handle.  */
+  TD_BADSH,	  /* Invalid synchronization handle.  */
+  TD_BADTA,	  /* Invalid thread agent.  */
+  TD_BADKEY,	  /* Invalid key.  */
+  TD_NOMSG,	  /* No event available.  */
+  TD_NOFPREGS,	  /* No floating-point register content available.  */
+  TD_NOLIBTHREAD, /* Application not linked with thread library.  */
+  TD_NOEVENT,	  /* Requested event is not supported.  */
+  TD_NOCAPAB,	  /* Capability not available.  */
+  TD_DBERR,	  /* Internal debug library error.  */
+  TD_NOAPLIC,	  /* Operation is not applicable.  */
+  TD_NOTSD,	  /* No thread-specific data available.  */
+  TD_MALLOC,	  /* Out of memory.  */
+  TD_PARTIALREG,  /* Not entire register set was read or written.  */
+  TD_NOXREGS	  /* X register set not available for given thread.  */
 } td_err_e;
 
 
@@ -81,9 +85,6 @@ typedef enum
 
 /* Types of the debugging library.  */
 
-/* Addresses.  */
-/*typedef void *psaddr_t;*/
-
 /* Handle for a process.  This type is opaque.  */
 typedef struct td_thragent td_thragent_t;
 
@@ -203,6 +204,14 @@ typedef struct td_event_msg
   } msg;
 } td_event_msg_t;
 
+/* Structure containing event data available in each thread structure.  */
+typedef struct
+{
+  td_thr_events_t eventmask;	/* Mask of enabled events.  */
+  td_event_e eventnum;		/* Number of last event.  */
+  void *eventdata;		/* Data associated with event.  */
+} td_eventbuf_t;
+
 
 /* Gathered statistics about the process.  */
 typedef struct td_ta_stats
@@ -229,26 +238,18 @@ typedef struct td_ta_stats
 typedef pthread_t thread_t;
 typedef pthread_key_t thread_key_t;
 
-/* Linux has different names for the register set types.  */
-/*typedef gregset_t prgregset_t;*/
-/*typedef fpregset_t prfpregset_t;*/
 
-
 /* Callback for iteration over threads.  */
-typedef int td_thr_iter_f __P ((const td_thrhandle_t *, void *));
+typedef int td_thr_iter_f (const td_thrhandle_t *, void *);
 
 /* Callback for iteration over thread local data.  */
-typedef int td_key_iter_f __P ((thread_key_t, void (*) (void *), void *));
+typedef int td_key_iter_f (thread_key_t, void (*) (void *), void *);
 
 
 
 /* Forward declaration.  This has to be defined by the user.  */
 struct ps_prochandle;
 
-/* We don't have any differences between processes and threads, therefore
-   have only one PID type.  */
-/*typedef pid_t lwpid_t;*/
-
 
 /* Information about the thread.  */
 typedef struct td_thrinfo
@@ -334,7 +335,19 @@ extern td_err_e td_ta_tsd_iter (const td
 extern td_err_e td_ta_event_addr (const td_thragent_t *__ta,
 				  td_event_e __event, td_notify_t *__ptr);
 
+/* Enable EVENT in global mask.  */
+extern td_err_e td_ta_set_event (const td_thragent_t *__ta,
+				 td_thr_events_t *__event);
+
+/* Disable EVENT in global mask.  */
+extern td_err_e td_ta_clear_event (const td_thragent_t *__ta,
+				   td_thr_events_t *__event);
+
+/* Return information about last event.  */
+extern td_err_e td_ta_event_getmsg (const td_thragent_t *__ta,
+				    td_event_msg_t *msg);
 
+
 /* Set suggested concurrency level for process associated with TA.  */
 extern td_err_e td_ta_setconcurrency (const td_thragent_t *__ta, int __level);
 
@@ -425,3 +438,5 @@ extern td_err_e td_thr_dbsuspend (const 
 extern td_err_e td_thr_dbresume (const td_thrhandle_t *__th);
 
 #endif	/* thread_db.h */
+
+#endif /* HAVE_THREAD_DB_H */
Index: config/nm-linux.h
===================================================================
RCS file: /cvs/src/src/gdb/config/nm-linux.h,v
retrieving revision 1.4
diff -u -p -r1.4 nm-linux.h
--- nm-linux.h	2000/05/28 16:26:17	1.4
+++ nm-linux.h	2000/09/02 13:00:06
@@ -1,6 +1,5 @@
-/* Native support for GNU/Linux, for GDB, the GNU debugger.
-   Copyright (C) 1999
-   Free Software Foundation, Inc.
+/* Native support for GNU/Linux.
+   Copyright 1999, 2000 Free Software Foundation, Inc.
 
    This file is part of GDB.
 
@@ -19,6 +18,8 @@
    Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.  */
 
+#include <signal.h>
+
 /* Linux is svr4ish but not that much */
 #undef USE_PROC_FS
 
@@ -32,19 +33,15 @@
 #define SVR4_SHARED_LIBS
 #include "solib.h"             /* Support for shared libraries. */
 #endif
-
-/* Support for the glibc linuxthreads package. */
-
-struct objfile;
 
-/* Hook to look at new objfiles (shared libraries) */
-extern void linuxthreads_new_objfile (struct objfile *objfile);
+extern int lin_lwp_prepare_to_proceed (void);
+#define PREPARE_TO_PROCEED(select_it) lin_lwp_prepare_to_proceed ()
 
-/* Method to print a human-readable thread description */
-extern char *linuxthreads_pid_to_str (int pid);
+extern void lin_lwp_attach_lwp (int pid, int verbose);
+#define ATTACH_LWP(pid, verbose) lin_lwp_attach_lwp ((pid), (verbose))
 
-extern int linuxthreads_prepare_to_proceed (int step);
-#define PREPARE_TO_PROCEED(select_it) linuxthreads_prepare_to_proceed (1)
+extern void lin_thread_get_thread_signals (sigset_t *mask);
+#define GET_THREAD_SIGNALS(mask) lin_thread_get_thread_signals (mask)
 
 /* Defined to make stepping-over-breakpoints be thread-atomic.  */
 #define USE_THREAD_STEP_NEEDED 1
Index: config/i386/linux.mh
===================================================================
RCS file: /cvs/src/src/gdb/config/i386/linux.mh,v
retrieving revision 1.3
diff -u -p -r1.3 linux.mh
--- linux.mh	2000/08/10 17:04:33	1.3
+++ linux.mh	2000/09/02 13:00:06
@@ -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
--- /dev/null	Thu Feb 19 16:30:24 1998
+++ lin-lwp.c	Tue Aug 29 00:22:58 2000
@@ -0,0 +1,1046 @@
+/* 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"
+
+extern const char *strsignal (int sig);
+
+#define DEBUG 1
+
+/* 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 assistence 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.  */
+
+/* Here is a list of 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;
+
+
+#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;
+
+
+/* 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;
+
+
+/* Prototypes for local functions.  */
+static void lin_lwp_mourn_inferior (void);
+
+
+/* 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;
+
+  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;
+}
+
+
+/* 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);
+}
+
+
+/* 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;
+}
+
+
+#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;
+
+  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");
+}
+
+
+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);
+      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)
+	{
+	  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?  */
+	  assert (signo == TARGET_SIGNAL_0);
+	  return;
+	}
+    }
+
+  if (resume_all)
+    iterate_over_lwps (resume_callback, NULL);
+
+  child_resume (pid, step, signo);
+}
+
+
+/* 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);
+      assert (ret == 0);
+
+      lp->signalled = 1;
+      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;
+
+      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;
+
+      assert (pid == GET_LWP (lp->pid));
+
+      if (WIFEXITED (status) || WIFSIGNALED (status))
+	{
+	  assert (num_lwps > 1);
+	  assert (! is_cloned (lp->pid));
+
+	  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;
+	}
+
+      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));
+      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)
+	{
+	  assert (pid == -1 || lwpid == pid);
+
+	  lp = find_lwp_pid (lwpid);
+	  if (! lp)
+	    {
+	      lp = add_lwp (BUILD_LWP (lwpid, inferior_pid));
+	      if (threaded)
+		{
+		  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.  */
+	      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.  */
+      assert (status == 0);
+    }
+
+  clear_sigio_trap ();
+  clear_sigint_trap ();
+
+  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));
+
+      assert (pid == -1 && errno == ECHILD);
+    }
+
+  do
+    {
+      pid = waitpid (GET_LWP (lp->pid), NULL, 0);
+    }
+  while (pid == GET_LWP (lp->pid));
+
+  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)
+{
+  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);
+}
+
+
+/* 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);
+}
--- /dev/null	Thu Feb 19 16:30:24 1998
+++ proc-service.c	Sat Aug 26 17:44:17 2000
@@ -0,0 +1,266 @@
+/* <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"
+
+
+/* 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
+
+
+/* Building process ids.  */
+
+#ifndef MERGEPID
+#define MERGEPID(PID, TID)	(((PID) & 0xffff) | ((TID) << 16))
+#endif
+
+#define BUILD_LWP(tid, pid)	MERGEPID (pid, tid)
+
+
+/* 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);
+}
+
+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);
+}
+
+ps_err_e
+ps_pstop (gdb_ps_prochandle_t ph)
+{
+  return PS_OK;
+}
+
+ps_err_e
+ps_pcontinue (gdb_ps_prochandle_t ph)
+{
+  return PS_OK;
+}
+
+ps_err_e
+ps_lstop (gdb_ps_prochandle_t ph, lwpid_t lwpid)
+{
+  return PS_OK;
+}
+
+ps_err_e
+ps_lcontinue (gdb_ps_prochandle_t ph, lwpid_t lwpid)
+{
+  return PS_OK;
+}
+
+ps_err_e
+ps_lgetxregsize (gdb_ps_prochandle_t ph, lwpid_t lwpid, int *xregsize)
+{
+  return PS_OK;
+}
+
+ps_err_e
+ps_lgetxregs (gdb_ps_prochandle_t ph, lwpid_t lwpid, caddr_t xregset)
+{
+  return PS_OK;
+}
+
+ps_err_e
+ps_lsetxregs (gdb_ps_prochandle_t ph, lwpid_t lwpid, caddr_t xregset)
+{
+  return PS_OK;
+}
+
+void
+ps_plog (const char *fmt, ...)
+{
+  va_list args;
+
+  va_start (args, fmt);
+  vfprintf_filtered (gdb_stderr, fmt, args);
+}
+
+ps_err_e
+ps_pglobal_lookup (gdb_ps_prochandle_t ph, const char *ld_object_name,
+		   const char *ld_symbol_name, paddr_t *ld_symbol_addr)
+{
+  struct minimal_symbol *ms;
+
+  ms = lookup_minimal_symbol (ld_symbol_name, NULL, NULL);
+  if (ms == NULL)
+    return PS_NOSYM;
+
+  *ld_symbol_addr = SYMBOL_VALUE_ADDRESS (ms);
+  return PS_OK;
+}
+
+ps_err_e
+ps_pdread (gdb_ps_prochandle_t ph, paddr_t addr,
+	   gdb_ps_read_buf_t buf, gdb_ps_size_t len)
+{
+  return ps_xfer_memory (ph, addr, buf, len, 0);
+}
+
+ps_err_e
+ps_pdwrite (gdb_ps_prochandle_t ph, paddr_t addr,
+	    gdb_ps_write_buf_t buf, gdb_ps_size_t len)
+{
+  return ps_xfer_memory (ph, addr, (char *) buf, len, 1);
+}
+
+ps_err_e
+ps_ptread (gdb_ps_prochandle_t ph, paddr_t addr,
+	   gdb_ps_read_buf_t buf, gdb_ps_size_t len)
+{
+  return ps_xfer_memory (ph, addr, buf, len, 0);
+}
+
+ps_err_e
+ps_ptwrite (gdb_ps_prochandle_t ph, paddr_t addr,
+	    gdb_ps_write_buf_t buf, gdb_ps_size_t len)
+{
+  return ps_xfer_memory (ph, addr, (char *) buf, len, 1);
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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 child process associated with 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.  */
+}
--- /dev/null	Thu Feb 19 16:30:24 1998
+++ thread-db.c	Sun Aug 27 16:31:01 2000
@@ -0,0 +1,989 @@
+/* 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;
+
+/* FIXME: Comment.  */
+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_mask;
+static sigset_t thread_print_mask;
+
+/* 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);
+
+
+/* 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)
+
+
+struct private_thread_info
+{
+  /* Cached LWP id.  Must come first, see lin-lwp.c.  */
+  lwpid_t lwpid;
+};
+
+
+/* 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);
+}
+
+
+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;
+    }
+}
+
+
+/* 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);
+
+  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));
+}
+
+
+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_mask);
+      sigemptyset (&thread_print_mask);
+      
+      for (i = 0; i < NSIG; i++)
+	{
+	  if (sigismember (&mask, i))
+	    {
+	      if (signal_stop_update (target_signal_from_host (i), 0))
+		sigaddset (&thread_stop_mask, i);
+	      if (signal_print_update (target_signal_from_host (i), 0))
+		sigaddset (&thread_print_mask, 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_mask, i))
+	    signal_stop_update (target_signal_from_host (i), 1);
+	  if (sigismember (&thread_print_mask, 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;
+
+#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)
+{
+  if (thread_db_load ())
+    {
+      init_thread_db_ops ();
+      add_target (&thread_db_ops);
+
+      target_new_objfile_chain = target_new_objfile_hook;
+      target_new_objfile_hook = thread_db_new_objfile;
+    }
+}
--- /dev/null	Thu Feb 19 16:30:24 1998
+++ gdb_assert.h	Thu Jul  6 22:45:36 2000
@@ -0,0 +1,58 @@
+/* GDB-friendly replacement for <assert.h>.
+   Copyright (C) 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.  */
+
+#if !defined (GDB_ASSERT_H)
+#define GDB_ASSERT_H
+
+#undef assert
+#undef assert_perror
+
+#define assert(expr)                                                          \
+  ((void) ((expr) ? 0 :                                                       \
+	   (assert_fail (#expr, __FILE__, __LINE__, ASSERT_FUNCTION), 0)))
+
+#define assert_perror(errnum)                                                 \
+  ((void) (!(errnum) ? 0 : (assert_perror_fail ((errnum),                     \
+						__FILE__, __LINE__,           \
+						ASSERT_FUNCTION), 0)))
+
+#if (GCC_VERSION >= 2004)
+#define ASSERT_FUNCTION		__PRETTY_FUNCTION__
+#else
+#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
+#define ASSERT_FUNCTION		__func__
+#else
+#define ASSERT_FUNCTION		((const char *) 0)
+#endif
+#endif
+
+#define assert_fail(assertion, file, line, function)                          \
+  internal_error ("%s:%u: %s%sAssertion `%s' failed.",                        \
+		  file, line,                                                 \
+		  function ? function : "", function ? ": " : "",             \
+		  assertion)
+
+#define assert_perror_fail(errnum, file, line, function)                      \
+  internal_error ("%s:%u: %s%sUnexpected error: %s.",                         \
+                  file, line,                                                 \
+                  function ? function : "", function ? ": " : "",             \
+                  strerror (errnum))
+
+#endif /* !defined (GDB_ASSERT_H) */

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]