diff options
author | Stan Shebs <shebs@apple.com> | 1999-09-09 00:01:39 +0000 |
---|---|---|
committer | Stan Shebs <shebs@apple.com> | 1999-09-09 00:01:39 +0000 |
commit | 88d27de31e77c81b718eb8d9aeb16b20814796d3 (patch) | |
tree | 5662fbf5d9f365dec5d17d246aeb8d37baf2a8c2 | |
parent | 30ef107dad79f4a79f966f8fbda2d88d37a327a4 (diff) | |
download | gdb-88d27de31e77c81b718eb8d9aeb16b20814796d3.tar.gz |
Initial revision
-rw-r--r-- | gdb/i386-linux-nat.c | 379 | ||||
-rw-r--r-- | gdb/linux-thread.c | 1637 | ||||
-rw-r--r-- | gdb/version.h | 33 | ||||
-rw-r--r-- | sim/common/cgen-par.c | 194 | ||||
-rw-r--r-- | sim/common/cgen-par.h | 124 |
5 files changed, 2367 insertions, 0 deletions
diff --git a/gdb/i386-linux-nat.c b/gdb/i386-linux-nat.c new file mode 100644 index 00000000000..8c63a9414d9 --- /dev/null +++ b/gdb/i386-linux-nat.c @@ -0,0 +1,379 @@ +/* Native-dependent code for Linux running on i386's, for GDB. + +This file is part of GDB. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "defs.h" +#include "inferior.h" +#include "gdbcore.h" + +/* For i386_linux_skip_solib_resolver */ +#include "symtab.h" +#include "frame.h" +#include "symfile.h" +#include "objfiles.h" + +#include <sys/ptrace.h> +#include <sys/user.h> +#include <sys/procfs.h> + +#ifdef HAVE_SYS_REG_H +#include <sys/reg.h> +#endif + +/* This is a duplicate of the table in i386-xdep.c. */ + +static int regmap[] = +{ + EAX, ECX, EDX, EBX, + UESP, EBP, ESI, EDI, + EIP, EFL, CS, SS, + DS, ES, FS, GS, +}; + + +/* FIXME: These routine absolutely depends upon (NUM_REGS - NUM_FREGS) + being less than or equal to the number of registers that can be stored + in a gregset_t. Note that with the current scheme there will typically + be more registers actually stored in a gregset_t that what we know + about. This is bogus and should be fixed. */ + +/* Given a pointer to a general register set in /proc format (gregset_t *), + unpack the register contents and supply them as gdb's idea of the current + register values. */ + +void +supply_gregset (gregsetp) + gregset_t *gregsetp; +{ + register int regi; + register greg_t *regp = (greg_t *) gregsetp; + + for (regi = 0 ; regi < (NUM_REGS - NUM_FREGS) ; regi++) + { + supply_register (regi, (char *) (regp + regmap[regi])); + } +} + +void +fill_gregset (gregsetp, regno) + gregset_t *gregsetp; + int regno; +{ + int regi; + register greg_t *regp = (greg_t *) gregsetp; + + for (regi = 0 ; regi < (NUM_REGS - NUM_FREGS) ; regi++) + { + if ((regno == -1) || (regno == regi)) + { + *(regp + regmap[regi]) = *(int *) ®isters[REGISTER_BYTE (regi)]; + } + } +} + + +/* Given a pointer to a floating point register set in (fpregset_t *) + format, unpack the register contents and supply them as gdb's + idea of the current floating point register values. */ + +void +supply_fpregset (fpregsetp) + fpregset_t *fpregsetp; +{ + register int regi; + char *from; + from = (char *) &(fpregsetp->st_space[0]); + for (regi = FPSTART_REGNUM ; regi <= FPEND_REGNUM ; regi++) + { + supply_register(regi, from); + from += REGISTER_RAW_SIZE(regi); + } +} + +/* Given a pointer to a floating point register set in (fpregset_t *) + format, update all of the registers from gdb's idea + of the current floating point register set. */ + +void +fill_fpregset (fpregsetp, regno) + fpregset_t *fpregsetp; + int regno; +{ + int regi; + char *to; + char *from; + + to = (char *) &(fpregsetp->st_space[0]); + for (regi = FPSTART_REGNUM ; regi <= FPEND_REGNUM ; regi++) + { + from = (char *) ®isters[REGISTER_BYTE (regi)]; + memcpy (to, from, REGISTER_RAW_SIZE (regi)); + to += REGISTER_RAW_SIZE(regi); + } +} + +/* + Get the whole floating point state of the process and + store the floating point stack into registers[]. + */ +static void +fetch_fpregs(void) +{ + int ret, regno; + char buf[FPREG_BYTES]; + + ret = ptrace (PTRACE_GETFPREGS, inferior_pid, 0, (int)buf); + if ( ret < 0 ) + { + warning ("Couldn't get floating point status"); + return; + } + + for ( regno = 0; regno < NUM_FREGS; regno++ ) + { + if ( regno < 7 ) + supply_register (NUM_REGS-NUM_FREGS+regno, buf + regno*4); + else + supply_register (NUM_REGS-NUM_FREGS+regno, + buf + FPENV_BYTES + (regno-7)*FPREG_RAW_SIZE); + } + +} + + +/* + Get the whole floating point state of the process and + replace the contents from registers[]. + */ +static void +store_fpregs(void) +{ + int ret, regno; + char buf[FPREG_BYTES]; + + ret = ptrace (PTRACE_GETFPREGS, inferior_pid, 0, (int)buf); + if ( ret < 0 ) + { + warning ("Couldn't get floating point status"); + return; + } + + for ( regno = 0; regno < NUM_FREGS; regno++ ) + { + if ( register_valid[regno] ) + { + if ( regno < 7 ) + { + read_register_gen (NUM_REGS-NUM_FREGS+regno, + buf + regno*4); + } + else + { + read_register_gen (NUM_REGS-NUM_FREGS+regno, + buf + FPENV_BYTES + (regno-7)*FPREG_RAW_SIZE); + } + } + } + + ret = ptrace (PTRACE_SETFPREGS, inferior_pid, 0, (int)buf); + if ( ret < 0 ) + { + warning ("Couldn't write floating point status"); + return; + } + +} + + +/* + Get state of all non-fp registers of the process and + store into registers[]. + */ +static void +fetch_regs(void) +{ + int ret, regno; + char buf[17*sizeof(unsigned int)]; + + ret = ptrace (PTRACE_GETREGS, inferior_pid, 0, (int)buf); + if ( ret < 0 ) + { + warning ("Couldn't get registers"); + return; + } + + for ( regno = 0; regno < NUM_REGS-NUM_FREGS; regno++ ) + supply_register (regno, buf + register_addr (regno, U_REGS_OFFSET)); + +} + + +/* + Get the whole non-floating-point register state of the process and + replace them in the process from registers[]. + */ +static void +store_regs(void) +{ + int ret, regno; + char buf[17*sizeof(unsigned int)]; + + ret = ptrace (PTRACE_GETREGS, inferior_pid, 0, (int)buf); + if ( ret < 0 ) + { + warning ("Couldn't get registers"); + return; + } + + for ( regno = 0; regno < NUM_REGS-NUM_FREGS; regno++ ) + { + if ( register_valid[regno] ) + read_register_gen (regno, buf + register_addr (regno, U_REGS_OFFSET)); + } + + ret = ptrace (PTRACE_SETREGS, inferior_pid, 0, (int)buf); + + if ( ret < 0 ) + { + warning ("Couldn't write floating point status"); + return; + } + +} + + +/* Fetch registers from the child process. + Fetch all if regno == -1, otherwise fetch all ordinary + registers or all floating point registers depending + upon the value of regno. */ + +void +fetch_inferior_registers (regno) + int regno; +{ + if ( (regno < NUM_REGS - NUM_FREGS) || (regno == -1) ) + fetch_regs(); + + if ( (regno >= NUM_REGS - NUM_FREGS) || (regno == -1) ) + fetch_fpregs(); +} + + +/* Store our register values back into the inferior. + If REGNO is -1, do this for all registers. + Otherwise, REGNO specifies which register, which + then determines whether we store all ordinary + registers or all of the floating point registers. */ + +void +store_inferior_registers (regno) + int regno; +{ + if ( (regno < NUM_REGS - NUM_FREGS) || (regno == -1) ) + store_regs(); + + if ( (regno >= NUM_REGS - NUM_FREGS) || (regno == -1) ) + store_fpregs(); +} + + +/* Find the minimal symbol named NAME, and return both the minsym + struct and its objfile. This probably ought to be in minsym.c, but + everything there is trying to deal with things like C++ and + SOFUN_ADDRESS_MAYBE_TURQUOISE, ... Since this is so simple, it may + be considered too special-purpose for general consumption. */ + +static struct minimal_symbol * +find_minsym_and_objfile (char *name, struct objfile **objfile_p) +{ + struct objfile *objfile; + + ALL_OBJFILES (objfile) + { + struct minimal_symbol *msym; + + ALL_OBJFILE_MSYMBOLS (objfile, msym) + { + if (SYMBOL_NAME (msym) + && STREQ (SYMBOL_NAME (msym), name)) + { + *objfile_p = objfile; + return msym; + } + } + } + + return 0; +} + + +static CORE_ADDR +skip_hurd_resolver (CORE_ADDR pc) +{ + /* The HURD dynamic linker is part of the GNU C library, so many + GNU/Linux distributions use it. (All ELF versions, as far as I + know.) An unresolved PLT entry points to "_dl_runtime_resolve", + which calls "fixup" to patch the PLT, and then passes control to + the function. + + We look for the symbol `_dl_runtime_resolve', and find `fixup' in + the same objfile. If we are at the entry point of `fixup', then + we set a breakpoint at the return address (at the top of the + stack), and continue. + + It's kind of gross to do all these checks every time we're + called, since they don't change once the executable has gotten + started. But this is only a temporary hack --- upcoming versions + of Linux will provide a portable, efficient interface for + debugging programs that use shared libraries. */ + + struct objfile *objfile; + struct minimal_symbol *resolver + = find_minsym_and_objfile ("_dl_runtime_resolve", &objfile); + + if (resolver) + { + struct minimal_symbol *fixup + = lookup_minimal_symbol ("fixup", 0, objfile); + + if (fixup && SYMBOL_VALUE_ADDRESS (fixup) == pc) + return (SAVED_PC_AFTER_CALL (get_current_frame ())); + } + + return 0; +} + + +/* See the comments for SKIP_SOLIB_RESOLVER at the top of infrun.c. + This function: + 1) decides whether a PLT has sent us into the linker to resolve + a function reference, and + 2) if so, tells us where to set a temporary breakpoint that will + trigger when the dynamic linker is done. */ + +CORE_ADDR +i386_linux_skip_solib_resolver (CORE_ADDR pc) +{ + CORE_ADDR result; + + /* Plug in functions for other kinds of resolvers here. */ + result = skip_hurd_resolver (pc); + if (result) + return result; + + return 0; +} diff --git a/gdb/linux-thread.c b/gdb/linux-thread.c new file mode 100644 index 00000000000..9f0a80713b4 --- /dev/null +++ b/gdb/linux-thread.c @@ -0,0 +1,1637 @@ +/* Low level interface for debugging GNU/Linux threads for GDB, + the GNU debugger. + Copyright 1998, 1999 Free Software Foundation, Inc. + +This file is part of GDB. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* This module implements the debugging interface of the linuxthreads package + of the glibc. This package implements a simple clone()-based implementation + of Posix threads for Linux. To use this module, be sure that you have at + least the version of the linuxthreads package that holds the support of + GDB (currently 0.8 included in the glibc-2.0.7). + + Right now, the linuxthreads package does not care of priority scheduling, + so, neither this module does; In particular, the threads are resumed + in any order, which could lead to different scheduling than the one + happening when GDB does not control the execution. + + The latest point is that ptrace(PT_ATTACH, ...) is intrusive in Linux: + When a process is attached, then the attaching process becomes the current + parent of the attached process, and the old parent has lost this child. + If the old parent does a wait[...](), then this child is no longer + considered by the kernel as a child of the old parent, thus leading to + results of the call different when the child is attached and when it's not. + + A fix has been submitted to the Linux community to solve this problem, + which consequences are not visible to the application itself, but on the + process which may wait() for the completion of the application (mostly, + it may consider that the application no longer exists (errno == ECHILD), + although it does, and thus being unable to get the exit status and resource + usage of the child. If by chance, it is able to wait() for the application + after it has died (by receiving first a SIGCHILD, and then doing a wait(), + then the exit status and resource usage may be wrong, because the + linuxthreads package heavily relies on wait() synchronization to keep + them correct. */ + +#include <sys/types.h> /* for pid_t */ +#include <sys/ptrace.h> /* for PT_* flags */ +#include <sys/wait.h> /* for WUNTRACED and __WCLONE flags */ +#include <signal.h> /* for struct sigaction and NSIG */ +#include <sys/utsname.h> + +#include "defs.h" +#include "target.h" +#include "inferior.h" +#include "gdbcore.h" +#include "gdbthread.h" +#include "wait.h" +#include "gdbcmd.h" +#include "breakpoint.h" + +#ifndef PT_ATTACH +#define PT_ATTACH PTRACE_ATTACH +#endif +#ifndef PT_KILL +#define PT_KILL PTRACE_KILL +#endif +#ifndef PT_READ_U +#define PT_READ_U PTRACE_PEEKUSR +#endif + +#ifdef NSIG +#define LINUXTHREAD_NSIG NSIG +#else +#ifdef _NSIG +#define LINUXTHREAD_NSIG _NSIG +#endif +#endif + +extern int child_suppress_run; /* make inftarg.c non-runnable */ +struct target_ops linuxthreads_ops; /* Forward declaration */ +extern struct target_ops child_ops; /* target vector for inftarg.c */ + +static CORE_ADDR linuxthreads_handles; /* array of linuxthreads handles */ +static CORE_ADDR linuxthreads_manager; /* pid of linuxthreads manager thread */ +static CORE_ADDR linuxthreads_initial; /* pid of linuxthreads initial thread */ +static CORE_ADDR linuxthreads_debug; /* linuxthreads internal debug flag */ +static CORE_ADDR linuxthreads_num; /* number of valid handle entries */ + +static int linuxthreads_max; /* Maximum number of linuxthreads. + Zero if this executable doesn't use + threads, or wasn't linked with a + debugger-friendly version of the + linuxthreads library. */ + +static int linuxthreads_sizeof_handle; /* size of a linuxthreads handle */ +static int linuxthreads_offset_descr; /* h_descr offset of the linuxthreads + handle */ +static int linuxthreads_offset_pid; /* p_pid offset of the linuxthreads + descr */ + +static int linuxthreads_manager_pid; /* manager pid */ +static int linuxthreads_initial_pid; /* initial pid */ + +/* These variables form a bag of threads with interesting status. If + wait_thread (PID) finds that PID stopped for some interesting + reason (i.e. anything other than stopped with SIGSTOP), then it + records its status in this queue. linuxthreads_wait and + linuxthreads_find_trap extract processes from here. */ +static int *linuxthreads_wait_pid; /* wait array of pid */ +static int *linuxthreads_wait_status; /* wait array of status */ +static int linuxthreads_wait_last; /* index of last valid elt in + linuxthreads_wait_{pid,status} */ + +static sigset_t linuxthreads_wait_mask; /* sigset with SIGCHLD */ + +static int linuxthreads_step_pid; /* current stepped pid */ +static int linuxthreads_step_signo; /* current stepped target signal */ +static int linuxthreads_exit_status; /* exit status of initial thread */ + +static int linuxthreads_inferior_pid; /* temporary internal inferior pid */ +static int linuxthreads_breakpoint_pid; /* last pid that hit a breakpoint */ +static int linuxthreads_attach_pending; /* attach command without wait */ + +static int linuxthreads_breakpoints_inserted; /* any breakpoints inserted */ + +/* LinuxThreads uses certain signals for communication between + processes; we need to tell GDB to pass them through silently to the + inferior. The LinuxThreads library has global variables we can + read containing the relevant signal numbers, but since the signal + numbers are chosen at run-time, those variables aren't initialized + until the shared library's constructors have had a chance to run. */ + +struct linuxthreads_signal { + + /* The name of the LinuxThreads library variable that contains + the signal number. */ + char *var; + + /* True if this variable must exist for us to debug properly. */ + int required; + + /* The variable's address in the inferior, or zero if the + LinuxThreads library hasn't been loaded into this inferior yet. */ + CORE_ADDR addr; + + /* The signal number, or zero if we don't know yet (either because + we haven't found the variable, or it hasn't been initialized). + This is an actual target signal number that you could pass to + `kill', not a GDB signal number. */ + int signal; + + /* GDB's original settings for `stop' and `print' for this signal. + We restore them when the user selects a different executable. + Invariant: if sig->signal != 0, then sig->{stop,print} contain + the original settings. */ + int stop, print; +}; + +struct linuxthreads_signal linuxthreads_sig_restart = { + "__pthread_sig_restart", 1, 0, 0, 0 +}; +struct linuxthreads_signal linuxthreads_sig_cancel = { + "__pthread_sig_cancel", 1, 0, 0, 0 +}; +struct linuxthreads_signal linuxthreads_sig_debug = { + "__pthread_sig_debug", 0, 0, 0, 0 +}; + +/* A table of breakpoint locations, one per PID. */ +static struct linuxthreads_breakpoint { + CORE_ADDR pc; /* PC of breakpoint */ + int pid; /* pid of breakpoint */ + int step; /* whether the pc has been reached after sstep */ +} *linuxthreads_breakpoint_zombie; /* Zombie breakpoints array */ +static int linuxthreads_breakpoint_last; /* Last zombie breakpoint */ + +/* linuxthreads_{insert,remove}_breakpoint pass the breakpoint address + to {insert,remove}_breakpoint via this variable, since + iterate_active_threads doesn't provide any way to pass values + through to the worker function. */ +static CORE_ADDR linuxthreads_breakpoint_addr; + +#define REMOVE_BREAKPOINT_ZOMBIE(_i) \ +{ \ + if ((_i) < linuxthreads_breakpoint_last) \ + linuxthreads_breakpoint_zombie[(_i)] = \ + linuxthreads_breakpoint_zombie[linuxthreads_breakpoint_last]; \ + linuxthreads_breakpoint_last--; \ +} + + + +#ifndef PTRACE_XFER_TYPE +#define PTRACE_XFER_TYPE int +#endif +/* Check to see if the given thread is alive. */ +static int +linuxthreads_thread_alive (pid) + int pid; +{ + errno = 0; + return ptrace (PT_READ_U, pid, (PTRACE_ARG3_TYPE)0, 0) >= 0 || errno == 0; +} + +/* On detach(), find a SIGTRAP status. If stop is non-zero, find a + SIGSTOP one, too. + + Make sure PID is ready to run, and free of interference from our + efforts to debug it (e.g., pending SIGSTOP or SIGTRAP signals). If + STOP is zero, just look for a SIGTRAP. If STOP is non-zero, look + for a SIGSTOP, too. Return non-zero if PID is alive and ready to + run; return zero if PID is dead. + + PID may or may not be stopped at the moment, and we may or may not + have waited for it already. We check the linuxthreads_wait bag in + case we've already got a status for it. We may possibly wait for + it ourselves. + + PID may have signals waiting to be delivered. If they're caused by + our efforts to debug it, accept them with wait, but don't pass them + through to PID. Do pass all other signals through. */ +static int +linuxthreads_find_trap (pid, stop) + int pid; + int stop; +{ + int i; + int rpid; + int status; + int found_stop = 0; + int found_trap = 0; + + /* PID may have any number of signals pending. The kernel will + report each of them to us via wait, and then it's up to us to + pass them along to the process via ptrace, if we so choose. + + We need to paw through the whole set until we've found a SIGTRAP + (or a SIGSTOP, if `stop' is set). We don't pass the SIGTRAP (or + SIGSTOP) through, but we do re-send all the others, so PID will + receive them when we resume it. */ + int *wstatus = alloca (LINUXTHREAD_NSIG * sizeof (int)); + int last = 0; + + /* Look at the pending status */ + for (i = linuxthreads_wait_last; i >= 0; i--) + if (linuxthreads_wait_pid[i] == pid) + { + status = linuxthreads_wait_status[i]; + + /* Delete the i'th member of the table. Since the table is + unordered, we can do this simply by copying the table's + last element to the i'th position, and shrinking the table + by one element. */ + if (i < linuxthreads_wait_last) + { + linuxthreads_wait_status[i] = + linuxthreads_wait_status[linuxthreads_wait_last]; + linuxthreads_wait_pid[i] = + linuxthreads_wait_pid[linuxthreads_wait_last]; + } + linuxthreads_wait_last--; + + if (!WIFSTOPPED(status)) /* Thread has died */ + return 0; + + if (WSTOPSIG(status) == SIGTRAP) + { + if (stop) + found_trap = 1; + else + return 1; + } + else if (WSTOPSIG(status) == SIGSTOP) + { + if (stop) + found_stop = 1; + } + else + { + wstatus[0] = status; + last = 1; + } + + break; + } + + if (stop) + { + /* Make sure that we'll find what we're looking for. */ + if (!found_trap) + kill (pid, SIGTRAP); + if (!found_stop) + kill (pid, SIGSTOP); + } + + /* Catch all status until SIGTRAP and optionally SIGSTOP show up. */ + for (;;) + { + child_resume (pid, 1, TARGET_SIGNAL_0); + + for (;;) + { + rpid = waitpid (pid, &status, __WCLONE); + if (rpid > 0) + break; + if (errno == EINTR) + continue; + + /* There are a few reasons the wait call above may have + failed. If the thread manager dies, its children get + reparented, and this interferes with GDB waiting for + them, in some cases. Another possibility is that the + initial thread was not cloned, so calling wait with + __WCLONE won't find it. I think neither of these should + occur in modern Linux kernels --- they don't seem to in + 2.0.36. */ + rpid = waitpid (pid, &status, 0); + if (rpid > 0) + break; + if (errno != EINTR) + perror_with_name ("waitpid"); + } + + if (!WIFSTOPPED(status)) /* Thread has died */ + return 0; + + if (WSTOPSIG(status) == SIGTRAP) + if (!stop || found_stop) + break; + else + found_trap = 1; + else if (WSTOPSIG(status) != SIGSTOP) + wstatus[last++] = status; + else if (stop) + if (found_trap) + break; + else + found_stop = 1; + } + + /* Resend any other signals we noticed to the thread, to be received + when we continue it. */ + while (--last >= 0) + kill (pid, WSTOPSIG(wstatus[last])); + + return 1; +} + +/* Cleanup stub for save_inferior_pid. */ +static void +restore_inferior_pid (arg) + void *arg; +{ + int pid = (int) arg; + inferior_pid = pid; +} + +/* Register a cleanup to restore the value of inferior_pid. */ +static struct cleanup * +save_inferior_pid () +{ + return make_cleanup (restore_inferior_pid, (void *) inferior_pid); +} + +static void +sigchld_handler (signo) + int signo; +{ + /* This handler is used to get an EINTR while doing waitpid() + when an event is received */ +} + +/* Have we already collected a wait status for PID in the + linuxthreads_wait bag? */ +static int +linuxthreads_pending_status (pid) + int pid; +{ + int i; + for (i = linuxthreads_wait_last; i >= 0; i--) + if (linuxthreads_wait_pid[i] == pid) + return 1; + return 0; +} + + +/* Internal linuxthreads signal management */ + +/* Check in OBJFILE for the variable that holds the number for signal SIG. + We assume that we've already found other LinuxThreads-ish variables + in OBJFILE, so we complain if it's required, but not there. + Return true iff things are okay. */ +static int +find_signal_var (sig, objfile) + struct linuxthreads_signal *sig; + struct objfile *objfile; +{ + struct minimal_symbol *ms = lookup_minimal_symbol (sig->var, NULL, objfile); + + if (! ms) + { + if (sig->required) + { + fprintf_unfiltered (gdb_stderr, + "Unable to find linuxthreads symbol \"%s\"\n", + sig->var); + return 0; + } + else + { + sig->addr = 0; + return 1; + } + } + + sig->addr = SYMBOL_VALUE_ADDRESS (ms); + + return 1; +} + +static int +find_all_signal_vars (objfile) + struct objfile *objfile; +{ + return ( find_signal_var (&linuxthreads_sig_restart, objfile) + && find_signal_var (&linuxthreads_sig_cancel, objfile) + && find_signal_var (&linuxthreads_sig_debug, objfile)); +} + +/* A struct complaint isn't appropriate here. */ +static int complained_cannot_determine_thread_signal_number = 0; + +/* Check to see if the variable holding the signal number for SIG has + been initialized yet. If it has, tell GDB to pass that signal + through to the inferior silently. */ +static void +check_signal_number (sig) + struct linuxthreads_signal *sig; +{ + int num; + + if (sig->signal) + /* We already know this signal number. */ + return; + + if (! sig->addr) + /* We don't know the variable's address yet. */ + return; + + if (target_read_memory (sig->addr, (char *)&num, sizeof (num)) + != 0) + { + /* If this happens once, it'll probably happen for all the + signals, so only complain once. */ + if (! complained_cannot_determine_thread_signal_number) + warning ("Cannot determine thread signal number; " + "GDB may report spurious signals."); + complained_cannot_determine_thread_signal_number = 1; + return; + } + + if (num == 0) + /* It hasn't been initialized yet. */ + return; + + /* We know sig->signal was zero, and is becoming non-zero, so it's + okay to sample GDB's original settings. */ + sig->signal = num; + sig->stop = signal_stop_update (target_signal_from_host (num), 0); + sig->print = signal_print_update (target_signal_from_host (num), 0); +} + + +static void +check_all_signal_numbers () +{ + /* If this isn't a LinuxThreads program, quit early. */ + if (! linuxthreads_max) + return; + + check_signal_number (&linuxthreads_sig_restart); + check_signal_number (&linuxthreads_sig_cancel); + check_signal_number (&linuxthreads_sig_debug); + + /* handle linuxthread exit */ + if (linuxthreads_sig_debug.signal + || linuxthreads_sig_restart.signal) + { + struct sigaction sact; + + sact.sa_handler = sigchld_handler; + sigemptyset(&sact.sa_mask); + sact.sa_flags = 0; + if (linuxthreads_sig_debug.signal > 0) + sigaction(linuxthreads_sig_cancel.signal, &sact, NULL); + else + sigaction(linuxthreads_sig_restart.signal, &sact, NULL); + } +} + + +/* Restore GDB's original settings for SIG. + This should only be called when we're no longer sure if we're + talking to an executable that uses LinuxThreads, so we clear the + signal number and variable address too. */ +static void +restore_signal (sig) + struct linuxthreads_signal *sig; +{ + if (! sig->signal) + return; + + /* We know sig->signal was non-zero, and is becoming zero, so it's + okay to restore GDB's original settings. */ + signal_stop_update (target_signal_from_host (sig->signal), sig->stop); + signal_print_update (target_signal_from_host (sig->signal), sig->print); + + sig->signal = 0; + sig->addr = 0; +} + + +/* Restore GDB's original settings for all LinuxThreads signals. + This should only be called when we're no longer sure if we're + talking to an executable that uses LinuxThreads, so we clear the + signal number and variable address too. */ +static void +restore_all_signals () +{ + restore_signal (&linuxthreads_sig_restart); + restore_signal (&linuxthreads_sig_cancel); + restore_signal (&linuxthreads_sig_debug); + + /* If it happens again, we should complain again. */ + complained_cannot_determine_thread_signal_number = 0; +} + + + + +/* Apply FUNC to the pid of each active thread. This consults the + inferior's handle table to find active threads. + + If ALL is non-zero, process all threads. + If ALL is zero, skip threads with pending status. */ +static void +iterate_active_threads (func, all) + void (*func)(int); + int all; +{ + CORE_ADDR descr; + int pid; + int i; + int num; + + read_memory (linuxthreads_num, (char *)&num, sizeof (int)); + + for (i = 0; i < linuxthreads_max && num > 0; i++) + { + read_memory (linuxthreads_handles + + linuxthreads_sizeof_handle * i + linuxthreads_offset_descr, + (char *)&descr, sizeof (void *)); + if (descr) + { + num--; + read_memory (descr + linuxthreads_offset_pid, + (char *)&pid, sizeof (pid_t)); + if (pid > 0 && pid != linuxthreads_manager_pid + && (all || (!linuxthreads_pending_status (pid)))) + (*func)(pid); + } + } + +} + +/* Insert a thread breakpoint at linuxthreads_breakpoint_addr. + This is the worker function for linuxthreads_insert_breakpoint, + which passes it to iterate_active_threads. */ +static void +insert_breakpoint (pid) + int pid; +{ + int j; + + /* Remove (if any) the positive zombie breakpoint. */ + for (j = linuxthreads_breakpoint_last; j >= 0; j--) + if (linuxthreads_breakpoint_zombie[j].pid == pid) + { + if ((linuxthreads_breakpoint_zombie[j].pc - DECR_PC_AFTER_BREAK + == linuxthreads_breakpoint_addr) + && !linuxthreads_breakpoint_zombie[j].step) + REMOVE_BREAKPOINT_ZOMBIE(j); + break; + } +} + +/* Note that we're about to remove a thread breakpoint at + linuxthreads_breakpoint_addr. + + This is the worker function for linuxthreads_remove_breakpoint, + which passes it to iterate_active_threads. The actual work of + overwriting the breakpoint instruction is done by + child_ops.to_remove_breakpoint; here, we simply create a zombie + breakpoint if the thread's PC is pointing at the breakpoint being + removed. */ +static void +remove_breakpoint (pid) + int pid; +{ + int j; + + /* Insert a positive zombie breakpoint (if needed). */ + for (j = 0; j <= linuxthreads_breakpoint_last; j++) + if (linuxthreads_breakpoint_zombie[j].pid == pid) + break; + + if (in_thread_list (pid) && linuxthreads_thread_alive (pid)) + { + CORE_ADDR pc = read_pc_pid (pid); + if (linuxthreads_breakpoint_addr == pc - DECR_PC_AFTER_BREAK + && j > linuxthreads_breakpoint_last) + { + linuxthreads_breakpoint_zombie[j].pid = pid; + linuxthreads_breakpoint_zombie[j].pc = pc; + linuxthreads_breakpoint_zombie[j].step = 0; + linuxthreads_breakpoint_last++; + } + } +} + +/* Kill a thread */ +static void +kill_thread (pid) + int pid; +{ + if (in_thread_list (pid)) + ptrace (PT_KILL, pid, (PTRACE_ARG3_TYPE) 0, 0); + else + kill (pid, SIGKILL); +} + +/* Resume a thread */ +static void +resume_thread (pid) + int pid; +{ + if (pid != inferior_pid + && in_thread_list (pid) + && linuxthreads_thread_alive (pid)) + if (pid == linuxthreads_step_pid) + child_resume (pid, 1, linuxthreads_step_signo); + else + child_resume (pid, 0, TARGET_SIGNAL_0); +} + +/* Detach a thread */ +static void +detach_thread (pid) + int pid; +{ + if (in_thread_list (pid) && linuxthreads_thread_alive (pid)) + { + /* Remove pending SIGTRAP and SIGSTOP */ + linuxthreads_find_trap (pid, 1); + + inferior_pid = pid; + detach (TARGET_SIGNAL_0); + inferior_pid = linuxthreads_manager_pid; + } +} + +/* Stop a thread */ +static void +stop_thread (pid) + int pid; +{ + if (pid != inferior_pid) + if (in_thread_list (pid)) + kill (pid, SIGSTOP); + else if (ptrace (PT_ATTACH, pid, (PTRACE_ARG3_TYPE) 0, 0) == 0) + { + if (!linuxthreads_attach_pending) + printf_unfiltered ("[New %s]\n", target_pid_to_str (pid)); + add_thread (pid); + if (linuxthreads_sig_debug.signal) + /* After a new thread in glibc 2.1 signals gdb its existence, + it suspends itself and wait for linuxthreads_sig_restart, + now we can wake up it. */ + kill (pid, linuxthreads_sig_restart.signal); + } + else + perror_with_name ("ptrace in stop_thread"); +} + +/* Wait for a thread */ +static void +wait_thread (pid) + int pid; +{ + int status; + int rpid; + + if (pid != inferior_pid && in_thread_list (pid)) + { + for (;;) + { + /* Get first pid status. */ + rpid = waitpid(pid, &status, __WCLONE); + if (rpid > 0) + break; + if (errno == EINTR) + continue; + + /* There are two reasons this might have failed: + + 1) PID is the initial thread, which wasn't cloned, so + passing the __WCLONE flag to waitpid prevented us from + finding it. + + 2) The manager thread is the parent of all but the + initial thread; if it dies, the children will all be + reparented to init, which will wait for them. This means + our call to waitpid won't find them. + + Actually, based on a casual look at the 2.0.36 kernel + code, I don't think either of these cases happen. But I + don't have things set up for remotely debugging the + kernel, so I'm not sure. And perhaps older kernels + didn't work. */ + rpid = waitpid(pid, &status, 0); + if (rpid > 0) + break; + if (errno != EINTR && linuxthreads_thread_alive (pid)) + perror_with_name ("waitpid"); + + /* the thread is dead. */ + return; + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) + { + linuxthreads_wait_pid[++linuxthreads_wait_last] = pid; + linuxthreads_wait_status[linuxthreads_wait_last] = status; + } + } +} + +/* Walk through the linuxthreads handles in order to detect all + threads and stop them */ +static void +update_stop_threads (test_pid) + int test_pid; +{ + struct cleanup *old_chain = NULL; + + check_all_signal_numbers (); + + if (linuxthreads_manager_pid == 0) + { + if (linuxthreads_manager) + { + if (test_pid > 0 && test_pid != inferior_pid) + { + old_chain = save_inferior_pid (); + inferior_pid = test_pid; + } + read_memory (linuxthreads_manager, + (char *)&linuxthreads_manager_pid, sizeof (pid_t)); + } + if (linuxthreads_initial) + { + if (test_pid > 0 && test_pid != inferior_pid) + { + old_chain = save_inferior_pid (); + inferior_pid = test_pid; + } + read_memory(linuxthreads_initial, + (char *)&linuxthreads_initial_pid, sizeof (pid_t)); + } + } + + if (linuxthreads_manager_pid != 0) + { + if (old_chain == NULL && test_pid > 0 && + test_pid != inferior_pid && linuxthreads_thread_alive (test_pid)) + { + old_chain = save_inferior_pid (); + inferior_pid = test_pid; + } + + if (linuxthreads_thread_alive (inferior_pid)) + { + if (test_pid > 0) + { + if (test_pid != linuxthreads_manager_pid + && !linuxthreads_pending_status (linuxthreads_manager_pid)) + { + stop_thread (linuxthreads_manager_pid); + wait_thread (linuxthreads_manager_pid); + } + if (!in_thread_list (test_pid)) + { + if (!linuxthreads_attach_pending) + printf_unfiltered ("[New %s]\n", + target_pid_to_str (test_pid)); + add_thread (test_pid); + if (linuxthreads_sig_debug.signal + && inferior_pid == test_pid) + /* After a new thread in glibc 2.1 signals gdb its + existence, it suspends itself and wait for + linuxthreads_sig_restart, now we can wake up + it. */ + kill (test_pid, linuxthreads_sig_restart.signal); + } + } + iterate_active_threads (stop_thread, 0); + iterate_active_threads (wait_thread, 0); + } + } + + if (old_chain != NULL) + do_cleanups (old_chain); +} + +/* This routine is called whenever a new symbol table is read in, or when all + symbol tables are removed. libpthread can only be initialized when it + finds the right variables in libpthread.so. Since it's a shared library, + those variables don't show up until the library gets mapped and the symbol + table is read in. */ + +void +linuxthreads_new_objfile (objfile) + struct objfile *objfile; +{ + struct minimal_symbol *ms; + + if (!objfile) + { + /* We're starting an entirely new executable, so we can no + longer be sure that it uses LinuxThreads. Restore the signal + flags to their original states. */ + restore_all_signals (); + + /* Indicate that we don't know anything's address any more. */ + linuxthreads_max = 0; + + return; + } + + /* If we've already found our variables in another objfile, don't + bother looking for them again. */ + if (linuxthreads_max) + return; + + if (! lookup_minimal_symbol ("__pthread_initial_thread", NULL, objfile)) + /* This object file isn't the pthreads library. */ + return; + + if ((ms = lookup_minimal_symbol ("__pthread_threads_debug", + NULL, objfile)) == NULL) + { + /* The debugging-aware libpthreads is not present in this objfile */ + warning ("\ +This program seems to use POSIX threads, but the thread library used\n\ +does not support debugging. This may make using GDB difficult. Don't\n\ +set breakpoints or single-step through code that might be executed by\n\ +any thread other than the main thread."); + return; + } + linuxthreads_debug = SYMBOL_VALUE_ADDRESS (ms); + + /* Read internal structures configuration */ + if ((ms = lookup_minimal_symbol ("__pthread_sizeof_handle", + NULL, objfile)) == NULL + || target_read_memory (SYMBOL_VALUE_ADDRESS (ms), + (char *)&linuxthreads_sizeof_handle, + sizeof (linuxthreads_sizeof_handle)) != 0) + { + fprintf_unfiltered (gdb_stderr, + "Unable to find linuxthreads symbol \"%s\"\n", + "__pthread_sizeof_handle"); + return; + } + + if ((ms = lookup_minimal_symbol ("__pthread_offsetof_descr", + NULL, objfile)) == NULL + || target_read_memory (SYMBOL_VALUE_ADDRESS (ms), + (char *)&linuxthreads_offset_descr, + sizeof (linuxthreads_offset_descr)) != 0) + { + fprintf_unfiltered (gdb_stderr, + "Unable to find linuxthreads symbol \"%s\"\n", + "__pthread_offsetof_descr"); + return; + } + + if ((ms = lookup_minimal_symbol ("__pthread_offsetof_pid", + NULL, objfile)) == NULL + || target_read_memory (SYMBOL_VALUE_ADDRESS (ms), + (char *)&linuxthreads_offset_pid, + sizeof (linuxthreads_offset_pid)) != 0) + { + fprintf_unfiltered (gdb_stderr, + "Unable to find linuxthreads symbol \"%s\"\n", + "__pthread_offsetof_pid"); + return; + } + + if (! find_all_signal_vars (objfile)) + return; + + /* Read adresses of internal structures to access */ + if ((ms = lookup_minimal_symbol ("__pthread_handles", + NULL, objfile)) == NULL) + { + fprintf_unfiltered (gdb_stderr, + "Unable to find linuxthreads symbol \"%s\"\n", + "__pthread_handles"); + return; + } + linuxthreads_handles = SYMBOL_VALUE_ADDRESS (ms); + + if ((ms = lookup_minimal_symbol ("__pthread_handles_num", + NULL, objfile)) == NULL) + { + fprintf_unfiltered (gdb_stderr, + "Unable to find linuxthreads symbol \"%s\"\n", + "__pthread_handles_num"); + return; + } + linuxthreads_num = SYMBOL_VALUE_ADDRESS (ms); + + if ((ms = lookup_minimal_symbol ("__pthread_manager_thread", + NULL, objfile)) == NULL) + { + fprintf_unfiltered (gdb_stderr, + "Unable to find linuxthreads symbol \"%s\"\n", + "__pthread_manager_thread"); + return; + } + linuxthreads_manager = SYMBOL_VALUE_ADDRESS (ms) + linuxthreads_offset_pid; + + if ((ms = lookup_minimal_symbol ("__pthread_initial_thread", + NULL, objfile)) == NULL) + { + fprintf_unfiltered (gdb_stderr, + "Unable to find linuxthreads symbol \"%s\"\n", + "__pthread_initial_thread"); + return; + } + linuxthreads_initial = SYMBOL_VALUE_ADDRESS (ms) + linuxthreads_offset_pid; + + /* Search for this last, so it won't be set to a non-zero value unless + we successfully found all the symbols above. */ + if ((ms = lookup_minimal_symbol ("__pthread_threads_max", + NULL, objfile)) == NULL + || target_read_memory (SYMBOL_VALUE_ADDRESS (ms), + (char *)&linuxthreads_max, + sizeof (linuxthreads_max)) != 0) + { + fprintf_unfiltered (gdb_stderr, + "Unable to find linuxthreads symbol \"%s\"\n", + "__pthread_threads_max"); + return; + } + + /* Allocate gdb internal structures */ + linuxthreads_wait_pid = + (int *) xmalloc (sizeof (int) * (linuxthreads_max + 1)); + linuxthreads_wait_status = + (int *) xmalloc (sizeof (int) * (linuxthreads_max + 1)); + linuxthreads_breakpoint_zombie = (struct linuxthreads_breakpoint *) + xmalloc (sizeof (struct linuxthreads_breakpoint) * (linuxthreads_max + 1)); + + if (inferior_pid && !linuxthreads_attach_pending) + { + int on = 1; + target_write_memory (linuxthreads_debug, (char *)&on, sizeof (on)); + linuxthreads_attach_pending = 1; + update_stop_threads (inferior_pid); + linuxthreads_attach_pending = 0; + } +} + +/* If we have switched threads from a one that stopped at breakpoint, + return 1 otherwise 0. */ + +int +linuxthreads_prepare_to_proceed (step) + int step; +{ + if (!linuxthreads_max + || !linuxthreads_manager_pid + || !linuxthreads_breakpoint_pid + || !breakpoint_here_p (read_pc_pid (linuxthreads_breakpoint_pid))) + return 0; + + if (step) + { + /* Mark the current inferior as single stepping process. */ + linuxthreads_step_pid = inferior_pid; + } + + linuxthreads_inferior_pid = linuxthreads_breakpoint_pid; + return linuxthreads_breakpoint_pid; +} + +/* Convert a pid to printable form. */ + +char * +linuxthreads_pid_to_str (pid) + int pid; +{ + static char buf[100]; + + sprintf (buf, "%s %d%s", linuxthreads_max ? "Thread" : "Pid", pid, + (pid == linuxthreads_manager_pid) ? " (manager thread)" + : (pid == linuxthreads_initial_pid) ? " (initial thread)" + : ""); + + return buf; +} + +/* Attach to process PID, then initialize for debugging it + and wait for the trace-trap that results from attaching. */ + +static void +linuxthreads_attach (args, from_tty) + char *args; + int from_tty; +{ + if (!args) + error_no_arg ("process-id to attach"); + + push_target (&linuxthreads_ops); + linuxthreads_breakpoints_inserted = 1; + linuxthreads_breakpoint_last = -1; + linuxthreads_wait_last = -1; + linuxthreads_exit_status = __W_STOPCODE(0); + + child_ops.to_attach (args, from_tty); + + if (linuxthreads_max) + linuxthreads_attach_pending = 1; +} + +/* Take a program previously attached to and detaches it. + The program resumes execution and will no longer stop + on signals, etc. We'd better not have left any breakpoints + in the program or it'll die when it hits one. For this + to work, it may be necessary for the process to have been + previously attached. It *might* work if the program was + started via the normal ptrace (PTRACE_TRACEME). */ + +static void +linuxthreads_detach (args, from_tty) + char *args; + int from_tty; +{ + if (linuxthreads_max) + { + int i; + int pid; + int off = 0; + target_write_memory (linuxthreads_debug, (char *)&off, sizeof (off)); + + /* Walk through linuxthreads array in order to detach known threads. */ + if (linuxthreads_manager_pid != 0) + { + /* Get rid of all positive zombie breakpoints. */ + for (i = 0; i <= linuxthreads_breakpoint_last; i++) + { + if (linuxthreads_breakpoint_zombie[i].step) + continue; + + pid = linuxthreads_breakpoint_zombie[i].pid; + if (!linuxthreads_thread_alive (pid)) + continue; + + if (linuxthreads_breakpoint_zombie[i].pc != read_pc_pid (pid)) + continue; + + /* Continue in STEP mode until the thread pc has moved or + until SIGTRAP is found on the same PC. */ + if (linuxthreads_find_trap (pid, 0) + && linuxthreads_breakpoint_zombie[i].pc == read_pc_pid (pid)) + write_pc_pid (linuxthreads_breakpoint_zombie[i].pc + - DECR_PC_AFTER_BREAK, pid); + } + + /* Detach thread after thread. */ + inferior_pid = linuxthreads_manager_pid; + iterate_active_threads (detach_thread, 1); + + /* Remove pending SIGTRAP and SIGSTOP */ + linuxthreads_find_trap (inferior_pid, 1); + + linuxthreads_wait_last = -1; + linuxthreads_exit_status = __W_STOPCODE(0); + } + + linuxthreads_inferior_pid = 0; + linuxthreads_breakpoint_pid = 0; + linuxthreads_step_pid = 0; + linuxthreads_step_signo = TARGET_SIGNAL_0; + linuxthreads_manager_pid = 0; + linuxthreads_initial_pid = 0; + linuxthreads_attach_pending = 0; + init_thread_list (); /* Destroy thread info */ + } + + child_ops.to_detach (args, from_tty); + + unpush_target (&linuxthreads_ops); +} + +/* Resume execution of process PID. If STEP is nozero, then + just single step it. If SIGNAL is nonzero, restart it with that + signal activated. */ + +static void +linuxthreads_resume (pid, step, signo) + int pid; + int step; + enum target_signal signo; +{ + if (!linuxthreads_max || stop_soon_quietly || linuxthreads_manager_pid == 0) + child_ops.to_resume (pid, step, signo); + else + { + int rpid; + if (linuxthreads_inferior_pid) + { + /* Prepare resume of the last thread that hit a breakpoint */ + linuxthreads_breakpoints_inserted = 0; + rpid = linuxthreads_inferior_pid; + linuxthreads_step_signo = signo; + } + else + { + struct cleanup *old_chain = NULL; + int i; + + if (pid < 0) + { + linuxthreads_step_pid = step ? inferior_pid : 0; + linuxthreads_step_signo = signo; + rpid = inferior_pid; + } + else + rpid = pid; + + if (pid < 0 || !step) + { + linuxthreads_breakpoints_inserted = 1; + + /* Walk through linuxthreads array in order to resume threads */ + if (pid >= 0 && inferior_pid != pid) + { + old_chain = save_inferior_pid (); + inferior_pid = pid; + } + + iterate_active_threads (resume_thread, 0); + if (linuxthreads_manager_pid != inferior_pid + && !linuxthreads_pending_status (linuxthreads_manager_pid)) + resume_thread (linuxthreads_manager_pid); + } + else + linuxthreads_breakpoints_inserted = 0; + + /* Deal with zombie breakpoint */ + for (i = 0; i <= linuxthreads_breakpoint_last; i++) + if (linuxthreads_breakpoint_zombie[i].pid == rpid) + { + if (linuxthreads_breakpoint_zombie[i].pc != read_pc_pid (rpid)) + { + /* The current pc is out of zombie breakpoint. */ + REMOVE_BREAKPOINT_ZOMBIE(i); + } + break; + } + + if (old_chain != NULL) + do_cleanups (old_chain); + } + + /* Resume initial thread. */ + if (!linuxthreads_pending_status (rpid)) + child_ops.to_resume (rpid, step, signo); + } +} + +/* Wait for any threads to stop. We may have to convert PID from a thread id + to a LWP id, and vice versa on the way out. */ + +static int +linuxthreads_wait (pid, ourstatus) + int pid; + struct target_waitstatus *ourstatus; +{ + int status; + int rpid; + int i; + int last; + int *wstatus; + + if (linuxthreads_max && !linuxthreads_breakpoints_inserted) + wstatus = alloca (LINUXTHREAD_NSIG * sizeof (int)); + + /* See if the inferior has chosen values for its signals yet. By + checking for them here, we can be sure we've updated GDB's signal + handling table before the inferior ever gets one of them. (Well, + before we notice, anyway.) */ + check_all_signal_numbers (); + + for (;;) + { + if (!linuxthreads_max) + rpid = 0; + else if (!linuxthreads_breakpoints_inserted) + { + if (linuxthreads_inferior_pid) + pid = linuxthreads_inferior_pid; + else if (pid < 0) + pid = inferior_pid; + last = rpid = 0; + } + else if (pid < 0 && linuxthreads_wait_last >= 0) + { + status = linuxthreads_wait_status[linuxthreads_wait_last]; + rpid = linuxthreads_wait_pid[linuxthreads_wait_last--]; + } + else if (pid > 0 && linuxthreads_pending_status (pid)) + { + for (i = linuxthreads_wait_last; i >= 0; i--) + if (linuxthreads_wait_pid[i] == pid) + break; + if (i < 0) + rpid = 0; + else + { + status = linuxthreads_wait_status[i]; + rpid = pid; + if (i < linuxthreads_wait_last) + { + linuxthreads_wait_status[i] = + linuxthreads_wait_status[linuxthreads_wait_last]; + linuxthreads_wait_pid[i] = + linuxthreads_wait_pid[linuxthreads_wait_last]; + } + linuxthreads_wait_last--; + } + } + else + rpid = 0; + + if (rpid == 0) + { + int save_errno; + sigset_t omask; + + set_sigint_trap(); /* Causes SIGINT to be passed on to the + attached process. */ + set_sigio_trap (); + + sigprocmask(SIG_BLOCK, &linuxthreads_wait_mask, &omask); + for (;;) + { + rpid = waitpid (pid, &status, __WCLONE | WNOHANG); + if (rpid > 0) + break; + if (rpid == 0) + save_errno = 0; + else if (errno != EINTR) + save_errno = errno; + else + continue; + + rpid = waitpid (pid, &status, WNOHANG); + if (rpid > 0) + break; + if (rpid < 0) + if (errno == EINTR) + continue; + else if (save_errno != 0) + break; + + sigsuspend(&omask); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + + save_errno = errno; + clear_sigio_trap (); + + clear_sigint_trap(); + + if (rpid == -1) + { + if (WIFEXITED(linuxthreads_exit_status)) + { + store_waitstatus (ourstatus, linuxthreads_exit_status); + return inferior_pid; + } + else + { + fprintf_unfiltered + (gdb_stderr, "Child process unexpectedly missing: %s.\n", + safe_strerror (save_errno)); + /* Claim it exited with unknown signal. */ + ourstatus->kind = TARGET_WAITKIND_SIGNALLED; + ourstatus->value.sig = TARGET_SIGNAL_UNKNOWN; + return -1; + } + } + + /* Signals arrive in any order. So get all signals until SIGTRAP + and resend previous ones to be held after. */ + if (linuxthreads_max + && !linuxthreads_breakpoints_inserted + && WIFSTOPPED(status)) + if (WSTOPSIG(status) == SIGTRAP) + { + while (--last >= 0) + kill (rpid, WSTOPSIG(wstatus[last])); + + /* insert negative zombie breakpoint */ + for (i = 0; i <= linuxthreads_breakpoint_last; i++) + if (linuxthreads_breakpoint_zombie[i].pid == rpid) + break; + if (i > linuxthreads_breakpoint_last) + { + linuxthreads_breakpoint_zombie[i].pid = rpid; + linuxthreads_breakpoint_last++; + } + linuxthreads_breakpoint_zombie[i].pc = read_pc_pid (rpid); + linuxthreads_breakpoint_zombie[i].step = 1; + } + else + { + if (WSTOPSIG(status) != SIGSTOP) + { + for (i = 0; i < last; i++) + if (wstatus[i] == status) + break; + if (i >= last) + wstatus[last++] = status; + } + child_resume (rpid, 1, TARGET_SIGNAL_0); + continue; + } + if (linuxthreads_inferior_pid) + linuxthreads_inferior_pid = 0; + } + + if (linuxthreads_max && !stop_soon_quietly) + { + if (linuxthreads_max + && WIFSTOPPED(status) + && WSTOPSIG(status) == SIGSTOP) + { + /* Skip SIGSTOP signals. */ + if (!linuxthreads_pending_status (rpid)) + if (linuxthreads_step_pid == rpid) + child_resume (rpid, 1, linuxthreads_step_signo); + else + child_resume (rpid, 0, TARGET_SIGNAL_0); + continue; + } + + /* Do no report exit status of cloned threads. */ + if (WIFEXITED(status)) + { + if (rpid == linuxthreads_initial_pid) + linuxthreads_exit_status = status; + + /* Remove any zombie breakpoint. */ + for (i = 0; i <= linuxthreads_breakpoint_last; i++) + if (linuxthreads_breakpoint_zombie[i].pid == rpid) + { + REMOVE_BREAKPOINT_ZOMBIE(i); + break; + } + if (pid > 0) + pid = -1; + continue; + } + + /* Deal with zombie breakpoint */ + for (i = 0; i <= linuxthreads_breakpoint_last; i++) + if (linuxthreads_breakpoint_zombie[i].pid == rpid) + break; + + if (i <= linuxthreads_breakpoint_last) + { + /* There is a potential zombie breakpoint */ + if (WIFEXITED(status) + || linuxthreads_breakpoint_zombie[i].pc != read_pc_pid (rpid)) + { + /* The current pc is out of zombie breakpoint. */ + REMOVE_BREAKPOINT_ZOMBIE(i); + } + else if (!linuxthreads_breakpoint_zombie[i].step + && WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) + { + /* This is a real one ==> decrement PC and restart. */ + write_pc_pid (linuxthreads_breakpoint_zombie[i].pc + - DECR_PC_AFTER_BREAK, rpid); + if (linuxthreads_step_pid == rpid) + child_resume (rpid, 1, linuxthreads_step_signo); + else + child_resume (rpid, 0, TARGET_SIGNAL_0); + continue; + } + } + + /* Walk through linuxthreads array in order to stop them */ + if (linuxthreads_breakpoints_inserted) + update_stop_threads (rpid); + + } + else if (rpid != inferior_pid) + continue; + + store_waitstatus (ourstatus, status); + + if (linuxthreads_attach_pending && !stop_soon_quietly) + { + int on = 1; + target_write_memory (linuxthreads_debug, (char *)&on, sizeof (on)); + update_stop_threads (rpid); + linuxthreads_attach_pending = 0; + } + + if (linuxthreads_breakpoints_inserted + && WIFSTOPPED(status) + && WSTOPSIG(status) == SIGTRAP) + linuxthreads_breakpoint_pid = rpid; + else if (linuxthreads_breakpoint_pid) + linuxthreads_breakpoint_pid = 0; + + return rpid; + } +} + +/* Fork an inferior process, and start debugging it with ptrace. */ + +static void +linuxthreads_create_inferior (exec_file, allargs, env) + char *exec_file; + char *allargs; + char **env; +{ + if (!exec_file && !exec_bfd) + { + error ("No executable file specified.\n\ +Use the \"file\" or \"exec-file\" command."); + return; + } + + push_target (&linuxthreads_ops); + linuxthreads_breakpoints_inserted = 1; + linuxthreads_breakpoint_last = -1; + linuxthreads_wait_last = -1; + linuxthreads_exit_status = __W_STOPCODE(0); + + if (linuxthreads_max) + linuxthreads_attach_pending = 1; + + child_ops.to_create_inferior (exec_file, allargs, env); +} + +/* Clean up after the inferior dies. */ + +static void +linuxthreads_mourn_inferior () +{ + if (linuxthreads_max) + { + int off = 0; + target_write_memory (linuxthreads_debug, (char *)&off, sizeof (off)); + + linuxthreads_inferior_pid = 0; + linuxthreads_breakpoint_pid = 0; + linuxthreads_step_pid = 0; + linuxthreads_step_signo = TARGET_SIGNAL_0; + linuxthreads_manager_pid = 0; + linuxthreads_initial_pid = 0; + linuxthreads_attach_pending = 0; + init_thread_list(); /* Destroy thread info */ + } + + child_ops.to_mourn_inferior (); + + unpush_target (&linuxthreads_ops); +} + +/* Kill the inferior process */ + +static void +linuxthreads_kill () +{ + int rpid; + int status; + + if (inferior_pid == 0) + return; + + if (linuxthreads_max && linuxthreads_manager_pid != 0) + { + /* Remove all threads status. */ + inferior_pid = linuxthreads_manager_pid; + iterate_active_threads (kill_thread, 1); + } + + kill_thread (inferior_pid); + +#if 0 + /* doing_quit_force solves a real problem, but I think a properly + placed call to catch_errors would do the trick much more cleanly. */ + if (doing_quit_force >= 0) + { + if (linuxthreads_max && linuxthreads_manager_pid != 0) + { + /* Wait for thread to complete */ + while ((rpid = waitpid (-1, &status, __WCLONE)) > 0) + if (!WIFEXITED(status)) + kill_thread (rpid); + + while ((rpid = waitpid (-1, &status, 0)) > 0) + if (!WIFEXITED(status)) + kill_thread (rpid); + } + else + while ((rpid = waitpid (inferior_pid, &status, 0)) > 0) + if (!WIFEXITED(status)) + ptrace (PT_KILL, inferior_pid, (PTRACE_ARG3_TYPE) 0, 0); + } +#endif + + /* Wait for all threads. */ + do + rpid = waitpid (-1, &status, __WCLONE | WNOHANG); + while (rpid > 0 || errno == EINTR); + + do + rpid = waitpid (-1, &status, WNOHANG); + while (rpid > 0 || errno == EINTR); + + linuxthreads_mourn_inferior (); +} + +/* Insert a breakpoint */ + +static int +linuxthreads_insert_breakpoint (addr, contents_cache) + CORE_ADDR addr; + char *contents_cache; +{ + if (linuxthreads_max && linuxthreads_manager_pid != 0) + { + linuxthreads_breakpoint_addr = addr; + iterate_active_threads (insert_breakpoint, 1); + insert_breakpoint (linuxthreads_manager_pid); + } + + return child_ops.to_insert_breakpoint (addr, contents_cache); +} + +/* Remove a breakpoint */ + +static int +linuxthreads_remove_breakpoint (addr, contents_cache) + CORE_ADDR addr; + char *contents_cache; +{ + if (linuxthreads_max && linuxthreads_manager_pid != 0) + { + linuxthreads_breakpoint_addr = addr; + iterate_active_threads (remove_breakpoint, 1); + remove_breakpoint (linuxthreads_manager_pid); + } + + return child_ops.to_remove_breakpoint (addr, contents_cache); +} + +/* Mark our target-struct as eligible for stray "run" and "attach" commands. */ + +static int +linuxthreads_can_run () +{ + return child_suppress_run; +} + +static void +init_linuxthreads_ops () +{ + linuxthreads_ops.to_shortname = "linuxthreads"; + linuxthreads_ops.to_longname = "LINUX threads and pthread."; + linuxthreads_ops.to_doc = "LINUX threads and pthread support."; + linuxthreads_ops.to_attach = linuxthreads_attach; + linuxthreads_ops.to_detach = linuxthreads_detach; + linuxthreads_ops.to_resume = linuxthreads_resume; + linuxthreads_ops.to_wait = linuxthreads_wait; + linuxthreads_ops.to_kill = linuxthreads_kill; + linuxthreads_ops.to_can_run = linuxthreads_can_run; + linuxthreads_ops.to_stratum = thread_stratum; + linuxthreads_ops.to_insert_breakpoint = linuxthreads_insert_breakpoint; + linuxthreads_ops.to_remove_breakpoint = linuxthreads_remove_breakpoint; + linuxthreads_ops.to_create_inferior = linuxthreads_create_inferior; + linuxthreads_ops.to_mourn_inferior = linuxthreads_mourn_inferior; + linuxthreads_ops.to_thread_alive = linuxthreads_thread_alive; + linuxthreads_ops.to_magic = OPS_MAGIC; +} + +void +_initialize_linuxthreads () +{ + struct sigaction sact; + + init_linuxthreads_ops (); + add_target (&linuxthreads_ops); + child_suppress_run = 1; + + /* Attach SIGCHLD handler */ + sact.sa_handler = sigchld_handler; + sigemptyset (&sact.sa_mask); + sact.sa_flags = 0; + sigaction (SIGCHLD, &sact, NULL); + + /* initialize SIGCHLD mask */ + sigemptyset (&linuxthreads_wait_mask); + sigaddset (&linuxthreads_wait_mask, SIGCHLD); +} diff --git a/gdb/version.h b/gdb/version.h new file mode 100644 index 00000000000..015caa80575 --- /dev/null +++ b/gdb/version.h @@ -0,0 +1,33 @@ +/* Version information for GDB. + Copyright (C) 1999, Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#ifndef VERSION_H +#define VERSION_H + +/* Version number of GDB, as a string. */ +extern const char version[]; + +/* Canonical host name as a string. */ +extern const char host_name[]; + +/* Canonical target name as a string. */ +extern const char target_name[]; + +#endif /* #ifndef VERSION_H */ diff --git a/sim/common/cgen-par.c b/sim/common/cgen-par.c new file mode 100644 index 00000000000..8b983fbe4c0 --- /dev/null +++ b/sim/common/cgen-par.c @@ -0,0 +1,194 @@ +/* Simulator parallel routines for CGEN simulators (and maybe others). + Copyright (C) 1999 Free Software Foundation, Inc. + Contributed by Cygnus Solutions. + +This file is part of the GNU instruction set simulator. + +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, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "sim-main.h" +#include "cgen-mem.h" +#include "cgen-par.h" + +/* Functions required by the cgen interface. These functions add various + kinds of writes to the write queue. */ +void sim_queue_qi_write (SIM_CPU *cpu, UQI *target, UQI value) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_QI_WRITE; + element->kinds.qi_write.target = target; + element->kinds.qi_write.value = value; +} + +void sim_queue_si_write (SIM_CPU *cpu, SI *target, SI value) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_SI_WRITE; + element->kinds.si_write.target = target; + element->kinds.si_write.value = value; +} + +void sim_queue_sf_write (SIM_CPU *cpu, SI *target, SF value) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_SF_WRITE; + element->kinds.sf_write.target = target; + element->kinds.sf_write.value = value; +} + +void sim_queue_pc_write (SIM_CPU *cpu, USI value) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_PC_WRITE; + element->kinds.pc_write.value = value; +} + +void sim_queue_fn_si_write ( + SIM_CPU *cpu, + void (*write_function)(SIM_CPU *cpu, UINT, USI), + UINT regno, + SI value +) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_FN_SI_WRITE; + element->kinds.fn_si_write.function = write_function; + element->kinds.fn_si_write.regno = regno; + element->kinds.fn_si_write.value = value; +} + +void sim_queue_fn_di_write ( + SIM_CPU *cpu, + void (*write_function)(SIM_CPU *cpu, UINT, DI), + UINT regno, + DI value +) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_FN_DI_WRITE; + element->kinds.fn_di_write.function = write_function; + element->kinds.fn_di_write.regno = regno; + element->kinds.fn_di_write.value = value; +} + +void sim_queue_fn_df_write ( + SIM_CPU *cpu, + void (*write_function)(SIM_CPU *cpu, UINT, DI), + UINT regno, + DF value +) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_FN_DF_WRITE; + element->kinds.fn_df_write.function = write_function; + element->kinds.fn_df_write.regno = regno; + element->kinds.fn_df_write.value = value; +} + +void sim_queue_mem_qi_write (SIM_CPU *cpu, SI address, QI value) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_MEM_QI_WRITE; + element->kinds.mem_qi_write.address = address; + element->kinds.mem_qi_write.value = value; +} + +void sim_queue_mem_hi_write (SIM_CPU *cpu, SI address, HI value) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_MEM_HI_WRITE; + element->kinds.mem_hi_write.address = address; + element->kinds.mem_hi_write.value = value; +} + +void sim_queue_mem_si_write (SIM_CPU *cpu, SI address, SI value) +{ + CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (cpu); + CGEN_WRITE_QUEUE_ELEMENT *element = CGEN_WRITE_QUEUE_NEXT (q); + element->kind = CGEN_MEM_SI_WRITE; + element->kinds.mem_si_write.address = address; + element->kinds.mem_si_write.value = value; +} + +/* Execute a write stored on the write queue. */ +void +cgen_write_queue_element_execute (SIM_CPU *cpu, CGEN_WRITE_QUEUE_ELEMENT *item) +{ + IADDR pc; + switch (CGEN_WRITE_QUEUE_ELEMENT_KIND (item)) + { + case CGEN_QI_WRITE: + *item->kinds.qi_write.target = item->kinds.qi_write.value; + break; + case CGEN_SI_WRITE: + *item->kinds.si_write.target = item->kinds.si_write.value; + break; + case CGEN_SF_WRITE: + *item->kinds.sf_write.target = item->kinds.sf_write.value; + break; + case CGEN_PC_WRITE: + CPU_PC_SET (cpu, item->kinds.pc_write.value); + break; + case CGEN_FN_SI_WRITE: + item->kinds.fn_si_write.function (cpu, + item->kinds.fn_si_write.regno, + item->kinds.fn_si_write.value); + break; + case CGEN_FN_DI_WRITE: + item->kinds.fn_di_write.function (cpu, + item->kinds.fn_di_write.regno, + item->kinds.fn_di_write.value); + break; + case CGEN_FN_DF_WRITE: + item->kinds.fn_df_write.function (cpu, + item->kinds.fn_df_write.regno, + item->kinds.fn_df_write.value); + break; + case CGEN_MEM_QI_WRITE: + pc = CPU_PC_GET (cpu); + SETMEMQI (cpu, pc, item->kinds.mem_qi_write.address, + item->kinds.mem_qi_write.value); + break; + case CGEN_MEM_HI_WRITE: + pc = CPU_PC_GET (cpu); + SETMEMHI (cpu, pc, item->kinds.mem_hi_write.address, + item->kinds.mem_hi_write.value); + break; + case CGEN_MEM_SI_WRITE: + pc = CPU_PC_GET (cpu); + SETMEMSI (cpu, pc, item->kinds.mem_si_write.address, + item->kinds.mem_si_write.value); + break; + default: + break; /* FIXME: for now....print message later. */ + } +} + +/* Utilities for the write queue. */ +CGEN_WRITE_QUEUE_ELEMENT * +cgen_write_queue_overflow (CGEN_WRITE_QUEUE *q) +{ + abort (); /* FIXME: for now....print message later. */ + return 0; +} diff --git a/sim/common/cgen-par.h b/sim/common/cgen-par.h new file mode 100644 index 00000000000..ce4efd59307 --- /dev/null +++ b/sim/common/cgen-par.h @@ -0,0 +1,124 @@ +/* Simulator header for cgen parallel support. + Copyright (C) 1999 Free Software Foundation, Inc. + Contributed by Cygnus Solutions. + +This file is part of the GNU instruction set simulator. + +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, 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. */ + +#ifndef CGEN_PAR_H +#define CGEN_PAR_H + +/* Kinds of writes stored on the write queue. */ +enum cgen_write_queue_kind { + CGEN_QI_WRITE, CGEN_SI_WRITE, CGEN_SF_WRITE, + CGEN_PC_WRITE, + CGEN_FN_SI_WRITE, CGEN_FN_DI_WRITE, CGEN_FN_DF_WRITE, + CGEN_MEM_QI_WRITE, CGEN_MEM_HI_WRITE, CGEN_MEM_SI_WRITE, + CGEN_NUM_WRITE_KINDS +}; + +/* Element of the write queue. */ +typedef struct { + enum cgen_write_queue_kind kind; /* Used to select union member below. */ + union { + struct { + UQI *target; + QI value; + } qi_write; + struct { + SI *target; + SI value; + } si_write; + struct { + SI *target; + SF value; + } sf_write; + struct { + USI value; + } pc_write; + struct { + UINT regno; + SI value; + void (*function)(SIM_CPU *, UINT, USI); + } fn_si_write; + struct { + UINT regno; + DI value; + void (*function)(SIM_CPU *, UINT, DI); + } fn_di_write; + struct { + UINT regno; + DI value; + void (*function)(SIM_CPU *, UINT, DI); + } fn_df_write; + struct { + SI address; + QI value; + } mem_qi_write; + struct { + SI address; + HI value; + } mem_hi_write; + struct { + SI address; + SI value; + } mem_si_write; + } kinds; +} CGEN_WRITE_QUEUE_ELEMENT; + +#define CGEN_WRITE_QUEUE_ELEMENT_KIND(element) ((element)->kind) + +extern void cgen_write_queue_element_execute ( + SIM_CPU *, CGEN_WRITE_QUEUE_ELEMENT * +); + +/* Instance of the queue for parallel write-after support. */ +/* FIXME: Should be dynamic? */ +#define CGEN_WRITE_QUEUE_SIZE (4 * 4) /* 4 writes x 4 insns -- for now. */ + +typedef struct { + int index; + CGEN_WRITE_QUEUE_ELEMENT q[CGEN_WRITE_QUEUE_SIZE]; +} CGEN_WRITE_QUEUE; + +#define CGEN_WRITE_QUEUE_CLEAR(queue) ((queue)->index = 0) +#define CGEN_WRITE_QUEUE_INDEX(queue) ((queue)->index) +#define CGEN_WRITE_QUEUE_ELEMENT(queue, ix) (&(queue)->q[(ix)]) + +#define CGEN_WRITE_QUEUE_NEXT(queue) ( \ + (queue)->index < CGEN_WRITE_QUEUE_SIZE \ + ? &(queue)->q[(queue)->index++] \ + : cgen_write_queue_overflow (queue) \ +) + +extern CGEN_WRITE_QUEUE_ELEMENT *cgen_write_queue_overflow (CGEN_WRITE_QUEUE *); + +/* Functions for queuing writes. Used by semantic code. */ +extern void sim_queue_qi_write (SIM_CPU *, UQI *, UQI); +extern void sim_queue_si_write (SIM_CPU *, SI *, SI); +extern void sim_queue_sf_write (SIM_CPU *, SI *, SF); + +extern void sim_queue_pc_write (SIM_CPU *, USI); + +extern void sim_queue_fn_si_write (SIM_CPU *, void (*)(SIM_CPU *, UINT, USI), UINT, SI); +extern void sim_queue_fn_di_write (SIM_CPU *, void (*)(SIM_CPU *, UINT, DI), UINT, DI); +extern void sim_queue_fn_df_write (SIM_CPU *, void (*)(SIM_CPU *, UINT, DI), UINT, DF); + +extern void sim_queue_mem_qi_write (SIM_CPU *, SI, QI); +extern void sim_queue_mem_hi_write (SIM_CPU *, SI, HI); +extern void sim_queue_mem_si_write (SIM_CPU *, SI, SI); + +#endif /* CGEN_PAR_H */ |