From 17a37d488c16b07d3a1f069ef727e2e53c622722 Mon Sep 17 00:00:00 2001 From: Paul Pluzhnikov Date: Fri, 15 May 2009 16:53:45 +0000 Subject: [PATCH] 2009-05-15 Paul Pluzhnikov * NEWS: Mention set/show libthread-db-search-path. * gdb_thread_db.h (LIBTHREAD_DB_SEARCH_PATH): New define. (LIBTHREAD_DB_SO): Moved from linux-thread-db.c * linux-thread-db.c (libthread_db_search_path): New setting. (thread_db_handle): New variable (replaces using_thread_db). (try_thread_db_load_1): New function. (try_thread_db_load, thread_db_load_search): Likewise. (dladdr_to_soname): Likewise. (thread_db_load): Iterate over possibly multiple libthread_db's. (check_for_thread_db): Attempt to load new libthread_db. (thread_db_detach, thread_db_wait): Unload libthread_db. (thread_db_mourn_inferior): Likewise. (_initialize_thread_db): Add new libthread-db-search-path option. Defer loading of libthread_db to check_for_thread_db. * solib.c (libpthread_name_p): New function. (libpthread_solib_p): Call it. * solib.h (libpthread_name_p): New prototype. --- gdb/ChangeLog | 20 ++ gdb/NEWS | 5 + gdb/doc/ChangeLog | 4 + gdb/doc/gdb.texinfo | 35 ++++ gdb/gdb_thread_db.h | 9 + gdb/linux-thread-db.c | 473 +++++++++++++++++++++++++++++------------- gdb/solib.c | 13 +- gdb/solib.h | 4 + 8 files changed, 419 insertions(+), 144 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 2b2acb98a73..14b12deec2f 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,23 @@ +2009-05-15 Paul Pluzhnikov + + * NEWS: Mention set/show libthread-db-search-path. + * gdb_thread_db.h (LIBTHREAD_DB_SEARCH_PATH): New define. + (LIBTHREAD_DB_SO): Moved from linux-thread-db.c + * linux-thread-db.c (libthread_db_search_path): New setting. + (thread_db_handle): New variable (replaces using_thread_db). + (try_thread_db_load_1): New function. + (try_thread_db_load, thread_db_load_search): Likewise. + (dladdr_to_soname): Likewise. + (thread_db_load): Iterate over possibly multiple libthread_db's. + (check_for_thread_db): Attempt to load new libthread_db. + (thread_db_detach, thread_db_wait): Unload libthread_db. + (thread_db_mourn_inferior): Likewise. + (_initialize_thread_db): Add new libthread-db-search-path option. + Defer loading of libthread_db to check_for_thread_db. + * solib.c (libpthread_name_p): New function. + (libpthread_solib_p): Call it. + * solib.h (libpthread_name_p): New prototype. + 2009-05-15 Pierre Muller * MAINTAINERS: Update my email address. diff --git a/gdb/NEWS b/gdb/NEWS index 231f6714c20..982a5b385f2 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -300,6 +300,11 @@ show tcp connect-timeout with a specified timeout period; this is useful if the stub is launched in parallel with GDB but may not be ready to accept connections immediately. +set libthread-db-search-path +show libthread-db-search-path + Control list of directories which GDB will search for appropriate + libthread_db. + * New native configurations x86/x86_64 Darwin i[34567]86-*-darwin* diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index e479a09f85a..6d81aef53c5 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,7 @@ +2009-05-15 Paul Pluzhnikov + + * gdb.texinfo (Threads): Document libthread-db-search-path. + 2009-05-15 Nick Roberts * gdb.texinfo (GDB/MI General Design): Break up into four nodes. diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 09407900e02..b7bf14e8cb5 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -2430,6 +2430,9 @@ a command to apply a command to a list of threads @item thread-specific breakpoints @item @samp{set print thread-events}, which controls printing of messages on thread start and exit. +@item @samp{set libthread-db-search-path @var{path}}, which lets +the user specify which @code{libthread_db} to use if the default choice +isn't compatible with the program. @end itemize @quotation @@ -2648,6 +2651,38 @@ programs with multiple threads. @xref{Set Watchpoints,,Setting Watchpoints}, for information about watchpoints in programs with multiple threads. +@table @code +@kindex set libthread-db-search-path +@cindex search path for @code{libthread_db} +@item set libthread-db-search-path @r{[}@var{path}@r{]} +If this variable is set, @var{path} is a colon-separated list of +directories @value{GDBN} will use to search for @code{libthread_db}. +If you omit @var{path}, @samp{libthread-db-search-path} will be reset to +an empty list. + +On @sc{gnu}/Linux and Solaris systems, @value{GDBN} uses a ``helper'' +@code{libthread_db} library to obtain information about threads in the +inferior process. @value{GDBN} will use @samp{libthread-db-search-path} +to find @code{libthread_db}. If that fails, @value{GDBN} will continue +with default system shared library directories, and finally the directory +from which @code{libpthread} was loaded in the inferior process. + +For any @code{libthread_db} library @value{GDBN} finds in above directories, +@value{GDBN} attempts to initialize it with the current inferior process. +If this initialization fails (which could happen because of a version +mismatch between @code{libthread_db} and @code{libpthread}), @value{GDBN} +will unload @code{libthread_db}, and continue with the next directory. +If none of @code{libthread_db} libraries initialize successfully, +@value{GDBN} will issue a warning and thread debugging will be disabled. + +Setting @code{libthread-db-search-path} is currently implemented +only on some platforms. + +@kindex show libthread-db-search-path +@item show libthread-db-search-path +Display current libthread_db search path. +@end table + @node Processes @section Debugging Programs with Multiple Processes diff --git a/gdb/gdb_thread_db.h b/gdb/gdb_thread_db.h index a5f2477210d..1cc5fda5ad2 100644 --- a/gdb/gdb_thread_db.h +++ b/gdb/gdb_thread_db.h @@ -1,5 +1,14 @@ #ifdef HAVE_THREAD_DB_H #include + +#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. diff --git a/gdb/linux-thread-db.c b/gdb/linux-thread-db.c index be7733e8066..80dd30d08a0 100644 --- a/gdb/linux-thread-db.c +++ b/gdb/linux-thread-db.c @@ -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 #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,14 +73,17 @@ 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. */ /* This module's target vector. */ static struct target_ops thread_db_ops; -/* Non-zero if we're using this module's target vector. */ -static int using_thread_db; +/* Handle from dlopen for libthread_db.so. Not NULL if we're using this + module's target vector. */ +static void *thread_db_handle; /* Non-zero if we have determined the signals used by the threads library. */ @@ -344,7 +346,7 @@ thread_db_attach_lwp (ptid_t ptid) td_thrinfo_t ti; td_err_e err; - if (!using_thread_db) + if (thread_db_handle == NULL) return 0; /* This ptid comes from linux-nat.c, which should always fill in the @@ -385,71 +387,6 @@ verbose_dlsym (void *handle, const char *name) return sym; } -static int -thread_db_load (void) -{ - 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; - - td_ta_new_p = verbose_dlsym (handle, "td_ta_new"); - if (td_ta_new_p == NULL) - return 0; - - td_ta_map_id2thr_p = verbose_dlsym (handle, "td_ta_map_id2thr"); - if (td_ta_map_id2thr_p == NULL) - return 0; - - td_ta_map_lwp2thr_p = verbose_dlsym (handle, "td_ta_map_lwp2thr"); - if (td_ta_map_lwp2thr_p == NULL) - return 0; - - td_ta_thr_iter_p = verbose_dlsym (handle, "td_ta_thr_iter"); - if (td_ta_thr_iter_p == NULL) - return 0; - - td_thr_validate_p = verbose_dlsym (handle, "td_thr_validate"); - if (td_thr_validate_p == NULL) - return 0; - - td_thr_get_info_p = verbose_dlsym (handle, "td_thr_get_info"); - 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"); - td_ta_event_getmsg_p = dlsym (handle, "td_ta_event_getmsg"); - td_thr_event_enable_p = dlsym (handle, "td_thr_event_enable"); - td_thr_tls_get_addr_p = dlsym (handle, "td_thr_tls_get_addr"); - - return 1; -} - static td_err_e enable_thread_event (td_thragent_t *thread_agent, int event, CORE_ADDR *bp) { @@ -541,6 +478,278 @@ enable_thread_event_reporting (void) } } +/* Attempt to initialize dlopen()ed libthread_db, described by HANDLE. + Return 1 on success. + Failure could happen if libthread_db does not have symbols we expect, + or when it refuses to work with the current inferior (e.g. due to + version mismatch between libthread_db and libpthread). */ + +static int +try_thread_db_load_1 (void *handle) +{ + td_err_e err; + + /* 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) + { + warning (_("Cannot initialize libthread_db: %s"), thread_db_err_str (err)); + 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 failed: %s\n"), + thread_db_err_str (err)); + else + switch (err) + { + case TD_NOLIBTHREAD: +#ifdef THREAD_DB_HAS_TD_VERSION + case TD_VERSION: +#endif + /* The errors above are not unexpected and silently ignored: + they just mean we haven't found correct version of + libthread_db yet. */ + break; + default: + warning (_("td_ta_new failed: %s"), 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; + + td_ta_map_lwp2thr_p = verbose_dlsym (handle, "td_ta_map_lwp2thr"); + if (td_ta_map_lwp2thr_p == NULL) + return 0; + + td_ta_thr_iter_p = verbose_dlsym (handle, "td_ta_thr_iter"); + if (td_ta_thr_iter_p == NULL) + return 0; + + td_thr_validate_p = verbose_dlsym (handle, "td_thr_validate"); + if (td_thr_validate_p == NULL) + return 0; + + td_thr_get_info_p = verbose_dlsym (handle, "td_thr_get_info"); + if (td_thr_get_info_p == NULL) + return 0; + + /* These are not essential. */ + td_ta_event_addr_p = dlsym (handle, "td_ta_event_addr"); + td_ta_set_event_p = dlsym (handle, "td_ta_set_event"); + td_ta_event_getmsg_p = dlsym (handle, "td_ta_event_getmsg"); + td_thr_event_enable_p = dlsym (handle, "td_thr_event_enable"); + td_thr_tls_get_addr_p = dlsym (handle, "td_thr_tls_get_addr"); + + printf_unfiltered (_("[Thread debugging using libthread_db enabled]\n")); + + /* The thread library was detected. Activate the thread_db target. */ + push_target (&thread_db_ops); + thread_db_handle = handle; + + enable_thread_event_reporting (); + thread_db_find_new_threads_1 (); + return 1; +} + +/* Lookup a library in which given symbol resides. + Note: this is looking in GDB process, not in the inferior. + Returns library name, or NULL. */ + +static const char * +dladdr_to_soname (const void *addr) +{ + Dl_info info; + + if (dladdr (addr, &info) != 0) + return info.dli_fname; + return NULL; +} + +/* Attempt to use LIBRARY as libthread_db. LIBRARY could be absolute, + relative, or just LIBTHREAD_DB. */ + +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 failed: %s.\n"), dlerror ()); + return 0; + } + + if (info_verbose && strchr (library, '/') == NULL) + { + void *td_init; + + td_init = dlsym (handle, "td_init"); + if (td_init != NULL) + { + const char *const libpath = dladdr_to_soname (td_init); + + if (libpath != NULL) + printf_unfiltered (_("Host %s resolved to: %s.\n"), + library, libpath); + } + } + + if (try_thread_db_load_1 (handle)) + return 1; + + /* This library "refused" to work on current inferior. */ + dlclose (handle); + return 0; +} + + +/* Search libthread_db_search_path for libthread_db which "agrees" + to work on current inferior. */ + +static int +thread_db_load_search (void) +{ + 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; + if (len + 1 + strlen (LIBTHREAD_DB_SO) + 1 > sizeof (path)) + { + char *cp = xmalloc (len + 1); + memcpy (cp, search_path, len); + cp[len] = '\0'; + warning (_("libthread_db_search_path component too long," + " ignored: %s."), cp); + xfree (cp); + search_path += len + 1; + continue; + } + memcpy (path, search_path, len); + path[len] = '\0'; + search_path += len + 1; + } + else + { + size_t len = strlen (search_path); + + if (len + 1 + strlen (LIBTHREAD_DB_SO) + 1 > sizeof (path)) + { + warning (_("libthread_db_search_path component too long," + " ignored: %s."), search_path); + break; + } + memcpy (path, search_path, len + 1); + search_path += len; + } + strcat (path, "/"); + strcat (path, LIBTHREAD_DB_SO); + if (try_thread_db_load (path)) + { + rc = 1; + break; + } + } + if (rc == 0) + rc = try_thread_db_load (LIBTHREAD_DB_SO); + return rc; +} + +/* Attempt to load and initialize libthread_db. + Return 1 on success. + */ + +static int +thread_db_load (void) +{ + struct objfile *obj; + + if (thread_db_handle != NULL) + 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 (¤t_target)) + return 0; + + if (thread_db_load_search ()) + return 1; + + /* None of the libthread_db's on our search path, not the system default + ones worked. If the executable is dynamically linked against + libpthread, try loading libthread_db from the same directory. */ + + ALL_OBJFILES (obj) + if (libpthread_name_p (obj->name)) + { + char path[PATH_MAX], *cp; + + gdb_assert (strlen (obj->name) < sizeof (path)); + strcpy (path, obj->name); + cp = strrchr (path, '/'); + + if (cp == NULL) + { + warning (_("Expected absolute pathname for libpthread in the" + " inferior, but got %s."), path); + } + else if (cp + 1 + strlen (LIBTHREAD_DB_SO) + 1 > path + sizeof (path)) + { + warning (_("Unexpected: path to libpthread in the inferior is" + " too long: %s"), path); + } + else + { + strcpy (cp + 1, LIBTHREAD_DB_SO); + if (try_thread_db_load (path)) + return 1; + } + warning (_("Unable to find libthread_db matching inferior's thread" + " library, thread debugging will not be available.")); + return 0; + } + /* Either this executable isn't using libpthread at all, or it is + statically linked. Since we can't easily distinguish these two cases, + no warning is issued. */ + return 0; +} + static void disable_thread_event_reporting (void) { @@ -593,75 +802,34 @@ 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. */ - - if (!already_loaded) - { - Dl_info info; - const char *library = NULL; - if (dladdr ((*td_ta_new_p), &info) != 0) - library = info.dli_fname; - - /* Try dlinfo? */ - - if (library == NULL) - /* 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 (¤t_target)) - return; - - /* Initialize the structure that identifies the child process. */ - proc_handle.ptid = inferior_ptid; + 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. */ - /* Now attempt to open a connection to the thread library. */ - err = td_ta_new_p (&proc_handle, &thread_agent); - switch (err) + if (last_loaded != td_ta_new_p) { - case TD_NOLIBTHREAD: - /* No thread library was detected. */ - break; - - case TD_OK: - printf_unfiltered (_("[Thread debugging using libthread_db enabled]\n")); + last_loaded = td_ta_new_p; - /* The thread library was detected. Activate the thread_db target. */ - push_target (&thread_db_ops); - using_thread_db = 1; + if (info_verbose || *libthread_db_search_path) + { + const char *library; - enable_thread_event_reporting (); - thread_db_find_new_threads_1 (); - break; + library = dladdr_to_soname (*td_ta_new_p); + if (library == NULL) + library = LIBTHREAD_DB_SO; - default: - warning (_("Cannot initialize thread debugging library: %s"), - thread_db_err_str (err)); - break; + printf_unfiltered (_("Using host libthread_db library \"%s\".\n"), + library); + } } } @@ -783,7 +951,9 @@ thread_db_detach (struct target_ops *ops, char *args, int from_tty) /* Detach thread_db target ops. */ unpush_target (&thread_db_ops); - using_thread_db = 0; + if (thread_db_handle) + dlclose (thread_db_handle); + thread_db_handle = NULL; target_beneath->to_detach (target_beneath, args, from_tty); } @@ -896,7 +1066,9 @@ thread_db_wait (struct target_ops *ops, { remove_thread_event_breakpoints (); unpush_target (&thread_db_ops); - using_thread_db = 0; + if (thread_db_handle) + dlclose (thread_db_handle); + thread_db_handle = NULL; return ptid; } @@ -944,7 +1116,9 @@ thread_db_mourn_inferior (struct target_ops *ops) /* Detach thread_db target ops. */ unpush_target (ops); - using_thread_db = 0; + if (thread_db_handle) + dlclose (thread_db_handle); + thread_db_handle = NULL; } static int @@ -1186,13 +1360,28 @@ extern initialize_file_ftype _initialize_thread_db; 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); - } + init_thread_db_ops (); + add_target (&thread_db_ops); + + /* 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_optional_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); } diff --git a/gdb/solib.c b/gdb/solib.c index 0eae2006254..8b330b16354 100644 --- a/gdb/solib.c +++ b/gdb/solib.c @@ -684,16 +684,25 @@ update_solib_list (int from_tty, struct target_ops *target) } } -/* Return non-zero if SO is the libpthread shared library. + +/* Return non-zero if NAME is the libpthread shared library. Uses a fairly simplistic heuristic approach where we check the file name against "/libpthread". This can lead to false positives, but this should be good enough in practice. */ +int +libpthread_name_p (const char *name) +{ + return (strstr (name, "/libpthread") != NULL); +} + +/* Return non-zero if SO is the libpthread shared library. */ + static int libpthread_solib_p (struct so_list *so) { - return (strstr (so->so_name, "/libpthread") != NULL); + return libpthread_name_p (so->so_name); } /* GLOBAL FUNCTION diff --git a/gdb/solib.h b/gdb/solib.h index b4b770e2ac3..ccc5b6358b0 100644 --- a/gdb/solib.h +++ b/gdb/solib.h @@ -65,4 +65,8 @@ extern void no_shared_libraries (char *ignored, int from_tty); extern void set_solib_ops (struct gdbarch *gdbarch, struct target_so_ops *new_ops); +/* Return non-zero if NAME is the libpthread shared library. */ + +extern int libpthread_name_p (const char *name); + #endif /* SOLIB_H */ -- 2.30.2