This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


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

[patch][rfc] Allow GDB to search for the right libthread_db.so.1


Greetings,

We have perhaps uncommon setup here, where we have several installed
versions of glibc, and need to debug executables which are compiled
and linked against them (using -rpath).

Currently, GDB will dlopen("libthread_db.so.1", ...), which means that
in order debug "non-standard" binary, one has to set LD_LIBRARY_PATH to
point to correct libthread_db before invoking GDB, or it will refuse to
see threads in the inferior. This is not automatic, and error prone.

Attached patch fixes this, by 

- first looking for libthread_db in the same directory from which
  libpthread.so came, and

- allowing user to set libthread-db-search-path
  at runtime (or maintainer to set LIBTHREAD_DB_SEARCH_PATH at GDB compile
  time) which GDB uses to iterate over available libthread_db's, until
  it finds one which "accepts" the inferior.

We've been running with this patch for a couple of month now; it works
well for us, but I'd like to get it upstream so we don't have to deal with
merge conflicts.

If this looks reasonable, I'll work on documentation next.
There is also a matching change to gdbserver, which I am postponing until
a decision on this patch is made.

Tested on Linux/x86_64 without new failures.

Thanks,

--
Paul Pluzhnikov

2009-04-06  Paul Pluzhnikov  <ppluzhnikov@google.com>

	    * gdb_thread_db.h (LIBTHREAD_DB_SEARCH_PATH): New define.
	    (LIBTHREAD_DB_SO): Moved from linux-thread-db.c

	    * linux-thread-db.c (try_thread_db_load_1): New function.
	    (try_thread_db_load, thread_db_load_search): Likewise.
	    (thread_db_load): Iterate over possibly multiple libthread_db's.

Index: gdb_thread_db.h
===================================================================
RCS file: /cvs/src/src/gdb/gdb_thread_db.h,v
retrieving revision 1.12
diff -u -p -u -r1.12 gdb_thread_db.h
--- gdb_thread_db.h	18 Mar 2009 08:51:11 -0000	1.12
+++ gdb_thread_db.h	6 Apr 2009 20:05:01 -0000
@@ -1,5 +1,14 @@
 #ifdef HAVE_THREAD_DB_H
 #include <thread_db.h>
+
+#ifndef LIBTHREAD_DB_SO
+#define LIBTHREAD_DB_SO "libthread_db.so.1"
+#endif
+
+#ifndef LIBTHREAD_DB_SEARCH_PATH
+#define LIBTHREAD_DB_SEARCH_PATH ""
+#endif
+
 #else
 
 /* Copyright (C) 1999, 2000, 2007, 2008, 2009 Free Software Foundation, Inc.
Index: linux-thread-db.c
===================================================================
RCS file: /cvs/src/src/gdb/linux-thread-db.c,v
retrieving revision 1.54
diff -u -p -u -r1.54 linux-thread-db.c
--- linux-thread-db.c	27 Feb 2009 20:34:41 -0000	1.54
+++ linux-thread-db.c	6 Apr 2009 20:05:01 -0000
@@ -26,13 +26,16 @@
 #include "gdb_thread_db.h"
 
 #include "bfd.h"
+#include "command.h"
 #include "exceptions.h"
+#include "gdbcmd.h"
 #include "gdbthread.h"
 #include "inferior.h"
 #include "symfile.h"
 #include "objfiles.h"
 #include "target.h"
 #include "regcache.h"
+#include "solib.h"
 #include "solib-svr4.h"
 #include "gdbcore.h"
 #include "observer.h"
@@ -44,10 +47,6 @@
 #include <gnu/libc-version.h>
 #endif
 
-#ifndef LIBTHREAD_DB_SO
-#define LIBTHREAD_DB_SO "libthread_db.so.1"
-#endif
-
 /* GNU/Linux libthread_db support.
 
    libthread_db is a library, provided along with libpthread.so, which
@@ -74,6 +73,8 @@
    of the ptid_t prevents thread IDs changing when libpthread is
    loaded or unloaded.  */
 
+static char *libthread_db_search_path;
+
 /* If we're running on GNU/Linux, we must explicitly attach to any new
    threads.  */
 
@@ -81,7 +82,7 @@
 static struct target_ops thread_db_ops;
 
 /* Non-zero if we're using this module's target vector.  */
-static int using_thread_db;
+static void *using_thread_db;
 
 /* Non-zero if we have determined the signals used by the threads
    library.  */
@@ -143,6 +144,10 @@ static void thread_db_find_new_threads_1
 static void attach_thread (ptid_t ptid, const td_thrhandle_t *th_p,
 			   const td_thrinfo_t *ti_p);
 static void detach_thread (ptid_t ptid);
+static void init_thread_db_ops (void);
+static td_err_e enable_thread_event (td_thragent_t *thread_agent,
+                                     int event, CORE_ADDR *bp);
+static void enable_thread_event_reporting (void);
 
 
 /* Use "struct private_thread_info" to cache thread state.  This is
@@ -386,32 +391,38 @@ verbose_dlsym (void *handle, const char 
 }
 
 static int
-thread_db_load (void)
+try_thread_db_load_1(void *handle)
 {
-  void *handle;
   td_err_e err;
 
-  handle = dlopen (LIBTHREAD_DB_SO, RTLD_NOW);
-  if (handle == NULL)
-    {
-      fprintf_filtered (gdb_stderr, "\n\ndlopen failed on '%s' - %s\n",
-			LIBTHREAD_DB_SO, dlerror ());
-      fprintf_filtered (gdb_stderr,
-			"GDB will not be able to debug pthreads.\n\n");
-      return 0;
-    }
-
   /* Initialize pointers to the dynamic library functions we will use.
      Essential functions first.  */
 
   td_init_p = verbose_dlsym (handle, "td_init");
   if (td_init_p == NULL)
     return 0;
+  err = td_init_p ();
+  if (err != TD_OK)
+    return 0;
 
   td_ta_new_p = verbose_dlsym (handle, "td_ta_new");
   if (td_ta_new_p == NULL)
     return 0;
 
+  /* Initialize the structure that identifies the child process.  */
+  proc_handle.ptid = inferior_ptid;
+
+  /* Now attempt to open a connection to the thread library.  */
+  err = td_ta_new_p (&proc_handle, &thread_agent);
+  if (err != TD_OK)
+    {
+      td_ta_new_p = NULL;
+      if (info_verbose)
+	printf_unfiltered (_("td_ta_new(): %s.\n"),
+			   thread_db_err_str (err));
+      return 0;
+    }
+
   td_ta_map_id2thr_p = verbose_dlsym (handle, "td_ta_map_id2thr");
   if (td_ta_map_id2thr_p == NULL)
     return 0;
@@ -432,14 +443,6 @@ thread_db_load (void)
   if (td_thr_get_info_p == NULL)
     return 0;
 
-  /* Initialize the library.  */
-  err = td_init_p ();
-  if (err != TD_OK)
-    {
-      warning (_("Cannot initialize libthread_db: %s"), thread_db_err_str (err));
-      return 0;
-    }
-
   /* These are not essential.  */
   td_ta_event_addr_p = dlsym (handle, "td_ta_event_addr");
   td_ta_set_event_p = dlsym (handle, "td_ta_set_event");
@@ -447,9 +450,141 @@ thread_db_load (void)
   td_thr_event_enable_p = dlsym (handle, "td_thr_event_enable");
   td_thr_tls_get_addr_p = dlsym (handle, "td_thr_tls_get_addr");
 
+  printf_unfiltered (_("[Thread debugging using libthread_db enabled]\n"));
+
+  init_thread_db_ops ();
+  add_target (&thread_db_ops);
+
+  /* The thread library was detected.  Activate the thread_db target.  */
+  push_target (&thread_db_ops);
+  using_thread_db = handle;
+
+  enable_thread_event_reporting ();
+  thread_db_find_new_threads_1 ();
   return 1;
 }
 
+static int
+try_thread_db_load (const char *library)
+{
+  void *handle;
+
+  if (info_verbose)
+    printf_unfiltered (_("Trying host libthread_db library \"%s\".\n"),
+                       library);
+  handle = dlopen (library, RTLD_NOW);
+  if (handle == NULL)
+    {
+      if (info_verbose)
+	printf_unfiltered (_("dlopen(): %s.\n"), dlerror ());
+      return 0;
+    }
+  if (try_thread_db_load_1 (handle))
+    return 1;
+
+  /* This library "refused" to work on current inferior.  */
+  dlclose (handle);
+  return 0;
+}
+
+static int
+thread_db_load_search ()
+{
+  char path[PATH_MAX];
+  const char *search_path = libthread_db_search_path;
+  int rc = 0;
+
+  while (*search_path)
+    {
+      const char *end = strchr (search_path, ':');
+      if (end)
+	{
+	  size_t len = end - search_path;
+	  strncpy (path, search_path, len);
+	  path[len] = '\0';
+	  search_path += len + 1;
+	}
+      else
+	{
+	  strcpy (path, search_path);
+	  search_path += strlen (search_path);
+	}
+      strcat (path, "/");
+      strcat (path, LIBTHREAD_DB_SO);
+      if (try_thread_db_load (path))
+	{
+	  rc = 1;
+	  break;
+	}
+    }
+  if (!rc)
+    rc = try_thread_db_load (LIBTHREAD_DB_SO);
+  if (rc)
+    {
+      Dl_info info;
+      const char *library = NULL;
+      if (dladdr ((*td_ta_new_p), &info) != 0)
+	library = info.dli_fname;
+      if (library == NULL)
+	library = LIBTHREAD_DB_SO;
+      printf_unfiltered (_("Warning: guessed host libthread_db "
+			   "library \"%s\".\n"), library);
+    }
+  else
+      printf_unfiltered (_("Warning: unable to guess libthread_db, "
+			   "thread debugging will not be available.\n"));
+  return rc;
+}
+
+static int
+thread_db_load (void)
+{
+  const char *soname;
+  struct minimal_symbol *msym;
+
+  if (using_thread_db)
+    return 1;
+
+  /* Don't attempt to use thread_db on targets which can not run
+     (executables not running yet, core files) for now.  */
+  if (!target_has_execution)
+    return 0;
+
+  /* Don't attempt to use thread_db for remote targets.  */
+  if (!target_can_run (&current_target))
+    return 0;
+
+  msym = lookup_minimal_symbol ("nptl_version", NULL, NULL);
+  if (!msym)
+    msym = lookup_minimal_symbol ("__linuxthreads_version", NULL, NULL);
+
+  if (!msym)
+    /* No threads yet */
+    return 0;
+
+  soname = solib_name_from_address (SYMBOL_VALUE_ADDRESS (msym));
+  if (soname)
+    {
+      /* Attempt to load libthread_db from the same directory. */
+      char path[PATH_MAX], *cp;
+      strcpy (path, soname);
+      cp = strrchr (path, '/');
+      if (cp == NULL)
+	{
+	  /* Expected to get fully resolved pathname, but got
+	     something else. Hope for the best.  */
+	  printf_unfiltered (_("warning: (Internal error: "
+			       "solib_name_from_address() returned \"%s\".\n"),
+			     soname);
+	  return thread_db_load_search ();
+	}
+      strcpy (cp + 1, LIBTHREAD_DB_SO);
+      if (try_thread_db_load (path))
+	return 1;
+    }
+  return thread_db_load_search ();
+}
+
 static td_err_e
 enable_thread_event (td_thragent_t *thread_agent, int event, CORE_ADDR *bp)
 {
@@ -593,17 +728,20 @@ void
 check_for_thread_db (void)
 {
   td_err_e err;
-  static int already_loaded;
+  static void *last_loaded;
 
   /* Do nothing if we couldn't load libthread_db.so.1.  */
-  if (td_ta_new_p == NULL)
+  if (!thread_db_load ())
     return;
 
   /* First time through, report that libthread_db was successfuly
      loaded.  Can't print this in in thread_db_load as, at that stage,
-     the interpreter and it's console haven't started.  */
+     the interpreter and it's console haven't started.
+     We track td_ta_new_p because the user may switch executables,
+     and as a result we may decide to use a different version of
+     libthread_db. */
 
-  if (!already_loaded)
+  if (last_loaded != td_ta_new_p)
     {
       Dl_info info;
       const char *library = NULL;
@@ -616,52 +754,9 @@ check_for_thread_db (void)
 	/* Paranoid - don't let a NULL path slip through.  */
 	library = LIBTHREAD_DB_SO;
 
-      if (info_verbose)
-	printf_unfiltered (_("Using host libthread_db library \"%s\".\n"),
-			   library);
-      already_loaded = 1;
-    }
-
-  if (using_thread_db)
-    /* Nothing to do.  The thread library was already detected and the
-       target vector was already activated.  */
-    return;
-
-  /* Don't attempt to use thread_db on targets which can not run
-     (executables not running yet, core files) for now.  */
-  if (!target_has_execution)
-    return;
-
-  /* Don't attempt to use thread_db for remote targets.  */
-  if (!target_can_run (&current_target))
-    return;
-
-  /* Initialize the structure that identifies the child process.  */
-  proc_handle.ptid = inferior_ptid;
-
-  /* Now attempt to open a connection to the thread library.  */
-  err = td_ta_new_p (&proc_handle, &thread_agent);
-  switch (err)
-    {
-    case TD_NOLIBTHREAD:
-      /* No thread library was detected.  */
-      break;
-
-    case TD_OK:
-      printf_unfiltered (_("[Thread debugging using libthread_db enabled]\n"));
-
-      /* The thread library was detected.  Activate the thread_db target.  */
-      push_target (&thread_db_ops);
-      using_thread_db = 1;
-
-      enable_thread_event_reporting ();
-      thread_db_find_new_threads_1 ();
-      break;
-
-    default:
-      warning (_("Cannot initialize thread debugging library: %s"),
-	       thread_db_err_str (err));
-      break;
+      printf_unfiltered (_("Using host libthread_db library \"%s\".\n"),
+                         library);
+      last_loaded = td_ta_new_p;
     }
 }
 
@@ -783,6 +878,8 @@ thread_db_detach (struct target_ops *ops
 
   /* Detach thread_db target ops.  */
   unpush_target (&thread_db_ops);
+  if (using_thread_db)
+    dlclose (using_thread_db);
   using_thread_db = 0;
 
   target_beneath->to_detach (target_beneath, args, from_tty);
@@ -896,7 +993,10 @@ thread_db_wait (struct target_ops *ops,
     {
       remove_thread_event_breakpoints ();
       unpush_target (&thread_db_ops);
+      if (using_thread_db)
+	dlclose (using_thread_db);
       using_thread_db = 0;
+      no_shared_libraries (NULL, 0);
 
       return ptid;
     }
@@ -944,6 +1044,8 @@ thread_db_mourn_inferior (struct target_
 
   /* Detach thread_db target ops.  */
   unpush_target (ops);
+  if (using_thread_db)
+    dlclose (using_thread_db);
   using_thread_db = 0;
 }
 
@@ -1187,13 +1289,24 @@ extern initialize_file_ftype _initialize
 void
 _initialize_thread_db (void)
 {
-  /* Only initialize the module if we can load libthread_db.  */
-  if (thread_db_load ())
-    {
-      init_thread_db_ops ();
-      add_target (&thread_db_ops);
-
-      /* Add ourselves to objfile event chain.  */
-      observer_attach_new_objfile (thread_db_new_objfile);
-    }
+  /* Defer loading of libthread_db.so until inferior is running.
+     This allows gdb to load correct libthread_db for a given
+     executable -- there could be mutiple versions of glibc,
+     compiled with LinuxThreads or NPTL, and until there is
+     a running inferior, we can't tell which libthread_db is
+     the correct one to load. */
+
+  libthread_db_search_path = xstrdup(LIBTHREAD_DB_SEARCH_PATH);
+
+  add_setshow_filename_cmd ("libthread-db-search-path", class_support,
+			    &libthread_db_search_path, _("\
+Set search path for libthread_db."), _("\
+Show the current search path or libthread_db."), _("\
+This path is used to search for libthread_db to be loaded into \
+gdb itself."),
+			    NULL,
+			    NULL,
+			    &setlist, &showlist);
+  /* Add ourselves to objfile event chain.  */
+  observer_attach_new_objfile (thread_db_new_objfile);
 }


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