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


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[gdbserver] Add NPTL support


Thanks to Jim, I can now support NPTL without any changes to GDB!  This
patch:
  - Adds support for fetching the thread area pointer, which thread_db
    needs.
  - Ditto the general purpose registers.
  - Looks up symbols aggressively once we have loaded libthread_db.
  - Uses tkill for sending signals when available.
  - Avoids killing the parent thread before its children - this works
    around a GNU/Linux kernel bug (ugh).

Tested on i686-pc-linux-gnu, where gdbserver test results are now
essentially the same between NPTL and LinuxThreads.  Committed.

-- 
Daniel Jacobowitz

2004-10-16  Daniel Jacobowitz  <dan@debian.org>

	* linux-i386-low.c (ps_get_thread_area): New.
	* linux-x86-64-low.c (ps_get_thread_area): New.
	* linux-low.c: Include <sys/syscall.h>.
	(linux_kill_one_process): Don't kill the first thread here.
	(linux_kill): Kill the first thread here.
	(kill_lwp): New function.
	(send_sigstop, linux_send_signal): Use it.
	* proc-service.c: Clean up #ifdefs.
	(fpregset_info): Delete.
	(ps_lgetregs): Update and enable implementation.
	(ps_lsetregs, ps_lgetfpregs, ps_lsetfpregs): Remove disabled
	implementations.
	* remote-utils.c (struct sym_cache, symbol_cache): New.
	(input_interrupt): Print a clearer message.
	(async_io_enabled): New variable.
	(enable_async_io, disable_async_io): Use it.  Update comments.
	(look_up_one_symbol): Use the symbol cache.
	* thread-db.c (thread_db_look_up_symbols): New function.
	(thread_db_init): Update comments.  Call thread_db_look_up_symbols.

Index: linux-i386-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-i386-low.c,v
retrieving revision 1.5
diff -u -p -r1.5 linux-i386-low.c
--- linux-i386-low.c	11 Jun 2002 17:32:39 -0000	1.5
+++ linux-i386-low.c	16 Oct 2004 16:38:06 -0000
@@ -1,5 +1,5 @@
 /* GNU/Linux/i386 specific low level interface, for the remote server for GDB.
-   Copyright 1995, 1996, 1998, 1999, 2000, 2001, 2002
+   Copyright 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2004
    Free Software Foundation, Inc.
 
    This file is part of GDB.
@@ -23,10 +23,30 @@
 #include "linux-low.h"
 #include "i387-fp.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"
+
+#include <sys/ptrace.h>
+
 #ifdef HAVE_SYS_REG_H
 #include <sys/reg.h>
 #endif
 
+#ifndef PTRACE_GET_THREAD_AREA
+#define PTRACE_GET_THREAD_AREA 25
+#endif
+
 /* This module only supports access to the general purpose registers.  */
 
 #define i386_num_regs 16
@@ -43,6 +63,22 @@ static int i386_regmap[] = 
   DS * 4, ES * 4, FS * 4, GS * 4
 };
 
+/* Called by libthread_db.  */
+
+ps_err_e
+ps_get_thread_area (const struct ps_prochandle *ph, 
+		    lwpid_t lwpid, int idx, void **base)
+{
+  unsigned int desc[4];
+
+  if (ptrace (PTRACE_GET_THREAD_AREA, lwpid,
+	      (void *) idx, (unsigned long) &desc) < 0)
+    return PS_ERR;
+
+  *(int *)base = desc[1];
+  return PS_OK;
+}
+
 static int
 i386_cannot_store_register (int regno)
 {
Index: linux-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-low.c,v
retrieving revision 1.32
diff -u -p -r1.32 linux-low.c
--- linux-low.c	23 Mar 2004 21:14:24 -0000	1.32
+++ linux-low.c	16 Oct 2004 16:38:06 -0000
@@ -35,6 +35,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
+#include <sys/syscall.h>
 
 /* ``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
@@ -223,6 +224,13 @@ linux_kill_one_process (struct inferior_
   struct process_info *process = get_thread_process (thread);
   int wstat;
 
+  /* We avoid killing the first thread here, because of a Linux kernel (at
+     least 2.6.0-test7 through 2.6.8-rc4) bug; if we kill the parent before
+     the children get a chance to be reaped, it will remain a zombie
+     forever.  */
+  if (entry == all_threads.head)
+    return;
+
   do
     {
       ptrace (PTRACE_KILL, pid_of (process), 0, 0);
@@ -235,7 +243,21 @@ linux_kill_one_process (struct inferior_
 static void
 linux_kill (void)
 {
+  struct thread_info *thread = (struct thread_info *) all_threads.head;
+  struct process_info *process = get_thread_process (thread);
+  int wstat;
+
   for_each_inferior (&all_threads, linux_kill_one_process);
+
+  /* See the comment in linux_kill_one_process.  We did not kill the first
+     thread in the list, so do so now.  */
+  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));
 }
 
 static void
@@ -709,6 +731,30 @@ retry:
   return ((unsigned char) WSTOPSIG (w));
 }
 
+/* Send a signal to an LWP.  For LinuxThreads, kill is enough; however, if
+   thread groups are in use, we need to use tkill.  */
+
+static int
+kill_lwp (int lwpid, int signo)
+{
+  static int tkill_failed;
+
+  errno = 0;
+
+#ifdef SYS_tkill
+  if (!tkill_failed)
+    {
+      int ret = syscall (SYS_tkill, lwpid, signo);
+      if (errno != ENOSYS)
+        return ret;
+      errno = 0;
+      tkill_failed = 1;
+    }
+#endif
+
+  return kill (lwpid, signo);
+}
+
 static void
 send_sigstop (struct inferior_list_entry *entry)
 {
@@ -728,7 +774,7 @@ send_sigstop (struct inferior_list_entry
   if (debug_threads)
     fprintf (stderr, "Sending sigstop to process %d\n", process->head.id);
 
-  kill (process->head.id, SIGSTOP);
+  kill_lwp (process->head.id, SIGSTOP);
   process->sigstop_sent = 1;
 }
 
@@ -1388,10 +1434,10 @@ linux_send_signal (int signum)
       struct process_info *process;
 
       process = get_thread_process (current_inferior);
-      kill (process->lwpid, signum);
+      kill_lwp (process->lwpid, signum);
     }
   else
-    kill (signal_pid, signum);
+    kill_lwp (signal_pid, signum);
 }
 
 /* Copy LEN bytes from inferior's auxiliary vector starting at OFFSET
Index: linux-x86-64-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-x86-64-low.c,v
retrieving revision 1.7
diff -u -p -r1.7 linux-x86-64-low.c
--- linux-x86-64-low.c	18 Jul 2002 15:18:02 -0000	1.7
+++ linux-x86-64-low.c	16 Oct 2004 16:38:06 -0000
@@ -1,6 +1,6 @@
 /* GNU/Linux/x86-64 specific low level interface, for the remote server
    for GDB.
-   Copyright 2002
+   Copyright 2002, 2004
    Free Software Foundation, Inc.
 
    This file is part of GDB.
@@ -24,10 +24,29 @@
 #include "linux-low.h"
 #include "i387-fp.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"
+
 #include <sys/reg.h>
 #include <sys/procfs.h>
 #include <sys/ptrace.h>
 
+/* This definition comes from prctl.h, but some kernels may not have it.  */
+#ifndef PTRACE_ARCH_PRCTL
+#define PTRACE_ARCH_PRCTL      30
+#endif
+
 static int x86_64_regmap[] = {
   RAX * 8, RBX * 8, RCX * 8, RDX * 8,
   RSI * 8, RDI * 8, RBP * 8, RSP * 8,
@@ -39,6 +58,28 @@ static int x86_64_regmap[] = {
 
 #define X86_64_NUM_GREGS (sizeof(x86_64_regmap)/sizeof(int))
 
+/* Called by libthread_db.  */
+
+ps_err_e
+ps_get_thread_area (const struct ps_prochandle *ph,
+                    lwpid_t lwpid, int idx, void **base)
+{
+  switch (idx)
+    {
+    case FS:
+      if (ptrace (PTRACE_ARCH_PRCTL, lwpid, base, ARCH_GET_FS) == 0)
+	return PS_OK;
+      break;
+    case GS:
+      if (ptrace (PTRACE_ARCH_PRCTL, lwpid, base, ARCH_GET_GS) == 0)
+	return PS_OK;
+      break;
+    default:
+      return PS_BADADDR;
+    }
+  return PS_ERR;
+}
+
 static void
 x86_64_fill_gregset (void *buf)
 {
Index: proc-service.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/proc-service.c,v
retrieving revision 1.1
diff -u -p -r1.1 proc-service.c
--- proc-service.c	11 Jun 2002 17:32:39 -0000	1.1
+++ proc-service.c	16 Oct 2004 16:38:06 -0000
@@ -1,5 +1,5 @@
 /* libthread_db helper functions for the remote server for GDB.
-   Copyright 2002
+   Copyright 2002, 2004
    Free Software Foundation, Inc.
 
    Contributed by MontaVista Software.
@@ -48,11 +48,11 @@ 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
+#ifdef HAVE_LINUX_REGSETS
+#define HAVE_REGSETS
+#endif
+
+#ifdef HAVE_REGSETS
 static struct regset_info *
 gregset_info(void)
 {
@@ -67,22 +67,6 @@ gregset_info(void)
 
   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
@@ -128,9 +112,8 @@ ps_pdwrite (gdb_ps_prochandle_t ph, padd
 ps_err_e
 ps_lgetregs (gdb_ps_prochandle_t ph, lwpid_t lwpid, prgregset_t gregset)
 {
-#if 0
+#ifdef HAVE_REGSETS
   struct thread_info *reg_inferior, *save_inferior;
-  void *regcache;
 
   reg_inferior = (struct thread_info *) find_inferior_id (&all_threads,
 							  lwpid);
@@ -140,16 +123,14 @@ ps_lgetregs (gdb_ps_prochandle_t ph, lwp
   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);
+  the_target->fetch_registers (0);
+  gregset_info()->fill_function (gregset);
 
   current_inferior = save_inferior;
   return PS_OK;
-#endif
-  /* FIXME */
+#else
   return PS_ERR;
+#endif
 }
 
 /* Set the general registers of LWP LWPID within the target process PH
@@ -158,27 +139,7 @@ ps_lgetregs (gdb_ps_prochandle_t ph, lwp
 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 */
+  /* Unneeded.  */
   return PS_ERR;
 }
 
@@ -189,27 +150,7 @@ 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 */
+  /* Unneeded.  */
   return PS_ERR;
 }
 
@@ -220,27 +161,7 @@ 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 */
+  /* Unneeded.  */
   return PS_ERR;
 }
 
Index: remote-utils.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/remote-utils.c,v
retrieving revision 1.21
diff -u -p -r1.21 remote-utils.c
--- remote-utils.c	29 Feb 2004 16:48:29 -0000	1.21
+++ remote-utils.c	16 Oct 2004 16:38:06 -0000
@@ -37,6 +37,17 @@
 #include <unistd.h>
 #include <arpa/inet.h>
 
+/* A cache entry for a successfully looked-up symbol.  */
+struct sym_cache
+{
+  const char *name;
+  CORE_ADDR addr;
+  struct sym_cache *next;
+};
+
+/* The symbol cache.  */
+static struct sym_cache *symbol_cache;
+
 int remote_debug = 0;
 struct ui_file *gdb_stdlog;
 
@@ -353,13 +364,14 @@ input_interrupt (int unused)
   if (select (remote_desc + 1, &readset, 0, 0, &immediate) > 0)
     {
       int cc;
-      char c;
+      char c = 0;
       
       cc = read (remote_desc, &c, 1);
 
       if (cc != 1 || c != '\003')
 	{
-	  fprintf (stderr, "input_interrupt, cc = %d c = %d\n", cc, c);
+	  fprintf (stderr, "input_interrupt, count = %d c = %d ('%c')\n",
+		   cc, c, c);
 	  return;
 	}
       
@@ -385,16 +397,33 @@ unblock_async_io (void)
   sigprocmask (SIG_UNBLOCK, &sigio_set, NULL);
 }
 
+/* Asynchronous I/O support.  SIGIO must be enabled when waiting, in order to
+   accept Control-C from the client, and must be disabled when talking to
+   the client.  */
+
+/* Current state of asynchronous I/O.  */
+static int async_io_enabled;
+
+/* Enable asynchronous I/O.  */
 void
 enable_async_io (void)
 {
+  if (async_io_enabled)
+    return;
+
   signal (SIGIO, input_interrupt);
+  async_io_enabled = 1;
 }
 
+/* Disable asynchronous I/O.  */
 void
 disable_async_io (void)
 {
+  if (!async_io_enabled)
+    return;
+
   signal (SIGIO, SIG_IGN);
+  async_io_enabled = 0;
 }
 
 /* Returns next char from remote GDB.  -1 if error.  */
@@ -692,11 +721,23 @@ decode_M_packet (char *from, CORE_ADDR *
   convert_ascii_to_int (&from[i++], to, *len_ptr);
 }
 
+/* Ask GDB for the address of NAME, and return it in ADDRP if found.
+   Returns 1 if the symbol is found, 0 if it is not, -1 on error.  */
+
 int
 look_up_one_symbol (const char *name, CORE_ADDR *addrp)
 {
   char own_buf[266], *p, *q;
   int len;
+  struct sym_cache *sym;
+
+  /* Check the cache first.  */
+  for (sym = symbol_cache; sym; sym = sym->next)
+    if (strcmp (name, sym->name) == 0)
+      {
+	*addrp = sym->addr;
+	return 1;
+      }
 
   /* Send the request.  */
   strcpy (own_buf, "qSymbol:");
@@ -731,6 +772,13 @@ look_up_one_symbol (const char *name, CO
     return 0;
 
   decode_address (addrp, p, q - p);
+
+  /* Save the symbol in our cache.  */
+  sym = malloc (sizeof (*sym));
+  sym->name = strdup (name);
+  sym->addr = *addrp;
+  sym->next = symbol_cache;
+  symbol_cache = sym;
+
   return 1;
 }
-
Index: thread-db.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/thread-db.c,v
retrieving revision 1.1
diff -u -p -r1.1 thread-db.c
--- thread-db.c	11 Jun 2002 17:32:40 -0000	1.1
+++ thread-db.c	16 Oct 2004 16:38:06 -0000
@@ -312,11 +312,36 @@ thread_db_find_new_threads (void)
     error ("Cannot find new threads: %s", thread_db_err_str (err));
 }
 
+/* Cache all future symbols that thread_db might request.  We can not
+   request symbols at arbitrary states in the remote protocol, only
+   when the client tells us that new symbols are available.  So when
+   we load the thread library, make sure to check the entire list.  */
+
+static void
+thread_db_look_up_symbols (void)
+{
+  const char **sym_list = td_symbol_list ();
+  CORE_ADDR unused;
+
+  for (sym_list = td_symbol_list (); *sym_list; sym_list++)
+    look_up_one_symbol (*sym_list, &unused);
+}
+
 int
 thread_db_init ()
 {
   int err;
 
+  /* FIXME drow/2004-10-16: This is the "overall process ID", which
+     GNU/Linux calls tgid, "thread group ID".  When we support
+     attaching to threads, the original thread may not be the correct
+     thread.  We would have to get the process ID from /proc for NPTL.
+     For LinuxThreads we could do something similar: follow the chain
+     of parent processes until we find the highest one we're attached
+     to, and use its tgid.
+
+     This isn't the only place in gdbserver that assumes that the first
+     process in the list is the thread group leader.  */
   proc_handle.pid = ((struct inferior_list_entry *)current_inferior)->id;
 
   err = td_ta_new (&proc_handle, &thread_agent);
@@ -332,6 +357,7 @@ thread_db_init ()
       if (thread_db_enable_reporting () == 0)
 	return 0;
       thread_db_find_new_threads ();
+      thread_db_look_up_symbols ();
       return 1;
 
     default:


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