517 lines
14 KiB
C
517 lines
14 KiB
C
|
/* BSD user-level threads support.
|
|||
|
|
|||
|
Copyright 2005 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 "gdbcore.h"
|
|||
|
#include "gdbthread.h"
|
|||
|
#include "inferior.h"
|
|||
|
#include "objfiles.h"
|
|||
|
#include "observer.h"
|
|||
|
#include "regcache.h"
|
|||
|
#include "solist.h"
|
|||
|
#include "symfile.h"
|
|||
|
#include "target.h"
|
|||
|
|
|||
|
#include "gdb_assert.h"
|
|||
|
#include "gdb_obstack.h"
|
|||
|
|
|||
|
#include "bsd-uthread.h"
|
|||
|
|
|||
|
/* HACK: Save the bsd_uthreads ops returned by bsd_uthread_target. */
|
|||
|
static struct target_ops *bsd_uthread_ops_hack;
|
|||
|
|
|||
|
|
|||
|
/* Architecture-specific operations. */
|
|||
|
|
|||
|
/* Per-architecture data key. */
|
|||
|
static struct gdbarch_data *bsd_uthread_data;
|
|||
|
|
|||
|
struct bsd_uthread_ops
|
|||
|
{
|
|||
|
/* Supply registers for an inactive thread to a register cache. */
|
|||
|
void (*supply_uthread)(struct regcache *, int, CORE_ADDR);
|
|||
|
|
|||
|
/* Collect registers for an inactive thread from a register cache. */
|
|||
|
void (*collect_uthread)(const struct regcache *, int, CORE_ADDR);
|
|||
|
};
|
|||
|
|
|||
|
static void *
|
|||
|
bsd_uthread_init (struct obstack *obstack)
|
|||
|
{
|
|||
|
struct bsd_uthread_ops *ops;
|
|||
|
|
|||
|
ops = OBSTACK_ZALLOC (obstack, struct bsd_uthread_ops);
|
|||
|
return ops;
|
|||
|
}
|
|||
|
|
|||
|
/* Set the function that supplies registers from an inactive thread
|
|||
|
for architecture GDBARCH to SUPPLY_UTHREAD. */
|
|||
|
|
|||
|
void
|
|||
|
bsd_uthread_set_supply_uthread (struct gdbarch *gdbarch,
|
|||
|
void (*supply_uthread) (struct regcache *,
|
|||
|
int, CORE_ADDR))
|
|||
|
{
|
|||
|
struct bsd_uthread_ops *ops = gdbarch_data (gdbarch, bsd_uthread_data);
|
|||
|
ops->supply_uthread = supply_uthread;
|
|||
|
}
|
|||
|
|
|||
|
/* Set the function that collects registers for an inactive thread for
|
|||
|
architecture GDBARCH to SUPPLY_UTHREAD. */
|
|||
|
|
|||
|
void
|
|||
|
bsd_uthread_set_collect_uthread (struct gdbarch *gdbarch,
|
|||
|
void (*collect_uthread) (const struct regcache *,
|
|||
|
int, CORE_ADDR))
|
|||
|
{
|
|||
|
struct bsd_uthread_ops *ops = gdbarch_data (gdbarch, bsd_uthread_data);
|
|||
|
ops->collect_uthread = collect_uthread;
|
|||
|
}
|
|||
|
|
|||
|
/* Magic number to help recognize a valid thread structure. */
|
|||
|
#define BSD_UTHREAD_PTHREAD_MAGIC 0xd09ba115
|
|||
|
|
|||
|
/* Check whether the thread structure at ADDR is valid. */
|
|||
|
|
|||
|
static void
|
|||
|
bsd_uthread_check_magic (CORE_ADDR addr)
|
|||
|
{
|
|||
|
ULONGEST magic = read_memory_unsigned_integer (addr, 4);
|
|||
|
|
|||
|
if (magic != BSD_UTHREAD_PTHREAD_MAGIC)
|
|||
|
error ("Bad magic");
|
|||
|
}
|
|||
|
|
|||
|
/* Thread states. */
|
|||
|
#define BSD_UTHREAD_PS_RUNNING 0
|
|||
|
#define BSD_UTHREAD_PS_DEAD 18
|
|||
|
|
|||
|
/* Address of the pointer to the the thread structure for the running
|
|||
|
thread. */
|
|||
|
static CORE_ADDR bsd_uthread_thread_run_addr;
|
|||
|
|
|||
|
/* Address of the list of all threads. */
|
|||
|
static CORE_ADDR bsd_uthread_thread_list_addr;
|
|||
|
|
|||
|
/* Offsets of various "interesting" bits in the thread structure. */
|
|||
|
static int bsd_uthread_thread_state_offset = -1;
|
|||
|
static int bsd_uthread_thread_next_offset = -1;
|
|||
|
static int bsd_uthread_thread_ctx_offset;
|
|||
|
|
|||
|
/* Name of shared threads library. */
|
|||
|
static const char *bsd_uthread_solib_name;
|
|||
|
|
|||
|
/* Non-zero if the thread startum implemented by this module is active. */
|
|||
|
static int bsd_uthread_active;
|
|||
|
|
|||
|
static CORE_ADDR
|
|||
|
bsd_uthread_lookup_address (const char *name, struct objfile *objfile)
|
|||
|
{
|
|||
|
struct minimal_symbol *sym;
|
|||
|
|
|||
|
sym = lookup_minimal_symbol (name, NULL, objfile);
|
|||
|
if (sym)
|
|||
|
return SYMBOL_VALUE_ADDRESS (sym);
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
bsd_uthread_lookup_offset (const char *name, struct objfile *objfile)
|
|||
|
{
|
|||
|
CORE_ADDR addr;
|
|||
|
|
|||
|
addr = bsd_uthread_lookup_address (name, objfile);
|
|||
|
if (addr == 0)
|
|||
|
return 0;
|
|||
|
|
|||
|
return read_memory_unsigned_integer (addr, 4);
|
|||
|
}
|
|||
|
|
|||
|
/* If OBJFILE contains the symbols corresponding to one of the
|
|||
|
supported user-level threads libraries, activate the thread stratum
|
|||
|
implemented by this module. */
|
|||
|
|
|||
|
static int
|
|||
|
bsd_uthread_activate (struct objfile *objfile)
|
|||
|
{
|
|||
|
struct gdbarch *gdbarch = current_gdbarch;
|
|||
|
struct bsd_uthread_ops *ops = gdbarch_data (gdbarch, bsd_uthread_data);
|
|||
|
|
|||
|
/* Skip if the thread stratum has already been activated. */
|
|||
|
if (bsd_uthread_active)
|
|||
|
return 0;
|
|||
|
|
|||
|
/* There's no point in enabling this module if no
|
|||
|
architecture-specific operations are provided. */
|
|||
|
if (!ops->supply_uthread)
|
|||
|
return 0;
|
|||
|
|
|||
|
bsd_uthread_thread_run_addr =
|
|||
|
bsd_uthread_lookup_address ("_thread_run", objfile);
|
|||
|
if (bsd_uthread_thread_run_addr == 0)
|
|||
|
return 0;
|
|||
|
|
|||
|
bsd_uthread_thread_list_addr =
|
|||
|
bsd_uthread_lookup_address ("_thread_list", objfile);
|
|||
|
if (bsd_uthread_thread_list_addr == 0)
|
|||
|
return 0;
|
|||
|
|
|||
|
bsd_uthread_thread_state_offset =
|
|||
|
bsd_uthread_lookup_offset ("_thread_state_offset", objfile);
|
|||
|
if (bsd_uthread_thread_state_offset == 0)
|
|||
|
return 0;
|
|||
|
|
|||
|
bsd_uthread_thread_next_offset =
|
|||
|
bsd_uthread_lookup_offset ("_thread_next_offset", objfile);
|
|||
|
if (bsd_uthread_thread_next_offset == 0)
|
|||
|
return 0;
|
|||
|
|
|||
|
bsd_uthread_thread_ctx_offset =
|
|||
|
bsd_uthread_lookup_offset ("_thread_ctx_offset", objfile);
|
|||
|
|
|||
|
push_target (bsd_uthread_ops_hack);
|
|||
|
bsd_uthread_active = 1;
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
/* Deactivate the thread stratum implemented by this module. */
|
|||
|
|
|||
|
static void
|
|||
|
bsd_uthread_deactivate (void)
|
|||
|
{
|
|||
|
/* Skip if the thread stratum has already been deactivated. */
|
|||
|
if (!bsd_uthread_active)
|
|||
|
return;
|
|||
|
|
|||
|
bsd_uthread_active = 0;
|
|||
|
unpush_target (bsd_uthread_ops_hack);
|
|||
|
|
|||
|
bsd_uthread_thread_run_addr = 0;
|
|||
|
bsd_uthread_thread_list_addr = 0;
|
|||
|
bsd_uthread_thread_state_offset = 0;
|
|||
|
bsd_uthread_thread_next_offset = 0;
|
|||
|
bsd_uthread_thread_ctx_offset = 0;
|
|||
|
bsd_uthread_solib_name = NULL;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
bsd_uthread_inferior_created (struct target_ops *ops, int from_tty)
|
|||
|
{
|
|||
|
bsd_uthread_activate (NULL);
|
|||
|
}
|
|||
|
|
|||
|
/* Likely candidates for the threads library. */
|
|||
|
static const char *bsd_uthread_solib_names[] =
|
|||
|
{
|
|||
|
"/usr/lib/libc_r.so", /* FreeBSD */
|
|||
|
"/usr/lib/libpthread.so", /* OpenBSD */
|
|||
|
NULL
|
|||
|
};
|
|||
|
|
|||
|
void
|
|||
|
bsd_uthread_solib_loaded (struct so_list *so)
|
|||
|
{
|
|||
|
const char **names = bsd_uthread_solib_names;
|
|||
|
|
|||
|
for (names = bsd_uthread_solib_names; *names; names++)
|
|||
|
{
|
|||
|
if (strncmp (so->so_original_name, *names, strlen (*names)) == 0)
|
|||
|
{
|
|||
|
solib_read_symbols (so, so->from_tty);
|
|||
|
|
|||
|
if (bsd_uthread_activate (so->objfile))
|
|||
|
{
|
|||
|
bsd_uthread_solib_name == so->so_original_name;
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
bsd_uthread_solib_unloaded (struct so_list *so)
|
|||
|
{
|
|||
|
if (!bsd_uthread_solib_name)
|
|||
|
return;
|
|||
|
|
|||
|
if (strcmp (so->so_original_name, bsd_uthread_solib_name) == 0)
|
|||
|
bsd_uthread_deactivate ();
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
bsd_uthread_mourn_inferior (void)
|
|||
|
{
|
|||
|
find_target_beneath (bsd_uthread_ops_hack)->to_mourn_inferior ();
|
|||
|
bsd_uthread_deactivate ();
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
bsd_uthread_fetch_registers (int regnum)
|
|||
|
{
|
|||
|
struct gdbarch *gdbarch = current_gdbarch;
|
|||
|
struct bsd_uthread_ops *ops = gdbarch_data (gdbarch, bsd_uthread_data);
|
|||
|
CORE_ADDR addr = ptid_get_tid (inferior_ptid);
|
|||
|
CORE_ADDR active_addr;
|
|||
|
|
|||
|
/* Always fetch the appropriate registers from the layer beneath. */
|
|||
|
find_target_beneath (bsd_uthread_ops_hack)->to_fetch_registers (regnum);
|
|||
|
|
|||
|
/* FIXME: That might have gotten us more than we asked for. Make
|
|||
|
sure we overwrite all relevant registers with values from the
|
|||
|
thread structure. This can go once we fix the underlying target. */
|
|||
|
regnum = -1;
|
|||
|
|
|||
|
active_addr = read_memory_typed_address (bsd_uthread_thread_run_addr,
|
|||
|
builtin_type_void_data_ptr);
|
|||
|
if (addr != 0 && addr != active_addr)
|
|||
|
{
|
|||
|
bsd_uthread_check_magic (addr);
|
|||
|
ops->supply_uthread (current_regcache, regnum,
|
|||
|
addr + bsd_uthread_thread_ctx_offset);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
bsd_uthread_store_registers (int regnum)
|
|||
|
{
|
|||
|
struct gdbarch *gdbarch = current_gdbarch;
|
|||
|
struct bsd_uthread_ops *ops = gdbarch_data (gdbarch, bsd_uthread_data);
|
|||
|
CORE_ADDR addr = ptid_get_tid (inferior_ptid);
|
|||
|
CORE_ADDR active_addr;
|
|||
|
|
|||
|
active_addr = read_memory_typed_address (bsd_uthread_thread_run_addr,
|
|||
|
builtin_type_void_data_ptr);
|
|||
|
if (addr != 0 && addr != active_addr)
|
|||
|
{
|
|||
|
bsd_uthread_check_magic (addr);
|
|||
|
ops->collect_uthread (current_regcache, regnum,
|
|||
|
addr + bsd_uthread_thread_ctx_offset);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Updating the thread that is currently running; pass the
|
|||
|
request to the layer beneath. */
|
|||
|
find_target_beneath (bsd_uthread_ops_hack)->to_store_registers (regnum);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* FIXME: This function is only there because otherwise GDB tries to
|
|||
|
invoke deprecate_xfer_memory. */
|
|||
|
|
|||
|
static LONGEST
|
|||
|
bsd_uthread_xfer_partial (struct target_ops *ops, enum target_object object,
|
|||
|
const char *annex, void *readbuf,
|
|||
|
const void *writebuf, ULONGEST offset, LONGEST len)
|
|||
|
{
|
|||
|
gdb_assert (ops->beneath->to_xfer_partial);
|
|||
|
return ops->beneath->to_xfer_partial (ops->beneath, object, annex, readbuf,
|
|||
|
writebuf, offset, len);
|
|||
|
}
|
|||
|
|
|||
|
static ptid_t
|
|||
|
bsd_uthread_wait (ptid_t ptid, struct target_waitstatus *status)
|
|||
|
{
|
|||
|
CORE_ADDR addr;
|
|||
|
|
|||
|
/* Pass the request to the layer beneath. */
|
|||
|
ptid = find_target_beneath (bsd_uthread_ops_hack)->to_wait (ptid, status);
|
|||
|
|
|||
|
/* Fetch the corresponding thread ID, and augment the returned
|
|||
|
process ID with it. */
|
|||
|
addr = read_memory_typed_address (bsd_uthread_thread_run_addr,
|
|||
|
builtin_type_void_data_ptr);
|
|||
|
if (addr != 0)
|
|||
|
{
|
|||
|
char buf[4];
|
|||
|
|
|||
|
/* FIXME: For executables linked statically with the threads
|
|||
|
library, we end up here before the program has actually been
|
|||
|
executed. In that case ADDR will be garbage since it has
|
|||
|
been read from the wrong virtual memory image. */
|
|||
|
if (target_read_memory (addr, buf, 4) == 0)
|
|||
|
{
|
|||
|
ULONGEST magic = extract_unsigned_integer (buf, 4);
|
|||
|
if (magic == BSD_UTHREAD_PTHREAD_MAGIC)
|
|||
|
ptid = ptid_build (ptid_get_pid (ptid), 0, addr);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* HACK: Twiddle INFERIOR_PTID such that the initial thread of a
|
|||
|
process isn't recognized as a new thread. */
|
|||
|
if (ptid_get_tid (ptid) != 0 && !in_thread_list (ptid)
|
|||
|
&& ptid_get_tid (inferior_ptid) == 0)
|
|||
|
{
|
|||
|
add_thread (ptid);
|
|||
|
inferior_ptid = ptid;
|
|||
|
}
|
|||
|
|
|||
|
return ptid;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
bsd_uthread_resume (ptid_t ptid, int step, enum target_signal sig)
|
|||
|
{
|
|||
|
/* Pass the request to the layer beneath. */
|
|||
|
find_target_beneath (bsd_uthread_ops_hack)->to_resume (ptid, step, sig);
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
bsd_uthread_thread_alive (ptid_t ptid)
|
|||
|
{
|
|||
|
CORE_ADDR addr = ptid_get_tid (inferior_ptid);
|
|||
|
|
|||
|
if (addr != 0)
|
|||
|
{
|
|||
|
int offset = bsd_uthread_thread_state_offset;
|
|||
|
ULONGEST state;
|
|||
|
|
|||
|
bsd_uthread_check_magic (addr);
|
|||
|
|
|||
|
state = read_memory_unsigned_integer (addr + offset, 4);
|
|||
|
if (state == BSD_UTHREAD_PS_DEAD)
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
return find_target_beneath (bsd_uthread_ops_hack)->to_thread_alive (ptid);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
bsd_uthread_find_new_threads (void)
|
|||
|
{
|
|||
|
pid_t pid = ptid_get_pid (inferior_ptid);
|
|||
|
int offset = bsd_uthread_thread_next_offset;
|
|||
|
CORE_ADDR addr;
|
|||
|
|
|||
|
addr = read_memory_typed_address (bsd_uthread_thread_list_addr,
|
|||
|
builtin_type_void_data_ptr);
|
|||
|
while (addr != 0)
|
|||
|
{
|
|||
|
ptid_t ptid = ptid_build (pid, 0, addr);
|
|||
|
|
|||
|
if (!in_thread_list (ptid))
|
|||
|
add_thread (ptid);
|
|||
|
|
|||
|
addr = read_memory_typed_address (addr + offset,
|
|||
|
builtin_type_void_data_ptr);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Possible states a thread can be in. */
|
|||
|
static char *bsd_uthread_state[] =
|
|||
|
{
|
|||
|
"RUNNING",
|
|||
|
"SIGTHREAD",
|
|||
|
"MUTEX_WAIT",
|
|||
|
"COND_WAIT",
|
|||
|
"FDLR_WAIT",
|
|||
|
"FDLW_WAIT",
|
|||
|
"FDR_WAIT",
|
|||
|
"FDW_WAIT",
|
|||
|
"FILE_WAIT",
|
|||
|
"POLL_WAIT",
|
|||
|
"SELECT_WAIT",
|
|||
|
"SLEEP_WAIT",
|
|||
|
"WAIT_WAIT",
|
|||
|
"SIGSUSPEND",
|
|||
|
"SIGWAIT",
|
|||
|
"SPINBLOCK",
|
|||
|
"JOIN",
|
|||
|
"SUSPENDED",
|
|||
|
"DEAD",
|
|||
|
"DEADLOCK"
|
|||
|
};
|
|||
|
|
|||
|
/* Return a string describing th state of the thread specified by
|
|||
|
INFO. */
|
|||
|
|
|||
|
static char *
|
|||
|
bsd_uthread_extra_thread_info (struct thread_info *info)
|
|||
|
{
|
|||
|
CORE_ADDR addr = ptid_get_tid (info->ptid);
|
|||
|
|
|||
|
if (addr != 0)
|
|||
|
{
|
|||
|
int offset = bsd_uthread_thread_state_offset;
|
|||
|
ULONGEST state;
|
|||
|
|
|||
|
state = read_memory_unsigned_integer (addr + offset, 4);
|
|||
|
if (state < ARRAY_SIZE (bsd_uthread_state))
|
|||
|
return bsd_uthread_state[state];
|
|||
|
}
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
static char *
|
|||
|
bsd_uthread_pid_to_str (ptid_t ptid)
|
|||
|
{
|
|||
|
if (ptid_get_tid (ptid) != 0)
|
|||
|
{
|
|||
|
static char buf[64];
|
|||
|
int size;
|
|||
|
|
|||
|
size = snprintf (buf, sizeof buf, "process %d, thread 0x%lx",
|
|||
|
ptid_get_pid (ptid), ptid_get_tid (ptid));
|
|||
|
gdb_assert (size < sizeof buf);
|
|||
|
return buf;
|
|||
|
}
|
|||
|
|
|||
|
return normal_pid_to_str (ptid);
|
|||
|
}
|
|||
|
|
|||
|
struct target_ops *
|
|||
|
bsd_uthread_target (void)
|
|||
|
{
|
|||
|
struct target_ops *t = XZALLOC (struct target_ops);
|
|||
|
|
|||
|
t->to_shortname = "bsd-uthreads";
|
|||
|
t->to_longname = "BSD user-level threads";
|
|||
|
t->to_doc = "BSD user-level threads";
|
|||
|
t->to_mourn_inferior = bsd_uthread_mourn_inferior;
|
|||
|
t->to_fetch_registers = bsd_uthread_fetch_registers;
|
|||
|
t->to_store_registers = bsd_uthread_store_registers;
|
|||
|
t->to_xfer_partial = bsd_uthread_xfer_partial;
|
|||
|
t->to_wait = bsd_uthread_wait;
|
|||
|
t->to_resume = bsd_uthread_resume;
|
|||
|
t->to_thread_alive = bsd_uthread_thread_alive;
|
|||
|
t->to_find_new_threads = bsd_uthread_find_new_threads;
|
|||
|
t->to_extra_thread_info = bsd_uthread_extra_thread_info;
|
|||
|
t->to_pid_to_str = bsd_uthread_pid_to_str;
|
|||
|
t->to_stratum = thread_stratum;
|
|||
|
t->to_magic = OPS_MAGIC;
|
|||
|
bsd_uthread_ops_hack = t;
|
|||
|
|
|||
|
return t;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
_initialize_bsd_uthread (void)
|
|||
|
{
|
|||
|
add_target (bsd_uthread_target ());
|
|||
|
|
|||
|
bsd_uthread_data = gdbarch_data_register_pre_init (bsd_uthread_init);
|
|||
|
|
|||
|
observer_attach_inferior_created (bsd_uthread_inferior_created);
|
|||
|
observer_attach_solib_loaded (bsd_uthread_solib_loaded);
|
|||
|
observer_attach_solib_unloaded (bsd_uthread_solib_unloaded);
|
|||
|
}
|