Index: gdb/linux-thread-db.c |
diff --git a/gdb/linux-thread-db.c b/gdb/linux-thread-db.c |
index b64ac2ed0c063e2d0423fc0a0e28a38037243cfd..bdf14dfeb437e37cc25cbe50e8dac74d3ec28a63 100644 |
--- a/gdb/linux-thread-db.c |
+++ b/gdb/linux-thread-db.c |
@@ -40,12 +40,11 @@ |
#include "observer.h" |
#include "linux-nat.h" |
#include "linux-procfs.h" |
+#include "linux-osdata.h" |
+#include "auto-load.h" |
#include <signal.h> |
- |
-#ifdef HAVE_GNU_LIBC_VERSION_H |
-#include <gnu/libc-version.h> |
-#endif |
+#include <ctype.h> |
/* GNU/Linux libthread_db support. |
@@ -75,6 +74,21 @@ |
static char *libthread_db_search_path; |
+/* Set to non-zero if thread_db auto-loading is enabled |
+ by the "set auto-load libthread-db" command. */ |
+static int auto_load_thread_db = 1; |
+ |
+/* "show" command for the auto_load_thread_db configuration variable. */ |
+ |
+static void |
+show_auto_load_thread_db (struct ui_file *file, int from_tty, |
+ struct cmd_list_element *c, const char *value) |
+{ |
+ fprintf_filtered (file, _("Auto-loading of inferior specific libthread_db " |
+ "is %s.\n"), |
+ value); |
+} |
+ |
static void |
set_libthread_db_search_path (char *ignored, int from_tty, |
struct cmd_list_element *c) |
@@ -119,6 +133,10 @@ struct thread_db_info |
/* Handle from dlopen for libthread_db.so. */ |
void *handle; |
+ /* Absolute pathname from gdb_realpath to disk file used for dlopen-ing |
+ HANDLE. It may be NULL for system library. */ |
+ char *filename; |
+ |
/* Structure that identifies the child process for the |
<proc_service.h> interface. */ |
struct ps_prochandle proc_handle; |
@@ -248,6 +266,8 @@ delete_thread_db_info (int pid) |
if (info->handle != NULL) |
dlclose (info->handle); |
+ xfree (info->filename); |
+ |
if (info_prev) |
info_prev->next = info->next; |
else |
@@ -552,15 +572,42 @@ enable_thread_event (int event, CORE_ADDR *bp) |
return TD_OK; |
} |
+/* Verify inferior's '\0'-terminated symbol VER_SYMBOL starts with "%d.%d" and |
+ return 1 if this version is lower (and not equal) to |
+ VER_MAJOR_MIN.VER_MINOR_MIN. Return 0 in all other cases. */ |
+ |
+static int |
+inferior_has_bug (const char *ver_symbol, int ver_major_min, int ver_minor_min) |
+{ |
+ struct minimal_symbol *version_msym; |
+ CORE_ADDR version_addr; |
+ char *version; |
+ int err, got, retval = 0; |
+ |
+ version_msym = lookup_minimal_symbol (ver_symbol, NULL, NULL); |
+ if (version_msym == NULL) |
+ return 0; |
+ |
+ version_addr = SYMBOL_VALUE_ADDRESS (version_msym); |
+ got = target_read_string (version_addr, &version, 32, &err); |
+ if (err == 0 && memchr (version, 0, got) == &version[got -1]) |
+ { |
+ int major, minor; |
+ |
+ retval = (sscanf (version, "%d.%d", &major, &minor) == 2 |
+ && (major < ver_major_min |
+ || (major == ver_major_min && minor < ver_minor_min))); |
+ } |
+ xfree (version); |
+ |
+ return retval; |
+} |
+ |
static void |
enable_thread_event_reporting (void) |
{ |
td_thr_events_t events; |
td_err_e err; |
-#ifdef HAVE_GNU_LIBC_VERSION_H |
- const char *libc_version; |
- int libc_major, libc_minor; |
-#endif |
struct thread_db_info *info; |
info = get_thread_db_info (GET_PID (inferior_ptid)); |
@@ -577,14 +624,13 @@ enable_thread_event_reporting (void) |
td_event_emptyset (&events); |
td_event_addset (&events, TD_CREATE); |
-#ifdef HAVE_GNU_LIBC_VERSION_H |
- /* The event reporting facility is broken for TD_DEATH events in |
- glibc 2.1.3, so don't enable it if we have glibc but a lower |
- version. */ |
- libc_version = gnu_get_libc_version (); |
- if (sscanf (libc_version, "%d.%d", &libc_major, &libc_minor) == 2 |
- && (libc_major > 2 || (libc_major == 2 && libc_minor > 1))) |
-#endif |
+ /* There is a bug fixed between linuxthreads 2.1.3 and 2.2 by |
+ commit 2e4581e4fba917f1779cd0a010a45698586c190a |
+ * manager.c (pthread_exited): Correctly report event as TD_REAP |
+ instead of TD_DEATH. Fix comments. |
+ where event reporting facility is broken for TD_DEATH events, |
+ so don't enable it if we have glibc but a lower version. */ |
+ if (!inferior_has_bug ("__linuxthreads_version", 2, 2)) |
td_event_addset (&events, TD_DEATH); |
err = info->td_ta_set_event_p (info->thread_agent, &events); |
@@ -619,9 +665,13 @@ enable_thread_event_reporting (void) |
} |
} |
-/* Same as thread_db_find_new_threads_1, but silently ignore errors. */ |
+/* Similar as thread_db_find_new_threads_1, but try to silently ignore errors |
+ if appropriate. |
-static void |
+ Return 1 if the caller should abort libthread_db initialization. Return 0 |
+ otherwise. */ |
+ |
+static int |
thread_db_find_new_threads_silently (ptid_t ptid) |
{ |
volatile struct gdb_exception except; |
@@ -631,11 +681,37 @@ thread_db_find_new_threads_silently (ptid_t ptid) |
thread_db_find_new_threads_2 (ptid, 1); |
} |
- if (except.reason < 0 && libthread_db_debug) |
+ if (except.reason < 0) |
{ |
- exception_fprintf (gdb_stderr, except, |
- "Warning: thread_db_find_new_threads_silently: "); |
+ if (libthread_db_debug) |
+ exception_fprintf (gdb_stderr, except, |
+ "Warning: thread_db_find_new_threads_silently: "); |
+ |
+ /* There is a bug fixed between nptl 2.6.1 and 2.7 by |
+ commit 7d9d8bd18906fdd17364f372b160d7ab896ce909 |
+ where calls to td_thr_get_info fail with TD_ERR for statically linked |
+ executables if td_thr_get_info is called before glibc has initialized |
+ itself. |
+ |
+ If the nptl bug is NOT present in the inferior and still thread_db |
+ reports an error return 1. It means the inferior has corrupted thread |
+ list and GDB should fall back only to LWPs. |
+ |
+ If the nptl bug is present in the inferior return 0 to silently ignore |
+ such errors, and let gdb enumerate threads again later. In such case |
+ GDB cannot properly display LWPs if the inferior thread list is |
+ corrupted. For core files it does not apply, no 'later enumeration' |
+ is possible. */ |
+ |
+ if (!target_has_execution || !inferior_has_bug ("nptl_version", 2, 7)) |
+ { |
+ exception_fprintf (gdb_stderr, except, |
+ _("Warning: couldn't activate thread debugging " |
+ "using libthread_db: ")); |
+ return 1; |
+ } |
} |
+ return 0; |
} |
/* Lookup a library in which given symbol resides. |
@@ -738,6 +814,14 @@ try_thread_db_load_1 (struct thread_db_info *info) |
info->td_thr_event_enable_p = dlsym (info->handle, "td_thr_event_enable"); |
info->td_thr_tls_get_addr_p = dlsym (info->handle, "td_thr_tls_get_addr"); |
+ if (thread_db_find_new_threads_silently (inferior_ptid) != 0) |
+ { |
+ /* Even if libthread_db initializes, if the thread list is |
+ corrupted, we'd not manage to list any threads. Better reject this |
+ thread_db, and fall back to at least listing LWPs. */ |
+ return 0; |
+ } |
+ |
printf_unfiltered (_("[Thread debugging using libthread_db enabled]\n")); |
if (libthread_db_debug || *libthread_db_search_path) |
@@ -761,12 +845,6 @@ try_thread_db_load_1 (struct thread_db_info *info) |
if (target_has_execution) |
enable_thread_event_reporting (); |
- /* There appears to be a bug in glibc-2.3.6: calls to td_thr_get_info fail |
- with TD_ERR for statically linked executables if td_thr_get_info is |
- called before glibc has initialized itself. Silently ignore such |
- errors, and let gdb enumerate threads again later. */ |
- thread_db_find_new_threads_silently (inferior_ptid); |
- |
return 1; |
} |
@@ -807,6 +885,10 @@ try_thread_db_load (const char *library) |
info = add_thread_db_info (handle); |
+ /* Do not save system library name, that one is always trusted. */ |
+ if (strchr (library, '/') != NULL) |
+ info->filename = gdb_realpath (library); |
+ |
if (try_thread_db_load_1 (info)) |
return 1; |
@@ -841,7 +923,13 @@ try_thread_db_load_from_pdir_1 (struct objfile *obj) |
/* This should at minimum hit the first character. */ |
gdb_assert (cp != NULL); |
strcpy (cp + 1, LIBTHREAD_DB_SO); |
- result = try_thread_db_load (path); |
+ |
+ if (!file_is_auto_load_safe (path, _("auto-load: Loading libthread-db " |
+ "library \"%s\" from $pdir.\n"), |
+ path)) |
+ result = 0; |
+ else |
+ result = try_thread_db_load (path); |
do_cleanups (cleanup); |
return result; |
@@ -856,6 +944,9 @@ try_thread_db_load_from_pdir (void) |
{ |
struct objfile *obj; |
+ if (!auto_load_thread_db) |
+ return 0; |
+ |
ALL_OBJFILES (obj) |
if (libpthread_name_p (obj->name)) |
{ |
@@ -895,13 +986,23 @@ try_thread_db_load_from_dir (const char *dir, size_t dir_len) |
char *path; |
int result; |
+ if (!auto_load_thread_db) |
+ return 0; |
+ |
path = xmalloc (dir_len + 1 + strlen (LIBTHREAD_DB_SO) + 1); |
cleanup = make_cleanup (xfree, path); |
memcpy (path, dir, dir_len); |
path[dir_len] = '/'; |
strcpy (path + dir_len + 1, LIBTHREAD_DB_SO); |
- result = try_thread_db_load (path); |
+ |
+ if (!file_is_auto_load_safe (path, _("auto-load: Loading libthread-db " |
+ "library \"%s\" from explicit " |
+ "directory.\n"), |
+ path)) |
+ result = 0; |
+ else |
+ result = try_thread_db_load (path); |
do_cleanups (cleanup); |
return result; |
@@ -1054,9 +1155,9 @@ check_thread_signals (void) |
{ |
if (sigismember (&mask, i)) |
{ |
- if (signal_stop_update (target_signal_from_host (i), 0)) |
+ if (signal_stop_update (gdb_signal_from_host (i), 0)) |
sigaddset (&thread_stop_set, i); |
- if (signal_print_update (target_signal_from_host (i), 0)) |
+ if (signal_print_update (gdb_signal_from_host (i), 0)) |
sigaddset (&thread_print_set, i); |
thread_signals = 1; |
} |
@@ -1085,6 +1186,12 @@ thread_db_new_objfile (struct objfile *objfile) |
correctly. */ |
if (objfile != NULL |
+ /* libpthread with separate debug info has its debug info file already |
+ loaded (and notified without successful thread_db initialization) |
+ the time observer_notify_new_objfile is called for the library itself. |
+ Static executables have their separate debug info loaded already |
+ before the inferior has started. */ |
+ && objfile->separate_debug_objfile_backlink == NULL |
/* Only check for thread_db if we loaded libpthread, |
or if this is the main symbol file. |
We need to check OBJF_MAINLINE to handle the case of debugging |
@@ -1116,7 +1223,7 @@ attach_thread (ptid_t ptid, const td_thrhandle_t *th_p, |
const td_thrinfo_t *ti_p) |
{ |
struct private_thread_info *private; |
- struct thread_info *tp = NULL; |
+ struct thread_info *tp; |
td_err_e err; |
struct thread_db_info *info; |
@@ -1130,11 +1237,9 @@ attach_thread (ptid_t ptid, const td_thrhandle_t *th_p, |
thread ID. In the first case we don't need to do anything; in |
the second case we should discard information about the dead |
thread and attach to the new one. */ |
- if (in_thread_list (ptid)) |
+ tp = find_thread_ptid (ptid); |
+ if (tp != NULL) |
{ |
- tp = find_thread_ptid (ptid); |
- gdb_assert (tp != NULL); |
- |
/* If tp->private is NULL, then GDB is already attached to this |
thread, but we do not know anything about it. We can learn |
about it here. This can only happen if we have some other |
@@ -1403,7 +1508,7 @@ thread_db_wait (struct target_ops *ops, |
thread_db_find_new_threads_1 (ptid); |
if (ourstatus->kind == TARGET_WAITKIND_STOPPED |
- && ourstatus->value.sig == TARGET_SIGNAL_TRAP) |
+ && ourstatus->value.sig == GDB_SIGNAL_TRAP) |
/* Check for a thread event. */ |
check_event (ptid); |
@@ -1567,7 +1672,7 @@ find_new_threads_once (struct thread_db_info *info, int iteration, |
static void |
thread_db_find_new_threads_2 (ptid_t ptid, int until_no_new) |
{ |
- td_err_e err; |
+ td_err_e err = TD_OK; |
struct thread_db_info *info; |
int pid = ptid_get_pid (ptid); |
int i, loop; |
@@ -1583,17 +1688,18 @@ thread_db_find_new_threads_2 (ptid_t ptid, int until_no_new) |
The 4 is a heuristic: there is an inherent race here, and I have |
seen that 2 iterations in a row are not always sufficient to |
"capture" all threads. */ |
- for (i = 0, loop = 0; loop < 4; ++i, ++loop) |
- if (find_new_threads_once (info, i, NULL) != 0) |
- /* Found some new threads. Restart the loop from beginning. */ |
- loop = -1; |
+ for (i = 0, loop = 0; loop < 4 && err == TD_OK; ++i, ++loop) |
+ if (find_new_threads_once (info, i, &err) != 0) |
+ { |
+ /* Found some new threads. Restart the loop from beginning. */ |
+ loop = -1; |
+ } |
} |
else |
- { |
- find_new_threads_once (info, 0, &err); |
- if (err != TD_OK) |
- error (_("Cannot find new threads: %s"), thread_db_err_str (err)); |
- } |
+ find_new_threads_once (info, 0, &err); |
+ |
+ if (err != TD_OK) |
+ error (_("Cannot find new threads: %s"), thread_db_err_str (err)); |
} |
static void |
@@ -1605,7 +1711,7 @@ thread_db_find_new_threads_1 (ptid_t ptid) |
static int |
update_thread_core (struct lwp_info *info, void *closure) |
{ |
- info->core = linux_nat_core_of_thread_1 (info->ptid); |
+ info->core = linux_common_core_of_thread (info->ptid); |
return 0; |
} |
@@ -1783,7 +1889,7 @@ thread_db_get_ada_task_ptid (long lwp, long thread) |
static void |
thread_db_resume (struct target_ops *ops, |
- ptid_t ptid, int step, enum target_signal signo) |
+ ptid_t ptid, int step, enum gdb_signal signo) |
{ |
struct target_ops *beneath = find_target_beneath (ops); |
struct thread_db_info *info; |
@@ -1802,6 +1908,150 @@ thread_db_resume (struct target_ops *ops, |
beneath->to_resume (beneath, ptid, step, signo); |
} |
+/* qsort helper function for info_auto_load_libthread_db, sort the |
+ thread_db_info pointers primarily by their FILENAME and secondarily by their |
+ PID, both in ascending order. */ |
+ |
+static int |
+info_auto_load_libthread_db_compare (const void *ap, const void *bp) |
+{ |
+ struct thread_db_info *a = *(struct thread_db_info **) ap; |
+ struct thread_db_info *b = *(struct thread_db_info **) bp; |
+ int retval; |
+ |
+ retval = strcmp (a->filename, b->filename); |
+ if (retval) |
+ return retval; |
+ |
+ return (a->pid > b->pid) - (a->pid - b->pid); |
+} |
+ |
+/* Implement 'info auto-load libthread-db'. */ |
+ |
+static void |
+info_auto_load_libthread_db (char *args, int from_tty) |
+{ |
+ struct ui_out *uiout = current_uiout; |
+ const char *cs = args ? args : ""; |
+ struct thread_db_info *info, **array; |
+ unsigned info_count, unique_filenames; |
+ size_t max_filename_len, max_pids_len, pids_len; |
+ struct cleanup *back_to; |
+ char *pids; |
+ int i; |
+ |
+ while (isspace (*cs)) |
+ cs++; |
+ if (*cs) |
+ error (_("'info auto-load libthread-db' does not accept any parameters")); |
+ |
+ info_count = 0; |
+ for (info = thread_db_list; info; info = info->next) |
+ if (info->filename != NULL) |
+ info_count++; |
+ |
+ array = xmalloc (sizeof (*array) * info_count); |
+ back_to = make_cleanup (xfree, array); |
+ |
+ info_count = 0; |
+ for (info = thread_db_list; info; info = info->next) |
+ if (info->filename != NULL) |
+ array[info_count++] = info; |
+ |
+ /* Sort ARRAY by filenames and PIDs. */ |
+ |
+ qsort (array, info_count, sizeof (*array), |
+ info_auto_load_libthread_db_compare); |
+ |
+ /* Calculate the number of unique filenames (rows) and the maximum string |
+ length of PIDs list for the unique filenames (columns). */ |
+ |
+ unique_filenames = 0; |
+ max_filename_len = 0; |
+ max_pids_len = 0; |
+ pids_len = 0; |
+ for (i = 0; i < info_count; i++) |
+ { |
+ int pid = array[i]->pid; |
+ size_t this_pid_len; |
+ |
+ for (this_pid_len = 0; pid != 0; pid /= 10) |
+ this_pid_len++; |
+ |
+ if (i == 0 || strcmp (array[i - 1]->filename, array[i]->filename) != 0) |
+ { |
+ unique_filenames++; |
+ max_filename_len = max (max_filename_len, |
+ strlen (array[i]->filename)); |
+ |
+ if (i > 0) |
+ { |
+ pids_len -= strlen (", "); |
+ max_pids_len = max (max_pids_len, pids_len); |
+ } |
+ pids_len = 0; |
+ } |
+ pids_len += this_pid_len + strlen (", "); |
+ } |
+ if (i) |
+ { |
+ pids_len -= strlen (", "); |
+ max_pids_len = max (max_pids_len, pids_len); |
+ } |
+ |
+ /* Table header shifted right by preceding "libthread-db: " would not match |
+ its columns. */ |
+ if (info_count > 0 && args == auto_load_info_scripts_pattern_nl) |
+ ui_out_text (uiout, "\n"); |
+ |
+ make_cleanup_ui_out_table_begin_end (uiout, 2, unique_filenames, |
+ "LinuxThreadDbTable"); |
+ |
+ ui_out_table_header (uiout, max_filename_len, ui_left, "filename", |
+ "Filename"); |
+ ui_out_table_header (uiout, pids_len, ui_left, "PIDs", "Pids"); |
+ ui_out_table_body (uiout); |
+ |
+ pids = xmalloc (max_pids_len + 1); |
+ make_cleanup (xfree, pids); |
+ |
+ /* Note I is incremented inside the cycle, not at its end. */ |
+ for (i = 0; i < info_count;) |
+ { |
+ struct cleanup *chain = make_cleanup_ui_out_tuple_begin_end (uiout, NULL); |
+ char *pids_end; |
+ |
+ info = array[i]; |
+ ui_out_field_string (uiout, "filename", info->filename); |
+ pids_end = pids; |
+ |
+ while (i < info_count && strcmp (info->filename, array[i]->filename) == 0) |
+ { |
+ if (pids_end != pids) |
+ { |
+ *pids_end++ = ','; |
+ *pids_end++ = ' '; |
+ } |
+ pids_end += xsnprintf (pids_end, &pids[max_pids_len + 1] - pids_end, |
+ "%u", array[i]->pid); |
+ gdb_assert (pids_end < &pids[max_pids_len + 1]); |
+ |
+ i++; |
+ } |
+ *pids_end = '\0'; |
+ |
+ ui_out_field_string (uiout, "pids", pids); |
+ |
+ ui_out_text (uiout, "\n"); |
+ do_cleanups (chain); |
+ } |
+ |
+ do_cleanups (back_to); |
+ |
+ if (info_count == 0) |
+ ui_out_message (uiout, 0, _("No auto-loaded libthread-db.\n")); |
+} |
+ |
static void |
init_thread_db_ops (void) |
{ |
@@ -1863,6 +2113,23 @@ When non-zero, libthread-db debugging is enabled."), |
show_libthread_db_debug, |
&setdebuglist, &showdebuglist); |
+ add_setshow_boolean_cmd ("libthread-db", class_support, |
+ &auto_load_thread_db, _("\ |
+Enable or disable auto-loading of inferior specific libthread_db."), _("\ |
+Show whether auto-loading inferior specific libthread_db is enabled."), _("\ |
+If enabled, libthread_db will be searched in 'set libthread-db-search-path'\n\ |
+locations to load libthread_db compatible with the inferior.\n\ |
+Standard system libthread_db still gets loaded even with this option off.\n\ |
+This options has security implications for untrusted inferiors."), |
+ NULL, show_auto_load_thread_db, |
+ auto_load_set_cmdlist_get (), |
+ auto_load_show_cmdlist_get ()); |
+ |
+ add_cmd ("libthread-db", class_info, info_auto_load_libthread_db, |
+ _("Print the list of loaded inferior specific libthread_db.\n\ |
+Usage: info auto-load libthread-db"), |
+ auto_load_info_cmdlist_get ()); |
+ |
/* Add ourselves to objfile event chain. */ |
observer_attach_new_objfile (thread_db_new_objfile); |