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]
Other format: [Raw text]

PATCH: Multithreaded debugging for gdbserver


I've promised this patch, at one time or another, to quite a few people.  I
promised most of them specific dates, too.  My apologies to all of them;
next time I won't give you a date :)

There's a few minor FIXMEs left; for instance, I use a wait/sleep loop
instead of sigsuspend in the clever way that thread-db.c in GDB does.  But
they are all fairly minor.  Five architectures are supported: PowerPC, MIPS,
i386, SH, and ARM.  Others will come as time permits, or as volunteers step
up; it's really quite easy.  Three functions, a constant, and one small
variable is all it took for four of those five.

To use it, you need a copy of the target libraries with symbol information,
in the appropriate filesystem layout.  Then you need to set
solib-absolute-prefix appropriately.  Also, libthread_db now must be present
on the target system and also at configure time for gdbserver.

BE SURE to use the right libraries, or something will almost assuredly
segfault.  I'm working on making that less fragile.

Left out of this patch are the client parts which require protocol changes;
namely async thread notification and accurate single-thread stepping.  The
former was turned down in favor of a synchronous mechanism (which will
require extensive GDB client changes, I believe, and be somewhat more
brittle, but has its own advantages) and the latter received no serious
comments.

Also left out are the tests, since they depend on the remote testing patches
I posted four or so months ago.

I intend to commit this in two or three days unless something comes up with
the patch (along with a suitable NEWS entry).  Longest changelog I ever
wrote, I think - 150 lines at last count.

-- 
Daniel Jacobowitz                           Carnegie Mellon University
MontaVista Software                         Debian GNU/Linux Developer

2002-06-09  Daniel Jacobowitz  <drow@mvista.com>

	* gdbserver/thread-db.c: New file.
	* gdbserver/proc-service.c: New file.
	* gdbserver/acinclude.m4: New file.
	* gdbserver/Makefile.in: Add GDBSERVER_LIBS, gdb_proc_service_h,
	proc-service.o, and thread-db.o.
	(linux-low.o): Add USE_THREAD_DB.
	* gdbserver/acconfig.h: Add HAVE_PRGREGSET_T, HAVE_PRFPREGSET_T,
	HAVE_LWPID_T, HAVE_PSADDR_T, and PRFPREGSET_T_BROKEN.
	* gdbserver/aclocal.m4: Regenerated.
	* gdbserver/config.in: Regenerated.
	* gdbserver/configure: Regenerated.
	* gdbserver/configure.in: Check for proc_service.h, sys/procfs.h,
	thread_db.h, and linux/elf.h headrs.
	Check for lwpid_t, psaddr_t, prgregset_t, prfpregset_t, and
	PRFPREGSET_T_BROKEN.  Introduce srv_thread_depfiles and USE_THREAD_DB.
	Check for -lthread_db and thread support.
	* gdbserver/configure.srv: Enable thread_db support for ARM, i386, MIPS,
	PowerPC, and SuperH.
	* gdbserver/i387-fp.c: Constify arguments.
	* gdbserver/i387-fp.h: Likewise.
	* gdbserver/inferiors.c: (struct thread_info): Renamed from
	`struct inferior_info'.  Remove PID member.  Use generic inferior
	list header.  All uses updated.
	(inferiors, signal_pid): Removed.
	(all_threads): New variable.
	(get_thread): Define.
	(add_inferior_to_list): New function.
	(for_each_inferior): New function.
	(change_inferior_id): New function.
	(add_inferior): Removed.
	(remove_inferior): New function.
	(add_thread): New function.
	(free_one_thread): New function.
	(remove_thread): New function.
	(clear_inferiors): Use for_each_inferior and free_one_thread.
	(find_inferior): New function.
	(find_inferior_id): New function.
	(inferior_target_data): Update argument type.
	(set_inferior_target_data): Likewise.
	(inferior_regcache_data): Likewise.
	(set_inferior_regcache_data): Likewise.
	* gdbserver/linux-low.c (linux_bp_reinsert): Remove.
	(all_processes, stopping_threads, using_thrads)
	(struct pending_signals, debug_threads, pid_of): New.
	(inferior_pid): Replace with macro.
	(struct inferior_linux_data): Remove.
	(get_stop_pc, add_process): New functions.
	(linux_create_inferior): Restore SIGRTMIN+1 before calling exec.
	Use add_process and add_thread.
	(linux_attach_lwp): New function, based on old linux_attach.  Use
	add_process and add_thread.  Set stop_expected for new threads.
	(linux_attach): New function.
	(linux_kill_one_process): New function.
	(linux_kill): Kill all LWPs.
	(linux_thread_alive): Use find_inferior_id.
	(check_removed_breakpoints, status_pending_p): New functions.
	(linux_wait_for_process): Renamed from linux_wait_for_one_inferior.
	Update.  Use WNOHANG.  Wait for cloned processes also.  Update process
	struct for the found process.
	(linux_wait_for_event): New function.
	(linux_wait): Use it.  Support LWPs.
	(send_sigstop, wait_for_sigstop, stop_all_processes)
	(linux_resume_one_process, linux_continue_one_process): New functions.
	(linux_resume): Support LWPs.
	(REGISTER_RAW_SIZE): Remove.
	(fetch_register): Use register_size instead.  Call supply_register.
	(usr_store_inferior_registers): Likewise.  Call collect_register.
	Fix recursive case.
	(regsets_fetch_inferior_registers): Improve error message.
	(regsets_store_inferior_registers): Add debugging.
	(linux_look_up_symbols): Call thread_db_init if USE_THREAD_DB.
	(unstopped_p, linux_signal_pid): New functions.
	(linux_target_ops): Add linux_signal_pid.
	(linux_init_signals): New function.
	(initialize_low): Call it.  Initialize using_threads.
	* gdbserver/regcache.c (inferior_regcache_data): Add valid
	flag.
	(get_regcache): Fetch registers lazily.  Add fetch argument
	and update all callers.
	(regcache_invalidate_one, regcache_invalidate): New
	functions.
	(new_register_cache): Renamed from create_register_cache.
	Return the new regcache.
	(free_register_cache): Change argument to a void *.
	(registers_to_string, registers_from_string): Call get_regcache
	with fetch flag set.
	(register_data): Make static.  Pass fetch flag to get_regcache.
	(supply_register): Call get_regcache with fetch flag clear.
	(collect_register): Call get_regcache with fetch flag set.
	(collect_register_as_string): New function.
	* gdbserver/regcache.h: Update.
	* gdbserver/remote-utils.c (putpkt): Flush after debug output and use
	stderr.
	Handle input interrupts while waiting for an ACK.
	(input_interrupt): Use signal_pid method.
	(getpkt): Flush after debug output and use stderr.
	(outreg): Use collect_register_as_string.
	(new_thread_notify, dead_thread_notify): New functions.
	(prepare_resume_reply): Check using_threads.  Set thread_from_wait
	and general_thread.
	(look_up_one_symbol): Flush after debug output.
	* gdbserver/server.c (step_thread, server_waiting): New variables.
	(start_inferior): Don't use signal_pid.  Update call to mywait.
	(attach_inferior): Update call to mywait.
	(handle_query): Handle qfThreadInfo and qsThreadInfo.
	(main): Don't fetch/store registers explicitly.  Use
	set_desired_inferior.  Support proposed ``Hs'' packet.  Update
	calls to mywait.
	* gdbserver/server.h: Update.
	(struct inferior_list, struct_inferior_list_entry): New.
	* gdbserver/target.c (set_desired_inferior): New.
	(write_inferior_memory): Constify.
	(mywait): New function.
	* gdbserver/target.h: Update.
	(struct target_ops): New signal_pid method.
	(mywait): Removed macro, added prototype.

	* gdbserver/linux-low.h (regset_func): Removed.
	(regset_fill_func, regset_store_func): New.
	(enum regset_type): New.
	(struct regset_info): Add type field.  Use new operation types.
	(struct linux_target_ops): stop_pc renamed to get_pc.
	Add decr_pc_after_break and breakpoint_at.
	(get_process, get_thread_proess, get_process_thread)
	(strut process_info, all_processes, linux_attach_lwp)
	(thread_db_init): New.

	* gdbserver/linux-arm-low.c (arm_get_pc, arm_set_pc,
	arm_breakpoint, arm_breakpoint_len, arm_breakpoint_at): New.
	(the_low_target): Add new members.
	* gdbserver/linux-i386-low.c (i386_store_gregset, i386_store_fpregset)
	(i386_store_fpxregset): Constify.
	(target_regsets): Add new kind identifier.
	(i386_get_pc): Renamed from i386_stop_pc.  Simplify.
	(i386_set_pc): Add debugging.
	(i386_breakpoint_at): New function.
	(the_low_target): Add new members.
	* gdbserver/linux-mips-low.c (mips_get_pc, mips_set_pc)
	(mips_breakpoint, mips_breakpoint_len, mips_reinsert_addr)
	(mips_breakpoint_at): New.
	(the_low_target): Add new members.
	* gdbserver/linux-ppc-low.c (ppc_get_pc, ppc_set_pc)
	(ppc_breakpoint, ppc_breakpoint_len, ppc_breakpoint_at): New.
	(the_low_target): Add new members.
	* gdbserver/linux-sh-low.c (sh_get_pc, sh_set_pc)
	(sh_breakpoint, sh_breakpoint_len, sh_breakpoint_at): New.
	(the_low_target): Add new members.
	* gdbserver/linux-x86-64-low.c (target_regsets): Add new kind
	identifier.

Index: thread-db.c
===================================================================
RCS file: N/A
diff -u /dev/null thread-db.c
--- /dev/null	Wed Dec 31 19:00:00 1969
+++ thread-db.c	Sun Jun  9 15:43:21 2002
@@ -0,0 +1,342 @@
+/* Thread management interface, for the remote server for GDB.
+   Copyright 2002
+   Free Software Foundation, Inc.
+
+   Contributed by MontaVista Software.
+
+   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 "server.h"
+
+#include "linux-low.h"
+
+extern int debug_threads;
+
+#ifdef HAVE_THREAD_DB_H
+#include <thread_db.h>
+#endif
+
+/* Correct for all GNU/Linux targets (for quite some time).  */
+#define GDB_GREGSET_T elf_gregset_t
+#define GDB_FPREGSET_T elf_fpregset_t
+
+#ifndef HAVE_ELF_FPREGSET_T
+/* Make sure we have said types.  Not all platforms bring in <linux/elf.h>
+   via <sys/procfs.h>.  */
+#ifdef HAVE_LINUX_ELF_H
+#include <linux/elf.h>
+#endif
+#endif
+
+#include "../gdb_proc_service.h"
+
+/* 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;
+
+static int find_new_threads_callback (const td_thrhandle_t *th_p, void *data);
+
+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;
+    }
+}
+
+#if 0
+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;
+    }
+}
+#endif
+
+static void
+thread_db_create_event (CORE_ADDR where)
+{
+  td_event_msg_t msg;
+  td_err_e err;
+  struct inferior_linux_data *tdata;
+
+  if (debug_threads)
+    fprintf (stderr, "Thread creation event.\n");
+
+  tdata = inferior_target_data (current_inferior);
+
+  /* FIXME: This assumes we don't get another event.
+     In the LinuxThreads implementation, this is safe,
+     because all events come from the manager thread
+     (except for its own creation, of course).  */
+  err = td_ta_event_getmsg (thread_agent, &msg);
+  if (err != TD_OK)
+    fprintf (stderr, "thread getmsg err: %s\n",
+	     thread_db_err_str (err));
+
+  /* msg.event == TD_EVENT_CREATE */
+
+  find_new_threads_callback (msg.th_p, NULL);
+}
+
+#if 0
+static void
+thread_db_death_event (CORE_ADDR where)
+{
+  if (debug_threads)
+    fprintf (stderr, "Thread death event.\n");
+}
+#endif
+
+static int
+thread_db_enable_reporting ()
+{
+  td_thr_events_t events;
+  td_notify_t notify;
+  td_err_e err;
+
+  /* Set the process wide mask saying which events we're interested in.  */
+  td_event_emptyset (&events);
+  td_event_addset (&events, TD_CREATE);
+
+#if 0
+  /* This is reported to be broken in glibc 2.1.3.  A different approach
+     will be necessary to support that.  */
+  td_event_addset (&events, TD_DEATH);
+#endif
+
+  err = td_ta_set_event (thread_agent, &events);
+  if (err != TD_OK)
+    {
+      warning ("Unable to set global thread event mask: %s",
+               thread_db_err_str (err));
+      return 0;
+    }
+
+  /* Get address for thread creation breakpoint.  */
+  err = td_ta_event_addr (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 0;
+    }
+  set_breakpoint_at ((CORE_ADDR) (unsigned long) notify.u.bptaddr,
+		     thread_db_create_event);
+
+#if 0
+  /* Don't concern ourselves with reported thread deaths, only
+     with actual thread deaths (via wait).  */
+
+  /* Get address for thread death breakpoint.  */
+  err = td_ta_event_addr (thread_agent, TD_DEATH, &notify);
+  if (err != TD_OK)
+    {
+      warning ("Unable to get location for thread death breakpoint: %s",
+	       thread_db_err_str (err));
+      return;
+    }
+  set_breakpoint_at ((CORE_ADDR) (unsigned long) notify.u.bptaddr,
+		     thread_db_death_event);
+#endif
+
+  return 1;
+}
+
+static void
+maybe_attach_thread (const td_thrhandle_t *th_p, td_thrinfo_t *ti_p)
+{
+  td_err_e err;
+  struct thread_info *inferior;
+  struct process_info *process;
+
+  /* If we are attaching to our first thread, things are a little
+     different.  */
+  if (all_threads.head == all_threads.tail)
+    {
+      inferior = (struct thread_info *) all_threads.head;
+      process = get_thread_process (inferior);
+      if (process->thread_known == 0)
+	{
+	  /* Switch to indexing the threads list by TID.  */
+	  change_inferior_id (&all_threads, ti_p->ti_tid);
+	  goto found;
+	}
+    }
+  
+  inferior = (struct thread_info *) find_inferior_id (&all_threads,
+						      ti_p->ti_tid);
+  if (inferior != NULL)
+    return;
+
+  if (debug_threads)
+    fprintf (stderr, "Attaching to thread %ld (LWP %d)\n",
+	     ti_p->ti_tid, ti_p->ti_lid);
+  linux_attach_lwp (ti_p->ti_lid, ti_p->ti_tid);
+  inferior = (struct thread_info *) find_inferior_id (&all_threads,
+						      ti_p->ti_tid);
+  if (inferior == NULL)
+    {
+      warning ("Could not attach to thread %ld (LWP %d)\n",
+	       ti_p->ti_tid, ti_p->ti_lid);
+      return;
+    }
+
+  process = inferior_target_data (inferior);
+
+found:
+  new_thread_notify (ti_p->ti_tid);
+
+  process->tid = ti_p->ti_tid;
+  process->lwpid = ti_p->ti_lid;
+
+  process->thread_known = 1;
+  err = td_thr_event_enable (th_p, 1);
+  if (err != TD_OK)
+    error ("Cannot enable thread event reporting for %d: %s",
+           ti_p->ti_lid, thread_db_err_str (err));
+}
+
+static int
+find_new_threads_callback (const td_thrhandle_t *th_p, void *data)
+{
+  td_thrinfo_t ti;
+  td_err_e err;
+
+  err = td_thr_get_info (th_p, &ti);
+  if (err != TD_OK)
+    error ("Cannot get thread info: %s", thread_db_err_str (err));
+
+  /* Check for zombies.  */
+  if (ti.ti_state == TD_THR_UNKNOWN || ti.ti_state == TD_THR_ZOMBIE)
+    return 0;
+
+  maybe_attach_thread (th_p, &ti);
+
+  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 (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));
+}
+
+int
+thread_db_init ()
+{
+  int err;
+
+  proc_handle.pid = ((struct inferior_list_entry *)current_inferior)->id;
+
+  err = td_ta_new (&proc_handle, &thread_agent);
+  switch (err)
+    {
+    case TD_NOLIBTHREAD:
+      /* No thread library was detected.  */
+      return 0;
+
+    case TD_OK:
+      /* The thread library was detected.  */
+
+      if (thread_db_enable_reporting () == 0)
+	return 0;
+      thread_db_find_new_threads ();
+      return 1;
+
+    default:
+      warning ("error initializing thread_db library.");
+    }
+
+  return 0;
+}
Index: proc-service.c
===================================================================
RCS file: N/A
diff -u /dev/null proc-service.c
--- /dev/null	Wed Dec 31 19:00:00 1969
+++ proc-service.c	Sun Jun  9 15:43:21 2002
@@ -0,0 +1,256 @@
+/* libthread_db helper functions for the remote server for GDB.
+   Copyright 2002
+   Free Software Foundation, Inc.
+
+   Contributed by MontaVista Software.
+
+   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 "server.h"
+
+/* This file is currently tied to GNU/Linux.  It should scale well to
+   another libthread_db implementation, with the approriate gdbserver
+   hooks, but for now this means we can use GNU/Linux's target data.  */
+
+#include "linux-low.h"
+
+/* Correct for all GNU/Linux targets (for quite some time).  */
+#define GDB_GREGSET_T elf_gregset_t
+#define GDB_FPREGSET_T elf_fpregset_t
+
+#ifndef HAVE_ELF_FPREGSET_T
+/* Make sure we have said types.  Not all platforms bring in <linux/elf.h>
+   via <sys/procfs.h>.  */
+#ifdef HAVE_LINUX_ELF_H   
+#include <linux/elf.h>    
+#endif
+#endif
+
+#include "../gdb_proc_service.h"
+
+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;
+
+/* FIXME redo this right */
+#if 0
+#ifndef HAVE_LINUX_REGSETS
+#error HAVE_LINUX_REGSETS required!
+#else
+static struct regset_info *
+gregset_info(void)
+{
+  int i = 0;
+
+  while (target_regsets[i].size != -1)
+    {
+      if (target_regsets[i].type == GENERAL_REGS)
+	break;
+      i++;
+    }
+
+  return &target_regsets[i];
+}
+
+static struct regset_info *
+fpregset_info(void)
+{
+  int i = 0;
+
+  while (target_regsets[i].size != -1)
+    {
+      if (target_regsets[i].type == FP_REGS)
+	break;
+      i++;
+    }
+
+  return &target_regsets[i];
+}
+#endif
+#endif
+
+/* 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)
+{
+  CORE_ADDR addr;
+
+  if (look_up_one_symbol (name, &addr) == 0)
+    return PS_NOSYM;
+
+  *sym_addr = (paddr_t) (unsigned long) addr;
+  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)
+{
+  read_inferior_memory (addr, buf, size);
+  return PS_OK;
+}
+
+/* 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 write_inferior_memory (addr, buf, size);
+}
+
+/* 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)
+{
+#if 0
+  struct thread_info *reg_inferior, *save_inferior;
+  void *regcache;
+
+  reg_inferior = (struct thread_info *) find_inferior_id (&all_threads,
+							  lwpid);
+  if (reg_inferior == NULL)
+    return PS_ERR;
+
+  save_inferior = current_inferior;
+  current_inferior = reg_inferior;
+
+  regcache = new_register_cache ();
+  the_target->fetch_registers (0, regcache);
+  gregset_info()->fill_function (gregset, regcache);
+  free_register_cache (regcache);
+
+  current_inferior = save_inferior;
+  return PS_OK;
+#endif
+  /* FIXME */
+  return PS_ERR;
+}
+
+/* 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)
+{
+#if 0
+  struct thread_info *reg_inferior, *save_inferior;
+  void *regcache;
+
+  reg_inferior = (struct thread_info *) find_inferior_id (&all_threads, lwpid);
+  if (reg_inferior == NULL)
+    return PS_ERR;
+
+  save_inferior = current_inferior;
+  current_inferior = reg_inferior;
+
+  regcache = new_register_cache ();
+  gregset_info()->store_function (gregset, regcache);
+  the_target->store_registers (0, regcache);
+  free_register_cache (regcache);
+
+  current_inferior = save_inferior;
+
+  return PS_OK;
+#endif
+  /* FIXME */
+  return PS_ERR;
+}
+
+/* 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)
+{
+#if 0
+  struct thread_info *reg_inferior, *save_inferior;
+  void *regcache;
+
+  reg_inferior = (struct thread_info *) find_inferior_id (&all_threads, lwpid);
+  if (reg_inferior == NULL)
+    return PS_ERR;
+
+  save_inferior = current_inferior;
+  current_inferior = reg_inferior;
+
+  regcache = new_register_cache ();
+  the_target->fetch_registers (0, regcache);
+  fpregset_info()->fill_function (fpregset, regcache);
+  free_register_cache (regcache);
+
+  current_inferior = save_inferior;
+
+  return PS_OK;
+#endif
+  /* FIXME */
+  return PS_ERR;
+}
+
+/* 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)
+{
+#if 0
+  struct thread_info *reg_inferior, *save_inferior;
+  void *regcache;
+
+  reg_inferior = (struct thread_info *) find_inferior_id (&all_threads, lwpid);
+  if (reg_inferior == NULL)
+    return PS_ERR;
+
+  save_inferior = current_inferior;
+  current_inferior = reg_inferior;
+
+  regcache = new_register_cache ();
+  fpregset_info()->store_function (fpregset, regcache);
+  the_target->store_registers (0, regcache);
+  free_register_cache (regcache);
+
+  current_inferior = save_inferior;
+
+  return PS_OK;
+#endif
+  /* FIXME */
+  return PS_ERR;
+}
+
+/* Return overall process id of the target PH.  Special for GNU/Linux
+   -- not used on Solaris.  */
+
+pid_t
+ps_getpid (gdb_ps_prochandle_t ph)
+{
+  return ph->pid;
+}
+
+
Index: acinclude.m4
===================================================================
RCS file: N/A
diff -u /dev/null acinclude.m4
--- /dev/null	Wed Dec 31 19:00:00 1969
+++ acinclude.m4	Sun Jun  9 15:43:21 2002
@@ -0,0 +1,41 @@
+dnl gdb/gdbserver/configure.in uses BFD_HAVE_SYS_PROCFS_TYPE.
+sinclude(../../bfd/acinclude.m4)
+
+AC_DEFUN([SRV_CHECK_THREAD_DB],
+[AC_CACHE_CHECK([for libthread_db],[srv_cv_thread_db],
+ [old_LIBS="$LIBS"
+  LIBS="$LIBS -lthread_db"
+  AC_TRY_LINK(
+  [void ps_pglobal_lookup() {}
+   void ps_pdread() {}
+   void ps_pdwrite() {}
+   void ps_lgetregs() {}
+   void ps_lsetregs() {}
+   void ps_lgetfpregs() {}
+   void ps_lsetfpregs() {}
+   void ps_getpid() {}],
+  [td_ta_new();],
+  [srv_cv_thread_db="-lthread_db"],
+  [srv_cv_thread_db=no
+
+ if test "$prefix" = "/usr" || test "$prefix" = "NONE"; then
+  thread_db="/lib/libthread_db.so.1"
+ else
+  thread_db='$prefix/lib/libthread_db.so.1'
+ fi
+ LIBS="$old_LIBS `eval echo "$thread_db"`"
+ AC_TRY_LINK(
+  [void ps_pglobal_lookup() {}
+   void ps_pdread() {}
+   void ps_pdwrite() {}
+   void ps_lgetregs() {}
+   void ps_lsetregs() {}
+   void ps_lgetfpregs() {}
+   void ps_lsetfpregs() {}
+   void ps_getpid() {}],
+  [td_ta_new();],
+  [srv_cv_thread_db="$thread_db"],
+  [srv_cv_thread_db=no])
+ LIBS="$old_LIBS"
+ ]])
+)])
Index: Makefile.in
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/Makefile.in,v
retrieving revision 1.18
diff -u -p -u -r1.18 Makefile.in
--- Makefile.in	4 May 2002 15:52:42 -0000	1.18
+++ Makefile.in	9 Jun 2002 21:04:35 -0000
@@ -126,6 +126,7 @@ OBS = inferiors.o regcache.o remote-util
 	utils.o \
 	mem-break.o \
 	$(DEPFILES)
+GDBSERVER_LIBS = @GDBSERVER_LIBS@
 
 # Prevent Sun make from putting in the machine type.  Setting
 # TARGET_ARCH to nothing works for SunOS 3, 4.0, but not for 4.1.
@@ -231,6 +232,7 @@ MAKEOVERRIDES=
 ## with no dependencies and no actions.
 unexport CHILLFLAGS CHILL_LIB CHILL_FOR_TARGET :
 
+gdb_proc_service_h = $(srcdir)/../gdb_proc_service.h $(srcdir)/../gregset.h
 regdat_sh = $(srcdir)/../regformats/regdat.sh
 regdef_h = $(srcdir)/../regformats/regdef.h
 regcache_h = $(srcdir)/regcache.h
@@ -239,10 +241,12 @@ server_h = $(srcdir)/server.h $(regcache
 
 inferiors.o: inferiors.c $(server_h)
 mem-break.o: mem-break.c $(server_h)
+proc-service.o: proc-service.c $(server_h) $(gdb_proc_service_h)
 regcache.o: regcache.c $(server_h) $(regdef_h)
 remote-utils.o: remote-utils.c terminal.h $(server_h)
 server.o: server.c $(server_h)
 target.o: target.c $(server_h)
+thread-db.o: thread-db.c $(server_h) $(gdb_proc_service_h)
 utils.o: utils.c $(server_h)
 
 signals.o: ../signals/signals.c $(server_h)
@@ -253,6 +257,8 @@ i387-fp.o: i387-fp.c $(server_h)
 linux_low_h = $(srcdir)/linux-low.h
 
 linux-low.o: linux-low.c $(linux_low_h) $(server_h)
+	$(CC) -c $(CPPFLAGS) $(INTERNAL_CFLAGS) $< @USE_THREAD_DB@
+
 linux-arm-low.o: linux-arm-low.c $(linux_low_h) $(server_h)
 linux-i386-low.o: linux-i386-low.c $(linux_low_h) $(server_h)
 linux-ia64-low.o: linux-ia64-low.c $(linux_low_h) $(server_h)
Index: acconfig.h
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/acconfig.h,v
retrieving revision 1.1
diff -u -p -u -r1.1 acconfig.h
--- acconfig.h	27 Feb 2002 07:07:38 -0000	1.1
+++ acconfig.h	9 Jun 2002 21:04:35 -0000
@@ -7,3 +7,18 @@
 /* Define if the target supports PTRACE_GETFPXREGS for extended
    register access.  */
 #undef HAVE_PTRACE_GETFPXREGS
+
+/* Define if <sys/procfs.h> has prgregset_t. */
+#undef HAVE_PRGREGSET_T
+
+/* Define if <sys/procfs.h> has prfpregset_t. */
+#undef HAVE_PRFPREGSET_T
+
+/* Define if <sys/procfs.h> has lwpid_t. */
+#undef HAVE_LWPID_T
+
+/* Define if <sys/procfs.h> has psaddr_t. */
+#undef HAVE_PSADDR_T
+
+/* Define if the prfpregset_t type is broken. */
+#undef PRFPREGSET_T_BROKEN
Index: configure.in
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/configure.in,v
retrieving revision 1.9
diff -u -p -u -r1.9 configure.in
--- configure.in	9 Apr 2002 21:25:29 -0000	1.9
+++ configure.in	9 Jun 2002 21:04:44 -0000
@@ -30,7 +30,8 @@ AC_PROG_INSTALL
 
 AC_HEADER_STDC
 
-AC_CHECK_HEADERS(sgtty.h termio.h termios.h sys/reg.h string.h)
+AC_CHECK_HEADERS(sgtty.h termio.h termios.h sys/reg.h string.h dnl
+		 proc_service.h sys/procfs.h thread_db.h linux/elf.h)
 
 . ${srcdir}/configure.srv
 
@@ -62,9 +63,67 @@ if test "${srv_linux_regsets}" = "yes"; 
   fi
 fi
 
-GDBSERVER_DEPFILES="$srv_regobj $srv_tgtobj"
+if test "$ac_cv_header_sys_procfs_h" = yes; then
+  BFD_HAVE_SYS_PROCFS_TYPE(lwpid_t)
+  BFD_HAVE_SYS_PROCFS_TYPE(psaddr_t)
+  BFD_HAVE_SYS_PROCFS_TYPE(prgregset_t)
+  BFD_HAVE_SYS_PROCFS_TYPE(prfpregset_t)
+
+  dnl Check for broken prfpregset_t type
+
+  dnl For Linux/i386, glibc 2.1.3 was released with a bogus
+  dnl prfpregset_t type (it's a typedef for the pointer to a struct
+  dnl instead of the struct itself).  We detect this here, and work
+  dnl around it in gdb_proc_service.h.
+
+  if test $bfd_cv_have_sys_procfs_type_prfpregset_t = yes; then
+    AC_MSG_CHECKING(whether prfpregset_t type is broken)
+    AC_CACHE_VAL(gdb_cv_prfpregset_t_broken,
+      [AC_TRY_RUN([#include <sys/procfs.h>
+       int main ()
+       {
+         if (sizeof (prfpregset_t) == sizeof (void *))
+           return 1;
+         return 0;
+       }],
+       gdb_cv_prfpregset_t_broken=no,
+       gdb_cv_prfpregset_t_broken=yes,
+       gdb_cv_prfpregset_t_broken=yes)])
+    AC_MSG_RESULT($gdb_cv_prfpregset_t_broken)
+    if test $gdb_cv_prfpregset_t_broken = yes; then
+      AC_DEFINE(PRFPREGSET_T_BROKEN)
+    fi
+  fi
+
+  BFD_HAVE_SYS_PROCFS_TYPE(elf_fpregset_t)
+fi
+
+srv_thread_depfiles=
+srv_libs=
+USE_THREAD_DB=
+
+if test "$srv_linux_thread_db" = "yes"; then
+  SRV_CHECK_THREAD_DB
+  if test "$srv_cv_thread_db" = no; then
+    AC_WARN([Could not find libthread_db.])
+    AC_WARN([Disabling thread support in gdbserver.])
+    srv_linux_thread_db=no
+  else
+    srv_libs="$srv_cv_thread_db"
+  fi
+fi
+
+if test "$srv_linux_thread_db" = "yes"; then
+  srv_thread_depfiles="thread-db.o proc-service.o"
+  USE_THREAD_DB="-DUSE_THREAD_DB"
+fi
+
+GDBSERVER_DEPFILES="$srv_regobj $srv_tgtobj $srv_thread_depfiles"
+GDBSERVER_LIBS="$srv_libs"
 
 AC_SUBST(GDBSERVER_DEPFILES)
+AC_SUBST(GDBSERVER_LIBS)
+AC_SUBST(USE_THREAD_DB)
 
 AC_OUTPUT(Makefile,
 [case x$CONFIG_HEADERS in
Index: configure.srv
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/configure.srv,v
retrieving revision 1.4
diff -u -p -u -r1.4 configure.srv
--- configure.srv	28 Feb 2002 16:58:14 -0000	1.4
+++ configure.srv	9 Jun 2002 21:04:44 -0000
@@ -21,11 +21,13 @@ case "${target}" in
   arm*-*-linux*)	srv_regobj=reg-arm.o
 			srv_tgtobj="linux-low.o linux-arm-low.o"
 			srv_linux_usrregs=yes
+			srv_linux_thread_db=yes
 			;;
   i[3456]86-*-linux*)	srv_regobj=reg-i386-linux.o
 			srv_tgtobj="linux-low.o linux-i386-low.o i387-fp.o"
 			srv_linux_usrregs=yes
 			srv_linux_regsets=yes
+			srv_linux_thread_db=yes
 			;;
   ia64-*-linux*)	srv_regobj=reg-ia64.o
 			srv_tgtobj="linux-low.o linux-ia64-low.o"
@@ -38,10 +40,12 @@ case "${target}" in
   mips*-*-linux*)	srv_regobj=reg-mips.o
 			srv_tgtobj="linux-low.o linux-mips-low.o"
 			srv_linux_usrregs=yes
+			srv_linux_thread_db=yes
 			;;
   powerpc*-*-linux*)	srv_regobj=reg-ppc.o
 			srv_tgtobj="linux-low.o linux-ppc-low.o"
 			srv_linux_usrregs=yes
+			srv_linux_thread_db=yes
 			;;
   s390-*-linux*)	srv_regobj=reg-s390.o
 			srv_tgtobj="linux-low.o linux-s390-low.o"
@@ -54,6 +58,7 @@ case "${target}" in
   sh*-*-linux*)		srv_regobj=reg-sh.o
 			srv_tgtobj="linux-low.o linux-sh-low.o"
 			srv_linux_usrregs=yes
+			srv_linux_thread_db=yes
 			;;
   x86_64-*-linux*)	srv_regobj=reg-x86-64.o
 			srv_tgtobj="linux-low.o linux-x86-64-low.o i387-fp.o"
Index: i387-fp.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/i387-fp.c,v
retrieving revision 1.1
diff -u -p -u -r1.1 i387-fp.c
--- i387-fp.c	27 Feb 2002 07:07:38 -0000	1.1
+++ i387-fp.c	9 Jun 2002 21:04:44 -0000
@@ -20,6 +20,7 @@
    Boston, MA 02111-1307, USA.  */
 
 #include "server.h"
+#include "i387-fp.h"
 
 int num_xmm_registers = 8;
 
@@ -108,7 +109,7 @@ i387_cache_to_fsave (void *buf)
 }
 
 void
-i387_fsave_to_cache (void *buf)
+i387_fsave_to_cache (const void *buf)
 {
   struct i387_fsave *fp = (struct i387_fsave *) buf;
   int i;
@@ -240,7 +241,7 @@ i387_ftag (struct i387_fxsave *fp, int r
 }
 
 void
-i387_fxsave_to_cache (void *buf)
+i387_fxsave_to_cache (const void *buf)
 {
   struct i387_fxsave *fp = (struct i387_fxsave *) buf;
   int i, top;
@@ -287,4 +288,3 @@ i387_fxsave_to_cache (void *buf)
   val = (fp->fop) & 0x7FF;
   supply_register_by_name ("fop", &val);
 }
-
Index: i387-fp.h
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/i387-fp.h,v
retrieving revision 1.1
diff -u -p -u -r1.1 i387-fp.h
--- i387-fp.h	27 Feb 2002 07:07:38 -0000	1.1
+++ i387-fp.h	9 Jun 2002 21:04:44 -0000
@@ -23,10 +23,10 @@
 #define I387_FP_H
 
 void i387_cache_to_fsave (void *buf);
-void i387_fsave_to_cache (void *buf);
+void i387_fsave_to_cache (const void *buf);
 
 void i387_cache_to_fxsave (void *buf);
-void i387_fxsave_to_cache (void *buf);
+void i387_fxsave_to_cache (const void *buf);
 
 extern int num_xmm_registers;
 
Index: inferiors.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/inferiors.c,v
retrieving revision 1.3
diff -u -p -u -r1.3 inferiors.c
--- inferiors.c	20 Apr 2002 17:22:48 -0000	1.3
+++ inferiors.c	9 Jun 2002 21:04:45 -0000
@@ -25,81 +25,175 @@
 
 #include "server.h"
 
-struct inferior_info
+struct thread_info
 {
-  int pid;
+  struct inferior_list_entry entry;
   void *target_data;
   void *regcache_data;
-  struct inferior_info *next;
 };
 
-static struct inferior_info *inferiors;
-struct inferior_info *current_inferior;
-int signal_pid;
+struct inferior_list all_threads;
+
+struct thread_info *current_inferior;
+
+#define get_thread(inf) ((struct thread_info *)(inf))
+
+void
+add_inferior_to_list (struct inferior_list *list,
+		      struct inferior_list_entry *new_inferior)
+{
+  new_inferior->next = NULL;
+  if (list->tail != NULL)
+    list->tail->next = new_inferior;
+  else
+    list->head = new_inferior;
+  list->tail = new_inferior;
+}
+
+void
+for_each_inferior (struct inferior_list *list,
+		   void (*action) (struct inferior_list_entry *))
+{
+  struct inferior_list_entry *cur = list->head, *next;
+
+  while (cur != NULL)
+    {
+      next = cur->next;
+      (*action) (cur);
+      cur = next;
+    }
+}
 
 void
-add_inferior (int pid)
+change_inferior_id (struct inferior_list *list,
+		    int new_id)
 {
-  struct inferior_info *new_inferior
-    = (struct inferior_info *) malloc (sizeof (*new_inferior));
+  if (list->head != list->tail)
+    error ("tried to change thread ID after multiple threads are created");
 
-  memset (new_inferior, 0, sizeof (*new_inferior));
+  list->head->id = new_id;
+}
 
-  new_inferior->pid = pid;
+void
+remove_inferior (struct inferior_list *list,
+		 struct inferior_list_entry *entry)
+{
+  struct inferior_list_entry **cur;
 
-  new_inferior->next = inferiors;
-  inferiors = new_inferior;
+  if (list->head == entry)
+    {
+      list->head = entry->next;
+      if (list->tail == entry)
+	list->tail = list->head;
+      return;
+    }
+
+  cur = &list->head;
+  while (*cur && (*cur)->next != entry)
+    cur = &(*cur)->next;
+
+  if (*cur == NULL)
+    return;
 
+  (*cur)->next = entry->next;
+
+  if (list->tail == entry)
+    list->tail = *cur;
+}
+
+void
+add_thread (int thread_id, void *target_data)
+{
+  struct thread_info *new_thread
+    = (struct thread_info *) malloc (sizeof (*new_thread));
+
+  memset (new_thread, 0, sizeof (*new_thread));
+
+  new_thread->entry.id = thread_id;
+
+  add_inferior_to_list (&all_threads, & new_thread->entry);
+  
   if (current_inferior == NULL)
-    current_inferior = inferiors;
+    current_inferior = new_thread;
 
-  create_register_cache (new_inferior);
+  new_thread->target_data = target_data;
+  set_inferior_regcache_data (new_thread, new_register_cache ());
+}
 
-  if (signal_pid == 0)
-    signal_pid = pid;
+static void
+free_one_thread (struct inferior_list_entry *inf)
+{
+  struct thread_info *thread = get_thread (inf);
+  free_register_cache (inferior_regcache_data (thread));
+  free (thread);
+}
+
+void
+remove_thread (struct thread_info *thread)
+{
+  remove_inferior (&all_threads, (struct inferior_list_entry *) thread);
+  free_one_thread (&thread->entry);
 }
 
 void
 clear_inferiors (void)
 {
-  struct inferior_info *inf = inferiors, *next_inf;
+  for_each_inferior (&all_threads, free_one_thread);
+
+  all_threads.head = all_threads.tail = NULL;
+}
+
+struct inferior_list_entry *
+find_inferior (struct inferior_list *list,
+	       int (*func) (struct inferior_list_entry *, void *), void *arg)
+{
+  struct inferior_list_entry *inf = list->head;
 
-  while (inf)
+  while (inf != NULL)
     {
-      next_inf = inf->next;
+      if ((*func) (inf, arg))
+	return inf;
+      inf = inf->next;
+    }
 
-      if (inf->target_data)
-	free (inf->target_data);
-      if (inf->regcache_data)
-	free_register_cache (inf);
+  return NULL;
+}
 
-      free (inf);
-      inf = next_inf;
+struct inferior_list_entry *
+find_inferior_id (struct inferior_list *list, int id)
+{
+  struct inferior_list_entry *inf = list->head;
+
+  while (inf != NULL)
+    {
+      if (inf->id == id)
+	return inf;
+      inf = inf->next;
     }
 
-  inferiors = NULL;
+  return NULL;
 }
 

void *
-inferior_target_data (struct inferior_info *inferior)
+inferior_target_data (struct thread_info *inferior)
 {
   return inferior->target_data;
 }
 
 void
-set_inferior_target_data (struct inferior_info *inferior, void *data)
+set_inferior_target_data (struct thread_info *inferior, void *data)
 {
   inferior->target_data = data;
 }
 
 void *
-inferior_regcache_data (struct inferior_info *inferior)
+inferior_regcache_data (struct thread_info *inferior)
 {
   return inferior->regcache_data;
 }
 
 void
-set_inferior_regcache_data (struct inferior_info *inferior, void *data)
+set_inferior_regcache_data (struct thread_info *inferior, void *data)
 {
   inferior->regcache_data = data;
 }
Index: linux-arm-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-arm-low.c,v
retrieving revision 1.3
diff -u -p -u -r1.3 linux-arm-low.c
--- linux-arm-low.c	9 Apr 2002 22:44:43 -0000	1.3
+++ linux-arm-low.c	9 Jun 2002 21:04:45 -0000
@@ -45,9 +45,49 @@ arm_cannot_fetch_register (int regno)
   return (regno >= arm_num_regs);
 }
 
+static CORE_ADDR
+arm_get_pc ()
+{
+  unsigned long pc;
+  collect_register_by_name ("pc", &pc);
+  return pc;
+}
+
+static void
+arm_set_pc (CORE_ADDR pc)
+{
+  unsigned long newpc = pc;
+  supply_register_by_name ("pc", &newpc);
+}
+
+/* Correct in either endianness.  We do not support Thumb yet.  */
+static const unsigned long arm_breakpoint = 0xef9f0001;
+#define arm_breakpoint_len 4
+
+static int
+arm_breakpoint_at (CORE_ADDR where)
+{
+  unsigned long insn;
+
+  (*the_target->read_memory) (where, (char *) &insn, 4);
+  if (insn == arm_breakpoint)
+    return 1;
+
+  /* If necessary, recognize more trap instructions here.  GDB only uses the
+     one.  */
+  return 0;
+}
+
 struct linux_target_ops the_low_target = {
   arm_num_regs,
   arm_regmap,
   arm_cannot_fetch_register,
   arm_cannot_store_register,
+  arm_get_pc,
+  arm_set_pc,
+  (const char *) &arm_breakpoint,
+  arm_breakpoint_len,
+  NULL,
+  0,
+  arm_breakpoint_at,
 };
Index: linux-i386-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-i386-low.c,v
retrieving revision 1.4
diff -u -p -u -r1.4 linux-i386-low.c
--- linux-i386-low.c	20 Apr 2002 17:04:09 -0000	1.4
+++ linux-i386-low.c	9 Jun 2002 21:04:46 -0000
@@ -72,7 +72,7 @@ i386_fill_gregset (void *buf)
 }
 
 static void
-i386_store_gregset (void *buf)
+i386_store_gregset (const void *buf)
 {
   int i;
 
@@ -89,7 +89,7 @@ i386_fill_fpregset (void *buf)
 }
 
 static void
-i386_store_fpregset (void *buf)
+i386_store_fpregset (const void *buf)
 {
   i387_fsave_to_cache (buf);
 }
@@ -101,7 +101,7 @@ i386_fill_fpxregset (void *buf)
 }
 
 static void
-i386_store_fpxregset (void *buf)
+i386_store_fpxregset (const void *buf)
 {
   i387_fxsave_to_cache (buf);
 }
@@ -109,14 +109,17 @@ i386_store_fpxregset (void *buf)
 
 struct regset_info target_regsets[] = {
   { PTRACE_GETREGS, PTRACE_SETREGS, sizeof (elf_gregset_t),
+    GENERAL_REGS,
     i386_fill_gregset, i386_store_gregset },
 #ifdef HAVE_PTRACE_GETFPXREGS
   { PTRACE_GETFPXREGS, PTRACE_SETFPXREGS, sizeof (elf_fpxregset_t),
+    EXTENDED_REGS,
     i386_fill_fpxregset, i386_store_fpxregset },
 #endif
   { PTRACE_GETFPREGS, PTRACE_SETFPREGS, sizeof (elf_fpregset_t),
+    FP_REGS,
     i386_fill_fpregset, i386_store_fpregset },
-  { 0, 0, -1, NULL, NULL }
+  { 0, 0, -1, -1, NULL, NULL }
 };
 
 #endif /* HAVE_LINUX_REGSETS */
@@ -124,25 +127,38 @@ struct regset_info target_regsets[] = {
 static const char i386_breakpoint[] = { 0xCC };
 #define i386_breakpoint_len 1
 
+extern int debug_threads;
+
 static CORE_ADDR
-i386_stop_pc ()
+i386_get_pc ()
 {
   unsigned long pc;
 
-  /* Overkill */
-  fetch_inferior_registers (0);
-
   collect_register_by_name ("eip", &pc);
-  return pc - 1;
+
+  if (debug_threads)
+    fprintf (stderr, "stop pc (before any decrement) is %08lx\n", pc);
+  return pc;
 }
 
 static void
 i386_set_pc (CORE_ADDR newpc)
 {
+  if (debug_threads)
+    fprintf (stderr, "set pc to %08lx\n", (long) newpc);
   supply_register_by_name ("eip", &newpc);
+}
+
+static int
+i386_breakpoint_at (CORE_ADDR pc)
+{
+  unsigned char c;
+
+  read_inferior_memory (pc, &c, 1);
+  if (c == 0xCC)
+    return 1;
 
-  /* Overkill */
-  store_inferior_registers (0);
+  return 0;
 }
 
 struct linux_target_ops the_low_target = {
@@ -150,8 +166,11 @@ struct linux_target_ops the_low_target =
   i386_regmap,
   i386_cannot_fetch_register,
   i386_cannot_store_register,
-  i386_stop_pc,
+  i386_get_pc,
   i386_set_pc,
   i386_breakpoint,
   i386_breakpoint_len,
+  NULL,
+  1,
+  i386_breakpoint_at,
 };
Index: linux-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-low.c,v
retrieving revision 1.16
diff -u -p -u -r1.16 linux-low.c
--- linux-low.c	24 Apr 2002 15:33:54 -0000	1.16
+++ linux-low.c	9 Jun 2002 21:04:50 -0000
@@ -35,9 +35,32 @@
 #include <stdlib.h>
 #include <unistd.h>
 
-static CORE_ADDR linux_bp_reinsert;
+/* ``all_threads'' is keyed by the LWP ID - it should be the thread ID instead,
+   however.  This requires changing the ID in place when we go from !using_threads
+   to using_threads, immediately.
 
+   ``all_processes'' is keyed by the process ID - which on Linux is (presently)
+   the same as the LWP ID.  */
+
+struct inferior_list all_processes;
+
+/* FIXME this is a bit of a hack, and could be removed.  */
+int stopping_threads;
+
+/* FIXME make into a target method?  */
+int using_threads;
+
+static void linux_resume_one_process (struct inferior_list_entry *entry,
+				      int step, int signal);
 static void linux_resume (int step, int signal);
+static void stop_all_processes (void);
+static int linux_wait_for_event (struct thread_info *child);
+
+struct pending_signals
+{
+  int signal;
+  struct pending_signals *prev;
+};
 
 #define PTRACE_ARG3_TYPE long
 #define PTRACE_XFER_TYPE long
@@ -48,12 +71,64 @@ static int use_regsets_p = 1;
 
 extern int errno;
 
-static int inferior_pid;
+int debug_threads = 0;
+
+#define pid_of(proc) ((proc)->head.id)
 
-struct inferior_linux_data
+/* FIXME: Delete eventually.  */
+#define inferior_pid (pid_of (get_thread_process (current_inferior)))
+
+/* This function should only be called if the process got a SIGTRAP.
+   The SIGTRAP could mean several things.
+
+   On i386, where decr_pc_after_break is non-zero:
+   If we were single-stepping this process using PTRACE_SINGLESTEP,
+   we will get only the one SIGTRAP (even if the instruction we
+   stepped over was a breakpoint).  The value of $eip will be the
+   next instruction.
+   If we continue the process using PTRACE_CONT, we will get a
+   SIGTRAP when we hit a breakpoint.  The value of $eip will be
+   the instruction after the breakpoint (i.e. needs to be
+   decremented).  If we report the SIGTRAP to GDB, we must also
+   report the undecremented PC.  If we cancel the SIGTRAP, we
+   must resume at the decremented PC.
+
+   (Presumably, not yet tested) On a non-decr_pc_after_break machine
+   with hardware or kernel single-step:
+   If we single-step over a breakpoint instruction, our PC will
+   point at the following instruction.  If we continue and hit a
+   breakpoint instruction, our PC will point at the breakpoint
+   instruction.  */
+
+static CORE_ADDR
+get_stop_pc (void)
 {
-  int pid;
-};
+  CORE_ADDR stop_pc = (*the_low_target.get_pc) ();
+
+  if (get_thread_process (current_inferior)->stepping)
+    return stop_pc;
+  else
+    return stop_pc - the_low_target.decr_pc_after_break;
+}
+
+static void *
+add_process (int pid)
+{
+  struct process_info *process;
+
+  process = (struct process_info *) malloc (sizeof (*process));
+  memset (process, 0, sizeof (*process));
+
+  process->head.id = pid;
+
+  /* Default to tid == lwpid == pid.  */
+  process->tid = pid;
+  process->lwpid = pid;
+
+  add_inferior_to_list (&all_processes, &process->head);
+
+  return process;
+}
 
 /* Start an inferior process and returns its pid.
    ALLARGS is a vector of program-name and args. */
@@ -61,7 +136,7 @@ struct inferior_linux_data
 static int
 linux_create_inferior (char *program, char **allargs)
 {
-  struct inferior_linux_data *tdata;
+  void *new_process;
   int pid;
 
   pid = fork ();
@@ -72,6 +147,8 @@ linux_create_inferior (char *program, ch
     {
       ptrace (PTRACE_TRACEME, 0, 0, 0);
 
+      signal (SIGRTMIN + 1, SIG_DFL);
+
       execv (program, allargs);
 
       fprintf (stderr, "Cannot exec %s: %s.\n", program,
@@ -80,22 +157,18 @@ linux_create_inferior (char *program, ch
       _exit (0177);
     }
 
-  add_inferior (pid);
-  tdata = (struct inferior_linux_data *) malloc (sizeof (*tdata));
-  tdata->pid = pid;
-  set_inferior_target_data (current_inferior, tdata);
+  new_process = add_process (pid);
+  add_thread (pid, new_process);
 
-  /* FIXME remove */
-  inferior_pid = pid;
   return 0;
 }
 
 /* Attach to an inferior process.  */
 
-static int
-linux_attach (int pid)
+void
+linux_attach_lwp (int pid, int tid)
 {
-  struct inferior_linux_data *tdata;
+  struct process_info *new_process;
 
   if (ptrace (PTRACE_ATTACH, pid, 0, 0) != 0)
     {
@@ -103,143 +176,712 @@ linux_attach (int pid)
 	       errno < sys_nerr ? sys_errlist[errno] : "unknown error",
 	       errno);
       fflush (stderr);
-      _exit (0177);
+
+      /* If we fail to attach to an LWP, just return.  */
+      if (!using_threads)
+	_exit (0177);
+      return;
     }
 
-  add_inferior (pid);
-  tdata = (struct inferior_linux_data *) malloc (sizeof (*tdata));
-  tdata->pid = pid;
-  set_inferior_target_data (current_inferior, tdata);
+  new_process = (struct process_info *) add_process (pid);
+  add_thread (tid, new_process);
+
+  /* The next time we wait for this LWP we'll see a SIGSTOP as PTRACE_ATTACH
+     brings it to a halt.  We should ignore that SIGSTOP and resume the process
+     (unless this is the first process, in which case the flag will be cleared
+     in linux_attach).
+
+     On the other hand, if we are currently trying to stop all threads, we
+     should treat the new thread as if we had sent it a SIGSTOP.  This works
+     because we are guaranteed that add_process added us to the end of the
+     list, and so the new thread has not yet reached wait_for_sigstop (but
+     will).  */
+  if (! stopping_threads)
+    new_process->stop_expected = 1;
+}
+
+int
+linux_attach (int pid)
+{
+  struct process_info *process;
+
+  linux_attach_lwp (pid, pid);
+
+  /* Don't ignore the initial SIGSTOP if we just attached to this process.  */
+  process = (struct process_info *) find_inferior_id (&all_processes, pid);
+  process->stop_expected = 0;
+
   return 0;
 }
 
 /* Kill the inferior process.  Make us have no inferior.  */
 
 static void
-linux_kill (void)
+linux_kill_one_process (struct inferior_list_entry *entry)
 {
-  if (inferior_pid == 0)
-    return;
-  ptrace (PTRACE_KILL, inferior_pid, 0, 0);
-  wait (0);
-  clear_inferiors ();
+  struct thread_info *thread = (struct thread_info *) entry;
+  struct process_info *process = get_thread_process (thread);
+  int wstat;
+
+  do
+    {
+      ptrace (PTRACE_KILL, pid_of (process), 0, 0);
+
+      /* Make sure it died.  The loop is most likely unnecessary.  */
+      wstat = linux_wait_for_event (thread);
+    } while (WIFSTOPPED (wstat));
 }
 
 /* Return nonzero if the given thread is still alive.  */
+static void
+linux_kill (void)
+{
+  for_each_inferior (&all_threads, linux_kill_one_process);
+}
+
+static int
+linux_thread_alive (int tid)
+{
+  if (find_inferior_id (&all_threads, tid) != NULL)
+    return 1;
+  else
+    return 0;
+}
+
+/* Return nonzero if this process stopped at a breakpoint which
+   no longer appears to be inserted.  Also adjust the PC
+   appropriately to resume where the breakpoint used to be.  */
 static int
-linux_thread_alive (int pid)
+check_removed_breakpoint (struct process_info *event_child)
 {
+  CORE_ADDR stop_pc;
+  struct thread_info *saved_inferior;
+
+  if (event_child->pending_is_breakpoint == 0)
+    return 0;
+
+  if (debug_threads)
+    fprintf (stderr, "Checking for breakpoint.\n");
+
+  saved_inferior = current_inferior;
+  current_inferior = get_process_thread (event_child);
+
+  stop_pc = get_stop_pc ();
+
+  /* If the PC has changed since we stopped, then we shouldn't do
+     anything.  This happens if, for instance, GDB handled the
+     decr_pc_after_break subtraction itself.  */
+  if (stop_pc != event_child->pending_stop_pc)
+    {
+      if (debug_threads)
+	fprintf (stderr, "Ignoring, PC was changed.\n");
+
+      event_child->pending_is_breakpoint = 0;
+      current_inferior = saved_inferior;
+      return 0;
+    }
+
+  /* If the breakpoint is still there, we will report hitting it.  */
+  if ((*the_low_target.breakpoint_at) (stop_pc))
+    {
+      if (debug_threads)
+	fprintf (stderr, "Ignoring, breakpoint is still present.\n");
+      current_inferior = saved_inferior;
+      return 0;
+    }
+
+  if (debug_threads)
+    fprintf (stderr, "Removed breakpoint.\n");
+
+  /* For decr_pc_after_break targets, here is where we perform the
+     decrement.  We go immediately from this function to resuming,
+     and can not safely call get_stop_pc () again.  */
+  if (the_low_target.set_pc != NULL)
+    (*the_low_target.set_pc) (stop_pc);
+
+  /* We consumed the pending SIGTRAP.  */
+  event_child->status_pending_p = 0;
+  event_child->status_pending = 0;
+
+  current_inferior = saved_inferior;
   return 1;
 }
 
+/* Return 1 if this process has an interesting status pending.  This function
+   may silently resume an inferior process.  */
 static int
-linux_wait_for_one_inferior (struct inferior_info *child)
+status_pending_p (struct inferior_list_entry *entry, void *dummy)
+{
+  struct process_info *process = (struct process_info *) entry;
+
+  if (process->status_pending_p)
+    if (check_removed_breakpoint (process))
+      {
+	/* This thread was stopped at a breakpoint, and the breakpoint
+	   is now gone.  We were told to continue (or step...) all threads,
+	   so GDB isn't trying to single-step past this breakpoint.
+	   So instead of reporting the old SIGTRAP, pretend we got to
+	   the breakpoint just after it was removed instead of just
+	   before; resume the process.  */
+	linux_resume_one_process (&process->head, 0, 0);
+	return 0;
+      }
+
+  return process->status_pending_p;
+}
+
+static void
+linux_wait_for_process (struct process_info **childp, int *wstatp)
 {
-  struct inferior_linux_data *child_data = inferior_target_data (child);
-  int pid, wstat;
+  int ret;
+  int to_wait_for = -1;
+
+  if (*childp != NULL)
+    to_wait_for = (*childp)->lwpid;
 
   while (1)
     {
-      pid = waitpid (child_data->pid, &wstat, 0);
+      ret = waitpid (to_wait_for, wstatp, WNOHANG);
+
+      if (ret == -1)
+	{
+	  if (errno != ECHILD)
+	    perror_with_name ("waitpid");
+	}
+      else if (ret > 0)
+	break;
 
-      if (pid != child_data->pid)
-	perror_with_name ("wait");
+      ret = waitpid (to_wait_for, wstatp, WNOHANG | __WCLONE);
 
-      /* If this target supports breakpoints, see if we hit one.  */
-      if (the_low_target.stop_pc != NULL
-	  && WIFSTOPPED (wstat)
-	  && WSTOPSIG (wstat) == SIGTRAP)
+      if (ret == -1)
 	{
-	  CORE_ADDR stop_pc;
+	  if (errno != ECHILD)
+	    perror_with_name ("waitpid (WCLONE)");
+	}
+      else if (ret > 0)
+	break;
+
+      usleep (1000);
+    }
+
+  if (debug_threads
+      && (!WIFSTOPPED (*wstatp)
+	  || (WSTOPSIG (*wstatp) != 32
+	      && WSTOPSIG (*wstatp) != 33)))
+    fprintf (stderr, "Got an event from %d (%x)\n", ret, *wstatp);
 
-	  if (linux_bp_reinsert != 0)
+  if (to_wait_for == -1)
+    *childp = (struct process_info *) find_inferior_id (&all_processes, ret);
+
+  (*childp)->stopped = 1;
+  (*childp)->pending_is_breakpoint = 0;
+
+  if (debug_threads
+      && WIFSTOPPED (*wstatp))
+    {
+      current_inferior = (struct thread_info *)
+	find_inferior_id (&all_threads, (*childp)->tid);
+      /* For testing only; i386_stop_pc prints out a diagnostic.  */
+      if (the_low_target.get_pc != NULL)
+	get_stop_pc ();
+    }
+}
+
+static int
+linux_wait_for_event (struct thread_info *child)
+{
+  CORE_ADDR stop_pc;
+  struct process_info *event_child;
+  int wstat;
+
+  /* Check for a process with a pending status.  */
+  /* It is possible that the user changed the pending task's registers since
+     it stopped.  We correctly handle the change of PC if we hit a breakpoint
+     (in check_removed_breakpoints); signals should be reported anyway.  */
+  if (child == NULL)
+    {
+      event_child = (struct process_info *)
+	find_inferior (&all_processes, status_pending_p, NULL);
+      if (debug_threads && event_child)
+	fprintf (stderr, "Got a pending child %d\n", event_child->lwpid);
+    }
+  else
+    {
+      event_child = get_thread_process (child);
+      if (event_child->status_pending_p
+	  && check_removed_breakpoint (event_child))
+	event_child = NULL;
+    }
+
+  if (event_child != NULL)
+    {
+      if (event_child->status_pending_p)
+	{
+	  if (debug_threads)
+	    fprintf (stderr, "Got an event from pending child %d (%04x)\n",
+		     event_child->lwpid, event_child->status_pending);
+	  wstat = event_child->status_pending;
+	  event_child->status_pending_p = 0;
+	  event_child->status_pending = 0;
+	  current_inferior = get_process_thread (event_child);
+	  return wstat;
+	}
+    }
+
+  /* We only enter this loop if no process has a pending wait status.  Thus
+     any action taken in response to a wait status inside this loop is
+     responding as soon as we detect the status, not after any pending
+     events.  */
+  while (1)
+    {
+      if (child == NULL)
+	event_child = NULL;
+      else
+	event_child = get_thread_process (child);
+
+      linux_wait_for_process (&event_child, &wstat);
+
+      if (event_child == NULL)
+	error ("event from unknown child");
+
+      current_inferior = (struct thread_info *)
+	find_inferior_id (&all_threads, event_child->tid);
+
+      if (using_threads)
+	{
+	  /* Check for thread exit.  */
+	  if (! WIFSTOPPED (wstat))
 	    {
-	      reinsert_breakpoint (linux_bp_reinsert);
-	      linux_bp_reinsert = 0;
-	      linux_resume (0, 0);
+	      if (debug_threads)
+		fprintf (stderr, "Thread %d (LWP %d) exiting\n",
+			 event_child->tid, event_child->head.id);
+
+	      /* If the last thread is exiting, just return.  */
+	      if (all_threads.head == all_threads.tail)
+		return wstat;
+
+	      dead_thread_notify (event_child->tid);
+
+	      remove_inferior (&all_processes, &event_child->head);
+	      free (event_child);
+	      remove_thread (current_inferior);
+	      current_inferior = (struct thread_info *) all_threads.head;
+
+	      /* If we were waiting for this particular child to do something...
+		 well, it did something.  */
+	      if (child != NULL)
+		return wstat;
+
+	      /* Wait for a more interesting event.  */
 	      continue;
 	    }
 
-	  fetch_inferior_registers (0);
-	  stop_pc = (*the_low_target.stop_pc) ();
+	  if (WIFSTOPPED (wstat)
+	      && WSTOPSIG (wstat) == SIGSTOP
+	      && event_child->stop_expected)
+	    {
+	      if (debug_threads)
+		fprintf (stderr, "Expected stop.\n");
+	      event_child->stop_expected = 0;
+	      linux_resume_one_process (&event_child->head,
+					event_child->stepping, 0);
+	      continue;
+	    }
 
-	  if (check_breakpoints (stop_pc) != 0)
+	  /* FIXME drow/2002-06-09: Get signal numbers from the inferior's
+	     thread library?  */
+	  if (WIFSTOPPED (wstat)
+	      && (WSTOPSIG (wstat) == SIGRTMIN
+		  || WSTOPSIG (wstat) == SIGRTMIN + 1))
 	    {
-	      if (the_low_target.set_pc != NULL)
-		(*the_low_target.set_pc) (stop_pc);
+	      if (debug_threads)
+		fprintf (stderr, "Ignored signal %d for %d (LWP %d).\n",
+			 WSTOPSIG (wstat), event_child->tid,
+			 event_child->head.id);
+	      linux_resume_one_process (&event_child->head,
+					event_child->stepping,
+					WSTOPSIG (wstat));
+	      continue;
+	    }
+	}
 
-	      if (the_low_target.breakpoint_reinsert_addr == NULL)
-		{
-		  linux_bp_reinsert = stop_pc;
-		  uninsert_breakpoint (stop_pc);
-		  linux_resume (1, 0);
-		}
-	      else
-		{
-		  reinsert_breakpoint_by_bp
-		    (stop_pc, (*the_low_target.breakpoint_reinsert_addr) ());
-		  linux_resume (0, 0);
-		}
+      /* If this event was not handled above, and is not a SIGTRAP, report
+	 it.  */
+      if (!WIFSTOPPED (wstat) || WSTOPSIG (wstat) != SIGTRAP)
+	return wstat;
+
+      /* If this target does not support breakpoints, we simply report the
+	 SIGTRAP; it's of no concern to us.  */
+      if (the_low_target.get_pc == NULL)
+	return wstat;
+
+      stop_pc = get_stop_pc ();
+
+      /* bp_reinsert will only be set if we were single-stepping.
+	 Notice that we will resume the process after hitting
+	 a gdbserver breakpoint; single-stepping to/over one
+	 is not supported (yet).  */
+      if (event_child->bp_reinsert != 0)
+	{
+	  if (debug_threads)
+	    fprintf (stderr, "Reinserted breakpoint.\n");
+	  reinsert_breakpoint (event_child->bp_reinsert);
+	  event_child->bp_reinsert = 0;
 
-	      continue;
+	  /* Clear the single-stepping flag and SIGTRAP as we resume.  */
+	  linux_resume_one_process (&event_child->head, 0, 0);
+	  continue;
+	}
+
+      if (debug_threads)
+	fprintf (stderr, "Hit a (non-reinsert) breakpoint.\n");
+
+      if (check_breakpoints (stop_pc) != 0)
+	{
+	  /* We hit one of our own breakpoints.  We mark it as a pending
+	     breakpoint, so that check_removed_breakpoints () will do the PC
+	     adjustment for us at the appropriate time.  */
+	  event_child->pending_is_breakpoint = 1;
+	  event_child->pending_stop_pc = stop_pc;
+
+	  /* Now we need to put the breakpoint back.  We continue in the event
+	     loop instead of simply replacing the breakpoint right away,
+	     in order to not lose signals sent to the thread that hit the
+	     breakpoint.  Unfortunately this increases the window where another
+	     thread could sneak past the removed breakpoint.  For the current
+	     use of server-side breakpoints (thread creation) this is
+	     acceptable; but it needs to be considered before this breakpoint
+	     mechanism can be used in more general ways.  For some breakpoints
+	     it may be necessary to stop all other threads, but that should
+	     be avoided where possible.
+
+	     If breakpoint_reinsert_addr is NULL, that means that we can
+	     use PTRACE_SINGLESTEP on this platform.  Uninsert the breakpoint,
+	     mark it for reinsertion, and single-step.
+
+	     Otherwise, call the target function to figure out where we need
+	     our temporary breakpoint, create it, and continue executing this
+	     process.  */
+	  if (the_low_target.breakpoint_reinsert_addr == NULL)
+	    {
+	      event_child->bp_reinsert = stop_pc;
+	      uninsert_breakpoint (stop_pc);
+	      linux_resume_one_process (&event_child->head, 1, 0);
+	    }
+	  else
+	    {
+	      reinsert_breakpoint_by_bp
+		(stop_pc, (*the_low_target.breakpoint_reinsert_addr) ());
+	      linux_resume_one_process (&event_child->head, 0, 0);
 	    }
+
+	  continue;
+	}
+
+      /* If we were single-stepping, we definitely want to report the
+	 SIGTRAP.  The single-step operation has completed, so also
+         clear the stepping flag; in general this does not matter, 
+	 because the SIGTRAP will be reported to the client, which
+	 will give us a new action for this thread, but clear it for
+	 consistency anyway.  It's safe to clear the stepping flag
+         because the only consumer of get_stop_pc () after this point
+	 is check_removed_breakpoints, and pending_is_breakpoint is not
+	 set.  It might be wiser to use a step_completed flag instead.  */
+      if (event_child->stepping)
+	{
+	  event_child->stepping = 0;
+	  return wstat;
+	}
+
+      /* A SIGTRAP that we can't explain.  It may have been a breakpoint.
+	 Check if it is a breakpoint, and if so mark the process information
+	 accordingly.  This will handle both the necessary fiddling with the
+	 PC on decr_pc_after_break targets and suppressing extra threads
+	 hitting a breakpoint if two hit it at once and then GDB removes it
+	 after the first is reported.  Arguably it would be better to report
+	 multiple threads hitting breakpoints simultaneously, but the current
+	 remote protocol does not allow this.  */
+      if ((*the_low_target.breakpoint_at) (stop_pc))
+	{
+	  event_child->pending_is_breakpoint = 1;
+	  event_child->pending_stop_pc = stop_pc;
 	}
 
       return wstat;
     }
+
   /* NOTREACHED */
   return 0;
 }
 
-/* Wait for process, returns status */
+/* Wait for process, returns status.  */
 
 static unsigned char
 linux_wait (char *status)
 {
   int w;
+  struct thread_info *child = NULL;
+
+retry:
+  /* If we were only supposed to resume one thread, only wait for
+     that thread - if it's still alive.  If it died, however - which
+     can happen if we're coming from the thread death case below -
+     then we need to make sure we restart the other threads.  We could
+     pick a thread at random or restart all; restarting all is less
+     arbitrary.  */
+  if (cont_thread > 0)
+    {
+      child = (struct thread_info *) find_inferior_id (&all_threads,
+						       cont_thread);
+
+      /* No stepping, no signal - unless one is pending already, of course.  */
+      if (child == NULL)
+	linux_resume (0, 0);
+    }
 
   enable_async_io ();
-  w = linux_wait_for_one_inferior (current_inferior);
+  w = linux_wait_for_event (child);
+  stop_all_processes ();
   disable_async_io ();
 
-  if (WIFEXITED (w))
+  /* If we are waiting for a particular child, and it exited,
+     linux_wait_for_event will return its exit status.  Similarly if
+     the last child exited.  If this is not the last child, however,
+     do not report it as exited until there is a 'thread exited' response
+     available in the remote protocol.  Instead, just wait for another event.
+     This should be safe, because if the thread crashed we will already
+     have reported the termination signal to GDB; that should stop any
+     in-progress stepping operations, etc.
+
+     Report the exit status of the last thread to exit.  This matches
+     LinuxThreads' behavior.  */
+
+  if (all_threads.head == all_threads.tail)
     {
-      fprintf (stderr, "\nChild exited with retcode = %x \n", WEXITSTATUS (w));
-      *status = 'W';
-      clear_inferiors ();
-      return ((unsigned char) WEXITSTATUS (w));
+      if (WIFEXITED (w))
+	{
+	  fprintf (stderr, "\nChild exited with retcode = %x \n", WEXITSTATUS (w));
+	  *status = 'W';
+	  clear_inferiors ();
+	  return ((unsigned char) WEXITSTATUS (w));
+	}
+      else if (!WIFSTOPPED (w))
+	{
+	  fprintf (stderr, "\nChild terminated with signal = %x \n", WTERMSIG (w));
+	  clear_inferiors ();
+	  *status = 'X';
+	  return ((unsigned char) WTERMSIG (w));
+	}
     }
-  else if (!WIFSTOPPED (w))
+  else
     {
-      fprintf (stderr, "\nChild terminated with signal = %x \n", WTERMSIG (w));
-      clear_inferiors ();
-      *status = 'X';
-      return ((unsigned char) WTERMSIG (w));
+      if (!WIFSTOPPED (w))
+	goto retry;
     }
 
-  fetch_inferior_registers (0);
-
   *status = 'T';
   return ((unsigned char) WSTOPSIG (w));
 }
 
+static void
+send_sigstop (struct inferior_list_entry *entry)
+{
+  struct process_info *process = (struct process_info *) entry;
+
+  if (process->stopped)
+    return;
+
+  /* If we already have a pending stop signal for this process, don't
+     send another.  */
+  if (process->stop_expected)
+    {
+      process->stop_expected = 0;
+      return;
+    }
+
+  if (debug_threads)
+    fprintf (stderr, "Sending sigstop to process %d\n", process->head.id);
+
+  kill (process->head.id, SIGSTOP);
+  process->sigstop_sent = 1;
+}
+
+static void
+wait_for_sigstop (struct inferior_list_entry *entry)
+{
+  struct process_info *process = (struct process_info *) entry;
+  struct thread_info *saved_inferior, *thread;
+  int wstat, saved_tid;
+
+  if (process->stopped)
+    return;
+
+  saved_inferior = current_inferior;
+  saved_tid = ((struct inferior_list_entry *) saved_inferior)->id;
+  thread = (struct thread_info *) find_inferior_id (&all_threads,
+						    process->tid);
+  wstat = linux_wait_for_event (thread);
+
+  /* If we stopped with a non-SIGSTOP signal, save it for later
+     and record the pending SIGSTOP.  If the process exited, just
+     return.  */
+  if (WIFSTOPPED (wstat)
+      && WSTOPSIG (wstat) != SIGSTOP)
+    {
+      if (debug_threads)
+	fprintf (stderr, "Stopped with non-sigstop signal\n");
+      process->status_pending_p = 1;
+      process->status_pending = wstat;
+      process->stop_expected = 1;
+    }
+
+  if (linux_thread_alive (saved_tid))
+    current_inferior = saved_inferior;
+  else
+    {
+      if (debug_threads)
+	fprintf (stderr, "Previously current thread died.\n");
+
+      /* Set a valid thread as current.  */
+      set_desired_inferior (0);
+    }
+}
+
+static void
+stop_all_processes (void)
+{
+  stopping_threads = 1;
+  for_each_inferior (&all_processes, send_sigstop);
+  for_each_inferior (&all_processes, wait_for_sigstop);
+  stopping_threads = 0;
+}
+
 /* Resume execution of the inferior process.
    If STEP is nonzero, single-step it.
    If SIGNAL is nonzero, give it that signal.  */
 
 static void
-linux_resume (int step, int signal)
+linux_resume_one_process (struct inferior_list_entry *entry,
+			  int step, int signal)
 {
+  struct process_info *process = (struct process_info *) entry;
+  struct thread_info *saved_inferior;
+
+  if (process->stopped == 0)
+    return;
+
+  /* If we have pending signals or status, and a new signal, enqueue the
+     signal.  Also enqueue the signal if we are waiting to reinsert a
+     breakpoint; it will be picked up again below.  */
+  if (signal != 0
+      && (process->status_pending_p || process->pending_signals != NULL
+	  || process->bp_reinsert != 0))
+    {
+      struct pending_signals *p_sig;
+      p_sig = malloc (sizeof (*p_sig));
+      p_sig->prev = process->pending_signals;
+      p_sig->signal = signal;
+      process->pending_signals = p_sig;
+    }
+
+  if (process->status_pending_p)
+    return;
+
+  saved_inferior = current_inferior;
+  current_inferior = get_process_thread (process);
+
+  if (debug_threads)
+    fprintf (stderr, "Resuming process %d (%s, signal %d, stop %s)\n", inferior_pid,
+	     step ? "step" : "continue", signal,
+	     process->stop_expected ? "expected" : "not expected");
+
+  /* This bit needs some thinking about.  If we get a signal that
+     we must report while a single-step reinsert is still pending,
+     we often end up resuming the thread.  It might be better to
+     (ew) allow a stack of pending events; then we could be sure that
+     the reinsert happened right away and not lose any signals.
+
+     Making this stack would also shrink the window in which breakpoints are
+     uninserted (see comment in linux_wait_for_process) but not enough for
+     complete correctness, so it won't solve that problem.  It may be
+     worthwhile just to solve this one, however.  */
+  if (process->bp_reinsert != 0)
+    {
+      if (debug_threads)
+	fprintf (stderr, "  pending reinsert at %08lx", (long)process->bp_reinsert);
+      if (step == 0)
+	fprintf (stderr, "BAD - reinserting but not stepping.\n");
+      step = 1;
+
+      /* Postpone any pending signal.  It was enqueued above.  */
+      signal = 0;
+    }
+
+  check_removed_breakpoint (process);
+
+  if (debug_threads && the_low_target.get_pc != NULL) 
+    {
+      fprintf (stderr, "  ");
+      (long) (*the_low_target.get_pc) ();
+    }
+
+  /* If we have pending signals, consume one unless we are trying to reinsert
+     a breakpoint.  */
+  if (process->pending_signals != NULL && process->bp_reinsert == 0)
+    {
+      struct pending_signals **p_sig;
+
+      p_sig = &process->pending_signals;
+      while ((*p_sig)->prev != NULL)
+	p_sig = &(*p_sig)->prev;
+
+      signal = (*p_sig)->signal;
+      free (*p_sig);
+      *p_sig = NULL;
+    }
+
+  regcache_invalidate_one ((struct inferior_list_entry *)
+			   get_process_thread (process));
   errno = 0;
-  ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT, inferior_pid, 1, signal);
+  process->stopped = 0;
+  process->stepping = step;
+  ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT, process->lwpid, 0, signal);
+
+  current_inferior = saved_inferior;
   if (errno)
     perror_with_name ("ptrace");
 }
 
+/* This function is called once per process other than the first
+   one.  The first process we are told the signal to continue
+   with, and whether to step or continue; for all others, any
+   existing signals will be marked in status_pending_p to be
+   reported momentarily, and we preserve the stepping flag.  */
+static void
+linux_continue_one_process (struct inferior_list_entry *entry)
+{
+  struct process_info *process;
 
-#ifdef HAVE_LINUX_USRREGS
+  process = (struct process_info *) entry;
+  linux_resume_one_process (entry, process->stepping, 0);
+}
+
+static void
+linux_resume (int step, int signal)
+{
+  struct process_info *process;
+
+  process = get_thread_process (current_inferior);
+
+  /* If the current process has a status pending, this signal will
+     be enqueued and sent later.  */
+  linux_resume_one_process (&process->head, step, signal);
 
-#define REGISTER_RAW_SIZE(regno) register_size((regno))
+  if (cont_thread == 0 || cont_thread == -1)
+    for_each_inferior (&all_processes, linux_continue_one_process);
+}
+
+#ifdef HAVE_LINUX_USRREGS
 
 int
 register_addr (int regnum)
@@ -262,6 +904,7 @@ fetch_register (int regno)
 {
   CORE_ADDR regaddr;
   register int i;
+  char *buf;
 
   if (regno >= the_low_target.num_regs)
     return;
@@ -271,10 +914,11 @@ fetch_register (int regno)
   regaddr = register_addr (regno);
   if (regaddr == -1)
     return;
-  for (i = 0; i < REGISTER_RAW_SIZE (regno); i += sizeof (PTRACE_XFER_TYPE))
+  buf = alloca (register_size (regno));
+  for (i = 0; i < register_size (regno); i += sizeof (PTRACE_XFER_TYPE))
     {
       errno = 0;
-      *(PTRACE_XFER_TYPE *) (register_data (regno) + i) =
+      *(PTRACE_XFER_TYPE *) (buf + i) =
 	ptrace (PTRACE_PEEKUSER, inferior_pid, (PTRACE_ARG3_TYPE) regaddr, 0);
       regaddr += sizeof (PTRACE_XFER_TYPE);
       if (errno != 0)
@@ -288,6 +932,8 @@ fetch_register (int regno)
 	  goto error_exit;
 	}
     }
+  supply_register (regno, buf);
+
 error_exit:;
 }
 
@@ -310,6 +956,7 @@ usr_store_inferior_registers (int regno)
 {
   CORE_ADDR regaddr;
   int i;
+  char *buf;
 
   if (regno >= 0)
     {
@@ -323,11 +970,13 @@ usr_store_inferior_registers (int regno)
       if (regaddr == -1)
 	return;
       errno = 0;
-      for (i = 0; i < REGISTER_RAW_SIZE (regno); i += sizeof (PTRACE_XFER_TYPE))
+      buf = alloca (register_size (regno));
+      collect_register (regno, buf);
+      for (i = 0; i < register_size (regno); i += sizeof (PTRACE_XFER_TYPE))
 	{
 	  errno = 0;
 	  ptrace (PTRACE_POKEUSER, inferior_pid, (PTRACE_ARG3_TYPE) regaddr,
-		  *(int *) (register_data (regno) + i));
+		  *(int *) (buf + i));
 	  if (errno != 0)
 	    {
 	      if ((*the_low_target.cannot_store_register) (regno) == 0)
@@ -345,7 +994,7 @@ usr_store_inferior_registers (int regno)
     }
   else
     for (regno = 0; regno < the_low_target.num_regs; regno++)
-      store_inferior_registers (regno);
+      usr_store_inferior_registers (regno);
 }
 #endif /* HAVE_LINUX_USRREGS */
 
@@ -354,7 +1003,7 @@ usr_store_inferior_registers (int regno)
 #ifdef HAVE_LINUX_REGSETS
 
 static int
-regsets_fetch_inferior_registers (void)
+regsets_fetch_inferior_registers ()
 {
   struct regset_info *regset;
 
@@ -392,7 +1041,10 @@ regsets_fetch_inferior_registers (void)
 	    }
 	  else
 	    {
-	      perror ("Warning: ptrace(regsets_fetch_inferior_registers)");
+	      char s[256];
+	      sprintf (s, "ptrace(regsets_fetch_inferior_registers) PID=%d",
+		       inferior_pid);
+	      perror (s);
 	    }
 	}
       regset->store_function (buf);
@@ -402,7 +1054,7 @@ regsets_fetch_inferior_registers (void)
 }
 
 static int
-regsets_store_inferior_registers (void)
+regsets_store_inferior_registers ()
 {
   struct regset_info *regset;
 
@@ -528,6 +1180,11 @@ linux_write_memory (CORE_ADDR memaddr, c
   register PTRACE_XFER_TYPE *buffer = (PTRACE_XFER_TYPE *) alloca (count * sizeof (PTRACE_XFER_TYPE));
   extern int errno;
 
+  if (debug_threads)
+    {
+      fprintf (stderr, "Writing %02x to %08lx\n", (unsigned)myaddr[0], (long)memaddr);
+    }
+
   /* Fill start and end extra bytes of buffer with existing memory data.  */
 
   buffer[0] = ptrace (PTRACE_PEEKTEXT, inferior_pid,
@@ -562,7 +1219,40 @@ linux_write_memory (CORE_ADDR memaddr, c
 static void
 linux_look_up_symbols (void)
 {
-  /* Don't need to look up any symbols yet.  */
+#ifdef USE_THREAD_DB
+  if (using_threads)
+    return;
+
+  using_threads = thread_db_init ();
+#endif
+}
+
+/* Return 1 if this process is not stopped.  */
+static int
+unstopped_p (struct inferior_list_entry *entry, void *dummy)
+{
+  struct process_info *process = (struct process_info *) entry;
+
+  if (process->stopped)
+    return 0;
+
+  return 1;
+}
+
+static int
+linux_signal_pid ()
+{
+  struct inferior_list_entry *process;
+
+  process = find_inferior (&all_processes, unstopped_p, NULL);
+
+  if (process == NULL)
+    {
+      warning ("no unstopped process");
+      return inferior_pid;
+    }
+
+  return pid_of ((struct process_info *) process);
 }
 
 
@@ -578,13 +1268,24 @@ static struct target_ops linux_target_op
   linux_read_memory,
   linux_write_memory,
   linux_look_up_symbols,
+  linux_signal_pid,
 };
 
+static void
+linux_init_signals ()
+{
+  /* FIXME drow/2002-06-09: As above, we should check with LinuxThreads
+     to find what the cancel signal actually is.  */
+  signal (SIGRTMIN+1, SIG_IGN);
+}
+
 void
 initialize_low (void)
 {
+  using_threads = 0;
   set_target_ops (&linux_target_ops);
   set_breakpoint_data (the_low_target.breakpoint,
 		       the_low_target.breakpoint_len);
   init_registers ();
+  linux_init_signals ();
 }
Index: linux-low.h
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-low.h,v
retrieving revision 1.5
diff -u -p -u -r1.5 linux-low.h
--- linux-low.h	20 Apr 2002 17:04:09 -0000	1.5
+++ linux-low.h	9 Jun 2002 21:04:50 -0000
@@ -19,12 +19,21 @@
    Boston, MA 02111-1307, USA.  */
 
 #ifdef HAVE_LINUX_REGSETS
-typedef void (*regset_func) (void *);
+typedef void (*regset_fill_func) (void *);
+typedef void (*regset_store_func) (const void *);
+enum regset_type {
+  GENERAL_REGS,
+  FP_REGS,
+  EXTENDED_REGS,
+};
+
 struct regset_info
 {
   int get_request, set_request;
   int size;
-  regset_func fill_function, store_function;
+  enum regset_type type;
+  regset_fill_func fill_function;
+  regset_store_func store_function;
 };
 extern struct regset_info target_regsets[];
 #endif
@@ -39,11 +48,67 @@ struct linux_target_ops
      store the register, and 2 if failure to store the register
      is acceptable.  */
   int (*cannot_store_register) (int);
-  CORE_ADDR (*stop_pc) (void);
+  CORE_ADDR (*get_pc) (void);
   void (*set_pc) (CORE_ADDR newpc);
   const char *breakpoint;
   int breakpoint_len;
   CORE_ADDR (*breakpoint_reinsert_addr) (void);
+
+
+  int decr_pc_after_break;
+  int (*breakpoint_at) (CORE_ADDR pc);
 };
 
 extern struct linux_target_ops the_low_target;
+
+#define get_process(inf) ((struct process_info *)(inf))
+#define get_thread_process(thr) (get_process (inferior_target_data (thr)))
+#define get_process_thread(proc) ((struct thread_info *) \
+				  find_inferior_id (&all_threads, \
+				  get_process (proc)->tid))
+
+struct process_info
+{
+  struct inferior_list_entry head;
+  int thread_known;
+  int lwpid;
+  int tid;
+
+  /* If this flag is set, the next SIGSTOP will be ignored (the process will
+     be immediately resumed).  */
+  int stop_expected;
+
+  /* If this flag is set, the process is known to be stopped right now (stop
+     event already received in a wait()).  */
+  int stopped;
+
+  /* If this flag is set, we have sent a SIGSTOP to this process and are
+     waiting for it to stop.  */
+  int sigstop_sent;
+
+  /* If this flag is set, STATUS_PENDING is a waitstatus that has not yet
+     been reported.  */
+  int status_pending_p;
+  int status_pending;
+
+  /* If this flag is set, the pending status is a (GDB-placed) breakpoint.  */
+  int pending_is_breakpoint;
+  CORE_ADDR pending_stop_pc;
+
+  /* If this is non-zero, it is a breakpoint to be reinserted at our next
+     stop (SIGTRAP stops only).  */
+  CORE_ADDR bp_reinsert;
+
+  /* If this flag is set, the last continue operation on this process
+     was a single-step.  */
+  int stepping;
+
+  /* If this is non-zero, it points to a chain of signals which need to
+     be delivered to this process.  */
+  struct pending_signals *pending_signals;
+};
+extern struct inferior_list all_processes;
+
+void linux_attach_lwp (int pid, int tid);
+
+int thread_db_init (void);
Index: linux-mips-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-mips-low.c,v
retrieving revision 1.5
diff -u -p -u -r1.5 linux-mips-low.c
--- linux-mips-low.c	9 Apr 2002 22:44:43 -0000	1.5
+++ linux-mips-low.c	9 Jun 2002 21:04:52 -0000
@@ -96,9 +96,60 @@ mips_cannot_store_register (int regno)
   return 0;
 }
 
+static CORE_ADDR
+mips_get_pc ()
+{
+  unsigned long pc;
+  collect_register_by_name ("pc", &pc);
+  return pc;
+}
+
+static void
+mips_set_pc (CORE_ADDR pc)
+{
+  unsigned long newpc = pc;
+  supply_register_by_name ("pc", &newpc);
+}
+
+/* Correct in either endianness.  */
+static const unsigned long mips_breakpoint = 0x0005000d;
+#define mips_breakpoint_len 4
+
+/* We only place breakpoints in empty marker functions, and thread locking
+   is outside of the function.  So rather than importing software single-step,
+   we can just run until exit.  */
+static CORE_ADDR
+mips_reinsert_addr ()
+{
+  unsigned long pc;
+  collect_register_by_name ("ra", &pc);
+  return pc;
+}
+
+static int
+mips_breakpoint_at (CORE_ADDR where)
+{
+  unsigned long insn;
+
+  (*the_target->read_memory) (where, (char *) &insn, 4);
+  if (insn == mips_breakpoint)
+    return 1;
+
+  /* If necessary, recognize more trap instructions here.  GDB only uses the
+     one.  */
+  return 0;
+}
+
 struct linux_target_ops the_low_target = {
   mips_num_regs,
   mips_regmap,
   mips_cannot_fetch_register,
   mips_cannot_store_register,
+  mips_get_pc,
+  mips_set_pc,
+  (const char *) &mips_breakpoint,
+  mips_breakpoint_len,
+  mips_reinsert_addr,
+  0,
+  mips_breakpoint_at,
 };
Index: linux-ppc-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-ppc-low.c,v
retrieving revision 1.4
diff -u -p -u -r1.4 linux-ppc-low.c
--- linux-ppc-low.c	11 Apr 2002 20:30:06 -0000	1.4
+++ linux-ppc-low.c	9 Jun 2002 21:04:52 -0000
@@ -64,9 +64,53 @@ ppc_cannot_fetch_register (int regno)
   return 0;
 }
 
+static CORE_ADDR
+ppc_get_pc (void)
+{
+  unsigned long pc;
+
+  collect_register_by_name ("pc", &pc);
+  return (CORE_ADDR) pc;
+}
+
+static void
+ppc_set_pc (CORE_ADDR pc)
+{
+  unsigned long newpc = pc;
+
+  supply_register_by_name ("pc", &newpc);
+}
+
+/* Correct in either endianness.  Note that this file is
+   for PowerPC only, not PowerPC64.
+   This instruction is "twge r2, r2", which GDB uses as a software
+   breakpoint.  */
+static const unsigned long ppc_breakpoint = 0x7d821008;
+#define ppc_breakpoint_len 4
+
+static int
+ppc_breakpoint_at (CORE_ADDR where)
+{
+  unsigned long insn;
+
+  (*the_target->read_memory) (where, (char *) &insn, 4);
+  if (insn == ppc_breakpoint)
+    return 1;
+  /* If necessary, recognize more trap instructions here.  GDB only uses the
+     one.  */
+  return 0;
+}
+
 struct linux_target_ops the_low_target = {
   ppc_num_regs,
   ppc_regmap,
   ppc_cannot_fetch_register,
   ppc_cannot_store_register,
+  ppc_get_pc,
+  ppc_set_pc,
+  (const char *) &ppc_breakpoint,
+  ppc_breakpoint_len,
+  NULL,
+  0,
+  ppc_breakpoint_at,
 };
Index: linux-sh-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-sh-low.c,v
retrieving revision 1.3
diff -u -p -u -r1.3 linux-sh-low.c
--- linux-sh-low.c	9 Apr 2002 22:44:43 -0000	1.3
+++ linux-sh-low.c	9 Jun 2002 21:04:52 -0000
@@ -57,9 +57,49 @@ sh_cannot_fetch_register (int regno)
   return 0;
 }
 
+static CORE_ADDR
+sh_get_pc ()
+{
+  unsigned long pc;
+  collect_register_by_name ("pc", &pc);
+  return pc;
+}
+
+static void
+sh_set_pc (CORE_ADDR pc)
+{
+  unsigned long newpc = pc;
+  supply_register_by_name ("pc", &newpc);
+}
+
+/* Correct in either endianness, obviously.  */
+static const unsigned short sh_breakpoint = 0xc3c3;
+#define sh_breakpoint_len 2
+
+static int
+sh_breakpoint_at (CORE_ADDR where)
+{
+  unsigned short insn;
+
+  (*the_target->read_memory) (where, (char *) &insn, 2);
+  if (insn == sh_breakpoint)
+    return 1;
+
+  /* If necessary, recognize more trap instructions here.  GDB only uses the
+     one.  */
+  return 0;
+}
+
 struct linux_target_ops the_low_target = {
   sh_num_regs,
   sh_regmap,
   sh_cannot_fetch_register,
   sh_cannot_store_register,
+  sh_get_pc,
+  sh_set_pc,
+  (const char *) &sh_breakpoint,
+  sh_breakpoint_len,
+  NULL,
+  0,
+  sh_breakpoint_at,
 };
Index: linux-x86-64-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-x86-64-low.c,v
retrieving revision 1.4
diff -u -p -u -r1.4 linux-x86-64-low.c
--- linux-x86-64-low.c	9 Apr 2002 22:44:43 -0000	1.4
+++ linux-x86-64-low.c	9 Jun 2002 21:04:53 -0000
@@ -71,10 +71,12 @@ x86_64_store_fpregset (void *buf)
 
 struct regset_info target_regsets[] = {
   { PTRACE_GETREGS, PTRACE_SETREGS, sizeof (elf_gregset_t),
+    GENERAL_REGS,
     x86_64_fill_gregset, x86_64_store_gregset },
   { PTRACE_GETFPREGS, PTRACE_SETFPREGS, sizeof (elf_fpregset_t),
+    FP_REGS,
     x86_64_fill_fpregset, x86_64_store_fpregset },
-  { 0, 0, -1, NULL, NULL }
+  { 0, 0, -1, -1, NULL, NULL }
 };
 
 struct linux_target_ops the_low_target = {
Index: regcache.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/regcache.c,v
retrieving revision 1.4
diff -u -p -u -r1.4 regcache.c
--- regcache.c	20 Apr 2002 17:22:48 -0000	1.4
+++ regcache.c	9 Jun 2002 21:04:53 -0000
@@ -27,6 +27,7 @@
 
 struct inferior_regcache_data
 {
+  int registers_valid;
   char *registers;
 };
 
@@ -38,7 +39,7 @@ static int num_registers;
 const char **gdbserver_expedite_regs;
 
 static struct inferior_regcache_data *
-get_regcache (struct inferior_info *inf)
+get_regcache (struct thread_info *inf, int fetch)
 {
   struct inferior_regcache_data *regcache;
 
@@ -47,17 +48,50 @@ get_regcache (struct inferior_info *inf)
   if (regcache == NULL)
     fatal ("no register cache");
 
+  /* FIXME - fetch registers for INF */
+  if (fetch && regcache->registers_valid == 0)
+    {
+      fetch_inferior_registers (0);
+      regcache->registers_valid = 1;
+    }
+
   return regcache;
 }
 
+void
+regcache_invalidate_one (struct inferior_list_entry *entry)
+{
+  struct thread_info *thread = (struct thread_info *) entry;
+  struct inferior_regcache_data *regcache;
+
+  regcache = (struct inferior_regcache_data *) inferior_regcache_data (thread);
+
+  if (regcache->registers_valid)
+    {
+      struct thread_info *saved_inferior = current_inferior;
+
+      current_inferior = thread;
+      store_inferior_registers (-1);
+      current_inferior = saved_inferior;
+    }
+
+  regcache->registers_valid = 0;
+}
+
+void
+regcache_invalidate ()
+{
+  for_each_inferior (&all_threads, regcache_invalidate_one);
+}
+
 int
 registers_length (void)
 {
   return 2 * register_bytes;
 }
 
-void
-create_register_cache (struct inferior_info *inferior)
+void *
+new_register_cache (void)
 {
   struct inferior_regcache_data *regcache;
 
@@ -67,15 +101,19 @@ create_register_cache (struct inferior_i
   if (regcache->registers == NULL)
     fatal ("Could not allocate register cache.");
 
-  set_inferior_regcache_data (inferior, regcache);
+  regcache->registers_valid = 0;
+
+  return regcache;
 }
 
 void
-free_register_cache (struct inferior_info *inferior)
+free_register_cache (void *regcache_p)
 {
-  free (get_regcache (current_inferior)->registers);
-  free (get_regcache (current_inferior));
-  set_inferior_regcache_data (inferior, NULL);
+  struct inferior_regcache_data *regcache
+    = (struct inferior_regcache_data *) regcache_p;
+
+  free (regcache->registers);
+  free (regcache);
 }
 
 void
@@ -99,7 +137,7 @@ set_register_cache (struct reg *regs, in
 void
 registers_to_string (char *buf)
 {
-  char *registers = get_regcache (current_inferior)->registers;
+  char *registers = get_regcache (current_inferior, 1)->registers;
 
   convert_int_to_ascii (registers, buf, register_bytes);
 }
@@ -108,7 +146,7 @@ void
 registers_from_string (char *buf)
 {
   int len = strlen (buf);
-  char *registers = get_regcache (current_inferior)->registers;
+  char *registers = get_regcache (current_inferior, 1)->registers;
 
   if (len != register_bytes * 2)
     {
@@ -155,10 +193,10 @@ register_size (int n)
   return reg_defs[n].size / 8;
 }
 
-char *
-register_data (int n)
+static char *
+register_data (int n, int fetch)
 {
-  char *registers = get_regcache (current_inferior)->registers;
+  char *registers = get_regcache (current_inferior, fetch)->registers;
 
   return registers + (reg_defs[n].offset / 8);
 }
@@ -166,7 +204,7 @@ register_data (int n)
 void
 supply_register (int n, const void *buf)
 {
-  memcpy (register_data (n), buf, register_size (n));
+  memcpy (register_data (n, 0), buf, register_size (n));
 }
 
 void
@@ -178,7 +216,13 @@ supply_register_by_name (const char *nam
 void
 collect_register (int n, void *buf)
 {
-  memcpy (buf, register_data (n), register_size (n));
+  memcpy (buf, register_data (n, 1), register_size (n));
+}
+
+void
+collect_register_as_string (int n, char *buf)
+{
+  convert_int_to_ascii (register_data (n, 1), buf, register_size (n));
 }
 
 void
Index: regcache.h
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/regcache.h,v
retrieving revision 1.4
diff -u -p -u -r1.4 regcache.h
--- regcache.h	24 Apr 2002 15:33:55 -0000	1.4
+++ regcache.h	9 Jun 2002 21:04:54 -0000
@@ -21,15 +21,20 @@
 #ifndef REGCACHE_H
 #define REGCACHE_H
 
-struct inferior_info;
+struct inferior_list_entry;
 
 /* Create a new register cache for INFERIOR.  */
 
-void create_register_cache (struct inferior_info *inferior);
+void *new_register_cache (void);
 
 /* Release all memory associated with the register cache for INFERIOR.  */
 
-void free_register_cache (struct inferior_info *inferior);
+void free_register_cache (void *regcache);
+
+/* Invalidate cached registers for one or all threads.  */
+
+void regcache_invalidate_one (struct inferior_list_entry *);
+void regcache_invalidate (void);
 
 /* Convert all registers to a string in the currently specified remote
    format.  */
@@ -48,8 +53,6 @@ int registers_length (void);
 
 struct reg *find_register_by_number (int n);
 
-char *register_data (int n);
-
 int register_size (int n);
 
 int find_regno (const char *name);
@@ -61,6 +64,8 @@ void supply_register (int n, const void 
 void supply_register_by_name (const char *name, const void *buf);
 
 void collect_register (int n, void *buf);
+
+void collect_register_as_string (int n, char *buf);
 
 void collect_register_by_name (const char *name, void *buf);
 
Index: remote-utils.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/remote-utils.c,v
retrieving revision 1.14
diff -u -p -u -r1.14 remote-utils.c
--- remote-utils.c	9 Apr 2002 23:52:06 -0000	1.14
+++ remote-utils.c	9 Jun 2002 21:04:55 -0000
@@ -42,6 +42,10 @@ struct ui_file *gdb_stdlog;
 
 static int remote_desc;
 
+/* FIXME headerize? */
+extern int using_threads;
+extern int debug_threads;
+
 /* Open a connection to a remote debugger.
    NAME is the filename used for communication.  */
 
@@ -296,10 +300,17 @@ putpkt (char *buf)
 	}
 
       if (remote_debug)
-	printf ("putpkt (\"%s\"); [looking for ack]\n", buf2);
+	{
+	  fprintf (stderr, "putpkt (\"%s\"); [looking for ack]\n", buf2);
+	  fflush (stderr);
+	}
       cc = read (remote_desc, buf3, 1);
       if (remote_debug)
-	printf ("[received '%c' (0x%x)]\n", buf3[0], buf3[0]);
+	{
+	  fprintf (stderr, "[received '%c' (0x%x)]\n", buf3[0], buf3[0]);
+	  fflush (stderr);
+	}
+
       if (cc <= 0)
 	{
 	  if (cc == 0)
@@ -310,6 +321,10 @@ putpkt (char *buf)
 	  free (buf2);
 	  return -1;
 	}
+
+      /* Check for an input interrupt while we're here.  */
+      if (buf3[0] == '\003')
+	kill ((*the_target->signal_pid) (), SIGINT);
     }
   while (buf3[0] != '+');
 
@@ -346,7 +361,7 @@ input_interrupt (int unused)
 	  return;
 	}
       
-      kill (signal_pid, SIGINT);
+      kill ((*the_target->signal_pid) (), SIGINT);
     }
 }
 
@@ -411,7 +426,11 @@ getpkt (char *buf)
 	  if (c == '$')
 	    break;
 	  if (remote_debug)
-	    printf ("[getpkt: discarding char '%c']\n", c);
+	    {
+	      fprintf (stderr, "[getpkt: discarding char '%c']\n", c);
+	      fflush (stderr);
+	    }
+
 	  if (c < 0)
 	    return -1;
 	}
@@ -441,12 +460,19 @@ getpkt (char *buf)
     }
 
   if (remote_debug)
-    printf ("getpkt (\"%s\");  [sending ack] \n", buf);
+    {
+      fprintf (stderr, "getpkt (\"%s\");  [sending ack] \n", buf);
+      fflush (stderr);
+    }
 
   write (remote_desc, "+", 1);
 
   if (remote_debug)
-    printf ("[sent ack]\n");
+    {
+      fprintf (stderr, "[sent ack]\n");
+      fflush (stderr);
+    }
+
   return bp - buf;
 }
 
@@ -499,8 +525,6 @@ convert_ascii_to_int (char *from, char *
 static char *
 outreg (int regno, char *buf)
 {
-  int regsize = register_size (regno);
-
   if ((regno >> 12) != 0)
     *buf++ = tohex ((regno >> 12) & 0xf);
   if ((regno >> 8) != 0)
@@ -508,14 +532,47 @@ outreg (int regno, char *buf)
   *buf++ = tohex ((regno >> 4) & 0xf);
   *buf++ = tohex (regno & 0xf);
   *buf++ = ':';
-  convert_int_to_ascii (register_data (regno), buf, regsize);
-  buf += 2 * regsize;
+  collect_register_as_string (regno, buf);
+  buf += 2 * register_size (regno);
   *buf++ = ';';
 
   return buf;
 }
 
 void
+new_thread_notify (int id)
+{
+  char own_buf[256];
+
+  /* The `n' response is not yet part of the remote protocol.  Do nothing.  */
+  if (1)
+    return;
+
+  if (server_waiting == 0)
+    return;
+
+  sprintf (own_buf, "n%x", id);
+  disable_async_io ();
+  putpkt (own_buf);
+  enable_async_io ();
+}
+
+void
+dead_thread_notify (int id)
+{
+  char own_buf[256];
+
+  /* The `x' response is not yet part of the remote protocol.  Do nothing.  */
+  if (1)
+    return;
+
+  sprintf (own_buf, "x%x", id);
+  disable_async_io ();
+  putpkt (own_buf);
+  enable_async_io ();
+}
+
+void
 prepare_resume_reply (char *buf, char status, unsigned char signo)
 {
   int nib, sig;
@@ -538,12 +595,23 @@ prepare_resume_reply (char *buf, char st
 	  regp ++;
 	}
 
-      /* If the debugger hasn't used any thread features, don't burden it with
-	 threads.  If we didn't check this, GDB 4.13 and older would choke.  */
-      if (cont_thread != 0)
+      /* Formerly, if the debugger had not used any thread features we would not
+	 burden it with a thread status response.  This was for the benefit of
+	 GDB 4.13 and older.  However, in recent GDB versions the check
+	 (``if (cont_thread != 0)'') does not have the desired effect because of
+	 sillyness in the way that the remote protocol handles specifying a thread.
+	 Since thread support relies on qSymbol support anyway, assume GDB can handle
+	 threads.  */
+
+      if (using_threads)
 	{
+	  /* FIXME right place to set this? */
+	  thread_from_wait = ((struct inferior_list_entry *)current_inferior)->id;
+	  if (debug_threads)
+	    fprintf (stderr, "Writing resume reply for %d\n\n", thread_from_wait);
 	  if (old_thread_from_wait != thread_from_wait)
 	    {
+	      general_thread = thread_from_wait;
 	      sprintf (buf, "thread:%x;", thread_from_wait);
 	      buf += strlen (buf);
 	      old_thread_from_wait = thread_from_wait;
@@ -620,7 +688,11 @@ look_up_one_symbol (const char *name, CO
     {
       /* Malformed response.  */
       if (remote_debug)
-	fprintf (stderr, "Malformed response to qSymbol, ignoring.\n");
+	{
+	  fprintf (stderr, "Malformed response to qSymbol, ignoring.\n");
+	  fflush (stderr);
+	}
+
       return -1;
     }
 
Index: server.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/server.c,v
retrieving revision 1.12
diff -u -p -u -r1.12 server.c
--- server.c	16 May 2002 02:33:04 -0000	1.12
+++ server.c	9 Jun 2002 21:04:56 -0000
@@ -23,9 +23,12 @@
 
 int cont_thread;
 int general_thread;
+int step_thread;
 int thread_from_wait;
 int old_thread_from_wait;
 int extended_protocol;
+int server_waiting;
+
 jmp_buf toplevel;
 
 static unsigned char
@@ -33,11 +36,12 @@ start_inferior (char *argv[], char *stat
 {
   /* FIXME Check error? Or turn to void.  */
   create_inferior (argv[0], argv);
-  /* FIXME Print pid properly.  */
-  fprintf (stderr, "Process %s created; pid = %d\n", argv[0], signal_pid);
+
+  fprintf (stderr, "Process %s created; pid = %d\n", argv[0],
+	   all_threads.head->id);
 
   /* Wait till we are at 1st instruction in program, return signal number.  */
-  return mywait (statusptr);
+  return mywait (statusptr, 0);
 }
 
 static int
@@ -48,7 +52,7 @@ attach_inferior (int pid, char *statuspt
   if (myattach (pid) != 0)
     return -1;
 
-  *sigptr = mywait (statusptr);
+  *sigptr = mywait (statusptr, 0);
 
   return 0;
 }
@@ -59,6 +63,8 @@ extern int remote_debug;
 void
 handle_query (char *own_buf)
 {
+  static struct inferior_list_entry *thread_ptr;
+
   if (strcmp ("qSymbol::", own_buf) == 0)
     {
       if (the_target->look_up_symbols != NULL)
@@ -68,6 +74,29 @@ handle_query (char *own_buf)
       return;
     }
 
+  if (strcmp ("qfThreadInfo", own_buf) == 0)
+    {
+      thread_ptr = all_threads.head;
+      sprintf (own_buf, "m%x", thread_ptr->id);
+      thread_ptr = thread_ptr->next;
+      return;
+    }
+  
+  if (strcmp ("qsThreadInfo", own_buf) == 0)
+    {
+      if (thread_ptr != NULL)
+	{
+	  sprintf (own_buf, "m%x", thread_ptr->id);
+	  thread_ptr = thread_ptr->next;
+	  return;
+	}
+      else
+	{
+	  sprintf (own_buf, "l");
+	  return;
+	}
+    }
+      
   /* Otherwise we didn't know what packet it was.  Say we didn't
      understand it.  */
   own_buf[0] = 0;
@@ -188,12 +217,16 @@ main (int argc, char *argv[])
 		case 'g':
 		  general_thread = strtol (&own_buf[2], NULL, 16);
 		  write_ok (own_buf);
-		  fetch_inferior_registers (0);
+		  set_desired_inferior (1);
 		  break;
 		case 'c':
 		  cont_thread = strtol (&own_buf[2], NULL, 16);
 		  write_ok (own_buf);
 		  break;
+		case 's':
+		  step_thread = strtol (&own_buf[2], NULL, 16);
+		  write_ok (own_buf);
+		  break;
 		default:
 		  /* Silently ignore it so that gdb can extend the protocol
 		     without compatibility headaches.  */
@@ -202,11 +235,12 @@ main (int argc, char *argv[])
 		}
 	      break;
 	    case 'g':
+	      set_desired_inferior (1);
 	      registers_to_string (own_buf);
 	      break;
 	    case 'G':
+	      set_desired_inferior (1);
 	      registers_from_string (&own_buf[1]);
-	      store_inferior_registers (-1);
 	      write_ok (own_buf);
 	      break;
 	    case 'm':
@@ -227,8 +261,9 @@ main (int argc, char *argv[])
 		signal = target_signal_to_host (sig);
 	      else
 		signal = 0;
+	      set_desired_inferior (0);
 	      myresume (0, signal);
-	      signal = mywait (&status);
+	      signal = mywait (&status, 1);
 	      prepare_resume_reply (own_buf, status, signal);
 	      break;
 	    case 'S':
@@ -237,18 +272,21 @@ main (int argc, char *argv[])
 		signal = target_signal_to_host (sig);
 	      else
 		signal = 0;
+	      set_desired_inferior (0);
 	      myresume (1, signal);
-	      signal = mywait (&status);
+	      signal = mywait (&status, 1);
 	      prepare_resume_reply (own_buf, status, signal);
 	      break;
 	    case 'c':
+	      set_desired_inferior (0);
 	      myresume (0, 0);
-	      signal = mywait (&status);
+	      signal = mywait (&status, 1);
 	      prepare_resume_reply (own_buf, status, signal);
 	      break;
 	    case 's':
+	      set_desired_inferior (0);
 	      myresume (1, 0);
-	      signal = mywait (&status);
+	      signal = mywait (&status, 1);
 	      prepare_resume_reply (own_buf, status, signal);
 	      break;
 	    case 'k':
Index: server.h
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/server.h,v
retrieving revision 1.11
diff -u -p -u -r1.11 server.h
--- server.h	20 Apr 2002 17:22:48 -0000	1.11
+++ server.h	9 Jun 2002 21:04:57 -0000
@@ -54,8 +54,21 @@
    least the size of a (void *).  */
 typedef long long CORE_ADDR;
 
-/* Opaque inferior process information.  */
-struct inferior_info;
+/* Generic information for tracking a list of ``inferiors'' - threads,
+   processes, etc.  */
+struct inferior_list
+{
+  struct inferior_list_entry *head;
+  struct inferior_list_entry *tail;
+};
+struct inferior_list_entry
+{
+  int id;
+  struct inferior_list_entry *next;
+};
+
+/* Opaque type for user-visible threads.  */
+struct thread_info;
 
 #include "regcache.h"
 #include "gdb/signals.h"
@@ -67,27 +80,41 @@ struct inferior_info;
 
 void initialize_low ();
 
-/* Target-specific variables */
-
-extern char *registers;
-
 /* From inferiors.c.  */
 
-extern struct inferior_info *current_inferior;
-extern int signal_pid;
-void add_inferior (int pid);
+extern struct inferior_list all_threads;
+void add_inferior_to_list (struct inferior_list *list,
+			   struct inferior_list_entry *new_inferior);
+void for_each_inferior (struct inferior_list *list,
+			void (*action) (struct inferior_list_entry *));
+extern struct thread_info *current_inferior;
+void remove_inferior (struct inferior_list *list,
+		      struct inferior_list_entry *entry);
+void remove_thread (struct thread_info *thread);
+void add_thread (int thread_id, void *target_data);
 void clear_inferiors (void);
-void *inferior_target_data (struct inferior_info *);
-void set_inferior_target_data (struct inferior_info *, void *);
-void *inferior_regcache_data (struct inferior_info *);
-void set_inferior_regcache_data (struct inferior_info *, void *);
+struct inferior_list_entry *find_inferior
+     (struct inferior_list *,
+      int (*func) (struct inferior_list_entry *,
+		   void *),
+      void *arg);
+struct inferior_list_entry *find_inferior_id (struct inferior_list *list,
+					      int id);
+void *inferior_target_data (struct thread_info *);
+void set_inferior_target_data (struct thread_info *, void *);
+void *inferior_regcache_data (struct thread_info *);
+void set_inferior_regcache_data (struct thread_info *, void *);
+void change_inferior_id (struct inferior_list *list,
+			 int new_id);
 
 /* Public variables in server.c */
 
 extern int cont_thread;
 extern int general_thread;
+extern int step_thread;
 extern int thread_from_wait;
 extern int old_thread_from_wait;
+extern int server_waiting;
 
 extern jmp_buf toplevel;
 
@@ -103,6 +130,8 @@ void enable_async_io (void);
 void disable_async_io (void);
 void convert_ascii_to_int (char *from, char *to, int n);
 void convert_int_to_ascii (char *from, char *to, int n);
+void new_thread_notify (int id);
+void dead_thread_notify (int id);
 void prepare_resume_reply (char *buf, char status, unsigned char sig);
 
 void decode_m_packet (char *from, CORE_ADDR * mem_addr_ptr,
Index: target.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/target.c,v
retrieving revision 1.2
diff -u -p -u -r1.2 target.c
--- target.c	20 Apr 2002 17:04:09 -0000	1.2
+++ target.c	9 Jun 2002 21:04:57 -0000
@@ -26,6 +26,38 @@
 struct target_ops *the_target;
 
 void
+set_desired_inferior (int use_general)
+{
+  struct thread_info *found;
+
+  if (use_general == 1)
+    {
+      found = (struct thread_info *) find_inferior_id (&all_threads,
+						       general_thread);
+    }
+  else
+    {
+      found = NULL;
+
+      /* If we are continuing any (all) thread(s), use step_thread
+	 to decide which thread to step and/or send the specified
+	 signal to.  */
+      if (step_thread > 0 && (cont_thread == 0 || cont_thread == -1))
+	found = (struct thread_info *) find_inferior_id (&all_threads,
+							 step_thread);
+
+      if (found == NULL)
+	found = (struct thread_info *) find_inferior_id (&all_threads,
+							 cont_thread);
+    }
+
+  if (found == NULL)
+    current_inferior = (struct thread_info *) all_threads.head;
+  else
+    current_inferior = found;
+}
+
+void
 read_inferior_memory (CORE_ADDR memaddr, char *myaddr, int len)
 {
   (*the_target->read_memory) (memaddr, myaddr, len);
@@ -33,10 +65,41 @@ read_inferior_memory (CORE_ADDR memaddr,
 }
 
 int
-write_inferior_memory (CORE_ADDR memaddr, char *myaddr, int len)
+write_inferior_memory (CORE_ADDR memaddr, const char *myaddr, int len)
 {
-  check_mem_write (memaddr, myaddr, len);
-  return (*the_target->write_memory) (memaddr, myaddr, len);
+  /* Lacking cleanups, there is some potential for a memory leak if the
+     write fails and we go through error().  Make sure that no more than
+     one buffer is ever pending by making BUFFER static.  */
+  static char *buffer = 0;
+  int res;
+
+  if (buffer != NULL)
+    free (buffer);
+
+  buffer = malloc (len);
+  memcpy (buffer, myaddr, len);
+  check_mem_write (memaddr, buffer, len);
+  res = (*the_target->write_memory) (memaddr, buffer, len);
+  free (buffer);
+  buffer = NULL;
+
+  return res;
+}
+
+unsigned char
+mywait (char *statusp, int connected_wait)
+{
+  unsigned char ret;
+
+  if (connected_wait)
+    server_waiting = 1;
+
+  ret = (*the_target->wait) (statusp);
+
+  if (connected_wait)
+    server_waiting = 0;
+
+  return ret;
 }
 
 void
Index: target.h
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/target.h,v
retrieving revision 1.3
diff -u -p -u -r1.3 target.h
--- target.h	20 Apr 2002 17:04:09 -0000	1.3
+++ target.h	9 Jun 2002 21:04:57 -0000
@@ -104,6 +104,11 @@ struct target_ops
      symbols.  */
 
   void (*look_up_symbols) (void);
+
+  /* Return the PID we should send a signal to.  Used for asynchronous
+     interrupts (user hitting Control-C).  */
+
+  int (*signal_pid) (void);
 };
 
 extern struct target_ops *the_target;
@@ -125,17 +130,18 @@ void set_target_ops (struct target_ops *
 #define myresume(step,signo) \
   (*the_target->resume) (step, signo)
 
-#define mywait(statusp) \
-  (*the_target->wait) (statusp)
-
 #define fetch_inferior_registers(regno) \
   (*the_target->fetch_registers) (regno)
 
 #define store_inferior_registers(regno) \
   (*the_target->store_registers) (regno)
 
+unsigned char mywait (char *statusp, int connected_wait);
+
 void read_inferior_memory (CORE_ADDR memaddr, char *myaddr, int len);
 
-int write_inferior_memory (CORE_ADDR memaddr, char *myaddr, int len);
+int write_inferior_memory (CORE_ADDR memaddr, const char *myaddr, int len);
+
+void set_desired_inferior (int id);
 
 #endif /* TARGET_H */


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