From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 19668 invoked by alias); 6 Apr 2009 20:39:35 -0000 Received: (qmail 19277 invoked by uid 22791); 6 Apr 2009 20:39:33 -0000 X-SWARE-Spam-Status: No, hits=-1.1 required=5.0 tests=AWL,BAYES_00,J_CHICKENPOX_102,KAM_STOCKGEN,SPF_PASS X-Spam-Check-By: sourceware.org Received: from smtp-out.google.com (HELO smtp-out.google.com) (216.239.45.13) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Mon, 06 Apr 2009 20:39:25 +0000 Received: from wpaz29.hot.corp.google.com (wpaz29.hot.corp.google.com [172.24.198.93]) by smtp-out.google.com with ESMTP id n36KdNmr032128 for ; Mon, 6 Apr 2009 13:39:23 -0700 Received: from localhost (elbrus.mtv.corp.google.com [172.18.118.100]) by wpaz29.hot.corp.google.com with ESMTP id n36KdLtB022783; Mon, 6 Apr 2009 13:39:21 -0700 Received: by localhost (Postfix, from userid 74925) id CCD6F19C4EC; Mon, 6 Apr 2009 13:39:20 -0700 (PDT) To: gdb-patches@sourceware.org Subject: [patch][rfc] Allow GDB to search for the right libthread_db.so.1 Message-Id: <20090406203920.CCD6F19C4EC@localhost> Date: Mon, 06 Apr 2009 20:39:00 -0000 From: ppluzhnikov@google.com (Paul Pluzhnikov) X-IsSubscribed: yes Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2009-04/txt/msg00115.txt.bz2 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 * 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 + +#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 #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 (¤t_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 (¤t_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); }