| 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);
|
|
|
|
|