/* libthread_db assisted debugging support, generic parts.
- Copyright 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
+ Copyright 1999, 2000, 2001, 2003, 2004 Free Software Foundation, Inc.
This file is part of GDB.
#include "regcache.h"
#include "solib-svr4.h"
+#ifdef HAVE_GNU_LIBC_VERSION_H
+#include <gnu/libc-version.h>
+#endif
+
#ifndef LIBTHREAD_DB_SO
#define LIBTHREAD_DB_SO "libthread_db.so.1"
#endif
/* Non-zero if we're using this module's target vector. */
static int using_thread_db;
-/* Non-zero if we have to keep this module's target vector active
- across re-runs. */
-static int keep_thread_db;
-
/* Non-zero if we have determined the signals used by the threads
library. */
static int thread_signals;
static void thread_db_find_new_threads (void);
static void attach_thread (ptid_t ptid, const td_thrhandle_t *th_p,
const td_thrinfo_t *ti_p, int verbose);
+static void detach_thread (ptid_t ptid, int verbose);
\f
/* Building process ids. */
struct private_thread_info
{
+ /* Flag set when we see a TD_DEATH event for this thread. */
+ unsigned int dying:1;
+
/* Cached thread state. */
unsigned int th_valid:1;
unsigned int ti_valid:1;
}
\f
/* A callback function for td_ta_thr_iter, which we use to map all
- threads to LWPs.
+ threads to LWPs.
THP is a handle to the current thread; if INFOP is not NULL, the
struct thread_info associated with this thread is returned in
- *INFOP. */
+ *INFOP.
+
+ If the thread is a zombie, TD_THR_ZOMBIE is returned. Otherwise,
+ zero is returned to indicate success. */
static int
thread_get_info_callback (const td_thrhandle_t *thp, void *infop)
thread_ptid = BUILD_THREAD (ti.ti_tid, GET_PID (inferior_ptid));
thread_info = find_thread_pid (thread_ptid);
+ /* In the case of a zombie thread, don't continue. We don't want to
+ attach to it thinking it is a new thread. */
+ if (ti.ti_state == TD_THR_UNKNOWN || ti.ti_state == TD_THR_ZOMBIE)
+ {
+ if (infop != NULL)
+ *(struct thread_info **) infop = thread_info;
+ if (thread_info != NULL)
+ {
+ memcpy (&thread_info->private->th, thp, sizeof (*thp));
+ thread_info->private->th_valid = 1;
+ memcpy (&thread_info->private->ti, &ti, sizeof (ti));
+ thread_info->private->ti_valid = 1;
+ }
+ return TD_THR_ZOMBIE;
+ }
+
if (thread_info == NULL)
{
/* New thread. Attach to it now (why wait?). */
GET_LWP (ptid), thread_db_err_str (err));
thread_info = NULL;
- thread_get_info_callback (&th, &thread_info);
+
+ /* Fetch the thread info. If we get back TD_THR_ZOMBIE, then the
+ event thread has already died. If another gdb interface has called
+ thread_alive() previously, the thread won't be found on the thread list
+ anymore. In that case, we don't want to process this ptid anymore
+ to avoid the possibility of later treating it as a newly
+ discovered thread id that we should add to the list. Thus,
+ we return a -1 ptid which is also how the thread list marks a
+ dead thread. */
+ if (thread_get_info_callback (&th, &thread_info) == TD_THR_ZOMBIE
+ && thread_info == NULL)
+ return pid_to_ptid (-1);
+
gdb_assert (thread_info && thread_info->private->ti_valid);
return BUILD_THREAD (thread_info->private->ti.ti_tid, GET_PID (ptid));
return 1;
}
-static int
+static td_err_e
enable_thread_event (td_thragent_t *thread_agent, int event, CORE_ADDR *bp)
{
td_notify_t notify;
- int err;
+ td_err_e err;
/* Get the breakpoint address for thread EVENT. */
err = td_ta_event_addr_p (thread_agent, event, ¬ify);
if (err != TD_OK)
- return 0;
+ return err;
/* Set up the breakpoint. */
(*bp) = gdbarch_convert_from_func_ptr_addr (current_gdbarch,
¤t_target);
create_thread_event_breakpoint ((*bp));
- return 1;
+ return TD_OK;
}
static void
td_thr_events_t events;
td_notify_t notify;
td_err_e err;
+#ifdef HAVE_GNU_LIBC_VERSION_H
+ const char *libc_version;
+ int libc_major, libc_minor;
+#endif
/* We cannot use the thread event reporting facility if these
functions aren't available. */
/* Set the process wide mask saying which events we're interested in. */
td_event_emptyset (&events);
td_event_addset (&events, TD_CREATE);
-#if 0
+
+#ifdef HAVE_GNU_LIBC_VERSION_H
/* FIXME: kettenis/2000-04-23: The event reporting facility is
broken for TD_DEATH events in glibc 2.1.3, so don't enable it for
now. */
- td_event_addset (&events, TD_DEATH);
+ 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
+ td_event_addset (&events, TD_DEATH);
err = td_ta_set_event_p (thread_agent, &events);
if (err != TD_OK)
td_death_bp_addr = 0;
/* Set up the thread creation event. */
- if (!enable_thread_event (thread_agent, TD_CREATE, &td_create_bp_addr))
+ err = enable_thread_event (thread_agent, TD_CREATE, &td_create_bp_addr);
+ if (err != TD_OK)
{
warning ("Unable to get location for thread creation breakpoint: %s",
thread_db_err_str (err));
}
/* Set up the thread death event. */
- if (!enable_thread_event (thread_agent, TD_DEATH, &td_death_bp_addr))
+ err = enable_thread_event (thread_agent, TD_DEATH, &td_death_bp_addr);
+ if (err != TD_OK)
{
warning ("Unable to get location for thread death breakpoint: %s",
thread_db_err_str (err));
#endif
}
-static void
-disable_thread_signals (void)
-{
-#ifdef GET_THREAD_SIGNALS
- if (thread_signals)
- {
- int i;
-
- for (i = 1; i < NSIG; i++)
- {
- if (sigismember (&thread_stop_set, i))
- signal_stop_update (target_signal_from_host (i), 1);
- if (sigismember (&thread_print_set, i))
- signal_print_update (target_signal_from_host (i), 1);
- }
-
- thread_signals = 0;
- }
-#endif
-}
-
static void
thread_db_new_objfile (struct objfile *objfile)
{
using_thread_db = 0;
}
- keep_thread_db = 0;
-
goto quit;
}
push_target (&thread_db_ops);
using_thread_db = 1;
- /* If the thread library was detected in the main symbol file
- itself, we assume that the program was statically linked
- against the thread library and well have to keep this
- module's target vector activated until forever... Well, at
- least until all symbols have been discarded anyway (see
- above). */
- if (objfile == symfile_objfile)
- {
- gdb_assert (proc_handle.pid == 0);
- keep_thread_db = 1;
- }
-
/* We can only poke around if there actually is a child process.
If there is no child process alive, postpone the steps below
until one has been created. */
target_new_objfile_chain (objfile);
}
+/* Attach to a new thread. This function is called when we receive a
+ TD_CREATE event or when we iterate over all threads and find one
+ that wasn't already in our list. */
+
static void
attach_thread (ptid_t ptid, const td_thrhandle_t *th_p,
const td_thrinfo_t *ti_p, int verbose)
struct thread_info *tp;
td_err_e err;
+ /* If we're being called after a TD_CREATE event, we may already
+ know about this thread. There are two ways this can happen. We
+ may have iterated over all threads between the thread creation
+ and the TD_CREATE event, for instance when the user has issued
+ the `info threads' command before the SIGTRAP for hitting the
+ thread creation breakpoint was reported. Alternatively, the
+ thread may have exited and a new one been created with the same
+ 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_pid (ptid);
+ gdb_assert (tp != NULL);
+
+ if (!tp->private->dying)
+ return;
+
+ delete_thread (ptid);
+ }
+
check_thread_signals ();
/* Add the thread to GDB's thread list. */
static void
detach_thread (ptid_t ptid, int verbose)
{
+ struct thread_info *thread_info;
+
if (verbose)
printf_unfiltered ("[%s exited]\n", target_pid_to_str (ptid));
+
+ /* Don't delete the thread now, because it still reports as active
+ until it has executed a few instructions after the event
+ breakpoint - if we deleted it now, "info threads" would cause us
+ to re-attach to it. Just mark it as having had a TD_DEATH
+ event. This means that we won't delete it from our thread list
+ until we notice that it's dead (via prune_threads), or until
+ something re-uses its thread ID. */
+ thread_info = find_thread_pid (ptid);
+ gdb_assert (thread_info != NULL);
+ thread_info->private->dying = 1;
}
static void
was created and cannot specifically locate the event message for it.
We have to call td_ta_event_getmsg() to get
the latest message. Since we have no way of correlating whether
- the event message we get back corresponds to our breakpoint, we must
+ the event message we get back corresponds to our breakpoint, we must
loop and read all event messages, processing them appropriately.
- This guarantees we will process the correct message before continuing
- from the breakpoint.
+ This guarantees we will process the correct message before continuing
+ from the breakpoint.
Currently, death events are not enabled. If they are enabled,
the death event can use the td_thr_event_getmsg() interface to
switch (msg.event)
{
case TD_CREATE:
-
- /* We may already know about this thread, for instance when the
- user has issued the `info threads' command before the SIGTRAP
- for hitting the thread creation breakpoint was reported. */
- if (!in_thread_list (ptid))
- attach_thread (ptid, msg.th_p, &ti, 1);
+ /* Call attach_thread whether or not we already know about a
+ thread with this thread ID. */
+ attach_thread (ptid, msg.th_p, &ti, 1);
break;
if (!ptid_equal (trap_ptid, null_ptid))
trap_ptid = thread_from_lwp (trap_ptid);
- return thread_from_lwp (ptid);
+ /* Change the ptid back into the higher level PID + TID format.
+ If the thread is dead and no longer on the thread list, we will
+ get back a dead ptid. This can occur if the thread death event
+ gets postponed by other simultaneous events. In such a case,
+ we want to just ignore the event and continue on. */
+ ptid = thread_from_lwp (ptid);
+ if (GET_PID (ptid) == -1)
+ ourstatus->kind = TARGET_WAITKIND_SPURIOUS;
+
+ return ptid;
}
static int
deprecated_read_register_gen (regno, raw);
thread_db_fetch_registers (-1);
- supply_register (regno, raw);
+ regcache_raw_supply (current_regcache, regno, raw);
}
fill_gregset ((gdb_gregset_t *) gregset, -1);
}
static void
-thread_db_create_inferior (char *exec_file, char *allargs, char **env)
+thread_db_create_inferior (char *exec_file, char *allargs, char **env,
+ int from_tty)
{
- if (!keep_thread_db)
- {
- unpush_target (&thread_db_ops);
- using_thread_db = 0;
- }
-
- target_beneath->to_create_inferior (exec_file, allargs, env);
+ unpush_target (&thread_db_ops);
+ using_thread_db = 0;
+ target_beneath->to_create_inferior (exec_file, allargs, env, from_tty);
}
static void
target_beneath->to_mourn_inferior ();
- /* Detach thread_db target ops if not dealing with a statically
- linked threaded program. This allows a corefile to be debugged
- after finishing debugging of a threaded program. At present,
- debugging a statically-linked threaded program is broken, but
- the check is added below in the event that it is fixed in the
- future. */
- if (!keep_thread_db)
- {
- unpush_target (&thread_db_ops);
- using_thread_db = 0;
- }
+ /* Detach thread_db target ops. */
+ unpush_target (&thread_db_ops);
+ using_thread_db = 0;
}
static int
add_target (&thread_db_ops);
/* Add ourselves to objfile event chain. */
- target_new_objfile_chain = target_new_objfile_hook;
- target_new_objfile_hook = thread_db_new_objfile;
+ target_new_objfile_chain = deprecated_target_new_objfile_hook;
+ deprecated_target_new_objfile_hook = thread_db_new_objfile;
}
}