summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Blandy <jimb@redhat.com>2004-10-29 23:49:55 +0000
committerJim Blandy <jimb@redhat.com>2004-10-29 23:49:55 +0000
commitee27141cae7d94d90af86cc34fb7cbc62fdcf78d (patch)
treee86bd023937a523726229738afc6cd901f42893a
parentef65240792cd3c68bbe9978c2d1c34401c80df46 (diff)
downloadgdb-ee27141cae7d94d90af86cc34fb7cbc62fdcf78d.tar.gz
* linux-target.c (linux_set_reg): Delete unused variables.
* ptrace-target.c (ptrace_read_user, ptrace_check_child_state): Same. * thread-db.c (continue_all_threads, thread_db_attach): Same.
-rw-r--r--rda/unix/ChangeLog5
-rw-r--r--rda/unix/linux-target.c1
-rw-r--r--rda/unix/ptrace-target.c1477
-rw-r--r--rda/unix/thread-db.c2864
4 files changed, 4346 insertions, 1 deletions
diff --git a/rda/unix/ChangeLog b/rda/unix/ChangeLog
index ef1aabd41d7..2333aa0a8bb 100644
--- a/rda/unix/ChangeLog
+++ b/rda/unix/ChangeLog
@@ -1,5 +1,10 @@
2004-10-29 Jim Blandy <jimb@redhat.com>
+ * linux-target.c (linux_set_reg): Delete unused variables.
+ * ptrace-target.c (ptrace_read_user, ptrace_check_child_state):
+ Same.
+ * thread-db.c (continue_all_threads, thread_db_attach): Same.
+
* linux-target.c (x86_make_arch): Use allocate_empty_arch.
* linux-target.c: #include <string.h>, <sys/types.h>, and
diff --git a/rda/unix/linux-target.c b/rda/unix/linux-target.c
index af709af64d3..610f1d1e5ae 100644
--- a/rda/unix/linux-target.c
+++ b/rda/unix/linux-target.c
@@ -1987,7 +1987,6 @@ linux_set_reg (struct gdbserv *serv, int regno, struct gdbserv_reg *reg)
elf_fpregset_t fpregs;
void *fpxregs = NULL;
char *buf;
- char tmp_buf[MAX_REG_SIZE];
if (regno < 0 || regno >= NUM_REGS)
{
diff --git a/rda/unix/ptrace-target.c b/rda/unix/ptrace-target.c
new file mode 100644
index 00000000000..754b530fdea
--- /dev/null
+++ b/rda/unix/ptrace-target.c
@@ -0,0 +1,1477 @@
+/* ptrace-target.c
+
+ Copyright 2000, 2001, 2002 Red Hat, Inc.
+
+ This file is part of RDA, the Red Hat Debug Agent (and library).
+
+ 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.
+
+ Alternative licenses for RDA may be arranged by contacting Red Hat,
+ Inc. */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include <sys/wait.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <linux/unistd.h>
+
+#include "gdbserv.h"
+#include "gdbserv-target.h"
+#include "gdbserv-utils.h"
+#include "gdb_proc_service.h"
+#include "gdbserv-thread-db.h"
+
+#include "server.h"
+#include "ptrace-target.h"
+/* This is unix ptrace gdbserv target that uses the RDA library to implement
+ a remote gdbserver on a unix ptrace host. It controls the process
+ to be debugged on the linux host, allowing GDB to pull the strings
+ from any host on the network (or on a serial port). */
+
+/* Track sole connection to a remote gdb client. */
+/* FIXME: needed? */
+static struct gdbserv* ptrace_connect_lock = NULL;
+
+/* Close all open file descriptors except for stdin, stdout, and
+ stderr. */
+
+static void
+close_open_files (void)
+{
+ long max_open_files = sysconf (_SC_OPEN_MAX);
+ int fd;
+
+ for (fd = 3; fd < max_open_files; fd++)
+ {
+ close (fd);
+ }
+}
+
+/* ptrace_create_child:
+
+ Either attach to an existing process or fork a child and capture
+ it via PTRACE_TRACEME.
+
+ The single argument PROCESS is a struct containing either the
+ process id to attach to or the file name and arguments to execute.
+
+*/
+
+/* Local Functions: */
+
+static int
+ptrace_create_child (struct child_process *process)
+{
+ int pid;
+
+ if (process->pid > 0)
+ {
+ pid = process->pid;
+
+ errno = 0;
+ ptrace (PTRACE_ATTACH, pid, 0L, 0L);
+ if (errno != 0)
+ {
+ fprintf (stderr, "Could not attach to process id %d\n", pid);
+ exit (1);
+ }
+ }
+ else
+ {
+ pid = vfork ();
+ if (pid < 0)
+ {
+ fprintf (stderr, "PTRACE: vfork failed!\n");
+ return 0;
+ }
+
+ if (pid == 0)
+ {
+ close_open_files ();
+ if (process->debug_backend)
+ fprintf (stderr, "PTRACE_TRACEME\n");
+ errno = 0;
+ ptrace (PTRACE_TRACEME, 0L, 0L, 0L);
+ if (errno != 0)
+ {
+ fprintf (stderr, "PTRACE: child cannot be traced!\n");
+ goto fail;
+ }
+ if (process->executable != NULL && process->argv != NULL)
+ execv (process->executable, process->argv);
+ else
+ sleep (-1); /* FIXME ??? */
+
+ fprintf (stderr, "Cannot exec %s: %s.\n", process->executable,
+ strerror (errno));
+ fail:
+ fflush (stderr);
+ _exit (0177);
+ }
+ }
+
+ return pid;
+}
+
+/* Decode the waitstatus returned by waitpid, and return the appropriate
+ stop status and stop_signal to gdb. FIXME: this is not specific to
+ ptrace, but there's no better place to put it (server.c?) */
+
+extern int
+handle_waitstatus (struct child_process *process, union wait w)
+{
+ if (WIFEXITED (w))
+ {
+ if (process->debug_informational)
+ fprintf (stderr, "\nChild %d exited with retcode = %d\n",
+ process->pid, WEXITSTATUS (w));
+ process->stop_status = 'W';
+ return (process->stop_signal = WEXITSTATUS (w));
+ }
+ else if (!WIFSTOPPED (w))
+ {
+ if (process->debug_informational)
+ fprintf (stderr, "\nChild %d terminated with signal = %d\n",
+ process->pid, WTERMSIG (w));
+ process->stop_status = 'X';
+ return (process->stop_signal = WTERMSIG (w));
+ }
+
+#if defined(_MIPSEL) || defined(_MIPSEB)
+ /*
+ * If we were single_stepping, restore the opcodes hoisted
+ * for the breakpoint[s].
+ */
+ if (process->is_ss)
+ {
+ int i;
+ for (i = 0; i < 2; i++)
+ if (process->ss_info[i].in_use)
+ {
+ ptrace_set_mem (process->serv,
+ &process->ss_info[i].ss_addr,
+ &process->ss_info[i].ss_val,
+ sizeof (process->ss_info[i].ss_val));
+ process->ss_info[i].in_use = 0;
+ }
+ process->is_ss = 0;
+ }
+#endif /* _MIPSEL */
+
+ process->stop_status = 'T';
+ process->stop_signal = WSTOPSIG (w);
+ return process->stop_signal;
+}
+
+static void
+ptrace_kill_program (struct child_process *process, int signum)
+{
+ if (process->debug_backend)
+ fprintf (stderr, "KILL %d, %d\n", process->pid, signum);
+ kill (process->pid, signum);
+}
+
+/*
+ * Exported functions
+ */
+
+/* Read user memory
+ *
+ * Returns 0 for success, errno for failure
+ */
+
+extern int
+ptrace_read_user (struct gdbserv *serv,
+ int pid,
+ ptrace_arg3_type addr,
+ int len,
+ void *buff)
+{
+ int i;
+
+ /* Require: addr is on the proper boundary, and
+ len is a proper multiple of PTRACE_XFER_SIZE.
+ Caller's responsibility. */
+
+ for (i = 0; i < len; i+= PTRACE_XFER_SIZE)
+ {
+ errno = 0;
+ *(ptrace_xfer_type *) &((char *)buff)[i] =
+ ptrace (PTRACE_PEEKUSER, pid, addr + i, 0);
+#if 0 /* too noisy! */
+ if (process->debug_backend)
+ fprintf (stderr, "PTRACE_PEEKUSER 0x%08llx in %d, 0x%08llx\n",
+ (long long) addr + i, pid,
+ (long long) * (ptrace_xfer_type *) &((char *)buff)[i]);
+#endif
+ if (errno != 0)
+ return errno;
+ }
+ return 0;
+}
+
+/* Write user memory
+ *
+ * Returns 0 for success, errno for failure
+ */
+
+extern int
+ptrace_write_user (struct gdbserv *serv,
+ int pid,
+ ptrace_arg3_type addr,
+ int len,
+ const void *buff)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ int i;
+
+ /* Require: addr is on the proper boundary, and
+ len is a proper multiple of PTRACE_XFER_SIZE.
+ Caller's responsibility. */
+
+ for (i = 0; i < len; i+= PTRACE_XFER_SIZE)
+ {
+#ifdef X86_LINUX_TARGET
+ if (addr + i == 44)
+ continue; /* Forbidden address/register, not writable. */
+#endif
+ errno = 0;
+ ptrace (PTRACE_POKEUSER, pid, addr + i,
+ * (ptrace_xfer_type *) &((char *)buff)[i]);
+ if (process->debug_backend)
+ fprintf (stderr, "PTRACE_POKEUSER 0x%08llx in %d, 0x%08llx\n",
+ (long long) addr + i, pid,
+ (long long) * (ptrace_xfer_type *) &((char *)buff)[i]);
+#if defined(_MIPSEL) || defined(MIPS_LINUX_TARGET)
+ /* mips linux kernel 2.4 has a bug where PTRACE_POKEUSER
+ returns -ESRCH even when it succeeds */
+ if (errno == ESRCH)
+ errno = 0;
+#endif
+ if (errno != 0)
+ return errno;
+ }
+ return 0;
+}
+
+#if defined (PTRACE_GETREGS) || defined (PT_GETREGS)
+
+/* get general regs */
+
+int
+ptrace_get_gregs (struct gdbserv *serv, int alt_pid, void *buff)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ int pid = alt_pid == 0 ? process->pid : alt_pid;
+
+ /* Require: buff is of the appropriate size for the target arch. */
+
+ errno = 0;
+ ptrace (PTRACE_GETREGS, pid, 0, (ptrace_arg4_type) buff);
+ return errno;
+}
+#endif
+
+#if defined (PTRACE_SETREGS) || defined (PT_SETREGS)
+/* set general regs */
+
+int
+ptrace_set_gregs (struct gdbserv *serv, int alt_pid, const void *buff)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ int pid = alt_pid == 0 ? process->pid : alt_pid;
+
+ /* Require: buff is of the appropriate size for the target arch. */
+
+ errno = 0;
+ ptrace (PTRACE_SETREGS, pid, 0, (ptrace_arg4_type) buff);
+ return errno;
+}
+#endif
+
+
+/* get floating point regs */
+
+extern int
+ptrace_get_fpregs (struct gdbserv *serv, int alt_pid, void *buff)
+{
+#if defined (PTRACE_GETFPREGS) || defined (PT_GETFPREGS)
+ struct child_process *process = gdbserv_target_data (serv);
+ int pid = alt_pid == 0 ? process->pid : alt_pid;
+
+ /* Require: buff is of the appropriate size for the target arch. */
+
+ errno = 0;
+ ptrace (PTRACE_GETFPREGS, pid, 0, (ptrace_arg4_type) buff);
+ return errno;
+#else
+ return -1;
+#endif
+}
+
+
+/* set floating point regs */
+
+extern int
+ptrace_set_fpregs (struct gdbserv *serv, int alt_pid, const void *buff)
+{
+#if defined (PTRACE_SETFPREGS) || defined (PT_SETFPREGS)
+ struct child_process *process = gdbserv_target_data (serv);
+ int pid = alt_pid == 0 ? process->pid : alt_pid;
+
+ /* Require: buff is of the appropriate size for the target arch. */
+
+ errno = 0;
+ ptrace (PTRACE_SETFPREGS, pid, 0, (ptrace_arg4_type) buff);
+ return errno;
+#else
+ return -1;
+#endif
+}
+
+
+/* get extended floating point regs */
+
+int
+ptrace_get_fpxregs (struct gdbserv *serv, int alt_pid, void *buff)
+{
+#if defined (PTRACE_GETFPXREGS) || defined (PT_GETFPXREGS)
+ struct child_process *process = gdbserv_target_data (serv);
+ int pid = alt_pid == 0 ? process->pid : alt_pid;
+
+ /* Require: buff is of the appropriate size for the target arch. */
+
+ errno = 0;
+ ptrace (PTRACE_GETFPXREGS, pid, 0, (ptrace_arg4_type) buff);
+ return errno;
+#else
+ return -1;
+#endif
+}
+
+
+/* set extended floating point regs */
+
+int
+ptrace_set_fpxregs (struct gdbserv *serv, int alt_pid, const void *buff)
+{
+#if defined (PTRACE_SETFPXREGS) || defined (PT_SETFPXREGS)
+ struct child_process *process = gdbserv_target_data (serv);
+ int pid = alt_pid == 0 ? process->pid : alt_pid;
+
+ /* Require: buff is of the appropriate size for the target arch. */
+
+ errno = 0;
+ ptrace (PTRACE_SETFPXREGS, pid, 0, (ptrace_arg4_type) buff);
+ return errno;
+#else
+ return -1;
+#endif
+}
+
+/* target vector: */
+
+static void
+ptrace_flush_i_cache (struct gdbserv *serv)
+{
+ /* Calls to ptrace() take care of this for us automatically when
+ needed. I.e, nothing to do... */
+ return;
+}
+
+/* sigkill vector
+ */
+
+static void
+ptrace_sigkill_program (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ ptrace_kill_program (process, SIGKILL);
+}
+
+/* exit program vector
+ */
+static void
+ptrace_exit_program (struct gdbserv *serv)
+{
+ ptrace_sigkill_program (serv);
+ gdbserv_fromtarget_exit (serv, GDBSERV_SIGKILL);
+ /* Quit out of main loop. */
+ server_quit_p = 1;
+}
+
+/* break program vector
+ */
+
+static void
+ptrace_break_program (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ if (process->debug_backend)
+ fprintf (stderr, " -- send SIGINT to child %d\n", process->pid);
+ kill (process->pid, SIGINT);
+}
+
+/* get_trap_number vector
+ */
+
+static unsigned long
+ptrace_get_trap_number (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ return process->stop_signal;
+}
+
+/* compute signal vector
+ * No translation necessary -- using unix native signals .
+ */
+
+static unsigned long
+ptrace_compute_signal (struct gdbserv *serv, unsigned long tgtsig)
+{
+ if (tgtsig == 0)
+ return GDBSERV_SIGNONE;
+#ifdef SIGHUP
+ if (tgtsig == SIGHUP)
+ return GDBSERV_SIGHUP;
+#endif
+#ifdef SIGINT
+ if (tgtsig == SIGINT)
+ return GDBSERV_SIGINT;
+#endif
+#ifdef SIGQUIT
+ if (tgtsig == SIGQUIT)
+ return GDBSERV_SIGQUIT;
+#endif
+#ifdef SIGILL
+ if (tgtsig == SIGILL)
+ return GDBSERV_SIGILL;
+#endif
+#ifdef SIGTRAP
+ if (tgtsig == SIGTRAP)
+ return GDBSERV_SIGTRAP;
+#endif
+#ifdef SIGABRT
+ if (tgtsig == SIGABRT)
+ return GDBSERV_SIGABRT;
+#endif
+#ifdef SIGIOT
+ if (tgtsig == SIGIOT)
+ return GDBSERV_SIGABRT;
+#endif
+#ifdef SIGEMT
+ if (tgtsig == SIGEMT)
+ return GDBSERV_SIGEMT;
+#endif
+#ifdef SIGFPE
+ if (tgtsig == SIGFPE)
+ return GDBSERV_SIGFPE;
+#endif
+#ifdef SIGKILL
+ if (tgtsig == SIGKILL)
+ return GDBSERV_SIGKILL;
+#endif
+#ifdef SIGBUS
+ if (tgtsig == SIGBUS)
+ return GDBSERV_SIGBUS;
+#endif
+#ifdef SIGSEGV
+ if (tgtsig == SIGSEGV)
+ return GDBSERV_SIGSEGV;
+#endif
+#ifdef SIGSYS
+ if (tgtsig == SIGSYS)
+ return GDBSERV_SIGSYS;
+#endif
+#ifdef SIGPIPE
+ if (tgtsig == SIGPIPE)
+ return GDBSERV_SIGPIPE;
+#endif
+#ifdef SIGALRM
+ if (tgtsig == SIGALRM)
+ return GDBSERV_SIGALRM;
+#endif
+#ifdef SIGTERM
+ if (tgtsig == SIGTERM)
+ return GDBSERV_SIGTERM;
+#endif
+#ifdef SIGURG
+ if (tgtsig == SIGURG)
+ return GDBSERV_SIGURG;
+#endif
+#ifdef SIGSTOP
+ if (tgtsig == SIGSTOP)
+ return GDBSERV_SIGSTOP;
+#endif
+#ifdef SIGTSTP
+ if (tgtsig == SIGTSTP)
+ return GDBSERV_SIGTSTP;
+#endif
+#ifdef SIGCONT
+ if (tgtsig == SIGCONT)
+ return GDBSERV_SIGCONT;
+#endif
+#ifdef SIGCHLD
+ if (tgtsig == SIGCHLD)
+ return GDBSERV_SIGCHLD;
+#endif
+#ifdef SIGCLD
+ if (tgtsig == SIGCLD)
+ return GDBSERV_SIGCHLD;
+#endif
+#ifdef SIGTTIN
+ if (tgtsig == SIGTTIN)
+ return GDBSERV_SIGTTIN;
+#endif
+#ifdef SIGTTOU
+ if (tgtsig == SIGTTOU)
+ return GDBSERV_SIGTTOU;
+#endif
+#ifdef SIGIO
+ if (tgtsig == SIGIO)
+ return GDBSERV_SIGIO;
+#endif
+#ifdef SIGXCPU
+ if (tgtsig == SIGXCPU)
+ return GDBSERV_SIGXCPU;
+#endif
+#ifdef SIGXFSZ
+ if (tgtsig == SIGXFSZ)
+ return GDBSERV_SIGXFSZ;
+#endif
+#ifdef SIGVTALRM
+ if (tgtsig == SIGVTALRM)
+ return GDBSERV_SIGVTALRM;
+#endif
+#ifdef SIGPROF
+ if (tgtsig == SIGPROF)
+ return GDBSERV_SIGPROF;
+#endif
+#ifdef SIGWINCH
+ if (tgtsig == SIGWINCH)
+ return GDBSERV_SIGWINCH;
+#endif
+#ifdef SIGLOST
+ if (tgtsig == SIGLOST)
+ return GDBSERV_SIGLOST;
+#endif
+#ifdef SIGUSR1
+ if (tgtsig == SIGUSR1)
+ return GDBSERV_SIGUSR1;
+#endif
+#ifdef SIGUSR2
+ if (tgtsig == SIGUSR2)
+ return GDBSERV_SIGUSR2;
+#endif
+#ifdef SIGPWR
+ if (tgtsig == SIGPWR)
+ return GDBSERV_SIGPWR;
+#endif
+#ifdef SIGPOLL
+ if (tgtsig == SIGPOLL)
+ return GDBSERV_SIGPOLL;
+#endif
+#ifdef SIGWIND
+ if (tgtsig == SIGWIND)
+ return GDBSERV_SIGWIND;
+#endif
+#ifdef SIGPHONE
+ if (tgtsig == SIGPHONE)
+ return GDBSERV_SIGPHONE;
+#endif
+#ifdef SIGWAITING
+ if (tgtsig == SIGWAITING)
+ return GDBSERV_SIGWAITING;
+#endif
+#ifdef SIGLWP
+ if (tgtsig == SIGLWP)
+ return GDBSERV_SIGLWP;
+#endif
+#ifdef SIGDANGER
+ if (tgtsig == SIGDANGER)
+ return GDBSERV_SIGDANGER;
+#endif
+#ifdef SIGGRANT
+ if (tgtsig == SIGGRANT)
+ return GDBSERV_SIGGRANT;
+#endif
+#ifdef SIGRETRACT
+ if (tgtsig == SIGRETRACT)
+ return GDBSERV_SIGRETRACT;
+#endif
+#ifdef SIGMSG
+ if (tgtsig == SIGMSG)
+ return GDBSERV_SIGMSG;
+#endif
+#ifdef SIGSOUND
+ if (tgtsig == SIGSOUND)
+ return GDBSERV_SIGSOUND;
+#endif
+#ifdef SIGSAC
+ if (tgtsig == SIGSAC)
+ return GDBSERV_SIGSAC;
+#endif
+#ifdef SIGPRIO
+ if (tgtsig == SIGPRIO)
+ return GDBSERV_SIGPRIO;
+#endif
+#ifdef SIGSTKFLT
+ if (tgtsig == SIGSTKFLT)
+ return GDBSERV_SIGSEGV; /* ? */
+#endif
+#ifdef SIGPWR
+ if (tgtsig == SIGPWR)
+ return GDBSERV_SIGPWR;
+#endif
+#if defined (SIGRTMIN) && defined (SIGRTMAX)
+ if (tgtsig == SIGRTMIN)
+ return GDBSERV_SIGRT32;
+ if (tgtsig == SIGRTMIN + 32)
+ return GDBSERV_SIGRT64;
+ if (tgtsig > SIGRTMIN && tgtsig < SIGRTMAX)
+ return GDBSERV_SIGRT33 + tgtsig - 1;
+ return GDBSERV_SIGNONE; /* ? */
+#endif
+}
+
+/* singlestep vector
+ */
+
+static void
+ptrace_singlestep_program (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ /* FIXME: handle signals! */
+ if (process->debug_backend)
+ fprintf (stderr, "PTRACE_SINGLESTEP %d signal %d\n",
+ process->pid, process->signal_to_send);
+ process->stop_signal = 0;
+ process->stop_status = 0;
+
+ errno = 0;
+ ptrace (PTRACE_SINGLESTEP, process->pid, 1L, process->signal_to_send);
+ if (errno)
+ fprintf (stderr, "singlestep: ptrace error %s in %d\n",
+ strerror (errno), process->pid);
+ process->signal_to_send = 0;
+}
+
+/*
+ * Continue vector
+ */
+
+static void
+ptrace_continue_program (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ /* FIXME: handle signals! */
+ if (process->debug_backend)
+ fprintf (stderr, "PTRACE_CONT %d signal %d\n",
+ process->pid, process->signal_to_send);
+ process->stop_signal = 0;
+ process->stop_status = 0;
+
+ errno = 0;
+ ptrace (PTRACE_CONT, process->pid, 1L, process->signal_to_send);
+ if (errno)
+ fprintf (stderr, "continue: ptrace error %s in %d\n",
+ strerror (errno), process->pid);
+ process->signal_to_send = 0;
+}
+
+/* Set continue-signal vector
+ */
+
+static int
+ptrace_process_signal (struct gdbserv *serv, int sig)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ /* Save the signal value for later use by continue/singlestep. */
+ switch (sig) {
+ case GDBSERV_SIGNONE:
+ process->signal_to_send = 0; break;
+#ifdef SIGHUP
+ case GDBSERV_SIGHUP:
+ process->signal_to_send = SIGHUP; break;
+#endif
+#ifdef SIGINT
+ case GDBSERV_SIGINT:
+ process->signal_to_send = SIGINT; break;
+#endif
+#ifdef SIGQUIT
+ case GDBSERV_SIGQUIT:
+ process->signal_to_send = SIGQUIT; break;
+#endif
+#ifdef SIGILL
+ case GDBSERV_SIGILL:
+ process->signal_to_send = SIGILL; break;
+#endif
+#ifdef SIGTRAP
+ case GDBSERV_SIGTRAP:
+ process->signal_to_send = SIGTRAP; break;
+#endif
+#ifdef SIGABRT
+ case GDBSERV_SIGABRT:
+ process->signal_to_send = SIGABRT; break;
+#endif
+#ifdef SIGEMT
+ case GDBSERV_SIGEMT:
+ process->signal_to_send = SIGEMT; break;
+#endif
+#ifdef SIGFPE
+ case GDBSERV_SIGFPE:
+ process->signal_to_send = SIGFPE; break;
+#endif
+#ifdef SIGKILL
+ case GDBSERV_SIGKILL:
+ process->signal_to_send = SIGKILL; break;
+#endif
+#ifdef SIGBUS
+ case GDBSERV_SIGBUS:
+ process->signal_to_send = SIGBUS; break;
+#endif
+#ifdef SIGSEGV
+ case GDBSERV_SIGSEGV:
+ process->signal_to_send = SIGSEGV; break;
+#endif
+#ifdef SIGSYS
+ case GDBSERV_SIGSYS:
+ process->signal_to_send = SIGSYS; break;
+#endif
+#ifdef SIGPIPE
+ case GDBSERV_SIGPIPE:
+ process->signal_to_send = SIGPIPE; break;
+#endif
+#ifdef SIGALRM
+ case GDBSERV_SIGALRM:
+ process->signal_to_send = SIGALRM; break;
+#endif
+#ifdef SIGTERM
+ case GDBSERV_SIGTERM:
+ process->signal_to_send = SIGTERM; break;
+#endif
+#ifdef SIGURG
+ case GDBSERV_SIGURG:
+ process->signal_to_send = SIGURG; break;
+#endif
+#ifdef SIGSTOP
+ case GDBSERV_SIGSTOP:
+ process->signal_to_send = SIGSTOP; break;
+#endif
+#ifdef SIGTSTP
+ case GDBSERV_SIGTSTP:
+ process->signal_to_send = SIGTSTP; break;
+#endif
+#ifdef SIGCONT
+ case GDBSERV_SIGCONT:
+ process->signal_to_send = SIGCONT; break;
+#endif
+#ifdef SIGCHLD
+ case GDBSERV_SIGCHLD:
+ process->signal_to_send = SIGCHLD; break;
+#endif
+#if defined (SIGCLD) && !defined (SIGCHLD)
+ case GDBSERV_SIGCHLD:
+ process->signal_to_send = SIGCLD; break;
+#endif
+#ifdef SIGTTIN
+ case GDBSERV_SIGTTIN:
+ process->signal_to_send = SIGTTIN; break;
+#endif
+#ifdef SIGTTOU
+ case GDBSERV_SIGTTOU:
+ process->signal_to_send = SIGTTOU; break;
+#endif
+#ifdef SIGIO
+ case GDBSERV_SIGIO:
+ process->signal_to_send = SIGIO; break;
+#endif
+#ifdef SIGXCPU
+ case GDBSERV_SIGXCPU:
+ process->signal_to_send = SIGXCPU; break;
+#endif
+#ifdef SIGXFSZ
+ case GDBSERV_SIGXFSZ:
+ process->signal_to_send = SIGXFSZ; break;
+#endif
+#ifdef SIGVTALRM
+ case GDBSERV_SIGVTALRM:
+ process->signal_to_send = SIGVTALRM; break;
+#endif
+#ifdef SIGPROF
+ case GDBSERV_SIGPROF:
+ process->signal_to_send = SIGPROF; break;
+#endif
+#ifdef SIGWINCH
+ case GDBSERV_SIGWINCH:
+ process->signal_to_send = SIGWINCH; break;
+#endif
+#ifdef SIGLOST
+ case GDBSERV_SIGLOST:
+ process->signal_to_send = SIGLOST; break;
+#endif
+#ifdef SIGUSR1
+ case GDBSERV_SIGUSR1:
+ process->signal_to_send = SIGUSR1; break;
+#endif
+#ifdef SIGUSR2
+ case GDBSERV_SIGUSR2:
+ process->signal_to_send = SIGUSR2; break;
+#endif
+#ifdef SIGPWR
+ case GDBSERV_SIGPWR:
+ process->signal_to_send = SIGPWR; break;
+#endif
+#ifdef SIGPOLL
+ case GDBSERV_SIGPOLL:
+ process->signal_to_send = SIGPOLL; break;
+#endif
+#ifdef SIGWIND
+ case GDBSERV_SIGWIND:
+ process->signal_to_send = SIGWIND; break;
+#endif
+#ifdef SIGPHONE
+ case GDBSERV_SIGPHONE:
+ process->signal_to_send = SIGPHONE; break;
+#endif
+#ifdef SIGWAITING
+ case GDBSERV_SIGWAITING:
+ process->signal_to_send = SIGWAITING; break;
+#endif
+#ifdef SIGLWP
+ case GDBSERV_SIGLWP:
+ process->signal_to_send = SIGLWP; break;
+#endif
+#ifdef SIGDANGER
+ case GDBSERV_SIGDANGER:
+ process->signal_to_send = SIGDANGER; break;
+#endif
+#ifdef SIGGRANT
+ case GDBSERV_SIGGRANT:
+ process->signal_to_send = SIGGRANT; break;
+#endif
+#ifdef SIGRETRACT
+ case GDBSERV_SIGRETRACT:
+ process->signal_to_send = SIGRETRACT; break;
+#endif
+#ifdef SIGMSG
+ case GDBSERV_SIGMSG:
+ process->signal_to_send = SIGMSG; break;
+#endif
+#ifdef SIGSOUND
+ case GDBSERV_SIGSOUND:
+ process->signal_to_send = SIGSOUND; break;
+#endif
+#ifdef SIGSAK
+ case GDBSERV_SIGSAK:
+ process->signal_to_send = SIGSAK; break;
+#endif
+#ifdef SIGPRIO
+ case GDBSERV_SIGPRIO:
+ process->signal_to_send = SIGPRIO; break;
+#endif
+#if defined (SIGRTMIN) && defined (SIGRTMAX)
+ case GDBSERV_SIGRT32:
+ process->signal_to_send = SIGRTMIN; break;
+ case GDBSERV_SIGRT33:
+ process->signal_to_send = SIGRTMIN+1; break;
+ case GDBSERV_SIGRT34:
+ process->signal_to_send = SIGRTMIN+2; break;
+ case GDBSERV_SIGRT35:
+ process->signal_to_send = SIGRTMIN+3; break;
+ case GDBSERV_SIGRT36:
+ process->signal_to_send = SIGRTMIN+4; break;
+ case GDBSERV_SIGRT37:
+ process->signal_to_send = SIGRTMIN+5; break;
+ case GDBSERV_SIGRT38:
+ process->signal_to_send = SIGRTMIN+6; break;
+ case GDBSERV_SIGRT39:
+ process->signal_to_send = SIGRTMIN+7; break;
+ case GDBSERV_SIGRT40:
+ process->signal_to_send = SIGRTMIN+8; break;
+ case GDBSERV_SIGRT41:
+ process->signal_to_send = SIGRTMIN+9; break;
+ case GDBSERV_SIGRT42:
+ process->signal_to_send = SIGRTMIN+10; break;
+ case GDBSERV_SIGRT43:
+ process->signal_to_send = SIGRTMIN+11; break;
+ case GDBSERV_SIGRT44:
+ process->signal_to_send = SIGRTMIN+12; break;
+ case GDBSERV_SIGRT45:
+ process->signal_to_send = SIGRTMIN+13; break;
+ case GDBSERV_SIGRT46:
+ process->signal_to_send = SIGRTMIN+14; break;
+ case GDBSERV_SIGRT47:
+ process->signal_to_send = SIGRTMIN+15; break;
+ case GDBSERV_SIGRT48:
+ process->signal_to_send = SIGRTMIN+16; break;
+ case GDBSERV_SIGRT49:
+ process->signal_to_send = SIGRTMIN+17; break;
+ case GDBSERV_SIGRT50:
+ process->signal_to_send = SIGRTMIN+18; break;
+ case GDBSERV_SIGRT51:
+ process->signal_to_send = SIGRTMIN+19; break;
+ case GDBSERV_SIGRT52:
+ process->signal_to_send = SIGRTMIN+20; break;
+ case GDBSERV_SIGRT53:
+ process->signal_to_send = SIGRTMIN+21; break;
+ case GDBSERV_SIGRT54:
+ process->signal_to_send = SIGRTMIN+22; break;
+ case GDBSERV_SIGRT55:
+ process->signal_to_send = SIGRTMIN+23; break;
+ case GDBSERV_SIGRT56:
+ process->signal_to_send = SIGRTMIN+24; break;
+ case GDBSERV_SIGRT57:
+ process->signal_to_send = SIGRTMIN+25; break;
+ case GDBSERV_SIGRT58:
+ process->signal_to_send = SIGRTMIN+26; break;
+ case GDBSERV_SIGRT59:
+ process->signal_to_send = SIGRTMIN+27; break;
+ case GDBSERV_SIGRT60:
+ process->signal_to_send = SIGRTMIN+28; break;
+ case GDBSERV_SIGRT61:
+ process->signal_to_send = SIGRTMIN+29; break;
+ case GDBSERV_SIGRT62:
+ process->signal_to_send = SIGRTMIN+30; break;
+ case GDBSERV_SIGRT63:
+ process->signal_to_send = SIGRTMIN+31; break;
+ case GDBSERV_SIGRT64:
+ process->signal_to_send = SIGRTMIN+32; break;
+#endif
+ }
+ /* Since we will handle the signal, we don't want gdbserv
+ to handle it by calling kill! Return zero. */
+ return 0;
+}
+
+/* Read memory vector
+ */
+
+static long
+ptrace_xfer_mem (struct gdbserv *serv,
+ struct gdbserv_reg *addr,
+ void *data,
+ long len,
+ int read)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ ptrace_arg3_type request_base;
+ ptrace_arg3_type xfer_base;
+ ptrace_arg3_type temp_addr;
+ ptrace_xfer_type *buf;
+ long xfer_count;
+ int i;
+
+ /* Get request address. */
+ gdbserv_host_bytes_from_reg (serv, &request_base, sizeof (request_base),
+ addr, 0);
+ /* Round down to a PTRACE word boundary. */
+ xfer_base = request_base & - PTRACE_XFER_SIZE;
+ /* Round length up to a PTRACE word boundary. */
+ xfer_count = (((request_base + len) - xfer_base) + PTRACE_XFER_SIZE - 1)
+ / PTRACE_XFER_SIZE;
+
+ /* Allocate space for xfer. */
+ buf = (ptrace_xfer_type *) alloca (xfer_count * PTRACE_XFER_SIZE);
+
+ /* Perform memory xfer. */
+ if (read)
+ {
+ for (i = 0; i < xfer_count; i++)
+ {
+ temp_addr = xfer_base + i * PTRACE_XFER_SIZE;
+
+ errno = 0;
+ buf[i] = ptrace (PTRACE_PEEKTEXT, process->pid, temp_addr, 0L);
+
+ if (process->debug_backend)
+ fprintf (stderr, "PTRACE_PEEKTEXT-1 0x%08llx in %d, 0x%08llx\n",
+ (long long) temp_addr, process->pid, (long long) buf[i]);
+ if (errno)
+ {
+ if (errno != EIO)
+ fprintf (stderr,
+ "xfer_mem(1): ptrace error at 0x%08lx in %d: %s\n",
+ (long) temp_addr, process->pid, strerror (errno));
+ return -1;
+ }
+ }
+
+ /* Copy results to caller's buffer space. */
+ memcpy (data, (char *) buf + (request_base - xfer_base), len);
+ }
+ else /* write */
+ {
+ /* If the xfer buffer overlaps the write-request buffer,
+ we must first read the values that are there before
+ replacing with the desired values (otherwise these bytes
+ would be uninitialized). */
+ if ((unsigned long long) xfer_base <
+ (unsigned long long) request_base)
+ {
+ errno = 0;
+ buf[0] = ptrace (PTRACE_PEEKTEXT,
+ process->pid, xfer_base, 0L);
+ if (process->debug_backend)
+ fprintf (stderr, "PTRACE_PEEKTEXT-2 0x%08llx in %d, 0x%08llx\n",
+ (long long) xfer_base, process->pid, (long long) buf[0]);
+
+ if (errno)
+ {
+ if (errno != EIO)
+ fprintf (stderr,
+ "xfer_mem(2): ptrace error at 0x%08llx in %d: %s\n",
+ (long long) xfer_base, process->pid, strerror (errno));
+ return -1;
+ }
+ }
+ if ((xfer_count > 0) &&
+ ((unsigned long long) (xfer_base + xfer_count * PTRACE_XFER_SIZE) >
+ (unsigned long long) (request_base + len)))
+ {
+ temp_addr = xfer_base + (xfer_count - 1) * PTRACE_XFER_SIZE;
+ errno = 0;
+ buf[xfer_count - 1] =
+ ptrace (PTRACE_PEEKTEXT, process->pid, temp_addr, 0L);
+ if (process->debug_backend)
+ fprintf (stderr, "PTRACE_PEEKTEXT-3 0x%08llx in %d, 0x%08llx\n",
+ (long long) temp_addr, process->pid,
+ (long long) buf[xfer_count - 1]);
+
+ if (errno)
+ {
+ if (errno != EIO)
+ fprintf (stderr,
+ "xfer_mem(3): ptrace error at 0x%08lx in %d: %s\n",
+ (long) temp_addr, process->pid, strerror (errno));
+ return -1;
+ }
+ }
+
+ /* Now copy user buffer to xfer buffer. */
+ memcpy ((char *) buf + (request_base - xfer_base), data, len);
+ /* Now write out the data. */
+ for (i = 0; i < xfer_count; i++)
+ {
+ temp_addr = xfer_base + i * PTRACE_XFER_SIZE;
+
+ errno = 0;
+ ptrace (PTRACE_POKETEXT, process->pid, temp_addr, buf[i]);
+
+ if (process->debug_backend)
+ fprintf (stderr, "PTRACE_POKETEXT 0x%08llx in %d, 0x%08llx\n",
+ (long long) temp_addr, process->pid, (long long) buf[i]);
+
+ if (errno)
+ {
+ if (errno != EIO)
+ fprintf (stderr,
+ "xfer_mem(4): ptrace error at 0x%08llx in %d: %s\n",
+ (long long) temp_addr, process->pid, strerror (errno));
+ return -1;
+ }
+ }
+ }
+
+ return len;
+}
+
+long
+ptrace_set_mem (struct gdbserv *serv,
+ struct gdbserv_reg *addr,
+ void *data,
+ long len)
+{
+ return ptrace_xfer_mem (serv, addr, data, len, 0);
+}
+
+long
+ptrace_get_mem (struct gdbserv *serv,
+ struct gdbserv_reg *addr,
+ void *data,
+ long len)
+{
+ return ptrace_xfer_mem (serv, addr, data, len, 1);
+}
+
+
+
+
+/* Detach vector -- shut down this target connection.
+ */
+
+static void
+ptrace_detach (struct gdbserv *serv, struct gdbserv_target *target)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ assert (ptrace_connect_lock == serv);
+
+ if (process->debug_informational)
+ fprintf (stderr, "ptrace - detached.\n");
+ ptrace_connect_lock = NULL;
+
+ /* Quit out of main loop for this demo. In general, this is not
+ necessary, as the next incoming connection could again be handled
+ by ptrace_attach() above. */
+ server_quit_p = 1;
+}
+
+/* This function is called from gdbloop_poll when a new incoming
+ connection is attempted. It may return NULL if the new connection
+ is to be refused, or a gdbserv_target vector if the connection is
+ accepted. */
+
+struct gdbserv_target*
+ptrace_attach (struct gdbserv *serv, void *data)
+{
+ struct gdbserv_target *ptrace_target;
+ struct child_process *process = data;
+ union wait w;
+ int pid;
+
+
+ /* Enable server tracing. */
+ /* gdbserv_state_trace = stderr;*/
+
+ if (ptrace_connect_lock != NULL)
+ {
+ fprintf (stderr, "ptrace: rejected duplicate connection.\n");
+ return NULL;
+ }
+
+ if (process->debug_informational)
+ fprintf (stderr, "ptrace: accepted gdb connection.\n");
+ ptrace_connect_lock = serv;
+
+ process->pid = ptrace_create_child (process);
+
+ do {
+ pid = wait (&w);
+ } while (pid != process->pid);
+
+ handle_waitstatus (process, w);
+
+ if (process->pid > 0)
+ {
+ if (process->debug_informational)
+ fprintf (stderr, "ptrace: created child process %d, %s\n",
+ process->pid, process->executable);
+ }
+ else
+ {
+ fprintf (stderr, "PTRACE: failed to create child process %s!\n",
+ process->executable);
+ return NULL;
+ }
+
+ ptrace_target = malloc (sizeof (struct gdbserv_target));
+ memset (ptrace_target, 0, sizeof (*ptrace_target));
+
+ /* Callback structure for function pointers that handle processed
+ control packets. See gdbserv-target.h for docs on the individual
+ functions. */
+
+ ptrace_target->process_get_gen = NULL;
+ ptrace_target->process_set_gen = NULL;
+ ptrace_target->process_rcmd = NULL;
+ ptrace_target->process_set_args = NULL;
+ ptrace_target->process_set_reg = NULL;
+ ptrace_target->process_get_reg = NULL;
+ ptrace_target->process_set_regs = NULL;
+ ptrace_target->process_get_regs = NULL;
+ ptrace_target->input_reg = NULL;
+ ptrace_target->output_reg = NULL;
+ ptrace_target->gg_reg_nr = NULL;
+ ptrace_target->expedited_reg_nr = NULL;
+ ptrace_target->sizeof_reg = NULL;
+ ptrace_target->set_reg = NULL;
+ ptrace_target->get_reg = NULL;
+ ptrace_target->get_mem = ptrace_get_mem;
+ ptrace_target->set_mem = ptrace_set_mem;
+ ptrace_target->process_set_pc = NULL;
+ ptrace_target->flush_i_cache = ptrace_flush_i_cache;
+ ptrace_target->process_signal = ptrace_process_signal;
+ ptrace_target->compute_signal = ptrace_compute_signal;
+ ptrace_target->get_trap_number = ptrace_get_trap_number;
+ ptrace_target->exit_program = ptrace_exit_program;
+ ptrace_target->break_program = ptrace_break_program;
+ ptrace_target->reset_program = NULL;
+ ptrace_target->restart_program = NULL;
+ ptrace_target->singlestep_program = ptrace_singlestep_program;
+ ptrace_target->cyclestep_program = NULL;
+ ptrace_target->sigkill_program = ptrace_sigkill_program;
+ ptrace_target->continue_program = ptrace_continue_program;
+ ptrace_target->remove_breakpoint = NULL;
+ ptrace_target->set_breakpoint = NULL;
+ ptrace_target->process_target_packet = NULL;
+ ptrace_target->detach = ptrace_detach;
+
+ ptrace_target->data = data; /* Save ptr to child_process struct. */
+
+#if defined(_MIPSEL) || defined(_MIPSEB)
+ process->is_ss = 0;
+#endif
+
+ return ptrace_target;
+}
+
+/* This function is called from the main loop, and waits for an event
+ (such as a signal or exception) from the running child process. */
+
+int
+ptrace_check_child_state (struct child_process *process)
+{
+ int ret;
+ union wait w;
+
+ ret = waitpid (process->pid, (int *) &w, WNOHANG);
+
+ if (ret > 0) /* found an event */
+ {
+ ret = handle_waitstatus (process, w);
+ if (process->debug_backend)
+ fprintf (stderr, "wait returned %d\n", ret);
+ return 1;
+ }
+ return 0;
+}
+
+/* Exported service functions */
+
+/* Function: continue_lwp
+ Send PTRACE_CONT to an lwp.
+ Returns -1 for failure, zero for success. */
+
+extern int
+continue_lwp (lwpid_t lwpid, int signal)
+{
+ if (thread_db_noisy)
+ fprintf (stderr, "<ptrace (PTRACE_CONT, %d, 0, %d)>\n", lwpid, signal);
+
+ if (ptrace (PTRACE_CONT, lwpid, 0, signal) < 0)
+ {
+ fprintf (stderr, "<<< ERROR: PTRACE_CONT %d failed >>>\n", lwpid);
+ return -1;
+ }
+ return 0;
+}
+
+/* Function: singlestep_lwp
+ Send PTRACE_SINGLESTEP to an lwp.
+ Returns -1 for failure, zero for success. */
+
+int
+singlestep_lwp (struct gdbserv *serv, lwpid_t lwpid, int signal)
+{
+
+#if defined (MIPS_LINUX_TARGET) || defined (MIPS64_LINUX_TARGET)
+ {
+ if (thread_db_noisy)
+ fprintf (stderr, "<singlestep_lwp lwpid=%d signal=%d>\n", lwpid, signal);
+ mips_singlestep (serv, lwpid, signal);
+ return 0;
+ }
+#else
+ if (thread_db_noisy)
+ fprintf (stderr, "<ptrace (PTRACE_SINGLESTEP, %d, 0, %d)>\n", lwpid, signal);
+
+ if (ptrace (PTRACE_SINGLESTEP, lwpid, 0, signal) < 0)
+ {
+ fprintf (stderr, "<<< ERROR: PTRACE_SINGLESTEP %d failed >>>\n", lwpid);
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+/* Function: attach_lwp
+ Send PTRACE_ATTACH to an lwp.
+ Returns -1 for failure, zero for success. */
+
+extern int
+attach_lwp (lwpid_t lwpid)
+{
+ errno = 0;
+ if (ptrace (PTRACE_ATTACH, lwpid, 0, 0) == 0)
+ {
+ if (thread_db_noisy)
+ fprintf (stderr, "<ptrace (PTRACE_ATTACH, %d, 0, 0)>\n", lwpid);
+ return 0;
+ }
+ else
+ {
+ fprintf (stderr, "<<< ERROR ptrace attach %d failed, %s >>>\n",
+ lwpid, strerror (errno));
+ return -1;
+ }
+}
+
+
+/* Generate code for the tkill system call. */
+_syscall2(int, tkill, pid_t, tid, int, sig)
+
+
+/* Function: stop_lwp
+ Use SIGSTOP to force an lwp to stop.
+ Returns -1 for failure, zero for success. */
+
+extern int
+stop_lwp (lwpid_t lwpid)
+{
+ if (tkill (lwpid, SIGSTOP) == 0)
+ {
+#if 0 /* Too noisy! */
+ if (thread_db_noisy)
+ fprintf (stderr, "<tkill (%d, SIGSTOP)>\n", lwpid);
+#endif
+ return 0;
+ }
+ else
+ {
+ fprintf (stderr, "<<< ERROR -- tkill (%d, SIGSTOP) failed >>>\n", lwpid);
+ return -1;
+ }
+}
+
+/* proc_service callback functions */
+
+ps_err_e
+ps_pstop (gdb_ps_prochandle_t ph) /* Process stop */
+{
+ fprintf (stderr, "<ps_pstop [UN-IMPLEMENTED]>\n");
+ return PS_ERR; /* unimplemented. */
+}
+
+ps_err_e
+ps_pcontinue (gdb_ps_prochandle_t ph) /* Process continue */
+{
+ fprintf (stderr, "<ps_pcontinue [UN-IMPLEMENTED]>\n");
+ return PS_ERR; /* unimplemented. */
+}
+
+ps_err_e
+ps_lstop (gdb_ps_prochandle_t ph, /* LWP stop */
+ lwpid_t lwpid)
+{
+ fprintf (stderr, "<ps_lstop [UN-IMPLEMENTED]>\n");
+ return PS_ERR; /* unimplemented. */
+}
+
+ps_err_e
+ps_lcontinue (gdb_ps_prochandle_t ph, /* LWP continue */
+ lwpid_t lwpid)
+{
+ if (continue_lwp (lwpid, 0) < 0)
+ return PS_OK;
+ else
+ return PS_ERR;
+}
+
+ps_err_e
+ps_pdread (gdb_ps_prochandle_t ph, /* read from data segment */
+ paddr_t addr,
+ gdb_ps_read_buf_t buf,
+ gdb_ps_size_t size)
+{
+ long bytes_read;
+ struct gdbserv_reg addr_reg;
+
+ /* Use unsigned long long for maximum portability. */
+ gdbserv_ulonglong_to_reg (ph->serv, (unsigned long long) addr, &addr_reg);
+
+ bytes_read = ptrace_get_mem (ph->serv, &addr_reg, buf, (long) size);
+
+ if (bytes_read == (long) size)
+ return PS_OK;
+ else
+ return PS_ERR;
+}
+
+ps_err_e
+ps_pdwrite (gdb_ps_prochandle_t ph, /* write to data segment */
+ paddr_t addr,
+ gdb_ps_write_buf_t buf,
+ gdb_ps_size_t size)
+{
+ long bytes_written;
+ struct gdbserv_reg addr_reg;
+
+ /* Use unsigned long long for maximum portability. */
+ gdbserv_ulonglong_to_reg (ph->serv, (unsigned long long) addr, &addr_reg);
+
+ bytes_written = ptrace_set_mem (ph->serv, &addr_reg, buf, (long) size);
+
+ if (bytes_written == (long) size)
+ return PS_OK;
+ else
+ return PS_ERR;
+}
+
+ps_err_e
+ps_ptread (gdb_ps_prochandle_t ph, /* read from text segment */
+ paddr_t addr,
+ gdb_ps_read_buf_t buf,
+ gdb_ps_size_t size)
+{
+ long bytes_read;
+ struct gdbserv_reg addr_reg;
+
+ /* Use unsigned long long for maximum portability. */
+ gdbserv_ulonglong_to_reg (ph->serv, (unsigned long long) addr, &addr_reg);
+
+ bytes_read = ptrace_get_mem (ph->serv, &addr_reg, buf, (long) size);
+
+ if (bytes_read == (long) size)
+ return PS_OK;
+ else
+ return PS_ERR;
+}
+
+ps_err_e
+ps_ptwrite (gdb_ps_prochandle_t ph, /* write to text segment */
+ paddr_t addr,
+ gdb_ps_write_buf_t buf,
+ gdb_ps_size_t size)
+{
+ long bytes_written;
+ struct gdbserv_reg addr_reg;
+
+ /* Use unsigned long long for maximum portability. */
+ gdbserv_ulonglong_to_reg (ph->serv, (unsigned long long) addr, &addr_reg);
+
+ bytes_written = ptrace_set_mem (ph->serv, &addr_reg, buf, (long) size);
+
+ if (bytes_written == (long) size)
+ return PS_OK;
+ else
+ return PS_ERR;
+}
+
diff --git a/rda/unix/thread-db.c b/rda/unix/thread-db.c
new file mode 100644
index 00000000000..43f5b492db3
--- /dev/null
+++ b/rda/unix/thread-db.c
@@ -0,0 +1,2864 @@
+/* thread-db.c
+
+ Copyright 2001, 2002 Red Hat, Inc.
+
+ This file is part of RDA, the Red Hat Debug Agent (and library).
+
+ 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.
+
+ Alternative licenses for RDA may be arranged by contacting Red Hat,
+ Inc. */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <thread_db.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <assert.h>
+
+#include "gdbserv.h"
+#include "gdbserv-target.h"
+#include "gdbserv-utils.h"
+#include "server.h"
+#include "arch.h"
+#include "gdb_proc_service.h"
+#include "gdbserv-thread-db.h"
+
+/* Make lots of noise (debugging output). */
+int thread_db_noisy = 1;
+int proc_service_noisy = 0;
+
+/*
+ * A tiny local symbol table.
+ *
+ * This is used by ps_pglobal_lookup, and is really just a
+ * local cache of symbols whose values we have obtained from gdb.
+ *
+ * Since the cache is expected to be small, and infrequently used,
+ * there is no effort to sort or hash it. Symbols may be added
+ * in an "undefined" state, and then defined later.
+ */
+
+/* The "defined_p" field may have one of the following three values. */
+enum symbol_cache_defined { UNDEFINED, REQUESTED, DEFINED };
+
+struct symbol_cache {
+ char *name;
+ paddr_t value;
+ enum symbol_cache_defined defined_p;
+ struct symbol_cache *next;
+} *symbol_list;
+
+/* Function: add_symbol_to_list
+ Add a symbol to the symbol cache. First checks to see if
+ an entry is already in there, and re-uses it if so. This way
+ the cache may be used for symbols awaiting lookup as well as
+ for those that have already been defined by the debugger. */
+
+static void
+add_symbol_to_list (const char *name, paddr_t value, int defined_p)
+{
+ struct symbol_cache *tmp;
+
+ for (tmp = symbol_list; tmp; tmp = tmp->next)
+ {
+ if (strcmp (name, tmp->name) == 0)
+ {
+ /* Symbol is already in cache -- set its value and definedness. */
+ tmp->value = value;
+ if (defined_p == DEFINED)
+ tmp->defined_p = defined_p;
+ return;
+ }
+ }
+
+ /* Symbol is not in cache -- add it. */
+ tmp = malloc (sizeof (struct symbol_cache));
+
+ tmp->value = value;
+ tmp->defined_p = defined_p;
+ tmp->name = malloc (strlen (name) + 1);
+ strcpy (tmp->name, name);
+ /* LIFO */
+ tmp->next = symbol_list;
+ symbol_list = tmp;
+}
+
+/* Function: free_symbol_list
+ Empty the symbol cache. */
+
+static void
+free_symbol_list (void)
+{
+ struct symbol_cache *tmp;
+
+ for (tmp = symbol_list; tmp; tmp = symbol_list)
+ {
+ symbol_list = tmp->next;
+ free (tmp->name);
+ free (tmp);
+ }
+}
+
+/* Function: sync_symbol_list
+ Return all "requested" symbols to the "undefined" state
+ (so they can be "requested" again). Called when a new
+ source of symbols becomes available (eg. a new shared object). */
+
+static void
+sync_symbol_list (void)
+{
+ struct symbol_cache *tmp;
+
+ for (tmp = symbol_list; tmp; tmp = tmp->next)
+ if (tmp->defined_p == REQUESTED)
+ tmp->defined_p = UNDEFINED;
+}
+
+/* Function: lookup_cached_symbol
+ If symbol is defined and cached, return its value in VALUE.
+ Return: 0 if not found, 1 if found. */
+
+static int
+lookup_cached_symbol (char *name, paddr_t *value)
+{
+ struct symbol_cache *tmp;
+
+ for (tmp = symbol_list; tmp; tmp = tmp->next)
+ if (strcmp (name, tmp->name) == 0 && tmp->defined_p == DEFINED)
+ {
+ *value = tmp->value; /* known and defined */
+ return 1;
+ }
+
+ return 0; /* not found */
+}
+
+/* Function: next_undefined_symbol
+ Find a symbol in the cache that needs lookup by GDB.
+ On returning a symbol, mark it REQUESTED, so that it won't
+ be requested again until a new source of symbols opens up
+ (eg. a new shared object). */
+
+static char *
+next_undefined_symbol (void)
+{
+ struct symbol_cache *tmp;
+ /* Make a pass thru the list, and return the first symbol that
+ hasn't been either requested or defined. */
+ for (tmp = symbol_list; tmp; tmp = tmp->next)
+ if (tmp->defined_p == UNDEFINED)
+ {
+ tmp->defined_p = REQUESTED;
+ return tmp->name;
+ }
+ return NULL;
+}
+
+/*
+ * A tiny local thread list.
+ *
+ * This local list of threads is used for gdbserv operations that
+ * require a struct gdbserv_thread. Its first use will be to
+ * implement "info threads" for gdb.
+ */
+
+/* Define the struct gdbserv_thread object. */
+
+struct gdbserv_thread {
+ td_thrinfo_t ti;
+
+ /* True if we have attached to this thread, but haven't yet
+ continued or single-stepped it. */
+ int attached : 1;
+
+ /* True if we have sent this thread a SIGSTOP (because some other
+ thread has had something interesting happen, and we want the
+ whole program to stop), but not yet continued or single-stepped it. */
+ int stopped : 1;
+
+ /* True if we have called waitpid, and consumed any extraneous wait
+ statuses created by attaching, stopping, etc. */
+ int waited : 1;
+
+ /* True if we last single-stepped this thread, instead of continuing
+ it. When choosing one event out of many to report to GDB, we
+ give stepped events higher priority than some others. */
+ int stepping : 1;
+ struct gdbserv_thread *next;
+} *thread_list;
+
+/* Function: add_thread_to_list
+ Add a thread (provided by libthread_db) to the local list. */
+
+static struct gdbserv_thread *
+add_thread_to_list (td_thrinfo_t *ti)
+{
+ struct gdbserv_thread *new = malloc (sizeof (struct gdbserv_thread));
+
+ /* First cut -- add to start of list. */
+ memcpy (&new->ti, ti, sizeof (td_thrinfo_t));
+ new->next = thread_list;
+ thread_list = new;
+ return new;
+}
+
+static struct gdbserv_thread *
+first_thread_in_list (void)
+{
+ return thread_list;
+}
+
+static struct gdbserv_thread *
+next_thread_in_list (struct gdbserv_thread *thread)
+{
+ if (thread == NULL)
+ return thread_list;
+ else
+ return thread->next;
+}
+
+static void
+delete_thread_from_list (struct gdbserv_thread *thread)
+{
+ struct gdbserv_thread *tmp;
+
+ for (tmp = thread_list; tmp; tmp = tmp->next)
+ {
+ if (tmp->next == thread)
+ {
+ tmp->next = tmp->next->next; /* unlink */
+ free (thread); /* discard */
+ return; /* finished */
+ }
+ }
+ /* Special case -- delete first element of list. */
+ if (thread == thread_list)
+ {
+ thread_list = thread->next; /* unlink */
+ free (thread); /* discard */
+ return; /* finished */
+ }
+ /* If we reach this point, the thread wasn't in the list. */
+}
+
+static void
+free_thread_list (void)
+{
+ struct gdbserv_thread *tmp;
+
+ for (tmp = thread_list; tmp; tmp = thread_list)
+ {
+ thread_list = tmp->next;
+ free (tmp);
+ }
+}
+
+static struct gdbserv_thread *
+thread_list_lookup_by_tid (thread_t tid)
+{
+ struct gdbserv_thread *tmp;
+
+ for (tmp = thread_list; tmp; tmp = tmp->next)
+ if (tmp->ti.ti_tid == tid)
+ break;
+
+ return tmp;
+}
+
+static struct gdbserv_thread *
+thread_list_lookup_by_lid (lwpid_t pid)
+{
+ struct gdbserv_thread *tmp;
+
+ for (tmp = thread_list; tmp; tmp = tmp->next)
+ if (tmp->ti.ti_lid == pid)
+ break;
+
+ return tmp;
+}
+
+/* Return a pointer to a statically allocated string describing
+ THREAD. For debugging. */
+static const char *
+thread_debug_name (struct gdbserv_thread *thread)
+{
+ if (thread)
+ {
+ static char buf[50];
+ sprintf (buf, "(%p %d)", thread, thread->ti.ti_lid);
+ return buf;
+ }
+ else
+ return "(null thread)";
+}
+
+/* A copy of the next lower layer's target vector, before we modify it. */
+static struct gdbserv_target parentvec;
+
+/* A pointer to the current target vector. */
+static struct gdbserv_target *currentvec;
+
+/*
+ * proc_service callback functions, called by thread_db.
+ */
+
+void
+ps_plog (const char *fmt, ...)
+{
+ fprintf (stderr, "<ps_plog: %s>\n", fmt);
+ return;
+}
+
+/* Look up a symbol in GDB's global symbol table.
+ Return the symbol's address.
+ FIXME: it would be more correct to look up the symbol in the context
+ of the LD_OBJECT_NAME provided. However we're probably fairly safe
+ as long as there aren't name conflicts with other libraries. */
+
+ps_err_e
+ps_pglobal_lookup (gdb_ps_prochandle_t ph,
+ const char *ld_object_name, /* the library name */
+ const char *ld_symbol_name, /* the symbol name */
+ paddr_t *ld_symbol_addr) /* return the symbol addr */
+{
+ paddr_t value;
+
+ if (lookup_cached_symbol ((char *) ld_symbol_name, &value) == 0)
+ {
+ /* Symbol not in cache -- ask GDB to look it up.
+ Add the symbol to the cache as undefined. */
+ add_symbol_to_list ((char *) ld_symbol_name, 0, UNDEFINED);
+ return PS_NOSYM;
+ }
+ else
+ {
+ /* Symbol is in the cache and defined -- return its value. */
+ *ld_symbol_addr = value;
+ return PS_OK;
+ }
+}
+
+
+/* Connection to the libthread_db library. */
+static struct ps_prochandle proc_handle;
+static td_thragent_t *thread_agent = NULL;
+
+/* 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_delete_p) (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,
+ td_thr_state_e state,
+ int ti_pri,
+ sigset_t *ti_sigmask,
+ 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,
+ FPREGSET_T *regset);
+static td_err_e (*td_thr_getgregs_p) (const td_thrhandle_t *th,
+ GREGSET_T gregs);
+static td_err_e (*td_thr_setfpregs_p) (const td_thrhandle_t *th,
+ const FPREGSET_T *fpregs);
+static td_err_e (*td_thr_setgregs_p) (const td_thrhandle_t *th,
+ GREGSET_T gregs);
+static td_err_e (*td_thr_getxregsize_p) (const td_thrhandle_t *th,
+ int *sizep);
+static td_err_e (*td_thr_getxregs_p) (const td_thrhandle_t *th,
+ void *xregs);
+static td_err_e (*td_thr_setxregs_p) (const td_thrhandle_t *th,
+ void *xregs);
+static td_err_e (*td_thr_event_enable_p) (const td_thrhandle_t *th,
+ int event);
+static const char **(*td_symbol_list_p) (void);
+
+
+/* Function: thread_db_state_str
+ Convert a thread_db state code to a string.
+ If state code is unknown, return an <unknown> message. */
+
+static char *
+thread_db_state_str (td_thr_state_e statecode)
+{
+ static char buf[64];
+
+ switch (statecode) {
+ case TD_THR_ANY_STATE: return "<any state>";
+ case TD_THR_UNKNOWN: return "<officially unknown>";
+ case TD_THR_STOPPED: return "<stopped>";
+ case TD_THR_RUN: return "<running>";
+ case TD_THR_ACTIVE: return "<active> ";
+ case TD_THR_ZOMBIE: return "<zombie> ";
+ case TD_THR_SLEEP: return "<sleep> ";
+ case TD_THR_STOPPED_ASLEEP: return "<stopped asleep>";
+ default:
+ sprintf (buf, "<unknown state code %d>", statecode);
+ return buf;
+ }
+}
+
+static char *
+thread_db_type_str (td_thr_type_e type)
+{
+ switch (type) {
+ case TD_THR_USER: return "<user> ";
+ case TD_THR_SYSTEM: return "<system>";
+ default: return "<unknown>";
+ }
+}
+
+/* Function: thread_db_err_string
+ Convert a thread_db error code to a string.
+ If errcode is unknown, then return an <unknown> message. */
+
+static char *
+thread_db_err_str (td_err_e errcode)
+{
+ static char buf[64];
+
+ switch (errcode) {
+ 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:
+ sprintf (buf, "unknown thread_db error '%d'", errcode);
+ return buf;
+ }
+}
+
+
+/* Return a string naming the event type EVENT. */
+static const char *
+thread_db_event_str (td_event_e event)
+{
+ switch (event) {
+ case TD_READY: return "TD_READY";
+ case TD_SLEEP: return "TD_SLEEP";
+ case TD_SWITCHTO: return "TD_SWITCHTO";
+ case TD_SWITCHFROM: return "TD_SWITCHFROM";
+ case TD_LOCK_TRY: return "TD_LOCK_TRY";
+ case TD_CATCHSIG: return "TD_CATCHSIG";
+ case TD_IDLE: return "TD_IDLE";
+ case TD_CREATE: return "TD_CREATE";
+ case TD_DEATH: return "TD_DEATH";
+ case TD_PREEMPT: return "TD_PREEMPT";
+ case TD_PRI_INHERIT: return "TD_PRI_INHERIT";
+ case TD_REAP: return "TD_REAP";
+ case TD_CONCURRENCY: return "TD_CONCURRENCY";
+ case TD_TIMEOUT: return "TD_TIMEOUT";
+ default: return "<unknown>";
+ }
+}
+
+
+/* flag which indicates if the map_id2thr cache is valid. See below. */
+static int thread_db_map_id2thr_cache_valid;
+
+/* Function: thread_db_map_id2thr
+ Calling td_ta_map_id2thr() is expensive. This function invokes
+ td_ta_map_id2thr() and caches the value for future reference. The
+ cache may be invalidated by calling thread_db_invalidate_cache().
+ Returns: TD_OK on success, an appropriate error code otherwise. */
+
+static td_err_e
+thread_db_map_id2thr (const td_thragent_t *ta, thread_t pt,
+ td_thrhandle_t *th)
+{
+ static td_thrhandle_t cached_handle;
+ static thread_t input_pt;
+
+ if (pt == input_pt && thread_db_map_id2thr_cache_valid)
+ {
+ *th = cached_handle;
+ return TD_OK;
+ }
+ else
+ {
+ td_err_e status;
+
+ status = td_ta_map_id2thr_p (ta, pt, th);
+ if (status == TD_OK)
+ {
+ thread_db_map_id2thr_cache_valid = 1;
+ input_pt = pt;
+ cached_handle = *th;
+ }
+ else
+ thread_db_map_id2thr_cache_valid = 0;
+ return status;
+ }
+}
+
+/* Invalidate the map_id2thr cache. */
+static void
+thread_db_invalidate_map_id2thr_cache (void)
+{
+ thread_db_map_id2thr_cache_valid = 0;
+}
+
+/* The regset cache object. This object keeps track of the most
+ recently fetched or set gregset (of a particular type) and whether
+ or not it needs to still needs to be synchronized with the target. */
+struct regset_cache
+{
+ /* Are the cache contents valid? */
+ int valid;
+
+ /* Does cache need to be flushed? */
+ int needs_flush;
+
+ /* Handle corresponding to cached regset. */
+ td_thrhandle_t handle;
+
+ /* Size of memory area used to hold regset. */
+ int regset_size;
+
+ /* Memory area used to hold regset. */
+ void *regset_buffer;
+
+ /* Functions used to get/set regset. */
+ td_err_e (*getregset) (const td_thrhandle_t *th, void *regset);
+ td_err_e (*setregset) (const td_thrhandle_t *th, const void *regset);
+};
+
+/* Declare fpregset and gregset cache objects. */
+static struct regset_cache fpregset_cache;
+static struct regset_cache gregset_cache;
+
+/* Wrappers for td_thr_getfpregs_p, td_thr_setfpregs_p, td_thr_getgregs_p,
+ and td_thr_setgregs_p. These simply allow us to pass a void * for the
+ regset parameter. */
+
+static td_err_e
+td_thr_getfpregs_wrapper (const td_thrhandle_t *th, void *fpregs)
+{
+ return td_thr_getfpregs_p (th, fpregs);
+}
+
+static td_err_e td_thr_getgregs_wrapper (const td_thrhandle_t *th, void *gregs)
+{
+ return td_thr_getgregs_p (th, gregs);
+}
+
+static td_err_e td_thr_setfpregs_wrapper (const td_thrhandle_t *th,
+ const void *fpregs)
+{
+ return td_thr_setfpregs_p (th, fpregs);
+}
+
+static td_err_e td_thr_setgregs_wrapper (const td_thrhandle_t *th,
+ const void *gregs)
+{
+ void * gregs_nonconst = (void *) gregs;
+
+ return td_thr_setgregs_p (th, gregs_nonconst);
+}
+
+/* Initialize a regset cache object. */
+static void
+initialize_regset_cache (struct regset_cache *regset_cache,
+ const int regset_size,
+ void * const regset_buffer,
+ td_err_e (* const getregset) (const td_thrhandle_t *th,
+ void *regset),
+ td_err_e (* const setregset) (const td_thrhandle_t *th,
+ const void *regset))
+{
+ regset_cache->valid = 0;
+ regset_cache->needs_flush = 0;
+ regset_cache->regset_size = regset_size;
+ regset_cache->regset_buffer = regset_buffer;
+ regset_cache->getregset = getregset;
+ regset_cache->setregset = setregset;
+}
+
+/* Initialize the fpregset and gregset cache objects. Space for
+ the regset buffer is statically allocated to avoid calls to malloc(). */
+static void
+initialize_regset_caches (void)
+{
+ static FPREGSET_T fpregset;
+ static GREGSET_T gregset;
+
+ initialize_regset_cache (&fpregset_cache, sizeof fpregset, &fpregset,
+ td_thr_getfpregs_wrapper, td_thr_setfpregs_wrapper);
+ initialize_regset_cache (&gregset_cache, sizeof gregset, gregset,
+ td_thr_getgregs_wrapper, td_thr_setgregs_wrapper);
+}
+
+/* Synchronize a cached regset with the target. */
+static td_err_e
+thread_db_flush_regset_cache (struct regset_cache *regset_cache)
+{
+ td_err_e status = TD_OK;
+ if (regset_cache->valid && regset_cache->needs_flush)
+ {
+ status = regset_cache->setregset (&regset_cache->handle,
+ regset_cache->regset_buffer);
+ if (status != TD_OK)
+ regset_cache->valid = 0;
+ regset_cache->needs_flush = 0;
+ }
+ return status;
+}
+
+/* Synchronize the gregset and fpregset caches with the target. */
+static td_err_e
+thread_db_flush_regset_caches (void)
+{
+ td_err_e status;
+ td_err_e ret_status = TD_OK;
+
+ status = thread_db_flush_regset_cache (&fpregset_cache);
+ if (status != TD_OK)
+ ret_status = status;
+
+ status = thread_db_flush_regset_cache (&gregset_cache);
+ if (status != TD_OK)
+ ret_status = status;
+
+ return status;
+}
+
+/* Fetch a regset, using a previously cached copy if possible. */
+static td_err_e
+thread_db_get_regset (struct regset_cache *regset_cache,
+ const td_thrhandle_t *th,
+ void *regset)
+{
+ if (regset_cache->valid
+ && memcmp (&regset_cache->handle, th, sizeof *th) == 0)
+ {
+ /* Cache is valid and handles match. Copy the cached regset. */
+ memcpy (regset, regset_cache->regset_buffer, regset_cache->regset_size);
+ return TD_OK;
+ }
+ else
+ {
+ td_err_e status;
+
+ /* Handles don't match. Write out old cache contents before
+ fetching contents w/ new handle if necessary. */
+ if (regset_cache->valid && regset_cache->needs_flush)
+ {
+ status = regset_cache->setregset (&regset_cache->handle,
+ regset_cache->regset_buffer);
+ if (status != TD_OK)
+ {
+ regset_cache->needs_flush = 0;
+ regset_cache->valid = 0;
+ return status;
+ }
+ }
+
+
+ /* Fetch the regset. */
+ status = regset_cache->getregset (th, regset);
+ if (status == TD_OK)
+ {
+ /* Preserve it in the cache. */
+ regset_cache->needs_flush = 0;
+ regset_cache->valid = 1;
+ memcpy (&regset_cache->handle, th, sizeof (*th));
+ memcpy (regset_cache->regset_buffer, regset,
+ regset_cache->regset_size);
+ }
+ else
+ regset_cache->valid = 0;
+ return status;
+ }
+}
+
+/* Set a regset deferring synchronization with the target until
+ later. */
+static td_err_e
+thread_db_set_regset (struct regset_cache *regset_cache,
+ const td_thrhandle_t *th,
+ const void *regset)
+{
+ td_err_e ret_status = TD_OK;
+
+ if (regset_cache->valid && regset_cache->needs_flush
+ && memcmp (&regset_cache->handle, th, sizeof *th) != 0)
+ {
+ /* Cached regset needs to be flushed because handles don't
+ match. */
+ ret_status = thread_db_flush_regset_cache (regset_cache);
+ }
+
+ memcpy (&regset_cache->handle, th, sizeof *th);
+ memcpy (regset_cache->regset_buffer, regset, regset_cache->regset_size);
+ regset_cache->valid = 1;
+ regset_cache->needs_flush = 1;
+
+ return ret_status;
+}
+
+/* Mark a regset cache as invalid. */
+static void
+thread_db_invalidate_regset_cache (struct regset_cache *regset_cache)
+{
+ regset_cache->valid = 0;
+}
+
+/* Mark the gregset and fpregset caches as invalid. */
+static void
+thread_db_invalidate_regset_caches (void)
+{
+ thread_db_invalidate_regset_cache (&fpregset_cache);
+ thread_db_invalidate_regset_cache (&gregset_cache);
+}
+
+/* Invalidate all caches. */
+static void
+thread_db_invalidate_caches (void)
+{
+ thread_db_invalidate_regset_caches ();
+ thread_db_invalidate_map_id2thr_cache ();
+}
+
+/* Fetch the floating point registers via the fpregset cache. */
+static td_err_e
+thread_db_getfpregs (const td_thrhandle_t *th, FPREGSET_T *fpregset)
+{
+ return thread_db_get_regset (&fpregset_cache, th, fpregset);
+}
+
+/* Set the floating point registers via the fpregset cache. */
+static td_err_e
+thread_db_setfpregs (const td_thrhandle_t *th, const FPREGSET_T *fpregset)
+{
+ return thread_db_set_regset (&fpregset_cache, th, fpregset);
+}
+
+/* Fetch the general purpose registers via the gregset cache. */
+static td_err_e
+thread_db_getgregs (const td_thrhandle_t *th, GREGSET_T gregset)
+{
+ return thread_db_get_regset (&gregset_cache, th, gregset);
+}
+
+/* Set the general purpose registers via the gregset cache. */
+static td_err_e
+thread_db_setgregs (const td_thrhandle_t *th, const GREGSET_T gregset)
+{
+ return thread_db_set_regset (&gregset_cache, th, gregset);
+}
+
+
+/* Function: get_target_int_by_name
+ Read the value of a target integer, given its name and size.
+ Returns -1 for failure, zero for success. */
+
+static int
+get_target_int_by_name (char *name, void *value, int size)
+{
+ paddr_t addr;
+
+ if (ps_pglobal_lookup (&proc_handle, NULL, name, &addr) == PS_OK)
+ {
+ if (ps_pdread (&proc_handle, addr,
+ (gdb_ps_read_buf_t) value,
+ (gdb_ps_size_t) size) == PS_OK)
+ return 0;
+ }
+ return -1; /* fail */
+}
+
+/* Function: set_target_int_by_name
+ Read the value of a target integer, given its name and size.
+ Returns -1 for failure, zero for success. */
+
+static int
+set_target_int_by_name (char *name, void *value, int size)
+{
+ paddr_t addr;
+
+ if (ps_pglobal_lookup (&proc_handle, NULL, name, &addr) == PS_OK)
+ {
+ if (ps_pdwrite (&proc_handle, addr,
+ (gdb_ps_write_buf_t) value,
+ (gdb_ps_size_t) size) == PS_OK)
+ return 0;
+ }
+ return -1; /* fail */
+}
+
+/* Function: get_thread_signals
+
+ Obtain the values of the "cancel", "restart" and "debug" signals
+ used by LinuxThreads, and store them in a set of global variables
+ for use by check_child_state and friends.
+
+ Return 0 for success: we obtained the signal numbers and enabled
+ debugging in the thread library. Return -1 for failure.
+
+ Recent versions of NPTL don't define these symbols at all; you must
+ use the libthread_db event functions instead (td_ta_event_addr,
+ ...) to find out about thread creation, thread exits, and so on.
+
+ Older versions of LinuxThreads provide both interfaces. To avoid
+ changing RDA's behavior on any system it supports, we use the older
+ signal-based interface if present, and use the event-based
+ interface as a fall-back. */
+
+static int cancel_signal;
+static int restart_signal;
+static int debug_signal;
+static int got_thread_signals;
+
+static int
+get_thread_signals (void)
+{
+ int cancel, restart, debug;
+
+ /* If we've already gotten the thread signals, that's great. */
+ if (got_thread_signals)
+ return 0;
+
+ if (get_target_int_by_name ("__pthread_sig_cancel",
+ &cancel, sizeof (cancel)) == -1
+ || get_target_int_by_name ("__pthread_sig_restart",
+ &restart, sizeof (restart)) == -1
+ || get_target_int_by_name ("__pthread_sig_debug",
+ &debug, sizeof (debug)) == -1)
+ return -1;
+
+ restart_signal = restart;
+ cancel_signal = cancel;
+ debug_signal = debug;
+
+ got_thread_signals = 1;
+
+ {
+ static int debug_flag = 1;
+ set_target_int_by_name ("__pthread_threads_debug",
+ &debug_flag, sizeof (debug_flag));
+ }
+
+ return 0;
+}
+
+
+/* Return true if PROCESS stopped for a libpthread-related signal that
+ should not be reported to GDB. */
+static int
+ignore_thread_signal (struct child_process *process)
+{
+ if (process->stop_status == 'T')
+ /* Child stopped with a signal.
+ See if it was one of our special signals. */
+ return (process->stop_signal == cancel_signal || /* ignore */
+ process->stop_signal == restart_signal || /* ignore */
+ process->stop_signal == debug_signal || /* ignore */
+ process->stop_signal == SIGCHLD); /* ignore */
+
+ return 0;
+}
+
+
+/* NPTL and later versions of LinuxThreads support a set of "event"
+ functions for notifying the debugger of interesting events that
+ have taken place in the thread library, like thread creation and
+ thread death.
+
+ There are three steps to using this interface:
+
+ - First, the debugger asks libthread_db how a given event will be
+ reported; libthread_db fills in a 'td_notify_t' structure whose
+ 'type' says how. The debuggee may call functions on which the
+ debugger can set breakpoints (type == NOTIFY_BPT), hit breakpoint
+ instructions hard-coded into the program (type == NOTIFY_AUTOBPT),
+ or perform system calls to indicate that an event has occurred
+ (type == NOTIFY_SYSCALL).
+
+ - Second, the debugger tells libthread_db which events it's
+ interested in. It can ask to be notified when a given event
+ occurs in any thread, or when a given event occurs in a given
+ thread.
+
+ - Finally, the debugger watches for the given event to occur.
+
+ We make a few simplifications here:
+
+ - NPTL and LinuxThreads only actually use one kind of event
+ notification: calling a function on which the debugger can set a
+ breakpoint (NOTIFY_BPT). So although, strictly speaking, the
+ thread library could notify us in other ways, we only support
+ NOTIFY_BPT.
+
+ - NPTL and LinuxThreads only support a few kinds of events:
+ TD_CREATE (a new thread has been created), TD_DEATH (a thread has
+ exited), and TD_REAP (not sure). We are only interested in
+ TD_CREATE and TD_DEATH. */
+
+/* Ideally, these would be members of some structure somewhere, and
+ not global variables, but that's how this file is written. */
+
+/* True if we're using libthread_db events. */
+int using_thread_db_events;
+
+/* How we are notified of thread creation and death. */
+static td_notify_t create_notification, death_notification;
+
+/* Breakpoints set at the addresses indicated by create_notification
+ and death_notification. These are raw arch breakpoints, so we have
+ to delete them to step over them; the objects here will generally
+ get regenerated every time we receive an event. */
+static struct arch_bp *create_event_breakpoint;
+static struct arch_bp *death_event_breakpoint;
+
+
+/* Set NOTIFICATION to the notification method for EVENT, and check
+ that it uses NOTIFY_BPT notification. Return -1 for failure, zero
+ for success. */
+static int
+get_event_notification (td_event_e event, td_notify_t *notification)
+{
+ td_err_e ret = td_ta_event_addr_p (thread_agent, event, notification);
+ if (ret != TD_OK)
+ {
+ if (thread_db_noisy)
+ fprintf (stderr, "td_ta_event_addr (%s) -> %s\n",
+ thread_db_event_str (event),
+ thread_db_err_str (ret));
+ return -1;
+ }
+
+ if (notification->type != NOTIFY_BPT)
+ {
+ if (thread_db_noisy)
+ fprintf (stderr, "notification for %s is not NOTIFY_BPT\n",
+ thread_db_event_str (event));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* Insert a breakpoint in SERV at the address given by NOTIFICATION.
+ Return NULL for failure, or the breakpoint for success. */
+static struct arch_bp *
+set_event_breakpoint (struct gdbserv *serv, td_notify_t *notification)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ struct gdbserv_reg addr;
+
+ /* Use the widest type for the conversion, just in case. */
+ gdbserv_ulonglong_to_reg (serv, (paddr_t) notification->u.bptaddr,
+ &addr);
+
+ return process->arch->set_bp (process->breakpoint_table, &addr);
+}
+
+
+/* Insert breakpoints at all functions needed for communication with
+ the underlying thread library. Return 0 for success, -1 for
+ failure. */
+static int
+insert_thread_db_event_breakpoints (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ create_event_breakpoint = set_event_breakpoint (serv, &create_notification);
+ death_event_breakpoint = set_event_breakpoint (serv, &death_notification);
+
+ if (! create_event_breakpoint || ! death_event_breakpoint)
+ {
+ if (create_event_breakpoint)
+ process->arch->delete_bp (create_event_breakpoint);
+ if (death_event_breakpoint)
+ process->arch->delete_bp (death_event_breakpoint);
+ create_event_breakpoint = death_event_breakpoint = 0;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* Remove breakpoints from all libthread_db event notification
+ addresses. Return 0 for success, -1 for failure. */
+static int
+delete_thread_db_event_breakpoints (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ int create_ret, death_ret;
+
+ create_ret = process->arch->delete_bp (create_event_breakpoint);
+ death_ret = process->arch->delete_bp (death_event_breakpoint);
+
+ create_event_breakpoint = death_event_breakpoint = 0;
+
+ if (create_ret == 0 && death_ret == 0)
+ return 0;
+ else
+ return -1;
+}
+
+
+/* If we don't have event set manipulation macros, then we can't use
+ the event interface. */
+#if defined (td_event_emptyset)
+
+/* Tell the program being debugged by SERV to notify us of thread
+ creation and death. Return -1 for failure, zero for success. */
+static int
+request_thread_db_events (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ /* If we don't have the libthread_db functions we need, then we
+ can't use the event interface. */
+ if (! td_ta_event_addr_p
+ || ! td_ta_event_getmsg_p
+ || ! td_thr_event_enable_p
+ || ! td_ta_set_event_p)
+ return -1;
+
+ /* If we don't have an architecture object, then we don't know how
+ to insert breakpoints, even if our thread library supports the
+ event interface. */
+ if (! process->arch
+ || ! process->breakpoint_table)
+ return -1;
+
+ /* Get the notification addresses for TD_CREATE and TD_DEATH,
+ and ensure that they use NOTIFY_BPT notification. */
+ if (get_event_notification (TD_CREATE, &create_notification) == -1
+ || get_event_notification (TD_DEATH, &death_notification) == -1)
+ return -1;
+
+ insert_thread_db_event_breakpoints (serv);
+
+ /* Tell the thread library to send us those events. */
+ {
+ td_thr_events_t events;
+ td_err_e err;
+
+ /* The td_event_ thingies are all documented to be macros. So we
+ don't need to access them via pointers. */
+ td_event_emptyset (&events);
+ td_event_addset (&events, TD_CREATE);
+ td_event_addset (&events, TD_DEATH);
+ err = td_ta_set_event_p (thread_agent, &events);
+ if (err != TD_OK)
+ fprintf (stderr, "couldn't set global event mask: %s",
+ thread_db_err_str (err));
+ }
+
+ using_thread_db_events = 1;
+ return 0;
+}
+
+#else /* ! defined (td_event_emptyset) */
+
+/* td_event_emptyset is not defined, so we can't use the event
+ interface. */
+static int
+request_thread_db_events (struct gdbserv *serv)
+{
+ return -1;
+}
+
+#endif /* ! defined (td_event_emptyset) */
+
+
+/* Return non-zero if BREAKPOINT is a libthread_db event breakpoint,
+ zero otherwise. */
+static int
+hit_thread_db_event_breakpoint (struct gdbserv *serv,
+ struct gdbserv_thread *thread)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ return (process->arch->bp_hit_p (thread, create_event_breakpoint)
+ || process->arch->bp_hit_p (thread, death_event_breakpoint));
+}
+
+
+/* Call dlsym() to find the address of a symbol. If symbol lookup fails,
+ print the reason to stderr. */
+
+static void *
+lookup_sym (void *dlhandle, char *symbol)
+{
+ void *addr;
+
+ addr = dlsym (dlhandle, symbol);
+
+ if (addr == NULL)
+ fprintf (stderr, "Symbol lookup of %s failed: %s\n",
+ symbol, dlerror ());
+
+ return addr;
+}
+
+/* Function: thread_db_dlopen
+ Attach to the libthread_db library.
+ This function does all the dynamic library stuff (dlopen, dlsym).
+ Return: -1 for failure, zero for success. */
+
+static int
+thread_db_dlopen (void)
+{
+ void *dlhandle;
+
+#ifndef LIBTHREAD_DB_SO
+#define LIBTHREAD_DB_SO "libthread_db.so.1"
+#endif
+
+ if ((dlhandle = dlopen (LIBTHREAD_DB_SO, RTLD_NOW)) == NULL)
+ {
+ fprintf (stderr, "Unable to open %s: %s\n",
+ LIBTHREAD_DB_SO, dlerror ());
+ return -1; /* fail */
+ }
+
+ /* Initialize pointers to the dynamic library functions we will use.
+ */
+
+ if ((td_init_p = lookup_sym (dlhandle, "td_init")) == NULL)
+ return -1; /* fail */
+
+ if ((td_ta_new_p = lookup_sym (dlhandle, "td_ta_new")) == NULL)
+ return -1; /* fail */
+
+ if ((td_ta_delete_p = lookup_sym (dlhandle, "td_ta_delete")) == NULL)
+ return -1; /* fail */
+
+ if ((td_ta_map_id2thr_p = lookup_sym (dlhandle, "td_ta_map_id2thr")) == NULL)
+ return -1; /* fail */
+
+ if ((td_ta_map_lwp2thr_p = lookup_sym (dlhandle, "td_ta_map_lwp2thr")) == NULL)
+ return -1; /* fail */
+
+ if ((td_ta_thr_iter_p = lookup_sym (dlhandle, "td_ta_thr_iter")) == NULL)
+ return -1; /* fail */
+
+ if ((td_thr_validate_p = lookup_sym (dlhandle, "td_thr_validate")) == NULL)
+ return -1; /* fail */
+
+ if ((td_thr_get_info_p = lookup_sym (dlhandle, "td_thr_get_info")) == NULL)
+ return -1; /* fail */
+
+ if ((td_thr_getfpregs_p = lookup_sym (dlhandle, "td_thr_getfpregs")) == NULL)
+ return -1; /* fail */
+
+ if ((td_thr_getgregs_p = lookup_sym (dlhandle, "td_thr_getgregs")) == NULL)
+ return -1; /* fail */
+
+ if ((td_thr_setfpregs_p = lookup_sym (dlhandle, "td_thr_setfpregs")) == NULL)
+ return -1; /* fail */
+
+ if ((td_thr_setgregs_p = lookup_sym (dlhandle, "td_thr_setgregs")) == NULL)
+ return -1; /* fail */
+
+ /* These are not essential. */
+ td_ta_event_addr_p = dlsym (dlhandle, "td_ta_event_addr");
+ td_ta_set_event_p = dlsym (dlhandle, "td_ta_set_event");
+ td_ta_event_getmsg_p = dlsym (dlhandle, "td_ta_event_getmsg");
+ td_thr_event_enable_p = dlsym (dlhandle, "td_thr_event_enable");
+ td_thr_getxregsize_p = dlsym (dlhandle, "td_thr_getxregsize");
+ td_thr_getxregs_p = dlsym (dlhandle, "td_thr_getxregs");
+ td_thr_setxregs_p = dlsym (dlhandle, "td_thr_setxregs");
+ td_symbol_list_p = dlsym (dlhandle, "td_symbol_list");
+
+ return 0; /* success */
+}
+
+/* Function: thread_db_open
+ Open a channel to the child's thread library.
+ Returns: 0 for success, -1 for failure
+ FIXME: closure.
+ FIXME: where should we be called from? We will not succeed
+ until the thread shlib is loaded. The call from attach will not
+ succeed even if the target is statically linked, 'cause there's
+ no symbol lookup handshake on attach. Therefore I can't handle
+ a statically linked threaded process. */
+
+static int
+thread_db_open (struct gdbserv *serv, int pid)
+{ /* FIXME: once we have the serv, we can derive the pid.
+ No, not true -- not when we're called from attach.
+ But then, there isn't much use in the call from attach unles
+ I make GDB respond to symbol callbacks from there somehow. */
+ td_err_e ret;
+
+ /* If we already have a thread agent, we're all set. */
+ if (thread_agent)
+ return 0;
+
+ /* Have the proc service handle point back to our serv object and
+ the target's overall pid. */
+ proc_handle.pid = pid;
+ proc_handle.serv = serv;
+
+ ret = td_ta_new_p (&proc_handle, &thread_agent);
+ if (ret != TD_OK)
+ {
+ if (thread_db_noisy)
+ fprintf (stderr, "< -- failed, thread_agent = 0x%08x>\n",
+ (long) thread_agent);
+
+ return -1; /* failure */
+ }
+
+ /* All LinuxThreads versions support the signal-based debugging
+ interface. Newer versions of LinuxThreads also provide the
+ event-based debugging interface. NPTL has only ever supported
+ the event-based debugging interface. Prefer the signal-based
+ interface to the event-based interface, to leave behavior on
+ older systems unchanged. */
+ if (get_thread_signals () == 0)
+ return 0;
+
+ if (request_thread_db_events (serv) == -1)
+ return 0;
+
+ return -1;
+}
+
+
+/* Function: thread_db_detach
+ FIXME: gdbserv kills the inferior and exits when gdb detaches.
+ This is the best place I have from which to shut down the
+ thread_db interface, but it's not really where this should
+ be done. */
+
+static void
+thread_db_detach (struct gdbserv *serv, struct gdbserv_target *target)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ /* FIXME: this isn't really enough, and detach isn't really the
+ right place for this anyway. Do this in exit_program. */
+ td_ta_delete_p (thread_agent);
+ thread_agent = NULL;
+ currentvec = NULL;
+
+ if (process->debug_informational)
+ fprintf (stderr, "<thread_db_detach>\n");
+ if (parentvec.detach)
+ parentvec.detach (serv, target);
+}
+
+static void
+attach_thread (struct gdbserv_thread *thread)
+{
+ if (thread->ti.ti_lid != 0 &&
+ thread->ti.ti_state != TD_THR_ZOMBIE) /* Don't attach a zombie. */
+ {
+ if (attach_lwp (thread->ti.ti_lid) == 0)
+ thread->attached = 1;
+ else
+ thread->attached = 0;
+ }
+}
+
+/* Function: find_new_threads_callback
+ Enter threads into a local thread database. */
+
+static int
+find_new_threads_callback (const td_thrhandle_t *thandle, void *data)
+{
+ struct gdbserv_thread *thread;
+ td_thrinfo_t ti;
+ td_err_e ret;
+
+ if ((ret = td_thr_get_info_p (thandle, &ti)) != TD_OK)
+ {
+ fprintf (stderr, "<find_new_threads_callback: get_info failed! %s>\n",
+ thread_db_err_str (ret));
+ return -1;
+ }
+
+ /* Enter the thread into a local list
+ (unless it is TD_THR_UNKNOWN, which means its defunct). */
+ if ((thread = thread_list_lookup_by_tid (ti.ti_tid)) == NULL)
+ {
+ if (ti.ti_state != TD_THR_UNKNOWN)
+ {
+ thread = add_thread_to_list (&ti);
+
+ if (thread_db_noisy)
+ fprintf (stderr, "(new thread %s)\n", thread_debug_name (thread));
+
+ /* Now make sure we've attached to it.
+ Skip the main pid (already attached). */
+ if (thread->ti.ti_lid != proc_handle.pid)
+ {
+ attach_thread (thread);
+ }
+
+ if (using_thread_db_events)
+ {
+ /* Enable event reporting in this thread. */
+ if (td_thr_event_enable_p (thandle, 1) != TD_OK)
+ fprintf (stderr, "couldn't enable event reporting "
+ "in thread %d\n",
+ ti.ti_lid);
+ }
+ }
+ }
+ else
+ {
+ /* Already in list -- cache new thread info */
+ memcpy (&thread->ti, &ti, sizeof (ti));
+ }
+
+ return 0;
+}
+
+/* Function: update_thread_list
+
+ First run td_ta_thr_iter to find all threads.
+ Then walk the list and validate that each thread is still running.
+ If not, prune it from the list. */
+
+static void
+update_thread_list (void)
+{
+ struct gdbserv_thread *thread, *next;
+ td_thrhandle_t handle;
+
+ /* First make sure all libthread threads are in the list. */
+ td_ta_thr_iter_p (thread_agent, find_new_threads_callback,
+ (void *) 0,
+ TD_THR_ANY_STATE,
+ TD_THR_LOWEST_PRIORITY,
+ TD_SIGNO_MASK,
+ TD_THR_ANY_USER_FLAGS);
+
+ /* Next, remove any defunct threads from the list. */
+ for (thread = first_thread_in_list ();
+ thread;
+ thread = next)
+ {
+ /* Thread may be deleted, so find its successor first! */
+ next = next_thread_in_list (thread);
+
+ /* Now ask if thread is still valid, and if not, delete it. */
+ if (thread_db_map_id2thr (thread_agent,
+ thread->ti.ti_tid,
+ &handle) != TD_OK
+ || td_thr_validate_p (&handle) != TD_OK)
+ {
+ if (thread->ti.ti_state == TD_THR_UNKNOWN)
+ {
+ /* Thread is no longer "valid".
+ By the time this happens, it's too late for us to
+ detach from it. Just delete it from the list. */
+
+ delete_thread_from_list (thread);
+ }
+ }
+ }
+}
+
+/* Function: thread_db_thread_next
+ Exported to gdbserv to implement "info threads" request from GDB. */
+
+static struct gdbserv_thread *
+thread_db_thread_next (struct gdbserv *serv, struct gdbserv_thread *thread)
+{
+ if (thread == NULL)
+ {
+ /* First request -- build up thread list using td_ta_thr_iter. */
+ /* NOTE: this should be unnecessary, once we begin to keep the
+ list up to date all the time. */
+ update_thread_list ();
+ }
+ return next_thread_in_list (thread);
+}
+
+
+/* Function: thread_db_get_gen
+ Handle 'q' requests:
+ qSymbol
+*/
+
+static void
+thread_db_get_gen (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ char tempname[1024], *symbol_query;
+ unsigned long tempval;
+ int len;
+
+ if (gdbserv_input_string_match (serv, "Symbol:") >= 0)
+ {
+ /* Message: qSymbol:<optional value>:<optional name hexified>
+ Reply: OK
+ Reply: qSymbol:<name hexified>
+
+ This message from GDB has three possible forms:
+
+ 1) "qSymbol::" (no value, no name).
+ This means the start of a symbol query session.
+ GDB is offering to serve up symbols.
+ The target should reply with the FIRST symbol whose value
+ it wants (or "OK" if it doesn't want any).
+
+ 2) "qSymbol:<value>:<name hexified>
+ This means "here is the value of the symbol you requested".
+ The target should reply with the NEXT symbol whose value
+ it wants (or "OK" if it doesn't want any more).
+
+ 3) "qSymbol::<name hexified>" (no value)
+ This means "I have no value for the symbol you requested".
+ The target should reply with the NEXT symbol whose value
+ it wants (or "OK" if it doesn't want any more).
+ */
+
+ if (gdbserv_input_string_match (serv, ":") >= 0)
+ {
+ /* So far we've matched "qSymbol::". We're looking at either
+ form #1 ("qSymbol::", open a symbol lookup session), or
+ form #3 ("qSymbol::<name>", a reply that "this symbol is
+ not defined". */
+
+ len = gdbserv_input_bytes (serv, tempname, sizeof (tempname));
+
+ if (len == 0)
+ {
+ /* Form #1, open a new symbol lookup session.
+ Prepare to request the first symbol in the list. */
+ sync_symbol_list ();
+ }
+ else
+ {
+ /* Form #3, this symbol not currently defined. Nothing
+ to do, since we marked it REQUESTED when we sent it,
+ and lookup_cached_symbol treats REQUESTED like
+ UNDEFINED. */
+ }
+ }
+ else if (gdbserv_input_hex_ulong (serv, &tempval) >= 0 &&
+ gdbserv_input_string_match (serv, ":") >= 0 &&
+ (len = gdbserv_input_bytes (serv, tempname, sizeof (tempname)))
+ > 0)
+ {
+ /* Message contains a symbol and a value (form #2). */
+
+ tempname[len] = '\0';
+ add_symbol_to_list (tempname, (paddr_t) tempval, DEFINED);
+ if (thread_agent != NULL)
+ {
+ /* We now have a new symbol in the cache, which was
+ requested by the last td_ta_new call. Delete the
+ current (not-completely-valid) thread agent, so that
+ a new one will have to be opened. */
+ td_ta_delete_p (thread_agent);
+ thread_agent = NULL;
+ }
+ }
+
+ /* If we have no more symbols to look up, try opening a thread
+ agent. It's possible that opening an agent could succeed
+ before we have finished looking up all the symbols, but since
+ we always loop until all the symbols we know about have been
+ requested anyway, it's unnecessary.
+
+ This ensures that ps_pglobal_lookup will always succeed in
+ the case where we can obtain the full list of symbol names
+ before opening the agent; this may be a little more robust
+ than assuming it will handle all errors gracefully.
+
+ Otherwise, if ps_pglobal_lookup fails, it will at least add
+ the missing symbol's name to the list, and we'll request
+ their values the next time around. */
+ symbol_query = next_undefined_symbol ();
+ if (! symbol_query)
+ {
+ thread_db_open (serv, process->pid);
+ symbol_query = next_undefined_symbol ();
+ }
+
+ /* Now the reply depends on whether there is another
+ symbol in need of lookup. */
+ if (! symbol_query)
+ {
+ gdbserv_output_string (serv, "OK");
+ }
+ else
+ {
+ gdbserv_output_string (serv, "qSymbol:");
+ gdbserv_output_bytes (serv, symbol_query, strlen (symbol_query));
+ }
+ }
+ else if (parentvec.process_get_gen)
+ parentvec.process_get_gen (serv);
+}
+
+/* Function: thread_db_set_gen
+ Handle 'Q' requests:
+*/
+
+static void
+thread_db_set_gen (struct gdbserv *serv)
+{
+ if (parentvec.process_set_gen)
+ parentvec.process_set_gen (serv);
+}
+
+static void
+thread_db_thread_id (struct gdbserv *serv,
+ struct gdbserv_thread *thread,
+ struct gdbserv_reg *id)
+{
+ gdbserv_ulonglong_to_reg (serv,
+ (unsigned long long) thread->ti.ti_tid,
+ id);
+}
+
+static int
+thread_db_thread_lookup_by_id (struct gdbserv *serv,
+ const struct gdbserv_reg *thread_id,
+ struct gdbserv_thread **thread)
+{
+ unsigned long id;
+
+ gdbserv_reg_to_ulong (serv, thread_id, &id);
+ if (id == 0) /* any thread */
+ {
+ *thread = next_thread_in_list (NULL); /* FIXME curthread? */
+ return 0;
+ }
+ else
+ {
+ *thread = thread_list_lookup_by_tid ((thread_t) id);
+ if (*thread == NULL) /* bad thread id */
+ {
+ *thread = next_thread_in_list (NULL); /* FIXME curthread? */
+ return -1;
+ }
+ else
+ {
+ return 1; /* success */
+ }
+ }
+}
+
+static char *
+thread_db_thread_info (struct gdbserv *serv, struct gdbserv_thread *thread)
+{
+ char *info = malloc (128);
+
+ sprintf (info, "PID %d Type %s State %s",
+ thread->ti.ti_lid,
+ thread_db_type_str (thread->ti.ti_type),
+ thread_db_state_str (thread->ti.ti_state));
+ return info;
+}
+
+/* Function: stop_thread
+ Use SIGSTOP to force a thread to stop. */
+
+static void
+stop_thread (struct gdbserv_thread *thread)
+{
+ if (thread->ti.ti_lid != 0)
+ {
+ if (thread_db_noisy)
+ fprintf (stderr, "(stop thread %s)\n", thread_debug_name (thread));
+ if (stop_lwp (thread->ti.ti_lid) == 0)
+ thread->stopped = 1;
+ else
+ thread->stopped = 0;
+ }
+}
+
+/* Function: stop_all_threads
+ Use SIGSTOP to make sure all child threads are stopped.
+ Do not send SIGSTOP to the event thread, or to any
+ new threads that have just been attached. */
+
+static void
+stop_all_threads (struct child_process *process)
+{
+ struct gdbserv_thread *thread;
+
+ for (thread = first_thread_in_list ();
+ thread;
+ thread = next_thread_in_list (thread))
+ {
+ if (thread->ti.ti_lid == process->pid)
+ {
+ /* HACK: mark him stopped.
+ It would make more sense to do this in
+ thread_db_check_child_state, where we received his
+ waitstatus and thus know he's stopped. But that code is
+ also used when we don't have a thread list yet, so the
+ 'struct gdbserv_thread' whose 'stopped' flag we want to
+ set may not exist. */
+ thread->stopped = 1;
+ continue; /* This thread is already stopped. */
+ }
+ /* All threads must be stopped, unless
+ a) they have only just been attached, or
+ b) they're already stopped. */
+ if (!thread->attached && !thread->stopped &&
+ thread->ti.ti_state != TD_THR_ZOMBIE &&
+ thread->ti.ti_state != TD_THR_UNKNOWN)
+ stop_thread (thread);
+ }
+}
+
+/* A list of signals that have been prematurely sucked out of the threads.
+ Because of the complexities of linux threads, we must send SIGSTOP to
+ every thread, and then call waitpid on the thread to retrieve the
+ SIGSTOP event. Sometimes another signal is pending on the thread,
+ and we get that one by mistake. Throw all such signals into this
+ list, and send them back to their respective threads once we're
+ finished calling waitpid. */
+
+static struct event_list {
+ struct gdbserv_thread *thread;
+ union wait waited;
+ int selected;
+ int thread_db_event;
+} *pending_events;
+static int pending_events_listsize;
+static int pending_events_top;
+
+/* Function: add_pending_event
+ Helper function for wait_all_threads.
+
+ When we call waitpid for each thread (trying to consume the SIGSTOP
+ events that we sent from stop_all_threads), we sometimes inadvertantly
+ get other events that we didn't send. We pend these to a list, and
+ then resend them to the child threads after our own SIGSTOP events
+ have been consumed.
+
+ This list will be used to choose which of the possible events
+ will be returned to the debugger by check_child_status. */
+
+static void
+add_pending_event (struct gdbserv_thread *thread, union wait waited)
+{
+ if (pending_events_top >= pending_events_listsize)
+ {
+ pending_events_listsize += 64;
+ pending_events =
+ realloc (pending_events,
+ pending_events_listsize * sizeof (*pending_events));
+ }
+ pending_events [pending_events_top].thread = thread;
+ pending_events [pending_events_top].waited = waited;
+ pending_events [pending_events_top].selected = 0;
+ pending_events [pending_events_top].thread_db_event = 0;
+ pending_events_top ++;
+}
+
+
+/* Delete the I'th pending event. This will reorder events at indices
+ I and higher, but not events whose indices are less than I.
+
+ This function runs in constant time, so you can iterate through the
+ whole pending event pool by deleting events as you process them.
+ But the nice thing about this function is that you can also handle
+ only selected events, and leave others for later. */
+static void
+delete_pending_event (int i)
+{
+ /* You shouldn't ask to delete an event that's not actually in the
+ list. */
+ assert (i <= i && i < pending_events_top);
+
+ /* Copy the last element down into this element's position, unless
+ this is the last element itself. */
+ if (i < pending_events_top - 1)
+ pending_events[i] = pending_events[pending_events_top - 1];
+
+ /* Now the deleted space is at the end of the array. So just
+ decrement the top pointer, and we're done. */
+ pending_events_top--;
+}
+
+
+/* Function: select_pending_event
+ Helper function for thread_db_check_child_state.
+
+ Having collected a list of events from various threads,
+ choose one "favored event" to be returned to the debugger.
+
+ Return non-zero if we selected an event, or zero if we couldn't
+ find anything interesting to report. */
+
+
+static int
+select_pending_event (struct child_process *process)
+{
+ int i = 0;
+ int num_wifstopped_events = 0;
+ int random_key;
+
+ /* Select the event that will be returned to the debugger. */
+
+ /* Selection criterion #0:
+ If there are no events, don't do anything! (paranoia) */
+ if (pending_events_top == 0)
+ {
+ if (thread_db_noisy)
+ fprintf (stderr, "(selected nothing)\n");
+ return 0;
+ }
+
+ /* Selection criterion #1:
+ If the thread pointer is null, then the thread library is
+ not in play yet, so this is the only thread and the only event. */
+ if (pending_events[0].thread == NULL)
+ {
+ i = 0;
+ goto selected;
+ }
+
+ /* Selection criterion #2:
+ Exit and terminate events take priority. */
+ for (i = 0; i < pending_events_top; i++)
+ if (WIFEXITED (pending_events[i].waited) ||
+ WIFSIGNALED (pending_events[i].waited))
+ {
+ goto selected;
+ }
+
+ /* Selection criterion #3:
+ Give priority to a stepping SIGTRAP. */
+ for (i = 0; i < pending_events_top; i++)
+ if (pending_events[i].thread->stepping &&
+ WIFSTOPPED (pending_events[i].waited) &&
+ WSTOPSIG (pending_events[i].waited) == SIGTRAP)
+ {
+ /* We don't actually know whether this sigtrap was the result
+ of a singlestep, or of executing a trap instruction. But
+ GDB has a better chance of figuring it out than we do. */
+ goto selected;
+ }
+
+ /* Selection criterion #4:
+ Count the WIFSTOPPED events and choose one at random. */
+ for (i = 0; i < pending_events_top; i++)
+ if (WIFSTOPPED (pending_events[i].waited))
+ num_wifstopped_events ++;
+
+ random_key = (int)
+ ((num_wifstopped_events * (double) rand ()) / (RAND_MAX + 1.0));
+
+ for (i = pending_events_top - 1; i >= 0; i--)
+ if (WIFSTOPPED (pending_events[i].waited))
+ {
+ if (random_key == --num_wifstopped_events)
+ {
+ goto selected;
+ }
+ else if (WSTOPSIG (pending_events[i].waited) == SIGINT)
+ {
+ goto selected; /* Give preference to SIGINT. */
+ }
+ }
+
+ /* Selection criterion #4 (should never get here):
+ If all else fails, take the first event in the list. */
+ i = 0;
+
+ selected: /* Got our favored event. */
+
+ if (thread_db_noisy)
+ fprintf (stderr, "(selected %s)\n",
+ thread_debug_name (pending_events[i].thread));
+
+ pending_events[i].selected = 1;
+ process->event_thread = pending_events[i].thread;
+ if (pending_events[i].thread)
+ process->pid = pending_events[i].thread->ti.ti_lid;
+
+ handle_waitstatus (process, pending_events[i].waited);
+ if (thread_db_noisy)
+ fprintf (stderr, "<select_pending_event: pid %d '%c' %d>\n",
+ process->pid, process->stop_status, process->stop_signal);
+ return 1;
+}
+
+/* Function: send_pending_signals
+ Helper function for thread_db_check_child_state.
+
+ When we call waitpid for each thread (trying to consume the SIGSTOP
+ events that we sent from stop_all_threads), we sometimes inadvertantly
+ get other events that we didn't send. We pend these to a list, and
+ then resend them to the child threads after our own SIGSTOP events
+ have been consumed.
+
+ Some events in the list require special treatment:
+ * One event is "selected" to be returned to the debugger.
+ Skip that one.
+ * Trap events may represent breakpoints. We can't just resend
+ the signal. Instead we must arrange for the breakpoint to be
+ hit again when the thread resumes. */
+
+static void
+send_pending_signals (struct child_process *process)
+{
+ int i;
+ int signum;
+
+ for (i = 0; i < pending_events_top; i++)
+ {
+ if (WIFSTOPPED (pending_events[i].waited) &&
+ ! pending_events[i].selected)
+ {
+ signum = WSTOPSIG (pending_events[i].waited);
+ if (signum == SIGTRAP &&
+ pending_events[i].thread->stepping == 0)
+ {
+ /* Breakpoint. Push it back. */
+ if (thread_db_noisy)
+ fprintf (stderr, "<send_pending_events: pushing back SIGTRAP for %d>\n",
+ pending_events[i].thread->ti.ti_lid);
+ decr_pc_after_break (process->serv,
+ pending_events[i].thread->ti.ti_lid);
+ }
+ else /* FIXME we're letting SIGINT go thru as normal */
+ {
+ /* Put the signal back into the child's queue. */
+ kill (pending_events[i].thread->ti.ti_lid,
+ WSTOPSIG (pending_events[i].waited));
+ }
+ }
+ }
+ pending_events_top = 0;
+}
+
+/* Function: wait_all_threads
+ Use waitpid to close the loop on all threads that have been
+ attached or SIGSTOP'd. Skip the eventpid -- it's already been waited.
+
+ Special considerations:
+ The debug signal does not go into the event queue,
+ does not get forwarded to the thread etc. */
+
+static void
+wait_all_threads (struct child_process *process)
+{
+ struct gdbserv_thread *thread;
+ union wait w;
+ int ret, stopsig;
+
+ for (thread = first_thread_in_list ();
+ thread;
+ thread = next_thread_in_list (thread))
+ {
+ /* Special handling for the thread that has already been waited. */
+ if (thread->ti.ti_lid == process->pid)
+ {
+ /* HACK mark him waited. */
+ thread->waited = 1;
+ continue;
+ }
+
+ while ((thread->stopped || thread->attached) &&
+ !thread->waited)
+ {
+ errno = 0;
+ if (thread_db_noisy)
+ fprintf (stderr, "(waiting for %s)\n",
+ thread_debug_name (thread));
+ ret = waitpid (thread->ti.ti_lid, (int *) &w,
+ thread->ti.ti_lid == proc_handle.pid ? 0 : __WCLONE);
+ if (ret == -1)
+ {
+ if (errno == ECHILD)
+ fprintf (stderr, "<wait_all_threads: %d has disappeared>\n",
+ thread->ti.ti_lid);
+ else
+ fprintf (stderr, "<wait_all_threads: waitpid %d failed, '%s'>\n",
+ thread->ti.ti_lid, strerror (errno));
+ break;
+ }
+ if (WIFEXITED (w))
+ {
+ add_pending_event (thread, w);
+ fprintf (stderr, "<wait_all_threads: %d has exited>\n",
+ thread->ti.ti_lid);
+ break;
+ }
+ if (WIFSIGNALED (w))
+ {
+ add_pending_event (thread, w);
+ fprintf (stderr, "<wait_all_threads: %d died with signal %d>\n",
+ thread->ti.ti_lid, WTERMSIG (w));
+ break;
+ }
+ stopsig = WSTOPSIG (w);
+ switch (stopsig) {
+ case SIGSTOP:
+ /* This is the one we're looking for.
+ Mark the thread as 'waited' and move on to the next thread. */
+#if 0 /* too noisy! */
+ if (thread_db_noisy)
+ fprintf (stderr, "<waitpid (%d, SIGSTOP)>\n", thread->ti.ti_lid);
+#endif
+ thread->waited = 1;
+ break;
+ default:
+ if (stopsig == debug_signal)
+ {
+ /* This signal does not need to be forwarded. */
+ if (thread_db_noisy)
+ fprintf (stderr, "<wait_all_threads: ignoring SIGDEBUG (%d) for %d>\n",
+ debug_signal,
+ thread->ti.ti_lid);
+ }
+ else
+ {
+ if (thread_db_noisy)
+ fprintf (stderr, "<wait_all_threads: stash sig %d for %d at 0x%08x>\n",
+ stopsig, thread->ti.ti_lid,
+ (unsigned long) debug_get_pc (process->serv,
+ thread->ti.ti_lid));
+ add_pending_event (thread, w);
+ }
+ }
+
+ if (!thread->waited) /* Signal was something other than STOP. */
+ {
+ /* Continue the thread so it can stop on the next signal. */
+ continue_lwp (thread->ti.ti_lid, 0);
+ }
+ }
+ }
+}
+
+
+/* Scan the list for threads that have stopped at libthread_db event
+ breakpoints, process the events they're reporting, and step the
+ threads past the breakpoints, updating the pending_events
+ table.
+
+ This function assumes that all threads have been stopped. */
+static void
+handle_thread_db_events (struct child_process *process)
+{
+ struct gdbserv *serv = process->serv;
+ int i;
+ int any_events;
+
+ /* Are there any threads at all stopped at libthread_db event
+ breakpoints? */
+ any_events = 0;
+ for (i = 0; i < pending_events_top; i++)
+ {
+ struct event_list *e = &pending_events[i];
+ if (e->thread
+ && WIFSTOPPED (e->waited)
+ && WSTOPSIG (e->waited) == SIGTRAP
+ && hit_thread_db_event_breakpoint (serv, e->thread))
+ {
+ any_events = 1;
+ e->thread_db_event = 1;
+ }
+ }
+
+ if (! any_events)
+ return;
+
+ /* Consume events. */
+ for (;;)
+ {
+ td_event_msg_t msg;
+ td_err_e status = td_ta_event_getmsg_p (thread_agent, &msg);
+
+ if (status == TD_NOMSG)
+ break;
+
+ if (status != TD_OK)
+ {
+ fprintf (stderr, "error getting thread messages: %s\n",
+ thread_db_err_str (status));
+ break;
+ }
+
+ /* The only messages we're concerned with are TD_CREATE and
+ TD_DEATH. But since we call update_thread_list every time
+ thread_db_check_child_state gets a wait status from waitpid,
+ our list is always up to date, so we don't actually need to
+ do anything with these messages.
+
+ (Ignore the question, for now, of how RDA loses when threads
+ spawn off new threads after we've updated our list, but
+ before we've managed to send each of the threads on our list
+ a SIGSTOP.) */
+ }
+
+ /* Disable the event breakpoints while we step the threads across
+ them. */
+ delete_thread_db_event_breakpoints (serv);
+
+ for (i = 0; i < pending_events_top;)
+ {
+ struct event_list *e = &pending_events[i];
+ if (e->thread_db_event)
+ {
+ struct gdbserv_thread *thread = e->thread;
+ lwpid_t lwp = thread->ti.ti_lid;
+ union wait w;
+
+ /* Delete this pending event. If appropriate, we'll add a
+ new pending event below, but if stepping across the event
+ breakpoint is successful, then this pending event, at
+ least, has been addressed. */
+ delete_pending_event (i);
+
+ /* Back up the thread, if needed. */
+ decr_pc_after_break (serv, lwp);
+
+ /* Single-step the thread across the breakpoint. */
+ singlestep_lwp (serv, lwp, 0);
+
+ /* Get a new status for that thread. */
+ if (thread_db_noisy)
+ fprintf (stderr, "(waiting after event bp step %s)\n",
+ thread_debug_name (thread));
+ if (waitpid (lwp, (int *) &w, lwp == proc_handle.pid ? 0 : __WCLONE)
+ < 0)
+ fprintf (stderr, "error waiting for thread %d after "
+ "stepping over event breakpoint:\n%s",
+ lwp, strerror (errno));
+ else
+ {
+ /* If the result is a SIGTRAP signal, then that means
+ the single-step proceeded normally. Otherwise, it's
+ a new pending event. */
+ if (WIFSTOPPED (w)
+ && WSTOPSIG (w) == SIGTRAP)
+ ;
+ else
+ add_pending_event (thread, w);
+ }
+ }
+ else
+ i++;
+ }
+
+ /* Re-insert the event breakpoints. */
+ insert_thread_db_event_breakpoints (serv);
+}
+
+
+/* Function: continue_thread
+ Send continue to a struct gdbserv_thread. */
+
+static void
+continue_thread (struct gdbserv_thread *thread, int signal)
+{
+ thread_db_flush_regset_caches();
+
+ /* Continue thread only if (a) it was just attached, or
+ (b) we stopped it and waited for it. */
+ if (thread->ti.ti_lid != 0)
+ if (thread->attached || (thread->stopped && thread->waited))
+ {
+ continue_lwp (thread->ti.ti_lid, signal);
+ thread->stopped = thread->attached = thread->waited = 0;
+ }
+ thread_db_invalidate_caches ();
+}
+
+/* Function: continue_all_threads
+ Send continue to all stopped or attached threads
+ except the event thread (which will be continued separately). */
+
+static void
+continue_all_threads (struct gdbserv *serv)
+{
+ struct gdbserv_thread *thread;
+
+ for (thread = first_thread_in_list ();
+ thread;
+ thread = next_thread_in_list (thread))
+ {
+ /* If we're using signals to communicate with the thread
+ library, send any newly attached thread the restart signal. */
+ if (got_thread_signals && thread->attached)
+ continue_thread (thread, restart_signal);
+ else
+ continue_thread (thread, 0);
+ }
+}
+
+/* Function: continue_program
+ Make sure every thread is running, starting with the event thread. */
+
+static void
+thread_db_continue_program (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ /* Synchronize the regset caches. */
+ thread_db_flush_regset_caches();
+
+ /* First resume the event thread. */
+ if (process->event_thread)
+ continue_thread (process->event_thread, process->signal_to_send);
+ else
+ continue_lwp (process->pid, process->signal_to_send);
+
+ process->stop_signal = process->stop_status =
+ process->signal_to_send = 0;
+
+ /* Then resume everyone else. */
+ continue_all_threads (serv);
+ process->running = 1;
+ thread_db_invalidate_caches ();
+}
+
+/* Function: singlestep_thread
+ Send SINGLESTEP to a struct gdbserv_thread. */
+
+static void
+singlestep_thread (struct gdbserv *serv,
+ struct gdbserv_thread *thread,
+ int signal)
+{
+ singlestep_lwp (serv, thread->ti.ti_lid, signal);
+ thread->stopped = thread->attached = thread->waited = 0;
+ thread->stepping = 1;
+}
+
+/* Function: singlestep_program
+ Make sure every thread is runnable, while the event thread gets to
+ do a singlestep. */
+
+static void
+thread_db_singlestep_program (struct gdbserv *serv)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+
+ /* Synchronize the regset caches. */
+ thread_db_flush_regset_caches();
+
+ /* First singlestep the event thread. */
+ if (process->event_thread)
+ singlestep_thread (serv, process->event_thread, process->signal_to_send);
+ else
+ singlestep_lwp (serv, process->pid, process->signal_to_send);
+
+ process->stop_status = process->stop_signal =
+ process->signal_to_send = 0;
+
+ /* Then resume everyone else. */
+ continue_all_threads (serv); /* All but the event thread. */
+ process->running = 1;
+ thread_db_invalidate_caches ();
+}
+
+/* Function: thread_db_continue_thread
+ Let a single thread continue, while everyone else waits. */
+
+static void
+thread_db_continue_thread (struct gdbserv *serv,
+ struct gdbserv_thread *thread,
+ const struct gdbserv_reg *signum)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ unsigned long sig;
+
+ /* Synchronize the regset caches. */
+ thread_db_flush_regset_caches();
+
+ /* Handle the signal value. */
+ if (parentvec.process_signal && signum)
+ {
+ gdbserv_reg_to_ulong (serv, signum, &sig);
+ parentvec.process_signal (serv, (int) sig);
+ }
+
+ /* A null thread argument is to be taken as a continue for all. */
+ if (thread == NULL)
+ thread_db_continue_program (serv);
+ else
+ {
+ process->pid = thread->ti.ti_lid; /* thread to be continued */
+ continue_thread (thread, process->signal_to_send);
+ process->stop_status = process->stop_signal =
+ process->signal_to_send = 0;
+ process->running = 1;
+ }
+ thread_db_invalidate_caches ();
+}
+
+/* Function: singlestep_thread
+ Let a single thread step, while everyone else waits. */
+
+static void
+thread_db_singlestep_thread (struct gdbserv *serv,
+ struct gdbserv_thread *thread,
+ const struct gdbserv_reg *signum)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ unsigned long sig;
+
+ /* Synchronize the regset caches. */
+ thread_db_flush_regset_caches();
+
+ /* Handle the signal value. */
+ if (parentvec.process_signal && signum)
+ {
+ gdbserv_reg_to_ulong (serv, signum, &sig);
+ parentvec.process_signal (serv, (int) sig);
+ }
+
+ /* A null thread argument is to be taken as a singlestep for all. */
+ if (thread == NULL)
+ thread_db_singlestep_program (serv);
+ else
+ {
+ singlestep_thread (serv, thread, process->signal_to_send);
+ process->stop_status = process->stop_signal =
+ process->signal_to_send = 0;
+ process->running = 1;
+ }
+ thread_db_invalidate_caches ();
+}
+
+/* Function: exit_program
+ Called by main loop when child exits. */
+
+static void
+thread_db_exit_program (struct gdbserv *serv)
+{
+ /* FIXME: stop and kill all threads. */
+
+ /* Shut down the thread_db library interface. */
+ td_ta_delete_p (thread_agent);
+ thread_agent = NULL;
+ currentvec = NULL;
+ /* Discard all cached symbol lookups. */
+ free_symbol_list ();
+ /* Discard all cached threads. */
+ free_thread_list ();
+ /* Call underlying exit_program method. */
+ parentvec.exit_program (serv);
+}
+
+/* Function: check_child_state
+
+ This function checks for signal events in the running child processes.
+ It does not block if there is no event in any child, but if there is
+ an event, it selectively calls other functions that will, if appropriate,
+ make sure that all the other children are stopped as well.
+
+ This is a polling (non-blocking) function, and may be called when
+ the child is already stopped. */
+
+static int
+thread_db_check_child_state (struct child_process *process)
+{
+ struct gdbserv *serv = process->serv;
+ int eventpid;
+ union wait w;
+
+ /* The "process" is likely to be the parent thread.
+ We will have to manage a list of threads/pids. */
+
+ /* Since this is a polling call, and threads don't all stop at once,
+ it is possible for a subsequent call to intercept a new wait event
+ before we've resumed from the previous wait event. Prevent this
+ with a resume flag. */
+
+ if (process->running)
+ {
+ eventpid = waitpid (-1, (int *) &w, WNOHANG);
+ /* If no event on main thread, check clone threads.
+ It doesn't matter what event we find first, since we now have
+ a fair algorithm for choosing which event to handle next. */
+ if (eventpid <= 0)
+ eventpid = waitpid (-1, (int *) &w, WNOHANG | __WCLONE);
+
+ if (eventpid > 0) /* found an event */
+ {
+ int selected_anything;
+
+ /* Allow underlying target to use the event process by default,
+ since it is stopped and the others are still running. */
+ process->pid = eventpid;
+
+ handle_waitstatus (process, w);
+
+ /* Look for thread exit.
+ This has to be done now -- if the eventpid has exited, I can't
+ run update_thread_list because there is no stopped process
+ thru which I can read memory. I could find another one to
+ stop, but it's not really worth it. */
+ if (process->stop_status == 'W')
+ {
+ if (eventpid == proc_handle.pid)
+ return 1; /* Main thread exited! */
+ else
+ return 0; /* Just a thread exit, don't tell GDB. */
+ }
+
+ /* FIXME: this debugging output will be removed soon, but
+ putting it here before the update_thread_list etc. is
+ bad from the point of view of synchronization. */
+ handle_waitstatus (process, w);
+ if (thread_db_noisy)
+ fprintf (stderr, "\n<check_child_state: %d got '%c' - %d at 0x%08x>\n",
+ process->pid, process->stop_status, process->stop_signal,
+ (unsigned long) debug_get_pc (process->serv, process->pid));
+ /* It shouldn't hurt to call this twice. But if there are a
+ lot of other threads running, it can take a *long* time
+ for the thread list update to complete. */
+ stop_all_threads (process);
+
+ /* Update the thread list. */
+ update_thread_list ();
+
+ /* For now, call get_thread_signals from here (FIXME:) */
+ get_thread_signals ();
+
+ /* Put this child's event into the pending list. */
+ add_pending_event (thread_list_lookup_by_lid ((lwpid_t) eventpid),
+ w);
+
+ stop_all_threads (process);
+ wait_all_threads (process);
+ if (using_thread_db_events)
+ handle_thread_db_events (process);
+ selected_anything = select_pending_event (process);
+ send_pending_signals (process);
+
+ /* If there weren't any pending events to report, then
+ continue the program, and let the main loop know that
+ nothing interesting happened. */
+ if (! selected_anything)
+ {
+ currentvec->continue_program (serv);
+ return 0;
+ }
+
+ /* Note: if more than one thread has an event ready to be
+ handled, wait_all_threads will have chosen one at random. */
+
+ if (got_thread_signals && ignore_thread_signal (process))
+ {
+ /* Ignore this signal, restart the child. */
+ if (thread_db_noisy)
+ fprintf (stderr, "<check_child_state: ignoring signal %d for %d>\n",
+ process->stop_signal, process->pid);
+ if (process->stop_signal == debug_signal)
+ {
+ /* The debug signal arrives under two circumstances:
+ 1) The main thread raises it once, upon the first call
+ to pthread_create. This lets us detect the manager
+ thread. The main thread MUST be given the restart
+ signal when this occurs.
+ 2) The manager thread raises it each time a new
+ child thread is created. The child thread will be
+ in sigsuspend, and MUST be sent the restart signal.
+ However, the manager thread, which raised the debug
+ signal, does not need to be restarted.
+
+ Sending the restart signal to the newly attached
+ child thread (which is not the event thread) is
+ handled in continue_all_threads. */
+
+ if (process->pid == proc_handle.pid) /* main thread */
+ process->stop_signal = restart_signal;
+ else /* not main thread */
+ process->stop_signal = 0;
+ }
+ process->signal_to_send = process->stop_signal;
+ currentvec->continue_program (serv);
+ return 0;
+ }
+
+ if (process->stop_status == 'W')
+ {
+ if (process->pid == proc_handle.pid)
+ return 1; /* Main thread exited! */
+ else
+ {
+ currentvec->continue_program (serv);
+ return 0; /* Just a thread exit, don't tell GDB. */
+ }
+ }
+
+ process->running = 0;
+
+ /* This is the place to cancel its 'stepping' flag. */
+ if (process && process->event_thread)
+ process->event_thread->stepping = 0;
+
+ /* Pass this event back to GDB. */
+ if (process->debug_backend)
+ fprintf (stderr, "wait returned '%c' (%d) for %d.\n",
+ process->stop_status, process->stop_signal, eventpid);
+ return 1;
+ }
+ }
+
+ /* NOTE: this function is called in a polling loop, so it
+ probably (?) should not block. Return when there's no event. */
+ return 0;
+}
+
+/* Function: fromtarget_thread_break
+ Called from the main loop when one of the child processes stops.
+ Notifies the RDA library and lets it know which thread took the event. */
+
+static void
+thread_db_fromtarget_thread_break (struct child_process *process)
+{
+ int gdb_signal = parentvec.compute_signal (process->serv,
+ process->stop_signal);
+
+ gdbserv_fromtarget_thread_break (process->serv,
+ process->event_thread,
+ gdb_signal);
+}
+
+/* Function: get_thread_reg
+ Get a register value for a specific thread. */
+
+static int
+thread_db_get_thread_reg (struct gdbserv *serv,
+ struct gdbserv_thread *thread,
+ int regnum,
+ struct gdbserv_reg *reg)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ td_thrhandle_t thread_handle;
+ td_thrinfo_t ti;
+ FPREGSET_T fpregset;
+ GREGSET_T gregset;
+ td_err_e ret;
+
+ if (thread == NULL)
+ thread = process->event_thread; /* Default to the event thread. */
+
+ if (thread_agent == NULL || /* Thread layer not alive yet? */
+ thread == NULL) /* No thread specified? */
+ {
+ /* Fall back on parentvec non-threaded method. */
+ if (parentvec.get_reg)
+ return parentvec.get_reg (serv, regnum, reg);
+ else
+ return -1; /* give up. */
+ }
+
+ /* Thread_db active, thread_agent valid.
+ The request goes to the thread_db library.
+ From there it will be dispatched to ps_lgetregs,
+ and from there it will be kicked back to the parent. */
+
+ if (thread->ti.ti_state == TD_THR_ZOMBIE ||
+ thread->ti.ti_state == TD_THR_UNKNOWN)
+ {
+ /* This thread is dead! Can't get its registers. */
+ return -1;
+ }
+
+ ret = thread_db_map_id2thr (thread_agent,
+ thread->ti.ti_tid,
+ &thread_handle);
+ if (ret == TD_NOTHR)
+ {
+ /* Thread has exited, no registers. */
+ return -1;
+ }
+ else if (ret != TD_OK)
+ {
+ fprintf (stderr, "<<< ERROR get_thread_reg map_id2thr %d >>>\n",
+ thread->ti.ti_tid);
+ return -1; /* fail */
+ }
+
+ if (is_fp_reg (regnum))
+ {
+ if (thread_db_getfpregs (&thread_handle, &fpregset) != TD_OK)
+ {
+ /* Failure to get the fpregs isn't necessarily an error.
+ Assume that the target just doesn't support fpregs. */
+ return 0;
+ }
+ /* Now extract the register from the fpregset. */
+ if (reg_from_fpregset (serv, reg, regnum, &fpregset) < 0)
+ {
+ fprintf (stderr, "<<< ERROR reg_from_fpregset %d %d>>>\n",
+ thread->ti.ti_tid, regnum);
+ return -1;
+ }
+ }
+ else if (td_thr_getxregsize_p != NULL
+ && td_thr_getxregs_p != NULL
+ && is_extended_reg (regnum))
+ {
+ int xregsize;
+ void *xregset;
+
+ if (td_thr_getxregsize_p (&thread_handle, &xregsize) != TD_OK)
+ {
+ /* Failure to get the size of the extended regs isn't
+ necessarily an error. Assume that the target just
+ doesn't support them. */
+ return 0;
+ }
+
+ if (xregsize <= 0)
+ {
+ /* Another form of not being supported... */
+ return 0;
+ }
+
+ /* Allocate space for the extended registers. */
+ xregset = alloca (xregsize);
+
+ /* Fetch the extended registers. */
+ if (td_thr_getxregs_p (&thread_handle, xregset) != TD_OK)
+ {
+ /* Failure to get the extended regs isn't necessarily an error.
+ Assume that the target just doesn't support them. */
+ return 0;
+ }
+
+ /* Now extract the register from the extended regset. */
+ if (reg_from_xregset (serv, reg, regnum, xregset) < 0)
+ {
+ fprintf (stderr, "<<< ERROR reg_from_xregset %d %d>>>\n",
+ thread->ti.ti_tid, regnum);
+ return -1;
+ }
+ }
+ else if (is_gp_reg (regnum)) /* GP reg */
+ {
+ if (thread_db_getgregs (&thread_handle, gregset) != TD_OK)
+ {
+ fprintf (stderr, "<<< ERROR get_thread_reg td_thr_getgregs %d >>>\n",
+ thread->ti.ti_tid);
+ return -1; /* fail */
+ }
+ /* Now extract the requested register from the gregset. */
+ if (reg_from_gregset (serv, reg, regnum, gregset) < 0)
+ {
+ fprintf (stderr, "<<< ERROR reg_from_gregset %d %d>>>\n",
+ thread->ti.ti_tid, regnum);
+ return -1; /* fail */
+ }
+ }
+ else
+ {
+ /* Register not supported by this target. This shouldn't be
+ construed as an error though. */
+ return 0;
+ }
+
+ return 0; /* success */
+}
+
+/* Function: set_thread_reg
+ Set a register value for a specific thread. */
+
+static int
+thread_db_set_thread_reg (struct gdbserv *serv,
+ struct gdbserv_thread *thread,
+ int regnum,
+ const struct gdbserv_reg *reg)
+{
+ struct child_process *process = gdbserv_target_data (serv);
+ td_thrhandle_t thread_handle;
+ FPREGSET_T fpregset;
+ GREGSET_T gregset;
+ td_err_e ret;
+
+ if (thread == NULL)
+ thread = process->event_thread; /* Default to the event thread. */
+
+ if (thread_agent == NULL || /* Thread layer not alive yet? */
+ thread == NULL) /* No thread specified? */
+ {
+ /* Fall back on parentvec non-threaded method. */
+ if (parentvec.set_reg)
+ return parentvec.set_reg (serv, regnum, (struct gdbserv_reg *) reg);
+ else
+ return -1; /* give up. */
+ }
+
+ /* Thread_db active, thread_agent valid.
+ The request goes to the thread_db library.
+ From there it will be dispatched to ps_lsetregs,
+ and from there it will be kicked back to the parent. */
+
+ if (thread->ti.ti_state == TD_THR_ZOMBIE ||
+ thread->ti.ti_state == TD_THR_UNKNOWN)
+ {
+ /* This thread is dead! Can't get its registers. */
+ return -1;
+ }
+
+ ret = thread_db_map_id2thr (thread_agent,
+ thread->ti.ti_tid,
+ &thread_handle);
+ if (ret == TD_NOTHR)
+ {
+ /* Thread has exited, no registers. */
+ return -1;
+ }
+ else if (ret != TD_OK)
+ {
+ fprintf (stderr, "<<< ERROR set_thread_reg map_id2thr %d >>>\n",
+ thread->ti.ti_tid);
+ return -1; /* fail */
+ }
+
+ if (is_fp_reg (regnum))
+ {
+ /* Get the current fpregset. */
+ if (thread_db_getfpregs (&thread_handle, &fpregset) != TD_OK)
+ {
+ /* Failing to get the fpregs is not necessarily an error.
+ Assume it simply means that this target doesn't support
+ fpregs. */
+ return 0;
+ }
+ /* Now write the new reg value into the fpregset. */
+ if (reg_to_fpregset (serv, reg, regnum, &fpregset) < 0)
+ {
+ fprintf (stderr, "<<< ERROR reg_to_fpregset %d %d >>>\n",
+ thread->ti.ti_tid, regnum);
+ return -1; /* fail */
+ }
+ /* Now write the fpregset back to the child. */
+ if (thread_db_setfpregs (&thread_handle, &fpregset) != TD_OK)
+ {
+ fprintf (stderr, "<<< ERROR set_thread_reg td_thr_setfpregs %d>>>\n",
+ thread->ti.ti_tid);
+ return -1; /* fail */
+ }
+ }
+ else if (td_thr_getxregsize_p != NULL
+ && td_thr_getxregs_p != NULL
+ && td_thr_setxregs_p != NULL
+ && is_extended_reg (regnum))
+ {
+ int xregsize;
+ void *xregset;
+
+ if (td_thr_getxregsize_p (&thread_handle, &xregsize) != TD_OK)
+ {
+ /* Failure to get the size of the extended regs isn't
+ necessarily an error. Assume that the target just
+ doesn't support them. */
+ return 0;
+ }
+
+ if (xregsize <= 0)
+ {
+ /* Another form of not being supported... */
+ return 0;
+ }
+
+ /* Allocate space for the extended registers. */
+ xregset = alloca (xregsize);
+
+ /* Fetch the extended registers. */
+ if (td_thr_getxregs_p (&thread_handle, xregset) != TD_OK)
+ {
+ /* Failure to get the extended regs isn't necessarily an error.
+ Assume that the target just doesn't support them. */
+ return 0;
+ }
+ /* Now write the new reg value into the extended regset. */
+ if (reg_to_xregset (serv, reg, regnum, xregset) < 0)
+ {
+ fprintf (stderr, "<<< ERROR reg_to_xregset %d %d >>>\n",
+ thread->ti.ti_tid, regnum);
+ return -1; /* fail */
+ }
+ /* Now write the extended regset back to the child. */
+ if (td_thr_setxregs_p (&thread_handle, gregset) != TD_OK)
+ {
+ fprintf (stderr, "<<< ERROR set_thread_reg td_thr_setxregs %d >>>\n",
+ thread->ti.ti_tid);
+ return -1; /* fail */
+ }
+ }
+ else if (is_gp_reg (regnum))
+ {
+ /* First get the current gregset. */
+ if (thread_db_getgregs (&thread_handle, gregset) != TD_OK)
+ {
+ fprintf (stderr, "<<< ERROR set_thread_reg td_thr_getgregs %d >>>\n",
+ thread->ti.ti_tid);
+ return -1; /* fail */
+ }
+ /* Now write the new reg value into the gregset. */
+ if (reg_to_gregset (serv, reg, regnum, gregset) < 0)
+ {
+ fprintf (stderr, "<<< ERROR reg_to_gregset %d %d >>>\n",
+ thread->ti.ti_tid, regnum);
+ return -1; /* fail */
+ }
+ /* Now write the gregset back to the child. */
+ if (thread_db_setgregs (&thread_handle, gregset) != TD_OK)
+ {
+ fprintf (stderr, "<<< ERROR set_thread_reg td_thr_setgregs %d >>>\n",
+ thread->ti.ti_tid);
+ return -1; /* fail */
+ }
+ }
+
+ return 0; /* success */
+}
+
+/* Function: thread_db_attach
+ gdbserv target function called upon attaching to gdb.
+ Return -1 for failure, zero for success.
+ Note that this has nothing to do with attaching to a running process
+ (which in fact we don't even know how to do), or a running thread. */
+
+int
+thread_db_attach (struct gdbserv *serv, struct gdbserv_target *target)
+{
+ td_err_e ret;
+ struct child_process *process = target->data;
+ extern struct server_vector gdbserver;
+
+ if ((thread_db_dlopen ()) < 0)
+ return -1; /* fail */
+
+ /* Save a copy of the existing target vector before we modify it. */
+ memcpy (&parentvec, target, sizeof (parentvec));
+ /* Save a pointer to the actual target vector. */
+ currentvec = target;
+
+ /* Initialize the library. */
+ if ((ret = td_init_p ()) != TD_OK)
+ {
+ fprintf (stderr,
+ "Cannot initialize libthread_db: %s", thread_db_err_str (ret));
+ currentvec = NULL;
+ return -1; /* fail */
+ }
+
+ /* Initialize threadish target methods. */
+ target->thread_info = thread_db_thread_info;
+ target->thread_next = thread_db_thread_next;
+ target->thread_id = thread_db_thread_id;
+ target->thread_lookup_by_id = thread_db_thread_lookup_by_id;
+ target->process_set_gen = thread_db_set_gen;
+ target->process_get_gen = thread_db_get_gen;
+ target->detach = thread_db_detach;
+
+ /* Take over selected target methods. */
+ target->exit_program = thread_db_exit_program;
+ target->continue_program = thread_db_continue_program;
+ target->singlestep_program = thread_db_singlestep_program;
+
+ target->continue_thread = thread_db_continue_thread;
+ target->singlestep_thread = thread_db_singlestep_thread;
+
+ /* Take over get_reg / set_reg methods with threaded versions. */
+ if (target->next_gg_reg != NULL &&
+ target->reg_format != NULL &&
+ target->output_reg != NULL &&
+ target->input_reg != NULL)
+ {
+ target->get_thread_reg = thread_db_get_thread_reg;
+ target->set_thread_reg = thread_db_set_thread_reg;
+ }
+ else
+ fprintf (stderr, "< ERROR attach: GDB will not read thread regs. >>>\n");
+
+ if (td_symbol_list_p)
+ {
+ /* Take all the symbol names libthread_db might try to look up
+ and place them in our cached symbol list, to be looked up
+ when invited by GDB. */
+ const char **symbol_list = td_symbol_list_p ();
+ int i;
+
+ for (i = 0; symbol_list[i]; i++)
+ add_symbol_to_list (symbol_list[i], 0, UNDEFINED);
+ }
+ else
+ {
+ /* KLUDGE: Insert some magic symbols into the cached symbol list,
+ to be looked up later. This is badly wrong -- we should be
+ obtaining these values thru the thread_db interface. Their names
+ should not be hard-coded here <sob>. */
+ add_symbol_to_list ("__pthread_sig_restart", 0, UNDEFINED);
+ add_symbol_to_list ("__pthread_sig_cancel", 0, UNDEFINED);
+ add_symbol_to_list ("__pthread_sig_debug", 0, UNDEFINED);
+ add_symbol_to_list ("__pthread_threads_debug", 0, UNDEFINED);
+ }
+
+ /* Attempt to open the thread_db interface. This attempt will
+ most likely fail (unles the child is statically linked). */
+ thread_db_open (serv, process->pid); /* Don't test return value */
+
+ /* Take over the "wait" vector. FIXME global object */
+ gdbserver.check_child_state = thread_db_check_child_state;
+ /* Take over the "fromtarget_break" vector. FIXME global object */
+ gdbserver.fromtarget_break = thread_db_fromtarget_thread_break;
+ /* FIXME what about terminate and exit? */
+
+ /* Set up the regset caches. */
+ initialize_regset_caches ();
+ return 0; /* success */
+}