2002-06-11 Daniel Jacobowitz <drow@mvista.com>
authorDaniel Jacobowitz <drow@false.org>
Tue, 11 Jun 2002 17:32:40 +0000 (17:32 +0000)
committerDaniel Jacobowitz <drow@false.org>
Tue, 11 Jun 2002 17:32:40 +0000 (17:32 +0000)
* 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.

28 files changed:
gdb/gdbserver/Makefile.in
gdb/gdbserver/acconfig.h
gdb/gdbserver/acinclude.m4 [new file with mode: 0644]
gdb/gdbserver/aclocal.m4
gdb/gdbserver/config.in
gdb/gdbserver/configure
gdb/gdbserver/configure.in
gdb/gdbserver/configure.srv
gdb/gdbserver/i387-fp.c
gdb/gdbserver/i387-fp.h
gdb/gdbserver/inferiors.c
gdb/gdbserver/linux-arm-low.c
gdb/gdbserver/linux-i386-low.c
gdb/gdbserver/linux-low.c
gdb/gdbserver/linux-low.h
gdb/gdbserver/linux-mips-low.c
gdb/gdbserver/linux-ppc-low.c
gdb/gdbserver/linux-sh-low.c
gdb/gdbserver/linux-x86-64-low.c
gdb/gdbserver/proc-service.c [new file with mode: 0644]
gdb/gdbserver/regcache.c
gdb/gdbserver/regcache.h
gdb/gdbserver/remote-utils.c
gdb/gdbserver/server.c
gdb/gdbserver/server.h
gdb/gdbserver/target.c
gdb/gdbserver/target.h
gdb/gdbserver/thread-db.c [new file with mode: 0644]

index 2c8cd33..bc9256b 100644 (file)
@@ -126,6 +126,7 @@ OBS = inferiors.o regcache.o remote-utils.o server.o signals.o target.o \
        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_h) config.h $(srcdir)/target.h \
 
 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 968feb8..f0464b0 100644 (file)
@@ -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
diff --git a/gdb/gdbserver/acinclude.m4 b/gdb/gdbserver/acinclude.m4
new file mode 100644 (file)
index 0000000..bbfa86f
--- /dev/null
@@ -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 24b9ced..2fc6cf8 100644 (file)
@@ -10,91 +10,45 @@ dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without
 dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 dnl PARTICULAR PURPOSE.
 
+dnl gdb/gdbserver/configure.in uses BFD_HAVE_SYS_PROCFS_TYPE.
+sinclude(../../bfd/acinclude.m4)
 
-# serial 1
+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
 
-# @defmac AC_PROG_CC_STDC
-# @maindex PROG_CC_STDC
-# @ovindex CC
-# If the C compiler in not in ANSI C mode by default, try to add an option
-# to output variable @code{CC} to make it so.  This macro tries various
-# options that select ANSI C on some system or another.  It considers the
-# compiler to be in ANSI C mode if it handles function prototypes correctly.
-#
-# If you use this macro, you should check after calling it whether the C
-# compiler has been set to accept ANSI C; if not, the shell variable
-# @code{am_cv_prog_cc_stdc} is set to @samp{no}.  If you wrote your source
-# code in ANSI C, you can make an un-ANSIfied copy of it by using the
-# program @code{ansi2knr}, which comes with Ghostscript.
-# @end defmac
-
-AC_DEFUN(AM_PROG_CC_STDC,
-[AC_REQUIRE([AC_PROG_CC])
-AC_BEFORE([$0], [AC_C_INLINE])
-AC_BEFORE([$0], [AC_C_CONST])
-dnl Force this before AC_PROG_CPP.  Some cpp's, eg on HPUX, require
-dnl a magic option to avoid problems with ANSI preprocessor commands
-dnl like #elif.
-dnl FIXME: can't do this because then AC_AIX won't work due to a
-dnl circular dependency.
-dnl AC_BEFORE([$0], [AC_PROG_CPP])
-AC_MSG_CHECKING(for ${CC-cc} option to accept ANSI C)
-AC_CACHE_VAL(am_cv_prog_cc_stdc,
-[am_cv_prog_cc_stdc=no
-ac_save_CC="$CC"
-# Don't try gcc -ansi; that turns off useful extensions and
-# breaks some systems' header files.
-# AIX                  -qlanglvl=ansi
-# Ultrix and OSF/1     -std1
-# HP-UX                        -Aa -D_HPUX_SOURCE
-# SVR4                 -Xc -D__EXTENSIONS__
-for ac_arg in "" -qlanglvl=ansi -std1 "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
-do
-  CC="$ac_save_CC $ac_arg"
-  AC_TRY_COMPILE(
-[#include <stdarg.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
-struct buf { int x; };
-FILE * (*rcsopen) (struct buf *, struct stat *, int);
-static char *e (p, i)
-     char **p;
-     int i;
-{
-  return p[i];
-}
-static char *f (char * (*g) (char **, int), char **p, ...)
-{
-  char *s;
-  va_list v;
-  va_start (v,p);
-  s = g (p, va_arg (v,int));
-  va_end (v);
-  return s;
-}
-int test (int i, double x);
-struct s1 {int (*f) (int a);};
-struct s2 {int (*f) (double a);};
-int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
-int argc;
-char **argv;
-], [
-return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
-],
-[am_cv_prog_cc_stdc="$ac_arg"; break])
-done
-CC="$ac_save_CC"
-])
-if test -z "$am_cv_prog_cc_stdc"; then
-  AC_MSG_RESULT([none needed])
-else
-  AC_MSG_RESULT($am_cv_prog_cc_stdc)
-fi
-case "x$am_cv_prog_cc_stdc" in
-  x|xno) ;;
-  *) CC="$CC $am_cv_prog_cc_stdc" ;;
-esac
-])
+ 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 9d553f2..cdaeb8d 100644 (file)
@@ -1,4 +1,4 @@
-/* config.in.  Generated automatically from configure.in by autoheader 2.13.  */
+/* config.in.  Generated automatically from configure.in by autoheader.  */
 
 /* Define if you have the ANSI C header files.  */
 #undef STDC_HEADERS
    register access.  */
 #undef HAVE_PTRACE_GETFPXREGS
 
+/* Define if the prfpregset_t type is broken. */
+#undef PRFPREGSET_T_BROKEN
+
+/* Define if you have the <linux/elf.h> header file.  */
+#undef HAVE_LINUX_ELF_H
+
+/* Define if you have the <proc_service.h> header file.  */
+#undef HAVE_PROC_SERVICE_H
+
 /* Define if you have the <sgtty.h> header file.  */
 #undef HAVE_SGTTY_H
 
 /* Define if you have the <string.h> header file.  */
 #undef HAVE_STRING_H
 
+/* Define if you have the <sys/procfs.h> header file.  */
+#undef HAVE_SYS_PROCFS_H
+
 /* Define if you have the <sys/reg.h> header file.  */
 #undef HAVE_SYS_REG_H
 
 
 /* Define if you have the <termios.h> header file.  */
 #undef HAVE_TERMIOS_H
+
+/* Define if you have the <thread_db.h> header file.  */
+#undef HAVE_THREAD_DB_H
+
+/* 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 <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 elf_fpregset_t. */
+#undef HAVE_ELF_FPREGSET_T
+
index 758d483..d2575c0 100755 (executable)
@@ -28,6 +28,7 @@ program_suffix=NONE
 program_transform_name=s,x,x,
 silent=
 site=
+sitefile=
 srcdir=
 target=NONE
 verbose=
@@ -142,6 +143,7 @@ Configuration:
   --help                  print this message
   --no-create             do not create output files
   --quiet, --silent       do not print \`checking...' messages
+  --site-file=FILE        use FILE as the site file
   --version               print the version of autoconf that created configure
 Directory and file names:
   --prefix=PREFIX         install architecture-independent files in PREFIX
@@ -312,6 +314,11 @@ EOF
   -site=* | --site=* | --sit=*)
     site="$ac_optarg" ;;
 
+  -site-file | --site-file | --site-fil | --site-fi | --site-f)
+    ac_prev=sitefile ;;
+  -site-file=* | --site-file=* | --site-fil=* | --site-fi=* | --site-f=*)
+    sitefile="$ac_optarg" ;;
+
   -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
     ac_prev=srcdir ;;
   -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
@@ -477,12 +484,16 @@ fi
 srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'`
 
 # Prefer explicitly selected file to automatically selected ones.
-if test -z "$CONFIG_SITE"; then
-  if test "x$prefix" != xNONE; then
-    CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
-  else
-    CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+if test -z "$sitefile"; then
+  if test -z "$CONFIG_SITE"; then
+    if test "x$prefix" != xNONE; then
+      CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+    else
+      CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+    fi
   fi
+else
+  CONFIG_SITE="$sitefile"
 fi
 for ac_site_file in $CONFIG_SITE; do
   if test -r "$ac_site_file"; then
@@ -526,7 +537,7 @@ fi
 # Extract the first word of "gcc", so it can be a program name with args.
 set dummy gcc; ac_word=$2
 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
-echo "configure:530: checking for $ac_word" >&5
+echo "configure:541: checking for $ac_word" >&5
 if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
@@ -556,7 +567,7 @@ if test -z "$CC"; then
   # Extract the first word of "cc", so it can be a program name with args.
 set dummy cc; ac_word=$2
 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
-echo "configure:560: checking for $ac_word" >&5
+echo "configure:571: checking for $ac_word" >&5
 if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
@@ -607,7 +618,7 @@ fi
       # Extract the first word of "cl", so it can be a program name with args.
 set dummy cl; ac_word=$2
 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
-echo "configure:611: checking for $ac_word" >&5
+echo "configure:622: checking for $ac_word" >&5
 if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
@@ -639,7 +650,7 @@ fi
 fi
 
 echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6
-echo "configure:643: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5
+echo "configure:654: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5
 
 ac_ext=c
 # CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
@@ -650,12 +661,12 @@ cross_compiling=$ac_cv_prog_cc_cross
 
 cat > conftest.$ac_ext << EOF
 
-#line 654 "configure"
+#line 665 "configure"
 #include "confdefs.h"
 
 main(){return(0);}
 EOF
-if { (eval echo configure:659: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+if { (eval echo configure:670: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
   ac_cv_prog_cc_works=yes
   # If we can't run a trivial program, we are probably using a cross compiler.
   if (./conftest; exit) 2>/dev/null; then
@@ -681,12 +692,12 @@ if test $ac_cv_prog_cc_works = no; then
   { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; }
 fi
 echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6
-echo "configure:685: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5
+echo "configure:696: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5
 echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6
 cross_compiling=$ac_cv_prog_cc_cross
 
 echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6
-echo "configure:690: checking whether we are using GNU C" >&5
+echo "configure:701: checking whether we are using GNU C" >&5
 if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
@@ -695,7 +706,7 @@ else
   yes;
 #endif
 EOF
-if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:699: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
+if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:710: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
   ac_cv_prog_gcc=yes
 else
   ac_cv_prog_gcc=no
@@ -714,7 +725,7 @@ ac_test_CFLAGS="${CFLAGS+set}"
 ac_save_CFLAGS="$CFLAGS"
 CFLAGS=
 echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6
-echo "configure:718: checking whether ${CC-cc} accepts -g" >&5
+echo "configure:729: checking whether ${CC-cc} accepts -g" >&5
 if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
@@ -793,7 +804,7 @@ else { echo "configure: error: can not run $ac_config_sub" 1>&2; exit 1; }
 fi
 
 echo $ac_n "checking host system type""... $ac_c" 1>&6
-echo "configure:797: checking host system type" >&5
+echo "configure:808: checking host system type" >&5
 
 host_alias=$host
 case "$host_alias" in
@@ -814,7 +825,7 @@ host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
 echo "$ac_t""$host" 1>&6
 
 echo $ac_n "checking target system type""... $ac_c" 1>&6
-echo "configure:818: checking target system type" >&5
+echo "configure:829: checking target system type" >&5
 
 target_alias=$target
 case "$target_alias" in
@@ -832,7 +843,7 @@ target_os=`echo $target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
 echo "$ac_t""$target" 1>&6
 
 echo $ac_n "checking build system type""... $ac_c" 1>&6
-echo "configure:836: checking build system type" >&5
+echo "configure:847: checking build system type" >&5
 
 build_alias=$build
 case "$build_alias" in
@@ -867,7 +878,7 @@ test "$host_alias" != "$target_alias" &&
 # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
 # ./install, which can be erroneously created by make from ./install.sh.
 echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6
-echo "configure:871: checking for a BSD compatible install" >&5
+echo "configure:882: checking for a BSD compatible install" >&5
 if test -z "$INSTALL"; then
 if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
@@ -921,7 +932,7 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
 
 
 echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6
-echo "configure:925: checking how to run the C preprocessor" >&5
+echo "configure:936: checking how to run the C preprocessor" >&5
 # On Suns, sometimes $CPP names a directory.
 if test -n "$CPP" && test -d "$CPP"; then
   CPP=
@@ -936,13 +947,13 @@ else
   # On the NeXT, cc -E runs the code through the compiler's parser,
   # not just through cpp.
   cat > conftest.$ac_ext <<EOF
-#line 940 "configure"
+#line 951 "configure"
 #include "confdefs.h"
 #include <assert.h>
 Syntax Error
 EOF
 ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:946: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:957: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
 ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
 if test -z "$ac_err"; then
   :
@@ -953,13 +964,13 @@ else
   rm -rf conftest*
   CPP="${CC-cc} -E -traditional-cpp"
   cat > conftest.$ac_ext <<EOF
-#line 957 "configure"
+#line 968 "configure"
 #include "confdefs.h"
 #include <assert.h>
 Syntax Error
 EOF
 ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:963: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:974: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
 ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
 if test -z "$ac_err"; then
   :
@@ -970,13 +981,13 @@ else
   rm -rf conftest*
   CPP="${CC-cc} -nologo -E"
   cat > conftest.$ac_ext <<EOF
-#line 974 "configure"
+#line 985 "configure"
 #include "confdefs.h"
 #include <assert.h>
 Syntax Error
 EOF
 ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:980: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:991: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
 ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
 if test -z "$ac_err"; then
   :
@@ -1001,12 +1012,12 @@ fi
 echo "$ac_t""$CPP" 1>&6
 
 echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6
-echo "configure:1005: checking for ANSI C header files" >&5
+echo "configure:1016: checking for ANSI C header files" >&5
 if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
   cat > conftest.$ac_ext <<EOF
-#line 1010 "configure"
+#line 1021 "configure"
 #include "confdefs.h"
 #include <stdlib.h>
 #include <stdarg.h>
@@ -1014,7 +1025,7 @@ else
 #include <float.h>
 EOF
 ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:1018: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:1029: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
 ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
 if test -z "$ac_err"; then
   rm -rf conftest*
@@ -1031,7 +1042,7 @@ rm -f conftest*
 if test $ac_cv_header_stdc = yes; then
   # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
 cat > conftest.$ac_ext <<EOF
-#line 1035 "configure"
+#line 1046 "configure"
 #include "confdefs.h"
 #include <string.h>
 EOF
@@ -1049,7 +1060,7 @@ fi
 if test $ac_cv_header_stdc = yes; then
   # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
 cat > conftest.$ac_ext <<EOF
-#line 1053 "configure"
+#line 1064 "configure"
 #include "confdefs.h"
 #include <stdlib.h>
 EOF
@@ -1070,7 +1081,7 @@ if test "$cross_compiling" = yes; then
   :
 else
   cat > conftest.$ac_ext <<EOF
-#line 1074 "configure"
+#line 1085 "configure"
 #include "confdefs.h"
 #include <ctype.h>
 #define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
@@ -1081,7 +1092,7 @@ if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2);
 exit (0); }
 
 EOF
-if { (eval echo configure:1085: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+if { (eval echo configure:1096: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
 then
   :
 else
@@ -1105,21 +1116,21 @@ EOF
 fi
 
 
-for ac_hdr in sgtty.h termio.h termios.h sys/reg.h string.h
+for ac_hdr in sgtty.h termio.h termios.h sys/reg.h string.h             proc_service.h sys/procfs.h thread_db.h linux/elf.h
 do
 ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
 echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
-echo "configure:1113: checking for $ac_hdr" >&5
+echo "configure:1124: checking for $ac_hdr" >&5
 if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
   cat > conftest.$ac_ext <<EOF
-#line 1118 "configure"
+#line 1129 "configure"
 #include "confdefs.h"
 #include <$ac_hdr>
 EOF
 ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:1123: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:1134: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
 ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
 if test -z "$ac_err"; then
   rm -rf conftest*
@@ -1157,19 +1168,19 @@ fi
 
 if test "${srv_linux_regsets}" = "yes"; then
   echo $ac_n "checking for PTRACE_GETREGS""... $ac_c" 1>&6
-echo "configure:1161: checking for PTRACE_GETREGS" >&5
+echo "configure:1172: checking for PTRACE_GETREGS" >&5
   if eval "test \"`echo '$''{'gdbsrv_cv_have_ptrace_getregs'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
   cat > conftest.$ac_ext <<EOF
-#line 1166 "configure"
+#line 1177 "configure"
 #include "confdefs.h"
 #include <sys/ptrace.h>
 int main() {
 PTRACE_GETREGS;
 ; return 0; }
 EOF
-if { (eval echo configure:1173: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+if { (eval echo configure:1184: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
   rm -rf conftest*
   gdbsrv_cv_have_ptrace_getregs=yes
 else
@@ -1190,19 +1201,19 @@ EOF
   fi
 
   echo $ac_n "checking for PTRACE_GETFPXREGS""... $ac_c" 1>&6
-echo "configure:1194: checking for PTRACE_GETFPXREGS" >&5
+echo "configure:1205: checking for PTRACE_GETFPXREGS" >&5
   if eval "test \"`echo '$''{'gdbsrv_cv_have_ptrace_getfpxregs'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
   cat > conftest.$ac_ext <<EOF
-#line 1199 "configure"
+#line 1210 "configure"
 #include "confdefs.h"
 #include <sys/ptrace.h>
 int main() {
 PTRACE_GETFPXREGS;
 ; return 0; }
 EOF
-if { (eval echo configure:1206: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+if { (eval echo configure:1217: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
   rm -rf conftest*
   gdbsrv_cv_have_ptrace_getfpxregs=yes
 else
@@ -1223,7 +1234,328 @@ EOF
   fi
 fi
 
-GDBSERVER_DEPFILES="$srv_regobj $srv_tgtobj"
+if test "$ac_cv_header_sys_procfs_h" = yes; then
+  echo $ac_n "checking for lwpid_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1240: checking for lwpid_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_lwpid_t'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1245 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+lwpid_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1254: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_lwpid_t=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_lwpid_t=no
+   
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_lwpid_t = yes; then
+   cat >> confdefs.h <<\EOF
+#define HAVE_LWPID_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_lwpid_t" 1>&6
+
+  echo $ac_n "checking for psaddr_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1276: checking for psaddr_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_psaddr_t'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1281 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+psaddr_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1290: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_psaddr_t=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_psaddr_t=no
+   
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_psaddr_t = yes; then
+   cat >> confdefs.h <<\EOF
+#define HAVE_PSADDR_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_psaddr_t" 1>&6
+
+  echo $ac_n "checking for prgregset_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1312: checking for prgregset_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_prgregset_t'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1317 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+prgregset_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1326: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_prgregset_t=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_prgregset_t=no
+   
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_prgregset_t = yes; then
+   cat >> confdefs.h <<\EOF
+#define HAVE_PRGREGSET_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_prgregset_t" 1>&6
+
+  echo $ac_n "checking for prfpregset_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1348: checking for prfpregset_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_prfpregset_t'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1353 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+prfpregset_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1362: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_prfpregset_t=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_prfpregset_t=no
+   
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_prfpregset_t = yes; then
+   cat >> confdefs.h <<\EOF
+#define HAVE_PRFPREGSET_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_prfpregset_t" 1>&6
+
+
+  
+        
+  if test $bfd_cv_have_sys_procfs_type_prfpregset_t = yes; then
+    echo $ac_n "checking whether prfpregset_t type is broken""... $ac_c" 1>&6
+echo "configure:1388: checking whether prfpregset_t type is broken" >&5
+    if eval "test \"`echo '$''{'gdb_cv_prfpregset_t_broken'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test "$cross_compiling" = yes; then
+  gdb_cv_prfpregset_t_broken=yes
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1396 "configure"
+#include "confdefs.h"
+#include <sys/procfs.h>
+       int main ()
+       {
+         if (sizeof (prfpregset_t) == sizeof (void *))
+           return 1;
+         return 0;
+       }
+EOF
+if { (eval echo configure:1406: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+  gdb_cv_prfpregset_t_broken=no
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -fr conftest*
+  gdb_cv_prfpregset_t_broken=yes
+fi
+rm -fr conftest*
+fi
+
+fi
+
+    echo "$ac_t""$gdb_cv_prfpregset_t_broken" 1>&6
+    if test $gdb_cv_prfpregset_t_broken = yes; then
+      cat >> confdefs.h <<\EOF
+#define PRFPREGSET_T_BROKEN 1
+EOF
+
+    fi
+  fi
+
+  echo $ac_n "checking for elf_fpregset_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1430: checking for elf_fpregset_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_elf_fpregset_t'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1435 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+elf_fpregset_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1444: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_elf_fpregset_t=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  bfd_cv_have_sys_procfs_type_elf_fpregset_t=no
+   
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_elf_fpregset_t = yes; then
+   cat >> confdefs.h <<\EOF
+#define HAVE_ELF_FPREGSET_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_elf_fpregset_t" 1>&6
+
+fi
+
+srv_thread_depfiles=
+srv_libs=
+USE_THREAD_DB=
+
+if test "$srv_linux_thread_db" = "yes"; then
+  echo $ac_n "checking for libthread_db""... $ac_c" 1>&6
+echo "configure:1473: checking for libthread_db" >&5
+if eval "test \"`echo '$''{'srv_cv_thread_db'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  old_LIBS="$LIBS"
+  LIBS="$LIBS -lthread_db"
+  cat > conftest.$ac_ext <<EOF
+#line 1480 "configure"
+#include "confdefs.h"
+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() {}
+int main() {
+td_ta_new();
+; return 0; }
+EOF
+if { (eval echo configure:1494: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  srv_cv_thread_db="-lthread_db"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  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"`"
+ cat > conftest.$ac_ext <<EOF
+#line 1510 "configure"
+#include "confdefs.h"
+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() {}
+int main() {
+td_ta_new();
+; return 0; }
+EOF
+if { (eval echo configure:1524: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  srv_cv_thread_db="$thread_db"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  srv_cv_thread_db=no
+fi
+rm -f conftest*
+ LIBS="$old_LIBS"
+fi
+
+echo "$ac_t""$srv_cv_thread_db" 1>&6
+
+fi
+rm -f conftest*
+  if test "$srv_cv_thread_db" = no; then
+    echo "configure: warning: Could not find libthread_db." 1>&2
+    echo "configure: warning: Disabling thread support in gdbserver." 1>&2
+    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"
+
+
 
 
 
@@ -1381,6 +1713,8 @@ s%@INSTALL_SCRIPT@%$INSTALL_SCRIPT%g
 s%@INSTALL_DATA@%$INSTALL_DATA%g
 s%@CPP@%$CPP%g
 s%@GDBSERVER_DEPFILES@%$GDBSERVER_DEPFILES%g
+s%@GDBSERVER_LIBS@%$GDBSERVER_LIBS%g
+s%@USE_THREAD_DB@%$USE_THREAD_DB%g
 
 CEOF
 EOF
index db7e301..7c94d40 100644 (file)
@@ -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"; then
   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 3268cd7..28dc2aa 100644 (file)
@@ -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 3d1d6a6..19a9929 100644 (file)
@@ -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 regno)
 }
 
 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 90fe4ca..d28c422 100644 (file)
 #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 774798d..68c91c4 100644 (file)
 
 #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 2958fdf..07e2792 100644 (file)
@@ -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 7126432..b79b601 100644 (file)
@@ -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 6cfe0d5..c272fed 100644 (file)
 #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)
+
+/* 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)
+{
+  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;
+}
 
-struct inferior_linux_data
+static void *
+add_process (int pid)
 {
-  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, char **allargs)
     {
       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, char **allargs)
       _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;
+
+      ret = waitpid (to_wait_for, wstatp, WNOHANG | __WCLONE);
+
+      if (ret == -1)
+       {
+         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 (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 ();
+    }
+}
 
-      if (pid != child_data->pid)
-       perror_with_name ("wait");
+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 this target supports breakpoints, see if we hit one.  */
-      if (the_low_target.stop_pc != NULL
-         && WIFSTOPPED (wstat)
-         && WSTOPSIG (wstat) == SIGTRAP)
+  if (event_child != NULL)
+    {
+      if (event_child->status_pending_p)
        {
-         CORE_ADDR stop_pc;
+         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");
 
-         if (linux_bp_reinsert != 0)
+      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;
 
-             continue;
+      /* 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;
+
+         /* 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, const char *myaddr, int len)
   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, const char *myaddr, int len)
 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);
 }
 
 \f
@@ -578,13 +1268,24 @@ static struct target_ops linux_target_ops = {
   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 b484982..bae76b7 100644 (file)
    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 f721ec9..51e74ce 100644 (file)
@@ -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 7cb315a..2bb0f50 100644 (file)
@@ -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 cdc390d..ee84561 100644 (file)
@@ -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 e124890..1f80d99 100644 (file)
@@ -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 = {
diff --git a/gdb/gdbserver/proc-service.c b/gdb/gdbserver/proc-service.c
new file mode 100644 (file)
index 0000000..becf565
--- /dev/null
@@ -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 701d092..6713909 100644 (file)
@@ -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_info *inferior)
   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, int n)
 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 *name, const void *buf)
 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 362288e..930bd9c 100644 (file)
 #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);
@@ -62,6 +65,8 @@ 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);
 
 #endif /* REGCACHE_H */
index 14734f1..c610c4c 100644 (file)
@@ -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 *to, int n)
 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 status, unsigned char signo)
          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, CORE_ADDR *addrp)
     {
       /* 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 a31547c..b674ed0 100644 (file)
 
 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 *statusptr)
 {
   /* 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 *statusptr, unsigned char *sigptr)
   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 32b90b5..746502b 100644 (file)
    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 53a4c1e..1c2860a 100644 (file)
 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, char *myaddr, int len)
 }
 
 int
-write_inferior_memory (CORE_ADDR memaddr, char *myaddr, int len)
+write_inferior_memory (CORE_ADDR memaddr, const char *myaddr, int 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)
 {
-  check_mem_write (memaddr, myaddr, len);
-  return (*the_target->write_memory) (memaddr, myaddr, len);
+  unsigned char ret;
+
+  if (connected_wait)
+    server_waiting = 1;
+
+  ret = (*the_target->wait) (statusp);
+
+  if (connected_wait)
+    server_waiting = 0;
+
+  return ret;
 }
 
 void
index 6d06b9f..c6aeee6 100644 (file)
@@ -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 */
diff --git a/gdb/gdbserver/thread-db.c b/gdb/gdbserver/thread-db.c
new file mode 100644 (file)
index 0000000..f3d57a5
--- /dev/null
@@ -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;
+}