/* Copyright (C) 2008-2014 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 . */
#include "defs.h"
#include "command.h"
#include "gdbcmd.h"
#include "target.h"
#include "observer.h"
#include
#include "gregset.h"
#include "regcache.h"
#include "inferior.h"
#include "gdbthread.h"
#include
/* 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;
/* 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 bound_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.minsym == NULL)
{
debug ("enable_dec_thread: No __pthread_dbg_symtable");
return;
}
status = pthreadDebugContextInit (&caller_context, &debug_callbacks,
(void *) SYMBOL_VALUE_ADDRESS (msym.minsym),
&debug_context);
if (status != ESUCCESS)
{
debug ("enable_dec_thread: pthreadDebugContextInit -> %d",
status);
return;
}
push_target (&dec_thread_ops);
dec_thread_active = 1;
debug ("enable_dec_thread: Thread support enabled.");
}
/* Deactivate thread support. Do nothing if 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);
}
/* Return non-zero if PTID is still alive.
Assumes that DEC_THREAD_LIST is up to date. */
static int
dec_thread_ptid_is_alive (ptid_t ptid)
{
pthreadDebugId_t tid = ptid_get_tid (ptid);
int i;
struct dec_thread_info *info;
if (tid == 0)
/* This is the thread corresponding to the process. This ptid
is always alive until the program exits. */
return 1;
/* Search whether an entry with the same tid exists in the dec-thread
list of threads. If it does, then the thread is still alive.
No match found means that the thread must be dead, now. */
for (i = 0; VEC_iterate (dec_thread_info_s, dec_thread_list, i, info); i++)
if (info->thread == tid)
return 1;
return 0;
}
/* 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);
}
/* Implement the update_thread_list target_ops method. */
static void
dec_thread_update_thread_list (struct target_ops *ops)
{
int i;
struct dec_thread_info *info;
struct thread_info *tp, *tmp;
update_dec_thread_list ();
/* Delete GDB-side threads no longer found in dec_thread_list. */
ALL_NON_EXITED_THREADS_SAFE (tp, tmp)
{
for (i = 0; VEC_iterate (dec_thread_info_s, dec_thread_list, i, info); i++)
{
if (ptid_equal (info->ptid, tp->ptid))
break;
}
if (i == VEC_length (dec_thread_info_s, dec_thread_list))
{
/* Not found. */
delete_thread (tp->ptid);
}
}
/* And now 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);
}
}
/* The "to_detach" method of the dec_thread_ops. */
static void
dec_thread_detach (struct target_ops *ops, const char *args, int from_tty)
{
struct target_ops *beneath = find_target_beneath (ops);
debug ("dec_thread_detach");
disable_dec_thread ();
beneath->to_detach (beneath, 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 (struct target_ops *ops,
ptid_t ptid, struct target_waitstatus *status, int options)
{
ptid_t active_ptid;
struct target_ops *beneath = find_target_beneath (ops);
debug ("dec_thread_wait");
ptid = beneath->to_wait (beneath, ptid, status, options);
/* The ptid returned by the target beneath us is the ptid of the process.
We need to find which thread is currently active and return its ptid. */
dec_thread_update_thread_list (ops);
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_get_regsets: pthreadDebugThdGetReg -> %d", res);
return -1;
}
memcpy (gregset->regs, ®s, sizeof (regs));
res = pthreadDebugThdGetFreg (debug_context, tid, &fregs);
if (res != ESUCCESS)
{
debug ("dec_thread_get_regsets: 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 target_ops *ops,
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 ()))
{
struct target_ops *beneath = find_target_beneath (ops);
beneath->to_fetch_registers (beneath, 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_set_regsets: pthreadDebugThdSetReg -> %d", res);
return -1;
}
memcpy (&fregs, fpregset.regs, sizeof (fregs));
res = pthreadDebugThdSetFreg (debug_context, tid, &fregs);
if (res != ESUCCESS)
{
debug ("dec_thread_set_regsets: 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 target_ops *ops,
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 ()))
{
struct target_ops *beneath = find_target_beneath (ops);
beneath->to_store_registers (beneath, 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 (struct target_ops *ops)
{
struct target_ops *beneath = find_target_beneath (ops);
debug ("dec_thread_mourn_inferior");
disable_dec_thread ();
beneath->to_mourn_inferior (beneath);
}
/* The "to_thread_alive" method of the dec_thread_ops. */
static int
dec_thread_thread_alive (struct target_ops *ops, 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 (struct target_ops *ops, ptid_t ptid)
{
static char *ret = NULL;
if (ptid_get_tid (ptid) == 0)
{
struct target_ops *beneath = find_target_beneath (ops);
return beneath->to_pid_to_str (beneath, 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 ();
}
/* The "to_get_ada_task_ptid" method of the dec_thread_ops. */
static ptid_t
dec_thread_get_ada_task_ptid (struct target_ops *self, long lwp, long thread)
{
int i;
struct dec_thread_info *info;
debug ("dec_thread_get_ada_task_ptid (struct target_ops *self,"
" lwp=0x%lx, thread=0x%lx)",
lwp, thread);
for (i = 0; VEC_iterate (dec_thread_info_s, dec_thread_list, i, info);
i++)
if (info->info.teb == (pthread_t) thread)
return ptid_build_from_info (*info);
warning (_("Could not find thread id from THREAD = 0x%lx"), thread);
return inferior_ptid;
}
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_update_thread_list = dec_thread_update_thread_list;
dec_thread_ops.to_pid_to_str = dec_thread_pid_to_str;
dec_thread_ops.to_stratum = thread_stratum;
dec_thread_ops.to_get_ada_task_ptid = dec_thread_get_ada_task_ptid;
dec_thread_ops.to_magic = OPS_MAGIC;
}
void
_initialize_dec_thread (void)
{
init_dec_thread_ops ();
complete_target_initialization (&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);
}