/* 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); }