diff options
author | Jim Blandy <jimb@redhat.com> | 2004-10-29 23:49:55 +0000 |
---|---|---|
committer | Jim Blandy <jimb@redhat.com> | 2004-10-29 23:49:55 +0000 |
commit | ee27141cae7d94d90af86cc34fb7cbc62fdcf78d (patch) | |
tree | e86bd023937a523726229738afc6cd901f42893a | |
parent | ef65240792cd3c68bbe9978c2d1c34401c80df46 (diff) | |
download | gdb-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/ChangeLog | 5 | ||||
-rw-r--r-- | rda/unix/linux-target.c | 1 | ||||
-rw-r--r-- | rda/unix/ptrace-target.c | 1477 | ||||
-rw-r--r-- | rda/unix/thread-db.c | 2864 |
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 (®set_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 (®set_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 (®set_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 (®set_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 (®set_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 (®set_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 */ +} |