--- /dev/null
+/* Copyright (C) 2008 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 3 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, see <http://www.gnu.org/licenses/>. */
+
+#include "defs.h"
+#include "command.h"
+#include "gdbcmd.h"
+#include "target.h"
+#include "observer.h"
+#include <sys/procfs.h>
+#include "gregset.h"
+#include "regcache.h"
+#include "inferior.h"
+#include "gdbthread.h"
+
+#include <pthread_debug.h>
+
+/* Print debugging traces if set to non-zero. */
+static int debug_dec_thread = 0;
+
+/* Non-zero if the dec-thread layer is active. */
+static int dec_thread_active = 0;
+
+/* The pthread_debug context. */
+pthreadDebugContext_t debug_context;
+
+/* The dec-thread target_ops structure. */
+static struct target_ops dec_thread_ops;
+
+/* A copy of the target_ops over which our dec_thread_ops is pushed. */
+static struct target_ops base_target;
+
+/* Print a debug trace if DEBUG_DEC_THREAD is set (its value is adjusted
+ by the user using "set debug dec-thread ..."). */
+
+static void
+debug (char *format, ...)
+{
+ if (debug_dec_thread)
+ {
+ va_list args;
+
+ va_start (args, format);
+ printf_unfiltered ("DEC Threads: ");
+ vprintf_unfiltered (format, args);
+ printf_unfiltered ("\n");
+ va_end (args);
+ }
+}
+
+/* pthread debug callbacks. */
+
+static int
+suspend_clbk (void *caller_context)
+{
+ return ESUCCESS;
+}
+
+static int
+resume_clbk (void *caller_context)
+{
+ return ESUCCESS;
+}
+
+static int
+hold_clbk (void *caller_context, pthreadDebugKId_t kernel_tid)
+{
+ return ESUCCESS;
+}
+
+static int
+unhold_clbk (void *caller_context, pthreadDebugKId_t kernel_tid)
+{
+ return ESUCCESS;
+}
+
+static int
+read_clbk (void *caller_context, void *address, void *buffer,
+ unsigned long size)
+{
+ int status = target_read_memory ((CORE_ADDR) address, buffer, size);
+
+ if (status != 0)
+ return EINVAL;
+
+ return ESUCCESS;
+}
+
+static int
+write_clbk (void *caller_context, void *address, void *buffer,
+ unsigned long size)
+{
+ int status = target_write_memory ((CORE_ADDR) address, buffer, size);
+
+ if (status != 0)
+ return EINVAL;
+
+ return ESUCCESS;
+}
+
+/* Get integer regs */
+
+static int
+get_reg_clbk(void *caller_context, pthreadDebugGetRegRtn_t regs,
+ pthreadDebugKId_t kernel_tid)
+{
+ debug ("get_reg_clbk");
+
+ /* Not sure that we actually need to do anything in this callback. */
+ return ESUCCESS;
+}
+
+/* Set integer regs */
+
+static int
+set_reg_clbk(void *caller_context, const pthreadDebugRegs_t *regs,
+ pthreadDebugKId_t kernel_tid)
+{
+ debug ("set_reg_clbk");
+
+ /* Not sure that we actually need to do anything in this callback. */
+ return ESUCCESS;
+}
+
+static int
+output_clbk (void *caller_context, char *line)
+{
+ printf_filtered ("%s\n", line);
+ return ESUCCESS;
+}
+
+static int
+error_clbk (void *caller_context, char *line)
+{
+ fprintf_filtered (gdb_stderr, "%s\n", line);
+ return ESUCCESS;
+}
+
+/* Get floating-point regs. */
+
+static int
+get_fpreg_clbk (void *caller_context, pthreadDebugFregs_p fregs,
+ pthreadDebugKId_t kernel_tid)
+{
+ debug ("get_fpreg_clbk");
+
+ /* Not sure that we actually need to do anything in this callback. */
+ return ESUCCESS;
+}
+
+/* Set floating-point regs. */
+
+static int
+set_fpreg_clbk (void *caller_context, const pthreadDebugFregs_t *fregs,
+ pthreadDebugKId_t kernel_tid)
+{
+ debug ("set_fpreg_clbk");
+
+ /* Not sure that we actually need to do anything in this callback. */
+ return ESUCCESS;
+}
+
+static void *
+malloc_clbk (void *caller_context, size_t size)
+{
+ return xmalloc (size);
+}
+
+static void
+free_clbk (void *caller_context, void *address)
+{
+ xfree (address);
+}
+
+static int
+kthdinfo_clbk (pthreadDebugClient_t caller_context,
+ pthreadDebugKId_t kernel_tid,
+ pthreadDebugKThreadInfo_p thread_info)
+{
+ return ENOTSUP;
+}
+
+static int
+speckthd_clbk (pthreadDebugClient_t caller_context,
+ pthreadDebugSpecialType_t type,
+ pthreadDebugKId_t *kernel_tid)
+{
+ return ENOTSUP;
+}
+
+static pthreadDebugCallbacks_t debug_callbacks =
+{
+ PTHREAD_DEBUG_VERSION,
+ (pthreadDebugGetMemRtn_t) read_clbk,
+ (pthreadDebugSetMemRtn_t) write_clbk,
+ suspend_clbk,
+ resume_clbk,
+ kthdinfo_clbk,
+ hold_clbk,
+ unhold_clbk,
+ (pthreadDebugGetFregRtn_t) get_fpreg_clbk,
+ (pthreadDebugSetFregRtn_t) set_fpreg_clbk,
+ (pthreadDebugGetRegRtn_t) get_reg_clbk,
+ (pthreadDebugSetRegRtn_t) set_reg_clbk,
+ (pthreadDebugOutputRtn_t) output_clbk,
+ (pthreadDebugOutputRtn_t) error_clbk,
+ malloc_clbk,
+ free_clbk,
+ speckthd_clbk
+};
+
+/* Activate thread support if appropriate. Do nothing if thread
+ support is already active. */
+
+static void
+enable_dec_thread (void)
+{
+ struct minimal_symbol *msym;
+ void* caller_context;
+ int status;
+
+ /* If already active, nothing more to do. */
+ if (dec_thread_active)
+ return;
+
+ msym = lookup_minimal_symbol ("__pthread_dbg_symtable", NULL, NULL);
+ if (msym == NULL)
+ {
+ debug ("enable_dec_thread: No __pthread_dbg_symtable");
+ return;
+ }
+
+ status = pthreadDebugContextInit (&caller_context, &debug_callbacks,
+ (void *) SYMBOL_VALUE_ADDRESS (msym),
+ &debug_context);
+ if (status != ESUCCESS)
+ {
+ debug ("enable_dec_thread: pthreadDebugContextInit -> %d",
+ status);
+ return;
+ }
+
+ base_target = current_target;
+ push_target (&dec_thread_ops);
+ dec_thread_active = 1;
+
+ debug ("enable_dec_thread: Thread support enabled.");
+}
+
+/* Deactivate thread support. Do nothing is thread support is
+ already inactive. */
+
+static void
+disable_dec_thread (void)
+{
+ if (!dec_thread_active)
+ return;
+
+ pthreadDebugContextDestroy (debug_context);
+ unpush_target (&dec_thread_ops);
+ dec_thread_active = 0;
+}
+
+/* A structure that contains a thread ID and is associated
+ pthreadDebugThreadInfo_t data. */
+
+struct dec_thread_info
+{
+ pthreadDebugId_t thread;
+ pthreadDebugThreadInfo_t info;
+};
+typedef struct dec_thread_info dec_thread_info_s;
+
+/* The list of user threads. */
+
+DEF_VEC_O (dec_thread_info_s);
+VEC(dec_thread_info_s) *dec_thread_list;
+
+/* Release the memory used by the given VECP thread list pointer.
+ Then set *VECP to NULL. */
+
+static void
+free_dec_thread_info_vec (VEC(dec_thread_info_s) **vecp)
+{
+ int i;
+ struct dec_thread_info *item;
+ VEC(dec_thread_info_s) *vec = *vecp;
+
+ for (i = 0; VEC_iterate (dec_thread_info_s, vec, i, item); i++)
+ xfree (item);
+ VEC_free (dec_thread_info_s, vec);
+ *vecp = NULL;
+}
+
+/* Return a thread's ptid given its associated INFO. */
+
+static ptid_t
+ptid_build_from_info (struct dec_thread_info info)
+{
+ int pid = ptid_get_pid (inferior_ptid);
+
+ return ptid_build (pid, 0, (long) info.thread);
+}
+
+/* Recompute the list of user threads and store the result in
+ DEC_THREAD_LIST. */
+
+static void
+update_dec_thread_list (void)
+{
+ pthreadDebugId_t thread;
+ pthreadDebugThreadInfo_t info;
+ int res;
+
+ free_dec_thread_info_vec (&dec_thread_list);
+ res = pthreadDebugThdSeqInit (debug_context, &thread);
+ while (res == ESUCCESS)
+ {
+
+ res = pthreadDebugThdGetInfo (debug_context, thread, &info);
+ if (res != ESUCCESS)
+ warning (_("unable to get thread info, ignoring thread %ld"),
+ thread);
+ else if (info.kind == PTHREAD_DEBUG_THD_KIND_INITIAL
+ || info.kind == PTHREAD_DEBUG_THD_KIND_NORMAL)
+ {
+ struct dec_thread_info *item =
+ xmalloc (sizeof (struct dec_thread_info));
+
+ item->thread = thread;
+ item->info = info;
+ VEC_safe_push (dec_thread_info_s, dec_thread_list, item);
+ }
+ res = pthreadDebugThdSeqNext (debug_context, &thread);
+ }
+ pthreadDebugThdSeqDestroy (debug_context);
+}
+
+/* A callback to count the number of threads known to GDB. */
+
+static int
+dec_thread_count_gdb_threads (struct thread_info *ignored, void *context)
+{
+ int *count = (int *) context;
+
+ *count++;
+ return 0;
+}
+
+/* A callback that saves the given thread INFO at the end of an
+ array. The end of the array is given in the CONTEXT and is
+ incremented once the info has been added. */
+
+static int
+dec_thread_add_gdb_thread (struct thread_info *info, void *context)
+{
+ struct thread_info ***listp = (struct thread_info ***) context;
+
+ **listp = info;
+ *listp++;
+ return 0;
+}
+
+/* Resynchronize the list of threads known by GDB with the actual
+ list of threads reported by libpthread_debug. */
+
+static void
+resync_thread_list (void)
+{
+ int i;
+ struct dec_thread_info *info;
+ int num_gdb_threads = 0;
+ struct thread_info **gdb_thread_list;
+ struct thread_info **next_thread_info;
+
+ update_dec_thread_list ();
+
+ /* Add new threads. */
+
+ for (i = 0; VEC_iterate (dec_thread_info_s, dec_thread_list, i, info);
+ i++)
+ {
+ ptid_t ptid = ptid_build_from_info (*info);
+
+ if (!in_thread_list (ptid))
+ add_thread (ptid);
+ }
+
+ /* Remove threads that no longer exist. To help with the search,
+ we build an array of GDB threads, and then iterate over this
+ array. */
+
+ iterate_over_threads (dec_thread_count_gdb_threads,
+ (void *) &num_gdb_threads);
+ gdb_thread_list = alloca (num_gdb_threads * sizeof (struct thread_info *));
+ next_thread_info = gdb_thread_list;
+ iterate_over_threads (dec_thread_add_gdb_thread, (void *) &next_thread_info);
+ for (i = 0; i < num_gdb_threads; i++)
+ {
+ int j;
+
+ for (j = 0; VEC_iterate (dec_thread_info_s, dec_thread_list, j, info);
+ j++)
+ if (ptid_equal (gdb_thread_list[i]->ptid,
+ ptid_build_from_info (*info)))
+ break;
+ delete_thread (gdb_thread_list[i]->ptid);
+ }
+}
+
+/* The "to_detach" method of the dec_thread_ops. */
+
+static void
+dec_thread_detach (char *args, int from_tty)
+{
+ debug ("dec_thread_detach");
+
+ disable_dec_thread ();
+ base_target.to_detach (args, from_tty);
+}
+
+/* Return the ptid of the thread that is currently active. */
+
+static ptid_t
+get_active_ptid (void)
+{
+ int i;
+ struct dec_thread_info *info;
+
+ for (i = 0; VEC_iterate (dec_thread_info_s, dec_thread_list, i, info);
+ i++)
+ if (info->info.state == PTHREAD_DEBUG_STATE_RUNNING)
+ return ptid_build_from_info (*info);
+
+ /* No active thread found. This can happen when the program
+ has just exited. */
+ return null_ptid;
+}
+
+/* The "to_wait" method of the dec_thread_ops. */
+
+static ptid_t
+dec_thread_wait (ptid_t ptid, struct target_waitstatus *status)
+{
+ ptid_t active_ptid;
+
+ debug ("dec_thread_wait");
+
+ ptid = base_target.to_wait (ptid, status);
+
+ /* The ptid returned by the base_target is the ptid of the process.
+ We need to find which thread is currently active and return its
+ ptid. */
+ resync_thread_list ();
+ active_ptid = get_active_ptid ();
+ if (ptid_equal (active_ptid, null_ptid))
+ return ptid;
+ return active_ptid;
+}
+
+/* Fetch the general purpose and floating point registers for the given
+ thread TID, and store the result in GREGSET and FPREGSET. Return
+ zero if successful. */
+
+static int
+dec_thread_get_regsets (pthreadDebugId_t tid, gdb_gregset_t *gregset,
+ gdb_fpregset_t *fpregset)
+{
+ int res;
+ pthreadDebugRegs_t regs;
+ pthreadDebugFregs_t fregs;
+
+ res = pthreadDebugThdGetReg (debug_context, tid, ®s);
+ if (res != ESUCCESS)
+ {
+ debug ("dec_thread_fetch_registers: pthreadDebugThdGetReg -> %d", res);
+ return -1;
+ }
+ memcpy (gregset->regs, ®s, sizeof (regs));
+
+ res = pthreadDebugThdGetFreg (debug_context, tid, &fregs);
+ if (res != ESUCCESS)
+ {
+ debug ("dec_thread_fetch_registers: pthreadDebugThdGetFreg -> %d", res);
+ return -1;
+ }
+ memcpy (fpregset->regs, &fregs, sizeof (fregs));
+
+ return 0;
+}
+
+/* The "to_fetch_registers" method of the dec_thread_ops.
+
+ Because the dec-thread debug API doesn't allow us to fetch
+ only one register, we simply ignore regno and fetch+supply all
+ registers. */
+
+static void
+dec_thread_fetch_registers (struct regcache *regcache, int regno)
+{
+ pthreadDebugId_t tid = ptid_get_tid (inferior_ptid);
+ gregset_t gregset;
+ fpregset_t fpregset;
+ int res;
+
+ debug ("dec_thread_fetch_registers (tid=%ld, regno=%d)", tid, regno);
+
+
+ if (tid == 0 || ptid_equal (inferior_ptid, get_active_ptid ()))
+ {
+ base_target.to_fetch_registers (regcache, regno);
+ return;
+ }
+
+ res = dec_thread_get_regsets (tid, &gregset, &fpregset);
+ if (res != 0)
+ return;
+
+ supply_gregset (regcache, &gregset);
+ supply_fpregset (regcache, &fpregset);
+}
+
+/* Store the registers given in GREGSET and FPREGSET into the associated
+ general purpose and floating point registers of thread TID. Return
+ zero if successful. */
+
+static int
+dec_thread_set_regsets (pthreadDebugId_t tid, gdb_gregset_t gregset,
+ gdb_fpregset_t fpregset)
+{
+ int res;
+ pthreadDebugRegs_t regs;
+ pthreadDebugFregs_t fregs;
+
+ memcpy (®s, gregset.regs, sizeof (regs));
+ res = pthreadDebugThdSetReg (debug_context, tid, ®s);
+ if (res != ESUCCESS)
+ {
+ debug ("dec_thread_fetch_registers: pthreadDebugThdSetReg -> %d", res);
+ return -1;
+ }
+
+ memcpy (&fregs, fpregset.regs, sizeof (fregs));
+ res = pthreadDebugThdSetFreg (debug_context, tid, &fregs);
+ if (res != ESUCCESS)
+ {
+ debug ("dec_thread_fetch_registers: pthreadDebugThdSetFreg -> %d", res);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* The "to_store_registers" method of the dec_thread_ops.
+
+ Because the dec-thread debug API doesn't allow us to store
+ just one register, we store all the registers. */
+
+static void
+dec_thread_store_registers (struct regcache *regcache, int regno)
+{
+ pthreadDebugId_t tid = ptid_get_tid (inferior_ptid);
+ gregset_t gregset;
+ fpregset_t fpregset;
+ int res;
+
+ debug ("dec_thread_store_registers (tid=%ld, regno=%d)", tid, regno);
+
+ if (tid == 0 || ptid_equal (inferior_ptid, get_active_ptid ()))
+ {
+ base_target.to_store_registers (regcache, regno);
+ return;
+ }
+
+ /* FIXME: brobecker/2008-05-28: I wonder if we could simply check
+ in which register set the register is and then only store the
+ registers for that register set, instead of storing both register
+ sets. */
+ fill_gregset (regcache, &gregset, -1);
+ fill_fpregset (regcache, &fpregset, -1);
+
+ res = dec_thread_set_regsets (tid, gregset, fpregset);
+ if (res != 0)
+ warning (_("failed to store registers."));
+}
+
+/* The "to_mourn_inferior" method of the dec_thread_ops. */
+
+static void
+dec_thread_mourn_inferior (void)
+{
+ debug ("dec_thread_mourn_inferior");
+
+ disable_dec_thread ();
+ base_target.to_mourn_inferior ();
+}
+
+/* The "to_thread_alive" method of the dec_thread_ops. */
+static int
+dec_thread_thread_alive (ptid_t ptid)
+{
+ debug ("dec_thread_thread_alive (tid=%ld)", ptid_get_tid (ptid));
+
+ /* The thread list maintained by GDB is up to date, since we update
+ it everytime we stop. So check this list. */
+ return in_thread_list (ptid);
+}
+
+/* The "to_pid_to_str" method of the dec_thread_ops. */
+
+static char *
+dec_thread_pid_to_str (ptid_t ptid)
+{
+ static char *ret = NULL;
+
+ if (ptid_get_tid (ptid) == 0)
+ return base_target.to_pid_to_str (ptid);
+
+ /* Free previous return value; a new one will be allocated by
+ xstrprintf(). */
+ xfree (ret);
+
+ ret = xstrprintf (_("Thread %ld"), ptid_get_tid (ptid));
+ return ret;
+}
+
+/* A "new-objfile" observer. Used to activate/deactivate dec-thread
+ support. */
+
+static void
+dec_thread_new_objfile_observer (struct objfile *objfile)
+{
+ if (objfile != NULL)
+ enable_dec_thread ();
+ else
+ disable_dec_thread ();
+}
+
+static void
+init_dec_thread_ops (void)
+{
+ dec_thread_ops.to_shortname = "dec-threads";
+ dec_thread_ops.to_longname = _("DEC threads support");
+ dec_thread_ops.to_doc = _("DEC threads support");
+ dec_thread_ops.to_detach = dec_thread_detach;
+ dec_thread_ops.to_wait = dec_thread_wait;
+ dec_thread_ops.to_fetch_registers = dec_thread_fetch_registers;
+ dec_thread_ops.to_store_registers = dec_thread_store_registers;
+ dec_thread_ops.to_mourn_inferior = dec_thread_mourn_inferior;
+ dec_thread_ops.to_thread_alive = dec_thread_thread_alive;
+ dec_thread_ops.to_pid_to_str = dec_thread_pid_to_str;
+ dec_thread_ops.to_stratum = thread_stratum;
+ dec_thread_ops.to_magic = OPS_MAGIC;
+}
+
+void
+_initialize_dec_thread (void)
+{
+ init_dec_thread_ops ();
+ add_target (&dec_thread_ops);
+
+ observer_attach_new_objfile (dec_thread_new_objfile_observer);
+
+ add_setshow_boolean_cmd ("dec-thread", class_maintenance, &debug_dec_thread,
+ _("Set debugging of DEC threads module."),
+ _("Show debugging of DEC threads module."),
+ _("Enables debugging output (used to debug GDB)."),
+ NULL, NULL,
+ &setdebuglist, &showdebuglist);
+}