diff options
author | Mark Kettenis <kettenis@gnu.org> | 2000-09-03 18:41:28 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@gnu.org> | 2000-09-03 18:41:28 +0000 |
commit | fb0e1ba78030bbd38e9733f50b79b9b691f59634 (patch) | |
tree | 590274afab0d6f022f637b57a3289ff2976fbd6b /gdb/thread-db.c | |
parent | 205b2e50545ed404e769cbc264633db5c1ca352e (diff) | |
download | gdb-fb0e1ba78030bbd38e9733f50b79b9b691f59634.zip gdb-fb0e1ba78030bbd38e9733f50b79b9b691f59634.tar.gz gdb-fb0e1ba78030bbd38e9733f50b79b9b691f59634.tar.bz2 |
* config/i386/nm-linux.h (PREPARE_TO_PROCEED, ATTCH_LWP,
GET_THREAD_SIGNALS): New defines.
* config/i386/linux.mh (NATDEPFILES): Remove lin-thread.o and
linux-threads.o. Add proc-service.o, thread-db.o and lin-lwp.o.
* proc-service.c: New file.
* thread-db.c: New file.
* lin-lwp.c: New file.
Diffstat (limited to 'gdb/thread-db.c')
-rw-r--r-- | gdb/thread-db.c | 992 |
1 files changed, 992 insertions, 0 deletions
diff --git a/gdb/thread-db.c b/gdb/thread-db.c new file mode 100644 index 0000000..7c3c0ae --- /dev/null +++ b/gdb/thread-db.c @@ -0,0 +1,992 @@ +/* libthread_db assisted debugging support, generic parts. + Copyright 1999, 2000 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#include "defs.h" + +#include "gdb_assert.h" +#include <dlfcn.h> +#include "gdb_proc_service.h" +#include "gdb_thread_db.h" + +#include "gdbthread.h" +#include "inferior.h" +#include "target.h" + +#ifndef LIBTHREAD_DB_SO +#define LIBTHREAD_DB_SO "libthread_db.so.1" +#endif + +/* If we're running on Linux, we must explicitly attach to any new threads. */ + +/* FIXME: There is certainly some room for improvements: + - Cache LWP ids. + - Bypass libthread_db when fetching or storing registers for + threads bound to a LWP. */ + +/* This module's target vector. */ +static struct target_ops thread_db_ops; + +/* The target vector that we call for things this module can't handle. */ +static struct target_ops *target_beneath; + +/* Pointer to the next function on the objfile event chain. */ +static void (*target_new_objfile_chain) (struct objfile *objfile); + +/* Non-zero if we're using this module's target vector. */ +static int using_thread_db; + +/* Non-zero if we have determined the signals used by the threads + library. */ +static int thread_signals; +static sigset_t thread_stop_set; +static sigset_t thread_print_set; + +/* Structure that identifies the child process for the + <proc_service.h> interface. */ +static struct ps_prochandle proc_handle; + +/* Connection to the libthread_db library. */ +static td_thragent_t *thread_agent; + +/* Pointers to the libthread_db functions. */ + +static td_err_e (*td_init_p) (void); + +static td_err_e (*td_ta_new_p) (struct ps_prochandle *ps, td_thragent_t **ta); +static td_err_e (*td_ta_map_id2thr_p) (const td_thragent_t *ta, thread_t pt, + td_thrhandle_t *__th); +static td_err_e (*td_ta_map_lwp2thr_p) (const td_thragent_t *ta, lwpid_t lwpid, + td_thrhandle_t *th); +static td_err_e (*td_ta_thr_iter_p) (const td_thragent_t *ta, + td_thr_iter_f *callback, + void *cbdata_p, td_thr_state_e state, + int ti_pri, sigset_t *ti_sigmask_p, + unsigned int ti_user_flags); +static td_err_e (*td_ta_event_addr_p) (const td_thragent_t *ta, + td_event_e event, td_notify_t *ptr); +static td_err_e (*td_ta_set_event_p) (const td_thragent_t *ta, + td_thr_events_t *event); +static td_err_e (*td_ta_event_getmsg_p) (const td_thragent_t *ta, + td_event_msg_t *msg); + +static td_err_e (*td_thr_validate_p) (const td_thrhandle_t *th); +static td_err_e (*td_thr_get_info_p) (const td_thrhandle_t *th, + td_thrinfo_t *infop); +static td_err_e (*td_thr_getfpregs_p) (const td_thrhandle_t *th, + gdb_prfpregset_t *regset); +static td_err_e (*td_thr_getgregs_p) (const td_thrhandle_t *th, + prgregset_t gregs); +static td_err_e (*td_thr_setfpregs_p) (const td_thrhandle_t *th, + const gdb_prfpregset_t *fpregs); +static td_err_e (*td_thr_setgregs_p) (const td_thrhandle_t *th, + prgregset_t gregs); +static td_err_e (*td_thr_event_enable_p) (const td_thrhandle_t *th, int event); + +/* Location of the thread creation event breakpoint. The code at this + location in the child process will be called by the pthread library + whenever a new thread is created. By setting a special breakpoint + at this location, GDB can detect when a new thread is created. We + obtain this location via the td_ta_event_addr call. */ +static CORE_ADDR td_create_bp_addr; + +/* Location of the thread death event breakpoint. */ +static CORE_ADDR td_death_bp_addr; + +/* Prototypes for local functions. */ +static int find_new_threads_callback (const td_thrhandle_t *th_p, void *data); + + +/* Building process ids. */ + +#ifndef TIDGET +#define TIDGET(PID) (((PID) & 0x7fffffff) >> 16) +#define PIDGET(PID) (((PID) & 0xffff)) +#define MERGEPID(PID, TID) (((PID) & 0xffff) | ((TID) << 16)) +#endif + +#define THREAD_FLAG 0x80000000 + +#define is_lwp(pid) (((pid) & THREAD_FLAG) == 0 && TIDGET (pid)) +#define is_thread(pid) ((pid) & THREAD_FLAG) + +#define GET_PID(pid) PIDGET (pid) +#define GET_LWP(pid) TIDGET (pid) +#define GET_THREAD(pid) TIDGET (pid) + +#define BUILD_LWP(tid, pid) MERGEPID (pid, tid) +#define BUILD_THREAD(tid, pid) (MERGEPID (pid, tid) | THREAD_FLAG) + + +struct private_thread_info +{ + /* Cached LWP id. Must come first, see lin-lwp.c. */ + lwpid_t lwpid; +}; + + +/* Helper functions. */ + +static void +restore_inferior_pid (void *arg) +{ + int *saved_pid_ptr = arg; + inferior_pid = *saved_pid_ptr; + free (arg); +} + +static struct cleanup * +save_inferior_pid (void) +{ + int *saved_pid_ptr; + + saved_pid_ptr = xmalloc (sizeof (int)); + *saved_pid_ptr = inferior_pid; + return make_cleanup (restore_inferior_pid, saved_pid_ptr); +} + + +static char * +thread_db_err_str (td_err_e err) +{ + static char buf[64]; + + switch (err) + { + case TD_OK: + return "generic 'call succeeded'"; + case TD_ERR: + return "generic error"; + case TD_NOTHR: + return "no thread to satisfy query"; + case TD_NOSV: + return "no sync handle to satisfy query"; + case TD_NOLWP: + return "no LWP to satisfy query"; + case TD_BADPH: + return "invalid process handle"; + case TD_BADTH: + return "invalid thread handle"; + case TD_BADSH: + return "invalid synchronization handle"; + case TD_BADTA: + return "invalid thread agent"; + case TD_BADKEY: + return "invalid key"; + case TD_NOMSG: + return "no event message for getmsg"; + case TD_NOFPREGS: + return "FPU register set not available"; + case TD_NOLIBTHREAD: + return "application not linked with libthread"; + case TD_NOEVENT: + return "requested event is not supported"; + case TD_NOCAPAB: + return "capability not available"; + case TD_DBERR: + return "debugger service failed"; + case TD_NOAPLIC: + return "operation not applicable to"; + case TD_NOTSD: + return "no thread-specific data for this thread"; + case TD_MALLOC: + return "malloc failed"; + case TD_PARTIALREG: + return "only part of register set was written/read"; + case TD_NOXREGS: + return "X register set not available for this thread"; + default: + snprintf (buf, sizeof (buf), "unknown thread_db error '%d'", err); + return buf; + } +} + +static char * +thread_db_state_str (td_thr_state_e state) +{ + static char buf[64]; + + switch (state) + { + case TD_THR_STOPPED: + return "stopped by debugger"; + case TD_THR_RUN: + return "runnable"; + case TD_THR_ACTIVE: + return "active"; + case TD_THR_ZOMBIE: + return "zombie"; + case TD_THR_SLEEP: + return "sleeping"; + case TD_THR_STOPPED_ASLEEP: + return "stopped by debugger AND blocked"; + default: + snprintf (buf, sizeof (buf), "unknown thread_db state %d", state); + return buf; + } +} + + +/* Convert between user-level thread ids and LWP ids. */ + +static int +thread_from_lwp (int pid) +{ + td_thrinfo_t ti; + td_thrhandle_t th; + td_err_e err; + + if (GET_LWP (pid) == 0) + pid = BUILD_LWP (pid, pid); + + gdb_assert (is_lwp (pid)); + + err = td_ta_map_lwp2thr_p (thread_agent, GET_LWP (pid), &th); + if (err != TD_OK) + error ("Cannot find user-level thread for LWP %d: %s", + GET_LWP (pid), thread_db_err_str (err)); + + err = td_thr_get_info_p (&th, &ti); + if (err != TD_OK) + error ("Cannot get thread info: %s", thread_db_err_str (err)); + + return BUILD_THREAD (ti.ti_tid, GET_PID (pid)); +} + +static int +lwp_from_thread (int pid) +{ + td_thrinfo_t ti; + td_thrhandle_t th; + td_err_e err; + + if (! is_thread (pid)) + return pid; + + err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (pid), &th); + if (err != TD_OK) + error ("Cannot find thread %ld: %s", + (long) GET_THREAD (pid), thread_db_err_str (err)); + + err = td_thr_get_info_p (&th, &ti); + if (err != TD_OK) + error ("Cannot get thread info: %s", thread_db_err_str (err)); + + return BUILD_LWP (ti.ti_lid, GET_PID (pid)); +} + + +void +thread_db_init (struct target_ops *target) +{ + target_beneath = target; +} + +static int +thread_db_load (void) +{ + void *handle; + td_err_e err; + + handle = dlopen (LIBTHREAD_DB_SO, RTLD_NOW); + if (handle == NULL) + return 0; + + /* Initialize pointers to the dynamic library functions we will use. + Essential functions first. */ + + td_init_p = dlsym (handle, "td_init"); + if (td_init_p == NULL) + return 0; + + td_ta_new_p = dlsym (handle, "td_ta_new"); + if (td_ta_new_p == NULL) + return 0; + + td_ta_map_id2thr_p = dlsym (handle, "td_ta_map_id2thr"); + if (td_ta_map_id2thr_p == NULL) + return 0; + + td_ta_map_lwp2thr_p = dlsym (handle, "td_ta_map_lwp2thr"); + if (td_ta_map_lwp2thr_p == NULL) + return 0; + + td_ta_thr_iter_p = dlsym (handle, "td_ta_thr_iter"); + if (td_ta_thr_iter_p == NULL) + return 0; + + td_thr_validate_p = dlsym (handle, "td_thr_validate"); + if (td_thr_validate_p == NULL) + return 0; + + td_thr_get_info_p = dlsym (handle, "td_thr_get_info"); + if (td_thr_get_info_p == NULL) + return 0; + + td_thr_getfpregs_p = dlsym (handle, "td_thr_getfpregs"); + if (td_thr_getfpregs_p == NULL) + return 0; + + td_thr_getgregs_p = dlsym (handle, "td_thr_getgregs"); + if (td_thr_getgregs_p == NULL) + return 0; + + td_thr_setfpregs_p = dlsym (handle, "td_thr_setfpregs"); + if (td_thr_setfpregs_p == NULL) + return 0; + + td_thr_setgregs_p = dlsym (handle, "td_thr_setgregs"); + if (td_thr_setgregs_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"); + + return 1; +} + +static void +enable_thread_event_reporting (void) +{ + td_thr_events_t events; + td_notify_t notify; + td_err_e err; + + /* We cannot use the thread event reporting facility if these + functions aren't available. */ + if (td_ta_event_addr_p == NULL || td_ta_set_event_p == NULL + || td_ta_event_getmsg_p == NULL || td_thr_event_enable_p == NULL) + return; + + /* Set the process wide mask saying which events we're interested in. */ + td_event_emptyset (&events); + td_event_addset (&events, TD_CREATE); +#if 0 + /* 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); +#endif + + err = td_ta_set_event_p (thread_agent, &events); + if (err != TD_OK) + { + warning ("Unable to set global thread event mask: %s", + thread_db_err_str (err)); + return; + } + + /* Delete previous thread event breakpoints, if any. */ + remove_thread_event_breakpoints (); + + /* Get address for thread creation breakpoint. */ + err = td_ta_event_addr_p (thread_agent, TD_CREATE, ¬ify); + if (err != TD_OK) + { + warning ("Unable to get location for thread creation breakpoint: %s", + thread_db_err_str (err)); + return; + } + + /* Set up the breakpoint. */ + td_create_bp_addr = (CORE_ADDR) notify.u.bptaddr; + create_thread_event_breakpoint (td_create_bp_addr); + + /* Get address for thread death breakpoint. */ + err = td_ta_event_addr_p (thread_agent, TD_DEATH, ¬ify); + if (err != TD_OK) + { + warning ("Unable to get location for thread creation breakpoint: %s", + thread_db_err_str (err)); + return; + } + + /* Set up the breakpoint. */ + td_death_bp_addr = (CORE_ADDR) notify.u.bptaddr; + create_thread_event_breakpoint (td_death_bp_addr); +} + +static void +disable_thread_event_reporting (void) +{ + td_thr_events_t events; + + /* Set the process wide mask saying we aren't interested in any + events anymore. */ + td_event_emptyset (&events); + td_ta_set_event_p (thread_agent, &events); + + /* Delete thread event breakpoints, if any. */ + remove_thread_event_breakpoints (); + td_create_bp_addr = 0; + td_death_bp_addr = 0; +} + +static void +check_thread_signals (void) +{ +#ifdef GET_THREAD_SIGNALS + if (! thread_signals) + { + sigset_t mask; + int i; + + GET_THREAD_SIGNALS (&mask); + sigemptyset (&thread_stop_set); + sigemptyset (&thread_print_set); + + for (i = 0; i < NSIG; i++) + { + if (sigismember (&mask, i)) + { + if (signal_stop_update (target_signal_from_host (i), 0)) + sigaddset (&thread_stop_set, i); + if (signal_print_update (target_signal_from_host (i), 0)) + sigaddset (&thread_print_set, i); + thread_signals = 1; + } + } + } +#endif +} + +static void +disable_thread_signals (void) +{ +#ifdef GET_THREAD_SIGNALS + if (thread_signals) + { + int i; + + for (i = 0; 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_push_target (void) +{ + using_thread_db = 1; + + /* Push this target vector. */ + push_target (&thread_db_ops); + + enable_thread_event_reporting (); +} + +static void +thread_db_unpush_target (void) +{ + /* Unpush this target vector. */ + unpush_target (&thread_db_ops); + + using_thread_db = 0; +} + +static void +thread_db_new_objfile (struct objfile *objfile) +{ + td_err_e err; + + if (using_thread_db) + /* Nothing to do. The thread library was already detected and the + target vector was already activated. */ + goto quit; + + if (objfile == NULL) + /* Un-interesting object file. */ + goto quit; + + /* Initialize the structure that identifies the child process. */ + proc_handle.pid = GET_PID (inferior_pid); + + /* Now attempt to open a connection to the thread library running in + the child process. */ + err = td_ta_new_p (&proc_handle, &thread_agent); + switch (err) + { + case TD_NOLIBTHREAD: + /* No thread library found in the child process, probably + because the child process isn't running yet. */ + break; + + case TD_OK: + /* The thread library was detected in the child; we go live now! */ + thread_db_push_target (); + + /* Find all user-space threads. */ + err = td_ta_thr_iter_p (thread_agent, find_new_threads_callback, + &inferior_pid, TD_THR_ANY_STATE, + TD_THR_LOWEST_PRIORITY, TD_SIGNO_MASK, + TD_THR_ANY_USER_FLAGS); + if (err != TD_OK) + error ("Finding new threads failed: %s", thread_db_err_str (err)); + break; + + default: + warning ("Cannot initialize thread debugging library: %s", + thread_db_err_str (err)); + break; + } + + quit: + if (target_new_objfile_chain) + target_new_objfile_chain (objfile); +} + +static void +attach_thread (int pid, const td_thrhandle_t *th_p, + const td_thrinfo_t *ti_p, int verbose) +{ + struct thread_info *tp; + td_err_e err; + + check_thread_signals (); + + if (verbose) + printf_unfiltered ("[New %s]\n", target_pid_to_str (pid)); + + /* Add the thread to GDB's thread list. */ + tp = add_thread (pid); + tp->private = xmalloc (sizeof (struct private_thread_info)); + tp->private->lwpid = ti_p->ti_lid; + + /* Under Linux, we have to attach to each and every thread. */ +#ifdef ATTACH_LWP + if (ti_p->ti_lid != GET_PID (pid)) + ATTACH_LWP (BUILD_LWP (ti_p->ti_lid, GET_PID (pid)), 0); +#endif + + /* Enable thread event reporting for this thread. */ + err = td_thr_event_enable_p (th_p, 1); + if (err != TD_OK) + error ("Cannot enable thread event reporting for %s: %s", + target_pid_to_str (pid), thread_db_err_str (err)); +} + +static void +detach_thread (int pid, int verbose) +{ + if (verbose) + printf_unfiltered ("[%s exited]\n", target_pid_to_str (pid)); +} + +static void +thread_db_detach (char *args, int from_tty) +{ + disable_thread_event_reporting (); + thread_db_unpush_target (); + + target_beneath->to_detach (args, from_tty); +} + +static void +thread_db_resume (int pid, int step, enum target_signal signo) +{ + struct cleanup *old_chain = save_inferior_pid (); + + if (pid == -1) + inferior_pid = lwp_from_thread (inferior_pid); + else if (is_thread (pid)) + pid = lwp_from_thread (pid); + + target_beneath->to_resume (pid, step, signo); + + do_cleanups (old_chain); +} + +/* Check if PID is currently stopped at the location of a thread event + breakpoint location. If it is, read the event message and act upon + the event. */ + +static void +check_event (int pid) +{ + td_event_msg_t msg; + td_thrinfo_t ti; + td_err_e err; + CORE_ADDR stop_pc; + + /* Bail out early if we're not at a thread event breakpoint. */ + stop_pc = read_pc_pid (pid) - DECR_PC_AFTER_BREAK; + if (stop_pc != td_create_bp_addr && stop_pc != td_death_bp_addr) + return; + + err = td_ta_event_getmsg_p (thread_agent, &msg); + if (err != TD_OK) + { + if (err == TD_NOMSG) + return; + + error ("Cannot get thread event message: %s", thread_db_err_str (err)); + } + + err = td_thr_get_info_p (msg.th_p, &ti); + if (err != TD_OK) + error ("Cannot get thread info: %s", thread_db_err_str (err)); + + pid = BUILD_THREAD (ti.ti_tid, GET_PID (pid)); + + switch (msg.event) + { + case TD_CREATE: +#if 0 + /* FIXME: kettenis/2000-08-26: Since we use td_ta_event_getmsg, + there is no guarantee that the breakpoint will match the + event. Should we use td_thr_event_getmsg instead? */ + + if (stop_pc != td_create_bp_addr) + error ("Thread creation event doesn't match breakpoint."); +#endif + + if (in_thread_list (pid)) + error ("Spurious thread creation event."); + + attach_thread (pid, msg.th_p, &ti, 1); + return; + + case TD_DEATH: +#if 0 + /* FIXME: See TD_CREATE. */ + + if (stop_pc != td_death_bp_addr) + error ("Thread death event doesn't match breakpoint."); +#endif + + if (! in_thread_list (pid)) + error ("Spurious thread death event."); + + detach_thread (pid, 1); + return; + + default: + error ("Spurious thread event."); + } +} + +static int +thread_db_wait (int pid, struct target_waitstatus *ourstatus) +{ + extern int trap_pid; + + if (pid != -1 && is_thread (pid)) + pid = lwp_from_thread (pid); + + pid = target_beneath->to_wait (pid, ourstatus); + + if (ourstatus->kind == TARGET_WAITKIND_EXITED) + return -1; + + if (ourstatus->kind == TARGET_WAITKIND_STOPPED + && ourstatus->value.sig == TARGET_SIGNAL_TRAP) + /* Check for a thread event. */ + check_event (pid); + + if (trap_pid) + trap_pid = thread_from_lwp (trap_pid); + + return thread_from_lwp (pid); +} + +static int +thread_db_xfer_memory (CORE_ADDR memaddr, char *myaddr, int len, int write, + struct target_ops *target) +{ + struct cleanup *old_chain = save_inferior_pid (); + int xfer; + + if (is_thread (inferior_pid)) + { + /* FIXME: This seems to be necessary to make sure breakpoints + are removed. */ + if (! target_thread_alive (inferior_pid)) + inferior_pid = GET_PID (inferior_pid); + else + inferior_pid = lwp_from_thread (inferior_pid); + } + + xfer = target_beneath->to_xfer_memory (memaddr, myaddr, len, write, target); + + do_cleanups (old_chain); + return xfer; +} + +static void +thread_db_fetch_registers (int regno) +{ + td_thrhandle_t th; + prgregset_t gregset; + gdb_prfpregset_t fpregset; + td_err_e err; + + if (! is_thread (inferior_pid)) + { + /* Pass the request to the target beneath us. */ + target_beneath->to_fetch_registers (regno); + return; + } + + err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (inferior_pid), &th); + if (err != TD_OK) + error ("Cannot find thread %ld: %s", + (long) GET_THREAD (inferior_pid), thread_db_err_str (err)); + + err = td_thr_getgregs_p (&th, gregset); + if (err != TD_OK) + error ("Cannot fetch general-purpose registers for thread %ld: %s", + (long) GET_THREAD (inferior_pid), thread_db_err_str (err)); + + err = td_thr_getfpregs_p (&th, &fpregset); + if (err != TD_OK) + error ("Cannot get floating-point registers for thread %ld: %s", + (long) GET_THREAD (inferior_pid), thread_db_err_str (err)); + + /* Note that we must call supply_gregset after calling the thread_db + routines because the thread_db routines call ps_lgetgregs and + friends which clobber GDB's register cache. */ + supply_gregset ((gdb_gregset_t *) gregset); + supply_fpregset (&fpregset); +} + +static void +thread_db_store_registers (int regno) +{ + td_thrhandle_t th; + prgregset_t gregset; + gdb_prfpregset_t fpregset; + td_err_e err; + + if (! is_thread (inferior_pid)) + { + /* Pass the request to the target beneath us. */ + target_beneath->to_store_registers (regno); + return; + } + + err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (inferior_pid), &th); + if (err != TD_OK) + error ("Cannot find thread %ld: %s", + (long) GET_THREAD (inferior_pid), thread_db_err_str (err)); + + if (regno != -1) + { + char raw[MAX_REGISTER_RAW_SIZE]; + + read_register_gen (regno, raw); + thread_db_fetch_registers (-1); + supply_register (regno, raw); + } + + fill_gregset ((gdb_gregset_t *) gregset, -1); + fill_fpregset (&fpregset, -1); + + err = td_thr_setgregs_p (&th, gregset); + if (err != TD_OK) + error ("Cannot store general-purpose registers for thread %ld: %s", + (long) GET_THREAD (inferior_pid), thread_db_err_str (err)); + err = td_thr_setfpregs_p (&th, &fpregset); + if (err != TD_OK) + error ("Cannot store floating-point registers for thread %ld: %s", + (long) GET_THREAD (inferior_pid), thread_db_err_str (err)); +} + +static void +thread_db_kill (void) +{ + target_beneath->to_kill (); +} + +static void +thread_db_create_inferior (char *exec_file, char *allargs, char **env) +{ + /* We never want to actually create the inferior! If this is ever + called, it means we were on the target stack when the user said + "run". But we don't want to be on the new inferior's target + stack until the libthread_db connection is ready to be made. So + we unpush ourselves from the stack, and then invoke + find_default_create_inferior, which will invoke the appropriate + process_stratum target to do the create. */ + + thread_db_unpush_target (); + + find_default_create_inferior (exec_file, allargs, env); +} + +static void +thread_db_mourn_inferior (void) +{ + remove_thread_event_breakpoints (); + thread_db_unpush_target (); + + target_beneath->to_mourn_inferior (); +} + +static int +thread_db_thread_alive (int pid) +{ + if (is_thread (pid)) + { + td_thrhandle_t th; + td_err_e err; + + err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (pid), &th); + if (err != TD_OK) + return 0; + + err = td_thr_validate_p (&th); + if (err != TD_OK) + return 0; + + return 1; + } + + if (target_beneath->to_thread_alive) + return target_beneath->to_thread_alive (pid); + + return 0; +} + +static int +find_new_threads_callback (const td_thrhandle_t *th_p, void *data) +{ + td_thrinfo_t ti; + td_err_e err; + int pid; + + err = td_thr_get_info_p (th_p, &ti); + if (err != TD_OK) + error ("Cannot get thread info: %s", thread_db_err_str (err)); + + pid = BUILD_THREAD (ti.ti_tid, GET_PID (inferior_pid)); + + if (! in_thread_list (pid)) + attach_thread (pid, th_p, &ti, 1); + + return 0; +} + +static void +thread_db_find_new_threads (void) +{ + td_err_e err; + + /* Iterate over all user-space threads to discover new threads. */ + err = td_ta_thr_iter_p (thread_agent, find_new_threads_callback, NULL, + TD_THR_ANY_STATE, TD_THR_LOWEST_PRIORITY, + TD_SIGNO_MASK, TD_THR_ANY_USER_FLAGS); + if (err != TD_OK) + error ("Cannot find new threads: %s", thread_db_err_str (err)); +} + +static char * +thread_db_pid_to_str (int pid) +{ + if (is_thread (pid)) + { + static char buf[64]; + td_thrhandle_t th; + td_thrinfo_t ti; + td_err_e err; + + err = td_ta_map_id2thr_p (thread_agent, GET_THREAD (pid), &th); + if (err != TD_OK) + error ("Cannot find thread %ld: %s", + (long) GET_THREAD (pid), thread_db_err_str (err)); + + err = td_thr_get_info_p (&th, &ti); + if (err != TD_OK) + error ("Cannot get thread info for thread %ld: %s", + (long) GET_THREAD (pid), thread_db_err_str (err)); + + if (ti.ti_state == TD_THR_ACTIVE && ti.ti_lid != 0) + { + snprintf (buf, sizeof (buf), "Thread %ld (LWP %d)", + (long) ti.ti_tid, ti.ti_lid); + } + else + { + snprintf (buf, sizeof (buf), "Thread %ld (%s)", + (long) ti.ti_tid, thread_db_state_str (ti.ti_state)); + } + + return buf; + } + + if (target_beneath->to_pid_to_str (pid)) + return target_beneath->to_pid_to_str (pid); + + return normal_pid_to_str (pid); +} + +static void +init_thread_db_ops (void) +{ + thread_db_ops.to_shortname = "multi-thread"; + thread_db_ops.to_longname = "multi-threaded child process."; + thread_db_ops.to_doc = "Threads and pthreads support."; + thread_db_ops.to_detach = thread_db_detach; + thread_db_ops.to_resume = thread_db_resume; + thread_db_ops.to_wait = thread_db_wait; + thread_db_ops.to_fetch_registers = thread_db_fetch_registers; + thread_db_ops.to_store_registers = thread_db_store_registers; + thread_db_ops.to_xfer_memory = thread_db_xfer_memory; + thread_db_ops.to_kill = thread_db_kill; + thread_db_ops.to_create_inferior = thread_db_create_inferior; + thread_db_ops.to_mourn_inferior = thread_db_mourn_inferior; + thread_db_ops.to_thread_alive = thread_db_thread_alive; + thread_db_ops.to_find_new_threads = thread_db_find_new_threads; + thread_db_ops.to_pid_to_str = thread_db_pid_to_str; + thread_db_ops.to_stratum = thread_stratum; + thread_db_ops.to_has_thread_control = tc_schedlock; + thread_db_ops.to_magic = OPS_MAGIC; +} + +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. */ + target_new_objfile_chain = target_new_objfile_hook; + target_new_objfile_hook = thread_db_new_objfile; + } +} |