aboutsummaryrefslogtreecommitdiff
path: root/gdb/infttrace.c
diff options
context:
space:
mode:
authorStan Shebs <shebs@codesourcery.com>1999-04-16 01:35:26 +0000
committerStan Shebs <shebs@codesourcery.com>1999-04-16 01:35:26 +0000
commitc906108c21474dfb4ed285bcc0ac6fe02cd400cc (patch)
treea0015aa5cedc19ccbab307251353a41722a3ae13 /gdb/infttrace.c
parentcd946cff9ede3f30935803403f06f6ed30cad136 (diff)
downloadgdb-c906108c21474dfb4ed285bcc0ac6fe02cd400cc.zip
gdb-c906108c21474dfb4ed285bcc0ac6fe02cd400cc.tar.gz
gdb-c906108c21474dfb4ed285bcc0ac6fe02cd400cc.tar.bz2
Initial creation of sourceware repositorygdb-4_18-branchpoint
Diffstat (limited to 'gdb/infttrace.c')
-rw-r--r--gdb/infttrace.c5674
1 files changed, 5674 insertions, 0 deletions
diff --git a/gdb/infttrace.c b/gdb/infttrace.c
new file mode 100644
index 0000000..61723df
--- /dev/null
+++ b/gdb/infttrace.c
@@ -0,0 +1,5674 @@
+/* Low level Unix child interface to ttrace, for GDB when running under HP-UX.
+ Copyright 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996
+ Free Software Foundation, Inc.
+
+This file is part of GDB.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "defs.h"
+#include "frame.h"
+#include "inferior.h"
+#include "target.h"
+#include "gdb_string.h"
+#include "wait.h"
+#include "command.h"
+
+/* Some hackery to work around a use of the #define name NO_FLAGS
+ * in both gdb and HPUX (bfd.h and /usr/include/machine/vmparam.h).
+ */
+#ifdef NO_FLAGS
+#define INFTTRACE_TEMP_HACK NO_FLAGS
+#undef NO_FLAGS
+#endif
+
+#ifdef USG
+#include <sys/types.h>
+#endif
+
+#include <sys/param.h>
+#include <sys/dir.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+
+#include <sys/ttrace.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <sys/mman.h>
+
+#ifndef NO_PTRACE_H
+#ifdef PTRACE_IN_WRONG_PLACE
+#include <ptrace.h>
+#else
+#include <sys/ptrace.h>
+#endif
+#endif /* NO_PTRACE_H */
+
+/* Second half of the hackery above. Non-ANSI C, so
+ * we can't use "#error", alas.
+ */
+#ifdef NO_FLAGS
+#if (NO_FLAGS != INFTTRACE_TEMP_HACK )
+ /* #error "Hackery to remove warning didn't work right" */
+#else
+ /* Ok, new def'n of NO_FLAGS is same as old one; no action needed. */
+#endif
+#else
+ /* #error "Didn't get expected re-definition of NO_FLAGS" */
+#define NO_FLAGS INFTTRACE_TEMP_HACK
+#endif
+
+#if !defined (PT_SETTRC)
+#define PT_SETTRC 0 /* Make process traceable by parent */
+#endif
+#if !defined (PT_READ_I)
+#define PT_READ_I 1 /* Read word from text space */
+#endif
+#if !defined (PT_READ_D)
+#define PT_READ_D 2 /* Read word from data space */
+#endif
+#if !defined (PT_READ_U)
+#define PT_READ_U 3 /* Read word from kernel user struct */
+#endif
+#if !defined (PT_WRITE_I)
+#define PT_WRITE_I 4 /* Write word to text space */
+#endif
+#if !defined (PT_WRITE_D)
+#define PT_WRITE_D 5 /* Write word to data space */
+#endif
+#if !defined (PT_WRITE_U)
+#define PT_WRITE_U 6 /* Write word to kernel user struct */
+#endif
+#if !defined (PT_CONTINUE)
+#define PT_CONTINUE 7 /* Continue after signal */
+#endif
+#if !defined (PT_STEP)
+#define PT_STEP 9 /* Set flag for single stepping */
+#endif
+#if !defined (PT_KILL)
+#define PT_KILL 8 /* Send child a SIGKILL signal */
+#endif
+
+#ifndef PT_ATTACH
+#define PT_ATTACH PTRACE_ATTACH
+#endif
+#ifndef PT_DETACH
+#define PT_DETACH PTRACE_DETACH
+#endif
+
+#include "gdbcore.h"
+#ifndef NO_SYS_FILE
+#include <sys/file.h>
+#endif
+
+/* This semaphore is used to coordinate the child and parent processes
+ after a fork(), and before an exec() by the child. See parent_attach_all
+ for details.
+ */
+typedef struct {
+ int parent_channel[2]; /* Parent "talks" to [1], child "listens" to [0] */
+ int child_channel[2]; /* Child "talks" to [1], parent "listens" to [0] */
+} startup_semaphore_t;
+
+#define SEM_TALK (1)
+#define SEM_LISTEN (0)
+
+static startup_semaphore_t startup_semaphore;
+
+/* See can_touch_threads_of_process for details. */
+static int vforking_child_pid = 0;
+static int vfork_in_flight = 0;
+
+/* To support PREPARE_TO_PROCEED (hppa_prepare_to_proceed).
+ */
+static pid_t old_gdb_pid = 0;
+static pid_t reported_pid = 0;
+static int reported_bpt = 0;
+
+/* 1 if ok as results of a ttrace or ttrace_wait call, 0 otherwise.
+ */
+#define TT_OK( _status, _errno ) \
+ (((_status) == 1) && ((_errno) == 0))
+
+#define TTRACE_ARG_TYPE uint64_t
+
+/* When supplied as the "addr" operand, ttrace interprets this
+ to mean, "from the current address".
+ */
+#define TT_USE_CURRENT_PC ((TTRACE_ARG_TYPE) TT_NOPC)
+
+/* When supplied as the "addr", "data" or "addr2" operand for most
+ requests, ttrace interprets this to mean, "pay no heed to this
+ argument".
+ */
+#define TT_NIL ((TTRACE_ARG_TYPE) TT_NULLARG)
+
+/* This is capable of holding the value of a 32-bit register. The
+ value is always left-aligned in the buffer; i.e., [0] contains
+ the most-significant byte of the register's value, and [sizeof(reg)]
+ contains the least-significant value.
+
+ ??rehrauer: Yes, this assumes that an int is 32-bits on HP-UX, and
+ that registers are 32-bits on HP-UX. The latter assumption changes
+ with PA2.0.
+ */
+typedef int register_value_t;
+
+/********************************************************************
+
+ How this works:
+
+ 1. Thread numbers
+
+ The rest of GDB sees threads as being things with different
+ "pid" (process id) values. See "thread.c" for details. The
+ separate threads will be seen and reacted to if infttrace passes
+ back different pid values (for _events_). See wait_for_inferior
+ in inftarg.c.
+
+ So infttrace is going to use thread ids externally, pretending
+ they are process ids, and keep track internally so that it can
+ use the real process id (and thread id) when calling ttrace.
+
+ The data structure that supports this is a linked list of the
+ current threads. Since at some date infttrace will have to
+ deal with multiple processes, each list element records its
+ corresponding pid, rather than having a single global.
+
+ Note that the list is only approximately current; that's ok, as
+ it's up to date when we need it (we hope!). Also, it can contain
+ dead threads, as there's no harm if it does.
+
+ The approach taken here is to bury the translation from external
+ to internal inside "call_ttrace" and a few other places.
+
+ There are some wrinkles:
+
+ o When GDB forks itself to create the debug target process,
+ there's only a pid of 0 around in the child, so the
+ TT_PROC_SETTRC operation uses a more direct call to ttrace;
+ Similiarly, the initial setting of the event mask happens
+ early as well, and so is also special-cased, and an attach
+ uses a real pid;
+
+ o We define an unthreaded application as having a "pseudo"
+ thread;
+
+ o To keep from confusing the rest of GDB, we don't switch
+ the PID for the pseudo thread to a TID. A table will help:
+
+ Rest of GDB sees these PIDs: pid tid1 tid2 tid3 ...
+
+ Our thread list stores: pid pid pid pid ...
+ tid0 tid1 tid2 tid3
+
+ Ttrace sees these TIDS: tid0 tid1 tid2 tid3 ...
+
+ Both pid and tid0 will map to tid0, as there are infttrace.c-internal
+ calls to ttrace using tid0.
+
+ 2. Step and Continue
+
+ Since we're implementing the "stop the world" model, sub-model
+ "other threads run during step", we have some stuff to do:
+
+ o User steps require continuing all threads other than the
+ one the user is stepping;
+
+ o Internal debugger steps (such as over a breakpoint or watchpoint,
+ but not out of a library load thunk) require stepping only
+ the selected thread; this means that we have to report the
+ step finish on that thread, which can lead to complications;
+
+ o When a thread is created, it is created running, rather
+ than stopped--so we have to stop it.
+
+ The OS doesn't guarantee the stopped thread list will be stable,
+ no does it guarantee where on the stopped thread list a thread
+ that is single-stepped will wind up: it's possible that it will
+ be off the list for a while, it's possible the step will complete
+ and it will be re-posted to the end...
+
+ This means we have to scan the stopped thread list, build up
+ a work-list, and then run down the work list; we can't do the
+ step/continue during the scan.
+
+ 3. Buffering events
+
+ Then there's the issue of waiting for an event. We do this by
+ noticing how many events are reported at the end of each wait.
+ From then on, we "fake" all resumes and steps, returning instantly,
+ and don't do another wait. Once all pending events are reported,
+ we can really resume again.
+
+ To keep this hidden, all the routines which know about tids and
+ pids or real events and simulated ones are static (file-local).
+
+ This code can make lots of calls to ttrace, in particular it
+ can spin down the list of thread states more than once. If this
+ becomes a performance hit, the spin could be done once and the
+ various "tsp" blocks saved, keeping all later spins in this
+ process.
+
+ The O/S doesn't promise to keep the list straight, and so we must
+ re-scan a lot. By observation, it looks like a single-step/wait
+ puts the stepped thread at the end of the list but doesn't change
+ it otherwise.
+
+****************************************************************
+*/
+
+/* Uncomment these to turn on various debugging output */
+/* #define THREAD_DEBUG */
+/* #define WAIT_BUFFER_DEBUG */
+/* #define PARANOIA */
+
+
+#define INFTTRACE_ALL_THREADS (-1)
+#define INFTTRACE_STEP (1)
+#define INFTTRACE_CONTINUE (0)
+
+/* FIX: this is used in inftarg.c/child_wait, in a hack.
+ */
+extern int not_same_real_pid;
+
+/* This is used to count buffered events.
+ */
+static unsigned int more_events_left = 0;
+
+/* Process state.
+ */
+typedef enum process_state_enum {
+ STOPPED,
+ FAKE_STEPPING,
+ FAKE_CONTINUE, /* For later use */
+ RUNNING,
+ FORKING,
+ VFORKING
+} process_state_t;
+
+static process_state_t process_state = STOPPED;
+
+/* User-specified stepping modality.
+ */
+typedef enum stepping_mode_enum {
+ DO_DEFAULT, /* ...which is a continue! */
+ DO_STEP,
+ DO_CONTINUE
+} stepping_mode_t;
+
+/* Action to take on an attach, depends on
+ * what kind (user command, fork, vfork).
+ *
+ * At the moment, this is either:
+ *
+ * o continue with a SIGTRAP signal, or
+ *
+ * o leave stopped.
+ */
+typedef enum attach_continue_enum {
+ DO_ATTACH_CONTINUE,
+ DONT_ATTACH_CONTINUE
+} attach_continue_t;
+
+/* This flag is true if we are doing a step-over-bpt
+ * with buffered events. We will have to be sure to
+ * report the right thread, as otherwise the spaghetti
+ * code in "infrun.c/wait_for_inferior" will get
+ * confused.
+ */
+static int doing_fake_step = 0;
+static lwpid_t fake_step_tid = 0;
+
+
+/****************************************************
+ * Thread information structure routines and types. *
+ ****************************************************
+ */
+typedef
+struct thread_info_struct
+{
+ int am_pseudo; /* This is a pseudo-thread for the process. */
+ int pid; /* Process ID */
+ lwpid_t tid; /* Thread ID */
+ int handled; /* 1 if a buffered event was handled. */
+ int seen; /* 1 if this thread was seen on a traverse. */
+ int terminated; /* 1 if thread has terminated. */
+ int have_signal; /* 1 if signal to be sent */
+ enum target_signal signal_value; /* Signal to send */
+ int have_start; /* 1 if alternate starting address */
+ stepping_mode_t stepping_mode; /* Whether to step or continue */
+ CORE_ADDR start; /* Where to start */
+ int have_state; /* 1 if the event state has been set */
+ ttstate_t last_stop_state;/* The most recently-waited event for this thread. */
+ struct thread_info_struct
+ *next; /* All threads are linked via this field. */
+ struct thread_info_struct
+ *next_pseudo; /* All pseudo-threads are linked via this field. */
+} thread_info;
+
+typedef
+struct thread_info_header_struct
+{
+ int count;
+ thread_info *head;
+ thread_info *head_pseudo;
+
+} thread_info_header;
+
+static thread_info_header thread_head = { 0, NULL, NULL };
+static thread_info_header deleted_threads = { 0, NULL, NULL };
+
+static saved_real_pid = 0;
+
+
+/*************************************************
+ * Debugging support functions *
+ *************************************************
+ */
+CORE_ADDR
+get_raw_pc( ttid )
+ lwpid_t ttid;
+{
+ unsigned long pc_val;
+ int offset;
+ int res;
+
+ offset = register_addr( PC_REGNUM, U_REGS_OFFSET );
+ res = read_from_register_save_state(
+ ttid,
+ (TTRACE_ARG_TYPE) offset,
+ (char *) &pc_val,
+ sizeof( pc_val ));
+ if( res <= 0 ) {
+ return (CORE_ADDR) pc_val;
+ }
+ else {
+ return (CORE_ADDR) 0;
+ }
+}
+
+static char *
+get_printable_name_of_stepping_mode( mode )
+ stepping_mode_t mode;
+{
+ switch( mode ) {
+ case DO_DEFAULT: return "DO_DEFAULT";
+ case DO_STEP: return "DO_STEP";
+ case DO_CONTINUE: return "DO_CONTINUE";
+ default: return "?unknown mode?";
+ }
+}
+
+/* This function returns a pointer to a string describing the
+ * ttrace event being reported.
+ */
+char *
+get_printable_name_of_ttrace_event (event)
+ ttevents_t event;
+{
+ /* This enumeration is "gappy", so don't use a table. */
+ switch (event) {
+
+ case TTEVT_NONE:
+ return "TTEVT_NONE";
+ case TTEVT_SIGNAL:
+ return "TTEVT_SIGNAL";
+ case TTEVT_FORK:
+ return "TTEVT_FORK";
+ case TTEVT_EXEC:
+ return "TTEVT_EXEC";
+ case TTEVT_EXIT:
+ return "TTEVT_EXIT";
+ case TTEVT_VFORK:
+ return "TTEVT_VFORK";
+ case TTEVT_SYSCALL_RETURN:
+ return "TTEVT_SYSCALL_RETURN";
+ case TTEVT_LWP_CREATE:
+ return "TTEVT_LWP_CREATE";
+ case TTEVT_LWP_TERMINATE:
+ return "TTEVT_LWP_TERMINATE";
+ case TTEVT_LWP_EXIT:
+ return "TTEVT_LWP_EXIT";
+ case TTEVT_LWP_ABORT_SYSCALL:
+ return "TTEVT_LWP_ABORT_SYSCALL";
+ case TTEVT_SYSCALL_ENTRY:
+ return "TTEVT_SYSCALL_ENTRY";
+ case TTEVT_SYSCALL_RESTART:
+ return "TTEVT_SYSCALL_RESTART";
+ default :
+ return "?new event?";
+ }
+}
+
+
+/* This function translates the ttrace request enumeration into
+ * a character string that is its printable (aka "human readable")
+ * name.
+ */
+char *
+get_printable_name_of_ttrace_request (request)
+ ttreq_t request;
+{
+ if (!IS_TTRACE_REQ (request))
+ return "?bad req?";
+
+ /* This enumeration is "gappy", so don't use a table. */
+ switch (request) {
+ case TT_PROC_SETTRC :
+ return "TT_PROC_SETTRC";
+ case TT_PROC_ATTACH :
+ return "TT_PROC_ATTACH";
+ case TT_PROC_DETACH :
+ return "TT_PROC_DETACH";
+ case TT_PROC_RDTEXT :
+ return "TT_PROC_RDTEXT";
+ case TT_PROC_WRTEXT :
+ return "TT_PROC_WRTEXT";
+ case TT_PROC_RDDATA :
+ return "TT_PROC_RDDATA";
+ case TT_PROC_WRDATA :
+ return "TT_PROC_WRDATA";
+ case TT_PROC_STOP :
+ return "TT_PROC_STOP";
+ case TT_PROC_CONTINUE :
+ return "TT_PROC_CONTINUE";
+ case TT_PROC_GET_PATHNAME :
+ return "TT_PROC_GET_PATHNAME";
+ case TT_PROC_GET_EVENT_MASK :
+ return "TT_PROC_GET_EVENT_MASK";
+ case TT_PROC_SET_EVENT_MASK :
+ return "TT_PROC_SET_EVENT_MASK";
+ case TT_PROC_GET_FIRST_LWP_STATE :
+ return "TT_PROC_GET_FIRST_LWP_STATE";
+ case TT_PROC_GET_NEXT_LWP_STATE :
+ return "TT_PROC_GET_NEXT_LWP_STATE";
+ case TT_PROC_EXIT :
+ return "TT_PROC_EXIT";
+ case TT_PROC_GET_MPROTECT :
+ return "TT_PROC_GET_MPROTECT";
+ case TT_PROC_SET_MPROTECT :
+ return "TT_PROC_SET_MPROTECT";
+ case TT_PROC_SET_SCBM :
+ return "TT_PROC_SET_SCBM";
+ case TT_LWP_STOP :
+ return "TT_LWP_STOP";
+ case TT_LWP_CONTINUE :
+ return "TT_LWP_CONTINUE";
+ case TT_LWP_SINGLE :
+ return "TT_LWP_SINGLE";
+ case TT_LWP_RUREGS :
+ return "TT_LWP_RUREGS";
+ case TT_LWP_WUREGS :
+ return "TT_LWP_WUREGS";
+ case TT_LWP_GET_EVENT_MASK :
+ return "TT_LWP_GET_EVENT_MASK";
+ case TT_LWP_SET_EVENT_MASK :
+ return "TT_LWP_SET_EVENT_MASK";
+ case TT_LWP_GET_STATE :
+ return "TT_LWP_GET_STATE";
+ default :
+ return "?new req?";
+ }
+}
+
+
+/* This function translates the process state enumeration into
+ * a character string that is its printable (aka "human readable")
+ * name.
+ */
+static char *
+get_printable_name_of_process_state (process_state)
+ process_state_t process_state;
+{
+ switch (process_state) {
+ case STOPPED:
+ return "STOPPED";
+ case FAKE_STEPPING:
+ return "FAKE_STEPPING";
+ case RUNNING:
+ return "RUNNING";
+ case FORKING:
+ return "FORKING";
+ case VFORKING:
+ return "VFORKING";
+ default:
+ return "?some unknown state?";
+ }
+}
+
+/* Set a ttrace thread state to a safe, initial state.
+ */
+static void
+clear_ttstate_t (tts)
+ ttstate_t * tts;
+{
+ tts->tts_pid = 0;
+ tts->tts_lwpid = 0;
+ tts->tts_user_tid = 0;
+ tts->tts_event = TTEVT_NONE;
+}
+
+/* Copy ttrace thread state TTS_FROM into TTS_TO.
+ */
+static void
+copy_ttstate_t (tts_to, tts_from)
+ ttstate_t * tts_to;
+ ttstate_t * tts_from;
+{
+ memcpy ((char *) tts_to, (char *) tts_from, sizeof (*tts_to));
+}
+
+/* Are there any live threads we know about?
+ */
+static int
+any_thread_records()
+{
+ return( thread_head.count > 0 );
+}
+
+/* Create, fill in and link in a thread descriptor.
+ */
+static thread_info *
+create_thread_info (pid, tid)
+ int pid;
+ lwpid_t tid;
+{
+ thread_info * new_p;
+ thread_info * p;
+ int thread_count_of_pid;
+
+ new_p = malloc( sizeof( thread_info ));
+ new_p->pid = pid;
+ new_p->tid = tid;
+ new_p->have_signal = 0;
+ new_p->have_start = 0;
+ new_p->have_state = 0;
+ clear_ttstate_t( &new_p->last_stop_state );
+ new_p->am_pseudo = 0;
+ new_p->handled = 0;
+ new_p->seen = 0;
+ new_p->terminated = 0;
+ new_p->next = NULL;
+ new_p->next_pseudo = NULL;
+ new_p->stepping_mode = DO_DEFAULT;
+
+ if( 0 == thread_head.count ) {
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "First thread, pid %d tid %d!\n", pid, tid );
+#endif
+ saved_real_pid = inferior_pid;
+ }
+ else {
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Subsequent thread, pid %d tid %d\n", pid, tid );
+#endif
+ }
+
+ /* Another day, another thread...
+ */
+ thread_head.count++;
+
+ /* The new thread always goes at the head of the list.
+ */
+ new_p->next = thread_head.head;
+ thread_head.head = new_p;
+
+ /* Is this the "pseudo" thread of a process? It is if there's
+ * no other thread for this process on the list. (Note that this
+ * accomodates multiple processes, such as we see even for simple
+ * cases like forking "non-threaded" programs.)
+ */
+ p = thread_head.head;
+ thread_count_of_pid = 0;
+ while (p)
+ {
+ if (p->pid == new_p->pid)
+ thread_count_of_pid++;
+ p = p->next;
+ }
+
+ /* Did we see any other threads for this pid? (Recall that we just
+ * added this thread to the list...)
+ */
+ if (thread_count_of_pid == 1)
+ {
+ new_p->am_pseudo = 1;
+ new_p->next_pseudo = thread_head.head_pseudo;
+ thread_head.head_pseudo = new_p;
+ }
+
+ return new_p;
+}
+
+/* Get rid of our thread info.
+ */
+static void
+clear_thread_info ()
+{
+ thread_info *p;
+ thread_info *q;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Clearing all thread info\n" );
+#endif
+
+ p = thread_head.head;
+ while( p ) {
+ q = p;
+ p = p->next;
+ free( q );
+ }
+
+ thread_head.head = NULL;
+ thread_head.head_pseudo = NULL;
+ thread_head.count = 0;
+
+ p = deleted_threads.head;
+ while( p ) {
+ q = p;
+ p = p->next;
+ free( q );
+ }
+
+ deleted_threads.head = NULL;
+ deleted_threads.head_pseudo = NULL;
+ deleted_threads.count = 0;
+
+ /* No threads, so can't have pending events.
+ */
+ more_events_left = 0;
+}
+
+/* Given a tid, find the thread block for it.
+ */
+static thread_info *
+find_thread_info (tid)
+ lwpid_t tid;
+{
+ thread_info *p;
+
+ for( p = thread_head.head; p; p = p->next ) {
+ if( p->tid == tid ) {
+ return p;
+ }
+ }
+
+ for( p = deleted_threads.head; p; p = p->next ) {
+ if( p->tid == tid ) {
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+/* For any but the pseudo thread, this maps to the
+ * thread ID. For the pseudo thread, if you pass either
+ * the thread id or the PID, you get the pseudo thread ID.
+ *
+ * We have to be prepared for core gdb to ask about
+ * deleted threads. We do the map, but we don't like it.
+ */
+static lwpid_t
+map_from_gdb_tid( gdb_tid )
+ lwpid_t gdb_tid;
+{
+ thread_info *p;
+
+ /* First assume gdb_tid really is a tid, and try to find a
+ * matching entry on the threads list.
+ */
+ for( p = thread_head.head; p; p = p->next ) {
+ if( p->tid == gdb_tid )
+ return gdb_tid;
+ }
+
+ /* It doesn't appear to be a tid; perhaps it's really a pid?
+ * Try to find a "pseudo" thread entry on the threads list.
+ */
+ for (p = thread_head.head_pseudo; p != NULL; p = p->next_pseudo)
+ {
+ if (p->pid == gdb_tid)
+ return p->tid;
+ }
+
+ /* Perhaps it's the tid of a deleted thread we may still
+ * have some knowledge of?
+ */
+ for( p = deleted_threads.head; p; p = p-> next ) {
+ if( p->tid == gdb_tid )
+ return gdb_tid;
+ }
+
+ /* Or perhaps it's the pid of a deleted process we may still
+ * have knowledge of?
+ */
+ for (p = deleted_threads.head_pseudo; p != NULL; p = p->next_pseudo)
+ {
+ if (p->pid == gdb_tid)
+ return p->tid;
+ }
+
+ return 0; /* Error? */
+}
+
+/* Map the other way: from a real tid to the
+ * "pid" known by core gdb. This tid may be
+ * for a thread that just got deleted, so we
+ * also need to consider deleted threads.
+ */
+static lwpid_t
+map_to_gdb_tid( real_tid )
+ lwpid_t real_tid;
+{
+ thread_info *p;
+
+ for( p = thread_head.head; p; p = p->next ) {
+ if( p->tid == real_tid ) {
+ if( p->am_pseudo )
+ return p->pid;
+ else
+ return real_tid;
+ }
+ }
+
+ for( p = deleted_threads.head; p; p = p-> next ) {
+ if( p->tid == real_tid )
+ if( p->am_pseudo )
+ return p->pid; /* Error? */
+ else
+ return real_tid;
+ }
+
+ return 0; /* Error? Never heard of this thread! */
+}
+
+/* Do any threads have saved signals?
+ */
+static int
+saved_signals_exist ()
+{
+ thread_info *p;
+
+ for( p = thread_head.head; p; p = p->next ) {
+ if( p->have_signal ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Is this the tid for the zero-th thread?
+ */
+static int
+is_pseudo_thread (tid)
+ lwpid_t tid;
+{
+ thread_info *p = find_thread_info( tid );
+ if( NULL == p || p->terminated )
+ return 0;
+ else
+ return p->am_pseudo;
+}
+
+/* Is this thread terminated?
+ */
+static int
+is_terminated (tid)
+ lwpid_t tid;
+{
+ thread_info *p = find_thread_info( tid );
+
+ if( NULL != p )
+ return p->terminated;
+
+ return 0;
+}
+
+/* Is this pid a real PID or a TID?
+ */
+static int
+is_process_id (pid)
+ int pid;
+{
+ lwpid_t tid;
+ thread_info * tinfo;
+ pid_t this_pid;
+ int this_pid_count;
+
+ /* What does PID really represent?
+ */
+ tid = map_from_gdb_tid (pid);
+ if (tid <= 0)
+ return 0; /* Actually, is probably an error... */
+
+ tinfo = find_thread_info (tid);
+
+ /* Does it appear to be a true thread?
+ */
+ if (! tinfo->am_pseudo)
+ return 0;
+
+ /* Else, it looks like it may be a process. See if there's any other
+ * threads with the same process ID, though. If there are, then TID
+ * just happens to be the first thread of several for this process.
+ */
+ this_pid = tinfo->pid;
+ this_pid_count = 0;
+ for (tinfo = thread_head.head; tinfo; tinfo = tinfo->next)
+ {
+ if (tinfo->pid == this_pid)
+ this_pid_count++;
+ }
+
+ return (this_pid_count == 1);
+}
+
+
+/* Add a thread to our info. Prevent duplicate entries.
+ */
+static thread_info *
+add_tthread (pid, tid)
+ int pid;
+ lwpid_t tid;
+{
+ thread_info *p;
+
+ p = find_thread_info( tid );
+ if( NULL == p )
+ p = create_thread_info( pid, tid );
+
+ return p;
+}
+
+/* Notice that a thread was deleted.
+ */
+static void
+del_tthread (tid)
+ lwpid_t tid;
+{
+ thread_info *p;
+ thread_info *chase;
+
+ if( thread_head.count <= 0 ) {
+ error( "Internal error in thread database." );
+ return;
+ }
+
+ chase = NULL;
+ for( p = thread_head.head; p; p = p->next ) {
+ if( p->tid == tid ) {
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Delete here: %d \n", tid );
+#endif
+
+ if( p->am_pseudo ) {
+ /*
+ * Deleting a main thread is ok if we're doing
+ * a parent-follow on a child; this is odd but
+ * not wrong. It apparently _doesn't_ happen
+ * on the child-follow, as we don't just delete
+ * the pseudo while keeping the rest of the
+ * threads around--instead, we clear out the whole
+ * thread list at once.
+ */
+ thread_info *q;
+ thread_info *q_chase;
+
+ q_chase = NULL;
+ for( q = thread_head.head_pseudo; q; q = q -> next ) {
+ if( q == p ) {
+ /* Remove from pseudo list.
+ */
+ if( q_chase == NULL )
+ thread_head.head_pseudo = p->next_pseudo;
+ else
+ q_chase-> next = p->next_pseudo;
+ }
+ else
+ q_chase = q;
+ }
+ }
+
+ /* Remove from live list.
+ */
+ thread_head.count--;
+
+ if( NULL == chase )
+ thread_head.head = p->next;
+ else
+ chase->next = p->next;
+
+ /* Add to deleted thread list.
+ */
+ p->next = deleted_threads.head;
+ deleted_threads.head = p;
+ deleted_threads.count++;
+ if( p->am_pseudo ) {
+ p->next_pseudo = deleted_threads.head_pseudo;
+ deleted_threads.head_pseudo = p;
+ }
+ p->terminated = 1;
+
+ return;
+ }
+
+ else
+ chase = p;
+ }
+}
+
+/* Get the pid for this tid. (Has to be a real TID!).
+ */
+static int
+get_pid_for (tid)
+ lwpid_t tid;
+{
+ thread_info *p;
+
+ for( p = thread_head.head; p; p = p->next ) {
+ if( p->tid == tid ) {
+ return p->pid;
+ }
+ }
+
+ for( p = deleted_threads.head; p; p = p->next ) {
+ if( p->tid == tid ) {
+ return p->pid;
+ }
+ }
+
+ return 0;
+}
+
+/* Note that this thread's current event has been handled.
+ */
+static void
+set_handled( pid, tid )
+ int pid;
+ lwpid_t tid;
+{
+ thread_info *p;
+
+ p = find_thread_info( tid );
+ if( NULL == p )
+ p = add_tthread( pid, tid );
+
+ p->handled = 1;
+}
+
+/* Was this thread's current event handled?
+ */
+static int
+was_handled( tid )
+ lwpid_t tid;
+{
+ thread_info *p;
+
+ p = find_thread_info( tid );
+ if( NULL != p )
+ return p->handled;
+
+ return 0; /* New threads have not been handled */
+}
+
+/* Set this thread to unhandled.
+ */
+static void
+clear_handled( tid )
+ lwpid_t tid;
+{
+ thread_info * p;
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "clear_handled %d\n", (int) tid );
+#endif
+
+ p = find_thread_info (tid);
+ if (p == NULL)
+ error ("Internal error: No thread state to clear?");
+
+ p->handled = 0;
+}
+
+/* Set all threads to unhandled.
+ */
+static void
+clear_all_handled ()
+{
+ thread_info *p;
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "clear_all_handled\n" );
+#endif
+
+ for( p = thread_head.head; p; p = p->next ) {
+ p->handled = 0;
+ }
+
+ for( p = deleted_threads.head; p; p = p->next ) {
+ p->handled = 0;
+ }
+}
+
+/* Set this thread to default stepping mode.
+ */
+static void
+clear_stepping_mode( tid )
+ lwpid_t tid;
+{
+ thread_info * p;
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "clear_stepping_mode %d\n", (int) tid );
+#endif
+
+ p = find_thread_info (tid);
+ if (p == NULL)
+ error ("Internal error: No thread state to clear?");
+
+ p->stepping_mode = DO_DEFAULT;
+}
+
+/* Set all threads to do default continue on resume.
+ */
+static void
+clear_all_stepping_mode ()
+{
+ thread_info *p;
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "clear_all_stepping_mode\n" );
+#endif
+
+ for( p = thread_head.head; p; p = p->next ) {
+ p->stepping_mode = DO_DEFAULT;
+ }
+
+ for( p = deleted_threads.head; p; p = p->next ) {
+ p->stepping_mode = DO_DEFAULT;
+ }
+}
+
+/* Set all threads to unseen on this pass.
+ */
+static void
+set_all_unseen ()
+{
+ thread_info *p;
+
+ for( p = thread_head.head; p; p = p->next ) {
+ p->seen = 0;
+ }
+}
+
+#if (defined( THREAD_DEBUG ) || defined( PARANOIA ))
+/* debugging routine.
+ */
+static void
+print_tthread (p)
+ thread_info * p;
+{
+ printf( " Thread pid %d, tid %d", p->pid, p->tid );
+ if( p->have_state )
+ printf( ", event is %s",
+ get_printable_name_of_ttrace_event( p->last_stop_state.tts_event ));
+
+ if( p->am_pseudo )
+ printf( ", pseudo thread" );
+
+ if( p->have_signal )
+ printf( ", have signal 0x%x", p->signal_value );
+
+ if( p->have_start )
+ printf( ", have start at 0x%x", p->start );
+
+ printf( ", step is %s", get_printable_name_of_stepping_mode( p->stepping_mode ));
+
+ if( p->handled )
+ printf( ", handled" );
+ else
+ printf( ", not handled" );
+
+ if( p->seen )
+ printf( ", seen" );
+ else
+ printf( ", not seen" );
+
+ printf( "\n" );
+}
+
+static void
+print_tthreads ()
+{
+ thread_info *p;
+
+ if( thread_head.count == 0 )
+ printf( "Thread list is empty\n" );
+ else {
+ printf( "Thread list has " );
+ if( thread_head.count == 1 )
+ printf( "1 entry:\n" );
+ else
+ printf( "%d entries:\n", thread_head.count );
+ for( p = thread_head.head; p; p = p->next ) {
+ print_tthread (p);
+ }
+ }
+
+ if( deleted_threads.count == 0 )
+ printf( "Deleted thread list is empty\n" );
+ else {
+ printf( "Deleted thread list has " );
+ if( deleted_threads.count == 1 )
+ printf( "1 entry:\n" );
+ else
+ printf( "%d entries:\n", deleted_threads.count );
+
+ for( p = deleted_threads.head; p; p = p->next ) {
+ print_tthread (p);
+ }
+ }
+}
+#endif
+
+/* Update the thread list based on the "seen" bits.
+ */
+static void
+update_thread_list ()
+{
+ thread_info *p;
+ thread_info *chase;
+
+ chase = NULL;
+ for( p = thread_head.head; p; p = p->next ) {
+ /* Is this an "unseen" thread which really happens to be a process?
+ If so, is it inferior_pid and is a vfork in flight? If yes to
+ all, then DON'T REMOVE IT! We're in the midst of moving a vfork
+ operation, which is a multiple step thing, to the point where we
+ can touch the parent again. We've most likely stopped to examine
+ the child at a late stage in the vfork, and if we're not following
+ the child, we'd best not treat the parent as a dead "thread"...
+ */
+ if( (!p->seen) && p->am_pseudo && vfork_in_flight
+ && (p->pid != vforking_child_pid))
+ p->seen = 1;
+
+ if( !p->seen ) {
+ /* Remove this one
+ */
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Delete unseen thread: %d \n", p->tid );
+#endif
+ del_tthread( p->tid );
+ }
+ }
+}
+
+
+
+/************************************************
+ * O/S call wrappers *
+ ************************************************
+ */
+
+/* This function simply calls ttrace with the given arguments.
+ * It exists so that all calls to ttrace are isolated. All
+ * parameters should be as specified by "man 2 ttrace".
+ *
+ * No other "raw" calls to ttrace should exist in this module.
+ */
+static int
+call_real_ttrace( request, pid, tid, addr, data, addr2 )
+ ttreq_t request;
+ pid_t pid;
+ lwpid_t tid;
+ TTRACE_ARG_TYPE addr, data, addr2;
+{
+ int tt_status;
+
+ errno = 0;
+ tt_status = ttrace( request, pid, tid, addr, data, addr2 );
+
+#ifdef THREAD_DEBUG
+ if (errno) {
+ /* Don't bother for a known benign error: if you ask for the
+ * first thread state, but there is only one thread and it's
+ * not stopped, ttrace complains.
+ *
+ * We have this inside the #ifdef because our caller will do
+ * this check for real.
+ */
+ if( request != TT_PROC_GET_FIRST_LWP_STATE
+ || errno != EPROTO ) {
+ if( debug_on )
+ printf( "TT fail for %s, with pid %d, tid %d, status %d \n",
+ get_printable_name_of_ttrace_request (request),
+ pid, tid, tt_status );
+ }
+ }
+#endif
+
+#if 0
+ /* ??rehrauer: It would probably be most robust to catch and report
+ * failed requests here. However, some clients of this interface
+ * seem to expect to catch & deal with them, so we'd best not.
+ */
+ if (errno) {
+ strcpy (reason_for_failure, "ttrace (");
+ strcat (reason_for_failure, get_printable_name_of_ttrace_request (request));
+ strcat (reason_for_failure, ")");
+ printf( "ttrace error, errno = %d\n", errno );
+ perror_with_name (reason_for_failure);
+ }
+#endif
+
+ return tt_status;
+}
+
+
+/* This function simply calls ttrace_wait with the given arguments.
+ * It exists so that all calls to ttrace_wait are isolated.
+ *
+ * No "raw" calls to ttrace_wait should exist elsewhere.
+ */
+static int
+call_real_ttrace_wait( pid, tid, option, tsp, tsp_size )
+ int pid;
+ lwpid_t tid;
+ ttwopt_t option;
+ ttstate_t *tsp;
+ size_t tsp_size;
+{
+ int ttw_status;
+ thread_info * tinfo = NULL;
+
+ errno = 0;
+ ttw_status = ttrace_wait (pid, tid, option, tsp, tsp_size);
+
+ if (errno) {
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "TW fail with pid %d, tid %d \n", pid, tid );
+#endif
+
+ perror_with_name ("ttrace wait");
+ }
+
+ return ttw_status;
+}
+
+
+/* A process may have one or more kernel threads, of which all or
+ none may be stopped. This function returns the ID of the first
+ kernel thread in a stopped state, or 0 if none are stopped.
+
+ This function can be used with get_process_next_stopped_thread_id
+ to iterate over the IDs of all stopped threads of this process.
+ */
+static lwpid_t
+get_process_first_stopped_thread_id (pid, thread_state)
+ int pid;
+ ttstate_t * thread_state;
+{
+ int tt_status;
+
+ tt_status = call_real_ttrace (
+ TT_PROC_GET_FIRST_LWP_STATE,
+ (pid_t) pid,
+ (lwpid_t) TT_NIL,
+ (TTRACE_ARG_TYPE) thread_state,
+ (TTRACE_ARG_TYPE) sizeof (*thread_state),
+ TT_NIL);
+
+ if (errno) {
+ if( errno == EPROTO) {
+ /* This is an error we can handle: there isn't any stopped
+ * thread. This happens when we're re-starting the application
+ * and it has only one thread. GET_NEXT handles the case of
+ * no more stopped threads well; GET_FIRST doesn't. (A ttrace
+ * "feature".)
+ */
+ tt_status = 1;
+ errno = 0;
+ return 0;
+ }
+ else
+ perror_with_name ("ttrace");
+ }
+
+ if( tt_status < 0 )
+ /* Failed somehow.
+ */
+ return 0;
+
+ return thread_state->tts_lwpid;
+}
+
+
+/* This function returns the ID of the "next" kernel thread in a
+ stopped state, or 0 if there are none. "Next" refers to the
+ thread following that of the last successful call to this
+ function or to get_process_first_stopped_thread_id, using
+ the value of thread_state returned by that call.
+
+ This function can be used with get_process_first_stopped_thread_id
+ to iterate over the IDs of all stopped threads of this process.
+ */
+static lwpid_t
+get_process_next_stopped_thread_id (pid, thread_state)
+ int pid;
+ ttstate_t * thread_state;
+{
+ int tt_status;
+
+ tt_status = call_real_ttrace (
+ TT_PROC_GET_NEXT_LWP_STATE,
+ (pid_t) pid,
+ (lwpid_t) TT_NIL,
+ (TTRACE_ARG_TYPE) thread_state,
+ (TTRACE_ARG_TYPE) sizeof (*thread_state),
+ TT_NIL);
+ if (errno)
+ perror_with_name ("ttrace");
+
+ if (tt_status < 0)
+ /* Failed
+ */
+ return 0;
+
+ else if( tt_status == 0 ) {
+ /* End of list, no next state. Don't return the
+ * tts_lwpid, as it's a meaningless "240".
+ *
+ * This is an HPUX "feature".
+ */
+ return 0;
+ }
+
+ return thread_state->tts_lwpid;
+}
+
+/* ??rehrauer: Eventually this function perhaps should be calling
+ pid_to_thread_id. However, that function currently does nothing
+ for HP-UX. Even then, I'm not clear whether that function
+ will return a "kernel" thread ID, or a "user" thread ID. If
+ the former, we can just call it here. If the latter, we must
+ map from the "user" tid to a "kernel" tid.
+
+ NOTE: currently not called.
+ */
+static lwpid_t
+get_active_tid_of_pid (pid)
+ int pid;
+{
+ ttstate_t thread_state;
+
+ return get_process_first_stopped_thread_id (pid, &thread_state);
+}
+
+/* This function returns 1 if tt_request is a ttrace request that
+ * operates upon all threads of a (i.e., the entire) process.
+ */
+int
+is_process_ttrace_request (tt_request)
+ ttreq_t tt_request;
+{
+ return IS_TTRACE_PROCREQ (tt_request);
+}
+
+
+/* This function translates a thread ttrace request into
+ * the equivalent process request for a one-thread process.
+ */
+static ttreq_t
+make_process_version( request )
+ ttreq_t request;
+{
+ if (!IS_TTRACE_REQ (request)) {
+ error( "Internal error, bad ttrace request made\n" );
+ return -1;
+ }
+
+ switch (request) {
+ case TT_LWP_STOP :
+ return TT_PROC_STOP;
+
+ case TT_LWP_CONTINUE :
+ return TT_PROC_CONTINUE;
+
+ case TT_LWP_GET_EVENT_MASK :
+ return TT_PROC_GET_EVENT_MASK;
+
+ case TT_LWP_SET_EVENT_MASK :
+ return TT_PROC_SET_EVENT_MASK;
+
+ case TT_LWP_SINGLE :
+ case TT_LWP_RUREGS :
+ case TT_LWP_WUREGS :
+ case TT_LWP_GET_STATE :
+ return -1; /* No equivalent */
+
+ default :
+ return request;
+ }
+}
+
+
+/* This function translates the "pid" used by the rest of
+ * gdb to a real pid and a tid. It then calls "call_real_ttrace"
+ * with the given arguments.
+ *
+ * In general, other parts of this module should call this
+ * function when they are dealing with external users, who only
+ * have tids to pass (but they call it "pid" for historical
+ * reasons).
+ */
+static int
+call_ttrace( request, gdb_tid, addr, data, addr2 )
+ ttreq_t request;
+ int gdb_tid;
+ TTRACE_ARG_TYPE addr, data, addr2;
+{
+ lwpid_t real_tid;
+ int real_pid;
+ ttreq_t new_request;
+ int tt_status;
+ char reason_for_failure [100]; /* Arbitrary size, should be big enough. */
+
+#ifdef THREAD_DEBUG
+ int is_interesting = 0;
+
+ if( TT_LWP_RUREGS == request ) {
+ is_interesting = 1; /* Adjust code here as desired */
+ }
+
+ if( is_interesting && 0 && debug_on ) {
+ if( !is_process_ttrace_request( request )) {
+ printf( "TT: Thread request, tid is %d", gdb_tid );
+ printf( "== SINGLE at %x", addr );
+ }
+ else {
+ printf( "TT: Process request, tid is %d\n", gdb_tid );
+ printf( "==! SINGLE at %x", addr );
+ }
+ }
+#endif
+
+ /* The initial SETTRC and SET_EVENT_MASK calls (and all others
+ * which happen before any threads get set up) should go
+ * directly to "call_real_ttrace", so they don't happen here.
+ *
+ * But hardware watchpoints do a SET_EVENT_MASK, so we can't
+ * rule them out....
+ */
+#ifdef THREAD_DEBUG
+ if( request == TT_PROC_SETTRC && debug_on )
+ printf( "Unexpected call for TT_PROC_SETTRC\n" );
+#endif
+
+ /* Sometimes we get called with a bogus tid (e.g., if a
+ * thread has terminated, we return 0; inftarg later asks
+ * whether the thread has exited/forked/vforked).
+ */
+ if( gdb_tid == 0 )
+ {
+ errno = ESRCH; /* ttrace's response would probably be "No such process". */
+ return -1;
+ }
+
+ /* All other cases should be able to expect that there are
+ * thread records.
+ */
+ if( !any_thread_records()) {
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ warning ("No thread records for ttrace call");
+#endif
+ errno = ESRCH; /* ttrace's response would be "No such process". */
+ return -1;
+ }
+
+ /* OK, now the task is to translate the incoming tid into
+ * a pid/tid pair.
+ */
+ real_tid = map_from_gdb_tid( gdb_tid );
+ real_pid = get_pid_for( real_tid );
+
+ /* Now check the result. "Real_pid" is NULL if our list
+ * didn't find it. We have some tricks we can play to fix
+ * this, however.
+ */
+ if( 0 == real_pid ) {
+ ttstate_t thread_state;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "No saved pid for tid %d\n", gdb_tid );
+#endif
+
+ if( is_process_ttrace_request( request )) {
+
+ /* Ok, we couldn't get a tid. Try to translate to
+ * the equivalent process operation. We expect this
+ * NOT to happen, so this is a desparation-type
+ * move. It can happen if there is an internal
+ * error and so no "wait()" call is ever done.
+ */
+ new_request = make_process_version( request );
+ if( new_request == -1 ) {
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "...and couldn't make process version of thread operation\n" );
+#endif
+
+ /* Use hacky saved pid, which won't always be correct
+ * in the multi-process future. Use tid as thread,
+ * probably dooming this to failure. FIX!
+ */
+ if( saved_real_pid != 0 ) {
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "...using saved pid %d\n", saved_real_pid );
+#endif
+
+ real_pid = saved_real_pid;
+ real_tid = gdb_tid;
+ }
+
+ else
+ error( "Unable to perform thread operation" );
+ }
+
+ else {
+ /* Sucessfully translated this to a process request,
+ * which needs no thread value.
+ */
+ real_pid = gdb_tid;
+ real_tid = 0;
+ request = new_request;
+
+#ifdef THREAD_DEBUG
+ if( debug_on ) {
+ printf( "Translated thread request to process request\n" );
+ if( saved_real_pid == 0 )
+ printf( "...but there's no saved pid\n" );
+
+ else {
+ if( gdb_tid != saved_real_pid )
+ printf( "...but have the wrong pid (%d rather than %d)\n",
+ gdb_tid, saved_real_pid );
+ }
+ }
+#endif
+ } /* Translated to a process request */
+ } /* Is a process request */
+
+ else {
+ /* We have to have a thread. Ooops.
+ */
+ error( "Thread request with no threads (%s)",
+ get_printable_name_of_ttrace_request( request ));
+ }
+ }
+
+ /* Ttrace doesn't like to see tid values on process requests,
+ * even if we have the right one.
+ */
+ if (is_process_ttrace_request (request)) {
+ real_tid = 0;
+ }
+
+#ifdef THREAD_DEBUG
+ if( is_interesting && 0 && debug_on ) {
+ printf( " now tid %d, pid %d\n", real_tid, real_pid );
+ printf( " request is %s\n", get_printable_name_of_ttrace_request (request));
+ }
+#endif
+
+ /* Finally, the (almost) real call.
+ */
+ tt_status = call_real_ttrace (request, real_pid, real_tid, addr, data, addr2);
+
+#ifdef THREAD_DEBUG
+ if(is_interesting && debug_on ) {
+ if( !TT_OK( tt_status, errno )
+ && !(tt_status == 0 & errno == 0))
+ printf( " got error (errno==%d, status==%d)\n", errno, tt_status );
+ }
+#endif
+
+ return tt_status;
+}
+
+
+/* Stop all the threads of a process.
+ *
+ * NOTE: use of TT_PROC_STOP can cause a thread with a real event
+ * to get a TTEVT_NONE event, discarding the old event. Be
+ * very careful, and only call TT_PROC_STOP when you mean it!
+ */
+static void
+stop_all_threads_of_process( real_pid )
+ pid_t real_pid;
+{
+ int ttw_status;
+
+ ttw_status = call_real_ttrace (TT_PROC_STOP,
+ (pid_t) real_pid,
+ (lwpid_t) TT_NIL,
+ (TTRACE_ARG_TYPE) TT_NIL,
+ (TTRACE_ARG_TYPE) TT_NIL,
+ TT_NIL );
+ if (errno)
+ perror_with_name ("ttrace stop of other threads");
+}
+
+
+/* Under some circumstances, it's unsafe to attempt to stop, or even
+ query the state of, a process' threads.
+
+ In ttrace-based HP-UX, an example is a vforking child process. The
+ vforking parent and child are somewhat fragile, w/r/t what we can do
+ what we can do to them with ttrace, until after the child exits or
+ execs, or until the parent's vfork event is delivered. Until that
+ time, we must not try to stop the process' threads, or inquire how
+ many there are, or even alter its data segments, or it typically dies
+ with a SIGILL. Sigh.
+
+ This function returns 1 if this stopped process, and the event that
+ we're told was responsible for its current stopped state, cannot safely
+ have its threads examined.
+ */
+#define CHILD_VFORKED(evt,pid) \
+ (((evt) == TTEVT_VFORK) && ((pid) != inferior_pid))
+#define CHILD_URPED(evt,pid) \
+ ((((evt) == TTEVT_EXEC) || ((evt) == TTEVT_EXIT)) && ((pid) != vforking_child_pid))
+#define PARENT_VFORKED(evt,pid) \
+ (((evt) == TTEVT_VFORK) && ((pid) == inferior_pid))
+
+static int
+can_touch_threads_of_process (pid, stopping_event)
+ int pid;
+ ttevents_t stopping_event;
+{
+ if (CHILD_VFORKED (stopping_event, pid))
+ {
+ vforking_child_pid = pid;
+ vfork_in_flight = 1;
+ }
+
+ else if (vfork_in_flight &&
+ (PARENT_VFORKED (stopping_event, pid) ||
+ CHILD_URPED (stopping_event, pid)))
+ {
+ vfork_in_flight = 0;
+ vforking_child_pid = 0;
+ }
+
+ return ! vfork_in_flight;
+}
+
+
+/* If we can find an as-yet-unhandled thread state of a
+ * stopped thread of this process return 1 and set "tsp".
+ * Return 0 if we can't.
+ *
+ * If this function is used when the threads of PIS haven't
+ * been stopped, undefined behaviour is guaranteed!
+ */
+static int
+select_stopped_thread_of_process (pid, tsp)
+ int pid;
+ ttstate_t * tsp;
+{
+ lwpid_t candidate_tid, tid;
+ ttstate_t candidate_tstate, tstate;
+
+ /* If we're not allowed to touch the process now, then just
+ * return the current value of *TSP.
+ *
+ * This supports "vfork". It's ok, really, to double the
+ * current event (the child EXEC, we hope!).
+ */
+ if (! can_touch_threads_of_process (pid, tsp->tts_event))
+ return 1;
+
+ /* Decide which of (possibly more than one) events to
+ * return as the first one. We scan them all so that
+ * we always return the result of a fake-step first.
+ */
+ candidate_tid = 0;
+ for (tid = get_process_first_stopped_thread_id (pid, &tstate);
+ tid != 0;
+ tid = get_process_next_stopped_thread_id (pid, &tstate))
+ {
+ /* TTEVT_NONE events are uninteresting to our clients. They're
+ * an artifact of our "stop the world" model--the thread is
+ * stopped because we stopped it.
+ */
+ if (tstate.tts_event == TTEVT_NONE) {
+ set_handled( pid, tstate.tts_lwpid );
+ }
+
+ /* Did we just single-step a single thread, without letting any
+ * of the others run? Is this an event for that thread?
+ *
+ * If so, we believe our client would prefer to see this event
+ * over any others. (Typically the client wants to just push
+ * one thread a little farther forward, and then go around
+ * checking for what all threads are doing.)
+ */
+ else if (doing_fake_step && (tstate.tts_lwpid == fake_step_tid))
+ {
+#ifdef WAIT_BUFFER_DEBUG
+ /* It's possible here to see either a SIGTRAP (due to
+ * successful completion of a step) or a SYSCALL_ENTRY
+ * (due to a step completion with active hardware
+ * watchpoints).
+ */
+ if( debug_on )
+ printf( "Ending fake step with tid %d, state %s\n",
+ tstate.tts_lwpid,
+ get_printable_name_of_ttrace_event( tstate.tts_event ));
+#endif
+
+ /* Remember this one, and throw away any previous
+ * candidate.
+ */
+ candidate_tid = tstate.tts_lwpid;
+ candidate_tstate = tstate;
+ }
+
+#ifdef FORGET_DELETED_BPTS
+
+ /* We can't just do this, as if we do, and then wind
+ * up the loop with no unhandled events, we need to
+ * handle that case--the appropriate reaction is to
+ * just continue, but there's no easy way to do that.
+ *
+ * Better to put this in the ttrace_wait call--if, when
+ * we fake a wait, we update our events based on the
+ * breakpoint_here_pc call and find there are no more events,
+ * then we better continue and so on.
+ *
+ * Or we could put it in the next/continue fake.
+ * But it has to go in the buffering code, not in the
+ * real go/wait code.
+ */
+ else if( (TTEVT_SIGNAL == tstate.tts_event)
+ && (5 == tstate.tts_u.tts_signal.tts_signo)
+ && (0 != get_raw_pc( tstate.tts_lwpid ))
+ && ! breakpoint_here_p( get_raw_pc( tstate.tts_lwpid )) ) {
+ /*
+ * If the user deleted a breakpoint while this
+ * breakpoint-hit event was buffered, we can forget
+ * it now.
+ */
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "Forgetting deleted bp hit for thread %d\n",
+ tstate.tts_lwpid );
+#endif
+
+ set_handled( pid, tstate.tts_lwpid );
+ }
+#endif
+
+ /* Else, is this the first "unhandled" event? If so,
+ * we believe our client wants to see it (if we don't
+ * see a fake-step later on in the scan).
+ */
+ else if( !was_handled( tstate.tts_lwpid ) && candidate_tid == 0 ) {
+ candidate_tid = tstate.tts_lwpid;
+ candidate_tstate = tstate;
+ }
+
+ /* This is either an event that has already been "handled",
+ * and thus we believe is uninteresting to our client, or we
+ * already have a candidate event. Ignore it...
+ */
+ }
+
+ /* What do we report?
+ */
+ if( doing_fake_step ) {
+ if( candidate_tid == fake_step_tid ) {
+ /* Fake step.
+ */
+ tstate = candidate_tstate;
+ }
+ else {
+ warning( "Internal error: fake-step failed to complete." );
+ return 0;
+ }
+ }
+ else if( candidate_tid != 0 ) {
+ /* Found a candidate unhandled event.
+ */
+ tstate = candidate_tstate;
+ }
+ else if( tid != 0 ) {
+ warning( "Internal error in call of ttrace_wait." );
+ return 0;
+ }
+ else {
+ warning ("Internal error: no unhandled thread event to select");
+ return 0;
+ }
+
+ copy_ttstate_t (tsp, &tstate);
+ return 1;
+} /* End of select_stopped_thread_of_process */
+
+#ifdef PARANOIA
+/* Check our internal thread data against the real thing.
+ */
+static void
+check_thread_consistency( real_pid )
+ pid_t real_pid;
+{
+ int tid; /* really lwpid_t */
+ ttstate_t tstate;
+ thread_info *p;
+
+ /* Spin down the O/S list of threads, checking that they
+ * match what we've got.
+ */
+ for (tid = get_process_first_stopped_thread_id( real_pid, &tstate );
+ tid != 0;
+ tid = get_process_next_stopped_thread_id( real_pid, &tstate )) {
+
+ p = find_thread_info( tid );
+
+ if( NULL == p ) {
+ warning( "No internal thread data for thread %d.", tid );
+ continue;
+ }
+
+ if( !p->seen ) {
+ warning( "Inconsistent internal thread data for thread %d.", tid );
+ }
+
+ if( p->terminated ) {
+ warning( "Thread %d is not terminated, internal error.", tid );
+ continue;
+ }
+
+
+#define TT_COMPARE( fld ) \
+ tstate.fld != p->last_stop_state.fld
+
+ if( p->have_state ) {
+ if( TT_COMPARE( tts_pid )
+ || TT_COMPARE( tts_lwpid )
+ || TT_COMPARE( tts_user_tid )
+ || TT_COMPARE( tts_event )
+ || TT_COMPARE( tts_flags )
+ || TT_COMPARE( tts_scno )
+ || TT_COMPARE( tts_scnargs )) {
+ warning( "Internal thread data for thread %d is wrong.", tid );
+ continue;
+ }
+ }
+ }
+}
+#endif /* PARANOIA */
+
+
+/* This function wraps calls to "call_real_ttrace_wait" so
+ * that a actual wait is only done when all pending events
+ * have been reported.
+ *
+ * Note that typically it is called with a pid of "0", i.e.
+ * the "don't care" value.
+ *
+ * Return value is the status of the pseudo wait.
+ */
+static int
+call_ttrace_wait( pid, option, tsp, tsp_size )
+ int pid;
+ ttwopt_t option;
+ ttstate_t *tsp;
+ size_t tsp_size;
+{
+ /* This holds the actual, for-real, true process ID.
+ */
+ static int real_pid;
+
+ /* As an argument to ttrace_wait, zero pid
+ * means "Any process", and zero tid means
+ * "Any thread of the specified process".
+ */
+ int wait_pid = 0;
+ lwpid_t wait_tid = 0;
+ lwpid_t real_tid;
+
+ int ttw_status = 0; /* To be returned */
+
+ thread_info * tinfo = NULL;
+
+ if( pid != 0 ) {
+ /* Unexpected case.
+ */
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "TW: Pid to wait on is %d\n", pid );
+#endif
+
+ if( !any_thread_records())
+ error( "No thread records for ttrace call w. specific pid" );
+
+ /* OK, now the task is to translate the incoming tid into
+ * a pid/tid pair.
+ */
+ real_tid = map_from_gdb_tid( pid );
+ real_pid = get_pid_for( real_tid );
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "==TW: real pid %d, real tid %d\n", real_pid, real_tid );
+#endif
+ }
+
+
+ /* Sanity checks and set-up.
+ * Process State
+ *
+ * Stopped Running Fake-step (v)Fork
+ * \________________________________________
+ * |
+ * No buffered events | error wait wait wait
+ * |
+ * Buffered events | debuffer error wait debuffer (?)
+ *
+ */
+ if( more_events_left == 0 ) {
+
+ if( process_state == RUNNING ) {
+ /* OK--normal call of ttrace_wait with no buffered events.
+ */
+ ;
+ }
+ else if( process_state == FAKE_STEPPING ) {
+ /* Ok--call of ttrace_wait to support
+ * fake stepping with no buffered events.
+ *
+ * But we better be fake-stepping!
+ */
+ if( !doing_fake_step ) {
+ warning( "Inconsistent thread state." );
+ }
+ }
+ else if( (process_state == FORKING)
+ || (process_state == VFORKING)) {
+ /* Ok--there are two processes, so waiting
+ * for the second while the first is stopped
+ * is ok. Handled bits stay as they were.
+ */
+ ;
+ }
+ else if( process_state == STOPPED ) {
+ warning( "Process not running at wait call." );
+ }
+ else
+ /* No known state.
+ */
+ warning( "Inconsistent process state." );
+ }
+
+ else {
+ /* More events left
+ */
+ if( process_state == STOPPED ) {
+ /* OK--buffered events being unbuffered.
+ */
+ ;
+ }
+ else if( process_state == RUNNING ) {
+ /* An error--shouldn't have buffered events
+ * when running.
+ */
+ warning( "Trying to continue with buffered events:" );
+ }
+ else if( process_state == FAKE_STEPPING ) {
+ /*
+ * Better be fake-stepping!
+ */
+ if( !doing_fake_step ) {
+ warning( "Losing buffered thread events!\n" );
+ }
+ }
+ else if( (process_state == FORKING)
+ || (process_state == VFORKING)) {
+ /* Ok--there are two processes, so waiting
+ * for the second while the first is stopped
+ * is ok. Handled bits stay as they were.
+ */
+ ;
+ }
+ else
+ warning( "Process in unknown state with buffered events." );
+ }
+
+ /* Sometimes we have to wait for a particular thread
+ * (if we're stepping over a bpt). In that case, we
+ * _know_ it's going to complete the single-step we
+ * asked for (because we're only doing the step under
+ * certain very well-understood circumstances), so it
+ * can't block.
+ */
+ if( doing_fake_step ) {
+ wait_tid = fake_step_tid;
+ wait_pid = get_pid_for( fake_step_tid );
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "Doing a wait after a fake-step for %d, pid %d\n",
+ wait_tid, wait_pid );
+#endif
+ }
+
+ if( more_events_left == 0 /* No buffered events, need real ones. */
+ || process_state != STOPPED ) {
+ /* If there are no buffered events, and so we need
+ * real ones, or if we are FORKING, VFORKING,
+ * FAKE_STEPPING or RUNNING, and thus have to do
+ * a real wait, then do a real wait.
+ */
+
+#ifdef WAIT_BUFFER_DEBUG
+ /* Normal case... */
+ if( debug_on )
+ printf( "TW: do it for real; pid %d, tid %d\n", wait_pid, wait_tid );
+#endif
+
+ /* The actual wait call.
+ */
+ ttw_status = call_real_ttrace_wait( wait_pid, wait_tid, option, tsp, tsp_size);
+
+ /* Note that the routines we'll call will be using "call_real_ttrace",
+ * not "call_ttrace", and thus need the real pid rather than the pseudo-tid
+ * the rest of the world uses (which is actually the tid).
+ */
+ real_pid = tsp->tts_pid;
+
+ /* For most events: Stop the world!
+ *
+ * It's sometimes not safe to stop all threads of a process.
+ * Sometimes it's not even safe to ask for the thread state
+ * of a process!
+ */
+ if (can_touch_threads_of_process (real_pid, tsp->tts_event))
+ {
+ /* If we're really only stepping a single thread, then don't
+ * try to stop all the others -- we only do this single-stepping
+ * business when all others were already stopped...and the stop
+ * would mess up other threads' events.
+ *
+ * Similiarly, if there are other threads with events,
+ * don't do the stop.
+ */
+ if( !doing_fake_step ) {
+ if( more_events_left > 0 )
+ warning( "Internal error in stopping process" );
+
+ stop_all_threads_of_process (real_pid);
+
+ /* At this point, we could scan and update_thread_list(),
+ * and only use the local list for the rest of the
+ * module! We'd get rid of the scans in the various
+ * continue routines (adding one in attach). It'd
+ * be great--UPGRADE ME!
+ */
+ }
+ }
+
+#ifdef PARANOIA
+ else if( debug_on ) {
+ if( more_events_left > 0 )
+ printf( "== Can't stop process; more events!\n" );
+ else
+ printf( "== Can't stop process!\n" );
+ }
+#endif
+
+ process_state = STOPPED;
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "Process set to STOPPED\n" );
+#endif
+ }
+
+ else {
+ /* Fake a call to ttrace_wait. The process must be
+ * STOPPED, as we aren't going to do any wait.
+ */
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "TW: fake it\n" );
+#endif
+
+ if( process_state != STOPPED ) {
+ warning( "Process not stopped at wait call, in state '%s'.\n",
+ get_printable_name_of_process_state( process_state ));
+ }
+
+ if( doing_fake_step )
+ error( "Internal error in stepping over breakpoint" );
+
+ ttw_status = 0; /* Faking it is always successful! */
+ } /* End of fake or not? if */
+
+ /* Pick an event to pass to our caller. Be paranoid.
+ */
+ if( !select_stopped_thread_of_process( real_pid, tsp ))
+ warning( "Can't find event, using previous event." );
+
+ else if( tsp->tts_event == TTEVT_NONE )
+ warning( "Internal error: no thread has a real event." );
+
+ else if( doing_fake_step ) {
+ if( fake_step_tid != tsp->tts_lwpid )
+ warning( "Internal error in stepping over breakpoint." );
+
+ /* This wait clears the (current) fake-step if there was one.
+ */
+ doing_fake_step = 0;
+ fake_step_tid = 0;
+ }
+
+ /* We now have a correct tsp and ttw_status for the thread
+ * which we want to report. So it's "handled"! This call
+ * will add it to our list if it's not there already.
+ */
+ set_handled( real_pid, tsp->tts_lwpid );
+
+ /* Save a copy of the ttrace state of this thread, in our local
+ thread descriptor.
+
+ This caches the state. The implementation of queries like
+ target_has_execd can then use this cached state, rather than
+ be forced to make an explicit ttrace call to get it.
+
+ (Guard against the condition that this is the first time we've
+ waited on, i.e., seen this thread, and so haven't yet entered
+ it into our list of threads.)
+ */
+ tinfo = find_thread_info (tsp->tts_lwpid);
+ if (tinfo != NULL) {
+ copy_ttstate_t (&tinfo->last_stop_state, tsp);
+ tinfo->have_state = 1;
+ }
+
+ return ttw_status;
+} /* call_ttrace_wait */
+
+#if defined(CHILD_REPORTED_EXEC_EVENTS_PER_EXEC_CALL)
+int
+child_reported_exec_events_per_exec_call ()
+{
+ return 1; /* ttrace reports the event once per call. */
+}
+#endif
+
+
+
+/* Our implementation of hardware watchpoints involves making memory
+ pages write-protected. We must remember a page's original permissions,
+ and we must also know when it is appropriate to restore a page's
+ permissions to its original state.
+
+ We use a "dictionary" of hardware-watched pages to do this. Each
+ hardware-watched page is recorded in the dictionary. Each page's
+ dictionary entry contains the original permissions and a reference
+ count. Pages are hashed into the dictionary by their start address.
+
+ When hardware watchpoint is set on page X for the first time, page X
+ is added to the dictionary with a reference count of 1. If other
+ hardware watchpoints are subsequently set on page X, its reference
+ count is incremented. When hardware watchpoints are removed from
+ page X, its reference count is decremented. If a page's reference
+ count drops to 0, it's permissions are restored and the page's entry
+ is thrown out of the dictionary.
+ */
+typedef struct memory_page {
+ CORE_ADDR page_start;
+ int reference_count;
+ int original_permissions;
+ struct memory_page * next;
+ struct memory_page * previous;
+} memory_page_t;
+
+#define MEMORY_PAGE_DICTIONARY_BUCKET_COUNT 128
+
+static struct {
+ LONGEST page_count;
+ int page_size;
+ int page_protections_allowed;
+ /* These are just the heads of chains of actual page descriptors. */
+ memory_page_t buckets [MEMORY_PAGE_DICTIONARY_BUCKET_COUNT];
+} memory_page_dictionary;
+
+
+static void
+require_memory_page_dictionary ()
+{
+ int i;
+
+ /* Is the memory page dictionary ready for use? If so, we're done. */
+ if (memory_page_dictionary.page_count >= (LONGEST) 0)
+ return;
+
+ /* Else, initialize it. */
+ memory_page_dictionary.page_count = (LONGEST) 0;
+
+ for (i=0; i<MEMORY_PAGE_DICTIONARY_BUCKET_COUNT; i++)
+ {
+ memory_page_dictionary.buckets[i].page_start = (CORE_ADDR) 0;
+ memory_page_dictionary.buckets[i].reference_count = 0;
+ memory_page_dictionary.buckets[i].next = NULL;
+ memory_page_dictionary.buckets[i].previous = NULL;
+ }
+}
+
+
+static void
+retire_memory_page_dictionary ()
+{
+ memory_page_dictionary.page_count = (LONGEST) -1;
+}
+
+
+/* Write-protect the memory page that starts at this address.
+
+ Returns the original permissions of the page.
+ */
+static int
+write_protect_page (pid, page_start)
+ int pid;
+ CORE_ADDR page_start;
+{
+ int tt_status;
+ int original_permissions;
+ int new_permissions;
+
+ tt_status = call_ttrace (TT_PROC_GET_MPROTECT,
+ pid,
+ (TTRACE_ARG_TYPE) page_start,
+ TT_NIL,
+ (TTRACE_ARG_TYPE) &original_permissions);
+ if (errno || (tt_status < 0))
+ {
+ return 0; /* What else can we do? */
+ }
+
+ /* We'll also write-protect the page now, if that's allowed. */
+ if (memory_page_dictionary.page_protections_allowed)
+ {
+ new_permissions = original_permissions & ~PROT_WRITE;
+ tt_status = call_ttrace (TT_PROC_SET_MPROTECT,
+ pid,
+ (TTRACE_ARG_TYPE) page_start,
+ (TTRACE_ARG_TYPE) memory_page_dictionary.page_size,
+ (TTRACE_ARG_TYPE) new_permissions);
+ if (errno || (tt_status < 0))
+ {
+ return 0; /* What else can we do? */
+ }
+ }
+
+ return original_permissions;
+}
+
+
+/* Unwrite-protect the memory page that starts at this address, restoring
+ (what we must assume are) its original permissions.
+ */
+static void
+unwrite_protect_page (pid, page_start, original_permissions)
+ int pid;
+ CORE_ADDR page_start;
+ int original_permissions;
+{
+ int tt_status;
+
+ tt_status = call_ttrace (TT_PROC_SET_MPROTECT,
+ pid,
+ (TTRACE_ARG_TYPE) page_start,
+ (TTRACE_ARG_TYPE) memory_page_dictionary.page_size,
+ (TTRACE_ARG_TYPE) original_permissions);
+ if (errno || (tt_status < 0))
+ {
+ return; /* What else can we do? */
+ }
+}
+
+
+/* Memory page-protections are used to implement "hardware" watchpoints
+ on HP-UX.
+
+ For every memory page that is currently being watched (i.e., that
+ presently should be write-protected), write-protect it.
+ */
+void
+hppa_enable_page_protection_events (pid)
+ int pid;
+{
+ int bucket;
+
+ memory_page_dictionary.page_protections_allowed = 1;
+
+ for (bucket=0; bucket<MEMORY_PAGE_DICTIONARY_BUCKET_COUNT; bucket++)
+ {
+ memory_page_t * page;
+
+ page = memory_page_dictionary.buckets[bucket].next;
+ while (page != NULL)
+ {
+ page->original_permissions = write_protect_page (pid, page->page_start);
+ page = page->next;
+ }
+ }
+}
+
+
+/* Memory page-protections are used to implement "hardware" watchpoints
+ on HP-UX.
+
+ For every memory page that is currently being watched (i.e., that
+ presently is or should be write-protected), un-write-protect it.
+ */
+void
+hppa_disable_page_protection_events (pid)
+ int pid;
+{
+ int bucket;
+
+ for (bucket=0; bucket<MEMORY_PAGE_DICTIONARY_BUCKET_COUNT; bucket++)
+ {
+ memory_page_t * page;
+
+ page = memory_page_dictionary.buckets[bucket].next;
+ while (page != NULL)
+ {
+ unwrite_protect_page (pid, page->page_start, page->original_permissions);
+ page = page->next;
+ }
+ }
+
+ memory_page_dictionary.page_protections_allowed = 0;
+}
+
+/* Count the number of outstanding events. At this
+ * point, we have selected one thread and its event
+ * as the one to be "reported" upwards to core gdb.
+ * That thread is already marked as "handled".
+ *
+ * Note: we could just scan our own thread list. FIXME!
+ */
+static int
+count_unhandled_events( real_pid, real_tid )
+ int real_pid;
+ lwpid_t real_tid;
+{
+ ttstate_t tstate;
+ lwpid_t ttid;
+ int events_left;
+
+ /* Ok, find out how many threads have real events to report.
+ */
+ events_left = 0;
+ ttid = get_process_first_stopped_thread_id( real_pid, &tstate );
+
+#ifdef THREAD_DEBUG
+ if( debug_on ) {
+ if( ttid == 0 )
+ printf( "Process %d has no threads\n", real_pid );
+ else
+ printf( "Process %d has these threads:\n", real_pid );
+ }
+#endif
+
+ while (ttid > 0 ) {
+ if( tstate.tts_event != TTEVT_NONE
+ && !was_handled( ttid )) {
+ /* TTEVT_NONE implies we just stopped it ourselves
+ * because we're the stop-the-world guys, so it's
+ * not an event from our point of view.
+ *
+ * If "was_handled" is true, this is an event we
+ * already handled, so don't count it.
+ *
+ * Note that we don't count the thread with the
+ * currently-reported event, as it's already marked
+ * as handled.
+ */
+ events_left++;
+ }
+
+#if defined( THREAD_DEBUG ) || defined( WAIT_BUFFER_DEBUG )
+ if( debug_on ) {
+ if( ttid == real_tid )
+ printf( "*" ); /* Thread we're reporting */
+ else
+ printf( " " );
+
+ if( tstate.tts_event != TTEVT_NONE )
+ printf( "+" ); /* Thread with a real event */
+ else
+ printf( " " );
+
+ if( was_handled( ttid ))
+ printf( "h" ); /* Thread has been handled */
+ else
+ printf( " " );
+
+ printf( " %d, with event %s", ttid,
+ get_printable_name_of_ttrace_event( tstate.tts_event ));
+
+ if( tstate.tts_event == TTEVT_SIGNAL
+ && 5 == tstate.tts_u.tts_signal.tts_signo ) {
+ CORE_ADDR pc_val;
+
+ pc_val = get_raw_pc( ttid );
+
+ if( pc_val > 0 )
+ printf( " breakpoint at 0x%x\n", pc_val );
+ else
+ printf( " bpt, can't fetch pc.\n" );
+ }
+ else
+ printf( "\n" );
+ }
+#endif
+
+ ttid = get_process_next_stopped_thread_id (real_pid, &tstate);
+ }
+
+#if defined( THREAD_DEBUG ) || defined( WAIT_BUFFER_DEBUG )
+ if( debug_on )
+ if( events_left > 0 )
+ printf( "There are thus %d pending events\n", events_left );
+#endif
+
+ return events_left;
+}
+
+/* This function is provided as a sop to clients that are calling
+ * ptrace_wait to wait for a process to stop. (see the
+ * implementation of child_wait.) Return value is the pid for
+ * the event that ended the wait.
+ *
+ * Note: used by core gdb and so uses the pseudo-pid (really tid).
+ */
+int
+ptrace_wait (pid, status)
+ int pid;
+ int *status;
+{
+ ttstate_t tsp;
+ int ttwait_return;
+ int real_pid;
+ ttstate_t state;
+ lwpid_t real_tid;
+ int return_pid;
+
+ /* The ptrace implementation of this also ignores pid.
+ */
+ *status = 0;
+
+ ttwait_return = call_ttrace_wait( 0, TTRACE_WAITOK, &tsp, sizeof (tsp) );
+ if (ttwait_return < 0)
+ {
+ /* ??rehrauer: It appears that if our inferior exits and we
+ haven't asked for exit events, that we're not getting any
+ indication save a negative return from ttrace_wait and an
+ errno set to ESRCH?
+ */
+ if (errno == ESRCH)
+ {
+ *status = 0; /* WIFEXITED */
+ return inferior_pid;
+ }
+
+ warning( "Call of ttrace_wait returned with errno %d.",
+ errno );
+ *status = ttwait_return;
+ return inferior_pid;
+ }
+
+ real_pid = tsp.tts_pid;
+ real_tid = tsp.tts_lwpid;
+
+ /* One complication is that the "tts_event" structure has
+ * a set of flags, and more than one can be set. So we
+ * either have to force an order (as we do here), or handle
+ * more than one flag at a time.
+ */
+ if (tsp.tts_event & TTEVT_LWP_CREATE) {
+
+ /* Unlike what you might expect, this event is reported in
+ * the _creating_ thread, and the _created_ thread (whose tid
+ * we have) is still running. So we have to stop it. This
+ * has already been done in "call_ttrace_wait", but should we
+ * ever abandon the "stop-the-world" model, here's the command
+ * to use:
+ *
+ * call_ttrace( TT_LWP_STOP, real_tid, TT_NIL, TT_NIL, TT_NIL );
+ *
+ * Note that this would depend on being called _after_ "add_tthread"
+ * below for the tid-to-pid translation to be done in "call_ttrace".
+ */
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "New thread: pid %d, tid %d, creator tid %d\n",
+ real_pid, tsp.tts_u.tts_thread.tts_target_lwpid,
+ real_tid );
+#endif
+
+ /* Now we have to return the tid of the created thread, not
+ * the creating thread, or "wait_for_inferior" won't know we
+ * have a new "process" (thread). Plus we should record it
+ * right, too.
+ */
+ real_tid = tsp.tts_u.tts_thread.tts_target_lwpid;
+
+ add_tthread( real_pid, real_tid );
+ }
+
+ else if( (tsp.tts_event & TTEVT_LWP_TERMINATE )
+ || (tsp.tts_event & TTEVT_LWP_EXIT) ) {
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Thread dies: %d\n", real_tid );
+#endif
+
+ del_tthread( real_tid );
+ }
+
+ else if (tsp.tts_event & TTEVT_EXEC) {
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Pid %d has zero'th thread %d; inferior pid is %d\n",
+ real_pid, real_tid, inferior_pid );
+#endif
+
+ add_tthread( real_pid, real_tid );
+ }
+
+#ifdef THREAD_DEBUG
+ else if( debug_on ) {
+ printf( "Process-level event %s, using tid %d\n",
+ get_printable_name_of_ttrace_event( tsp.tts_event ),
+ real_tid );
+
+ /* OK to do this, as "add_tthread" won't add
+ * duplicate entries. Also OK not to do it,
+ * as this event isn't one which can change the
+ * thread state.
+ */
+ add_tthread( real_pid, real_tid );
+ }
+#endif
+
+
+ /* How many events are left to report later?
+ * In a non-stop-the-world model, this isn't needed.
+ *
+ * Note that it's not always safe to query the thread state of a process,
+ * which is what count_unhandled_events does. (If unsafe, we're left with
+ * no other resort than to assume that no more events remain...)
+ */
+ if (can_touch_threads_of_process (real_pid, tsp.tts_event))
+ more_events_left = count_unhandled_events( real_pid, real_tid );
+
+ else {
+ if( more_events_left > 0 )
+ warning( "Vfork or fork causing loss of %d buffered events.",
+ more_events_left );
+
+ more_events_left = 0;
+ }
+
+ /* Attempt to translate the ttrace_wait-returned status into the
+ ptrace equivalent.
+
+ ??rehrauer: This is somewhat fragile. We really ought to rewrite
+ clients that expect to pick apart a ptrace wait status, to use
+ something a little more abstract.
+ */
+ if ( (tsp.tts_event & TTEVT_EXEC)
+ || (tsp.tts_event & TTEVT_FORK)
+ || (tsp.tts_event & TTEVT_VFORK))
+ {
+ /* Forks come in pairs (parent and child), so core gdb
+ * will do two waits. Be ready to notice this.
+ */
+ if (tsp.tts_event & TTEVT_FORK)
+ {
+ process_state = FORKING;
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "Process set to FORKING\n" );
+#endif
+ }
+ else if (tsp.tts_event & TTEVT_VFORK)
+ {
+ process_state = VFORKING;
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "Process set to VFORKING\n" );
+#endif
+ }
+
+ /* Make an exec or fork look like a breakpoint. Definitely a hack,
+ but I don't think non HP-UX-specific clients really carefully
+ inspect the first events they get after inferior startup, so
+ it probably almost doesn't matter what we claim this is.
+ */
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "..a process 'event'\n" );
+#endif
+
+ /* Also make fork and exec events look like bpts, so they can be caught.
+ */
+ *status = 0177 | (_SIGTRAP << 8);
+ }
+
+ /* Special-cases: We ask for syscall entry and exit events to implement
+ "fast" (aka "hardware") watchpoints.
+
+ When we get a syscall entry, we want to disable page-protections,
+ and resume the inferior; this isn't an event we wish for
+ wait_for_inferior to see. Note that we must resume ONLY the
+ thread that reported the syscall entry; we don't want to allow
+ other threads to run with the page protections off, as they might
+ then be able to write to watch memory without it being caught.
+
+ When we get a syscall exit, we want to reenable page-protections,
+ but we don't want to resume the inferior; this is an event we wish
+ wait_for_inferior to see. Make it look like the signal we normally
+ get for a single-step completion. This should cause wait_for_inferior
+ to evaluate whether any watchpoint triggered.
+
+ Or rather, that's what we'd LIKE to do for syscall exit; we can't,
+ due to some HP-UX "features". Some syscalls have problems with
+ write-protections on some pages, and some syscalls seem to have
+ pending writes to those pages at the time we're getting the return
+ event. So, we'll single-step the inferior to get out of the syscall,
+ and then reenable protections.
+
+ Note that we're intentionally allowing the syscall exit case to
+ fall through into the succeeding cases, as sometimes we single-
+ step out of one syscall only to immediately enter another...
+ */
+ else if ((tsp.tts_event & TTEVT_SYSCALL_ENTRY)
+ || (tsp.tts_event & TTEVT_SYSCALL_RETURN))
+ {
+ /* Make a syscall event look like a breakpoint. Same comments
+ as for exec & fork events.
+ */
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "..a syscall 'event'\n" );
+#endif
+
+ /* Also make syscall events look like bpts, so they can be caught.
+ */
+ *status = 0177 | (_SIGTRAP << 8);
+ }
+
+ else if ((tsp.tts_event & TTEVT_LWP_CREATE)
+ || (tsp.tts_event & TTEVT_LWP_TERMINATE)
+ || (tsp.tts_event & TTEVT_LWP_EXIT))
+ {
+ /* Make a thread event look like a breakpoint. Same comments
+ * as for exec & fork events.
+ */
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "..a thread 'event'\n" );
+#endif
+
+ /* Also make thread events look like bpts, so they can be caught.
+ */
+ *status = 0177 | (_SIGTRAP << 8);
+ }
+
+ else if ((tsp.tts_event & TTEVT_EXIT))
+ { /* WIFEXITED */
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "..an exit\n" );
+#endif
+
+ /* Prevent rest of gdb from thinking this is
+ * a new thread if for some reason it's never
+ * seen the main thread before.
+ */
+ inferior_pid = map_to_gdb_tid( real_tid ); /* HACK, FIX */
+
+ *status = 0 | (tsp.tts_u.tts_exit.tts_exitcode);
+ }
+
+ else if (tsp.tts_event & TTEVT_SIGNAL)
+ { /* WIFSTOPPED */
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "..a signal, %d\n", tsp.tts_u.tts_signal.tts_signo );
+#endif
+
+ *status = 0177 | (tsp.tts_u.tts_signal.tts_signo << 8);
+ }
+
+ else
+ { /* !WIFSTOPPED */
+
+ /* This means the process or thread terminated. But we should've
+ caught an explicit exit/termination above. So warn (this is
+ really an internal error) and claim the process or thread
+ terminated with a SIGTRAP.
+ */
+
+ warning ("process_wait: unknown process state");
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Process-level event %s, using tid %d\n",
+ get_printable_name_of_ttrace_event( tsp.tts_event ),
+ real_tid );
+#endif
+
+ *status = _SIGTRAP;
+ }
+
+ target_post_wait (tsp.tts_pid, *status);
+
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Done waiting, pid is %d, tid %d\n", real_pid, real_tid );
+#endif
+
+ /* All code external to this module uses the tid, but calls
+ * it "pid". There's some tweaking so that the outside sees
+ * the first thread as having the same number as the starting
+ * pid.
+ */
+ return_pid = map_to_gdb_tid( real_tid );
+
+ /* Remember this for later use in "hppa_prepare_to_proceed".
+ */
+ old_gdb_pid = inferior_pid;
+ reported_pid = return_pid;
+ reported_bpt = ((tsp.tts_event & TTEVT_SIGNAL) && (5 == tsp.tts_u.tts_signal.tts_signo));
+
+ if( real_tid == 0 || return_pid == 0 ) {
+ warning( "Internal error: process-wait failed." );
+ }
+
+ return return_pid;
+}
+
+
+/* This function causes the caller's process to be traced by its
+ parent. This is intended to be called after GDB forks itself,
+ and before the child execs the target. Despite the name, it
+ is called by the child.
+
+ Note that HP-UX ttrace is rather funky in how this is done.
+ If the parent wants to get the initial exec event of a child,
+ it must set the ttrace event mask of the child to include execs.
+ (The child cannot do this itself.) This must be done after the
+ child is forked, but before it execs.
+
+ To coordinate the parent and child, we implement a semaphore using
+ pipes. After SETTRC'ing itself, the child tells the parent that
+ it is now traceable by the parent, and waits for the parent's
+ acknowledgement. The parent can then set the child's event mask,
+ and notify the child that it can now exec.
+
+ (The acknowledgement by parent happens as a result of a call to
+ child_acknowledge_created_inferior.)
+ */
+int
+parent_attach_all ()
+{
+ int tt_status;
+
+ /* We need a memory home for a constant, to pass it to ttrace.
+ The value of the constant is arbitrary, so long as both
+ parent and child use the same value. Might as well use the
+ "magic" constant provided by ttrace...
+ */
+ uint64_t tc_magic_child = TT_VERSION;
+ uint64_t tc_magic_parent = 0;
+
+ tt_status = call_real_ttrace (
+ TT_PROC_SETTRC,
+ (int) TT_NIL,
+ (lwpid_t) TT_NIL,
+ TT_NIL,
+ (TTRACE_ARG_TYPE) TT_VERSION,
+ TT_NIL );
+
+ if (tt_status < 0)
+ return tt_status;
+
+ /* Notify the parent that we're potentially ready to exec(). */
+ write (startup_semaphore.child_channel[SEM_TALK],
+ &tc_magic_child,
+ sizeof (tc_magic_child));
+
+ /* Wait for acknowledgement from the parent. */
+ read (startup_semaphore.parent_channel[SEM_LISTEN],
+ &tc_magic_parent,
+ sizeof (tc_magic_parent));
+
+ if (tc_magic_child != tc_magic_parent)
+ warning ("mismatched semaphore magic");
+
+ /* Discard our copy of the semaphore. */
+ (void) close (startup_semaphore.parent_channel[SEM_LISTEN]);
+ (void) close (startup_semaphore.parent_channel[SEM_TALK]);
+ (void) close (startup_semaphore.child_channel[SEM_LISTEN]);
+ (void) close (startup_semaphore.child_channel[SEM_TALK]);
+
+ return tt_status;
+}
+
+/* Despite being file-local, this routine is dealing with
+ * actual process IDs, not thread ids. That's because it's
+ * called before the first "wait" call, and there's no map
+ * yet from tids to pids.
+ *
+ * When it is called, a forked child is running, but waiting on
+ * the semaphore. If you stop the child and re-start it,
+ * things get confused, so don't do that! An attached child is
+ * stopped.
+ *
+ * Since this is called after either attach or run, we
+ * have to be the common part of both.
+ */
+static void
+require_notification_of_events ( real_pid )
+ int real_pid;
+{
+ int tt_status;
+ ttevent_t notifiable_events;
+
+ lwpid_t tid;
+ ttstate_t thread_state;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Require notif, pid is %d\n", real_pid );
+#endif
+
+ /* Temporary HACK: tell inftarg.c/child_wait to not
+ * loop until pids are the same.
+ */
+ not_same_real_pid = 0;
+
+ sigemptyset (&notifiable_events.tte_signals);
+ notifiable_events.tte_opts = TTEO_NONE;
+
+ /* This ensures that forked children inherit their parent's
+ * event mask, which we're setting here.
+ *
+ * NOTE: if you debug gdb with itself, then the ultimate
+ * debuggee gets flags set by the outermost gdb, as
+ * a child of a child will still inherit.
+ */
+ notifiable_events.tte_opts |= TTEO_PROC_INHERIT;
+
+ notifiable_events.tte_events = TTEVT_DEFAULT;
+ notifiable_events.tte_events |= TTEVT_SIGNAL;
+ notifiable_events.tte_events |= TTEVT_EXEC;
+ notifiable_events.tte_events |= TTEVT_EXIT;
+ notifiable_events.tte_events |= TTEVT_FORK;
+ notifiable_events.tte_events |= TTEVT_VFORK;
+ notifiable_events.tte_events |= TTEVT_LWP_CREATE;
+ notifiable_events.tte_events |= TTEVT_LWP_EXIT;
+ notifiable_events.tte_events |= TTEVT_LWP_TERMINATE;
+
+ tt_status = call_real_ttrace (
+ TT_PROC_SET_EVENT_MASK,
+ real_pid,
+ (lwpid_t) TT_NIL,
+ (TTRACE_ARG_TYPE) &notifiable_events,
+ (TTRACE_ARG_TYPE) sizeof (notifiable_events),
+ TT_NIL);
+}
+
+static void
+require_notification_of_exec_events ( real_pid )
+ int real_pid;
+{
+ int tt_status;
+ ttevent_t notifiable_events;
+
+ lwpid_t tid;
+ ttstate_t thread_state;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Require notif, pid is %d\n", real_pid );
+#endif
+
+ /* Temporary HACK: tell inftarg.c/child_wait to not
+ * loop until pids are the same.
+ */
+ not_same_real_pid = 0;
+
+ sigemptyset (&notifiable_events.tte_signals);
+ notifiable_events.tte_opts = TTEO_NOSTRCCHLD;
+
+ /* This ensures that forked children don't inherit their parent's
+ * event mask, which we're setting here.
+ */
+ notifiable_events.tte_opts &= ~TTEO_PROC_INHERIT;
+
+ notifiable_events.tte_events = TTEVT_DEFAULT;
+ notifiable_events.tte_events |= TTEVT_EXEC;
+ notifiable_events.tte_events |= TTEVT_EXIT;
+
+ tt_status = call_real_ttrace (
+ TT_PROC_SET_EVENT_MASK,
+ real_pid,
+ (lwpid_t) TT_NIL,
+ (TTRACE_ARG_TYPE) &notifiable_events,
+ (TTRACE_ARG_TYPE) sizeof (notifiable_events),
+ TT_NIL);
+}
+
+
+/* This function is called by the parent process, with pid being the
+ * ID of the child process, after the debugger has forked.
+ */
+void
+child_acknowledge_created_inferior (pid)
+ int pid;
+{
+ /* We need a memory home for a constant, to pass it to ttrace.
+ The value of the constant is arbitrary, so long as both
+ parent and child use the same value. Might as well use the
+ "magic" constant provided by ttrace...
+ */
+ uint64_t tc_magic_parent = TT_VERSION;
+ uint64_t tc_magic_child = 0;
+
+ /* Wait for the child to tell us that it has forked. */
+ read (startup_semaphore.child_channel[SEM_LISTEN],
+ &tc_magic_child,
+ sizeof(tc_magic_child));
+
+ /* Clear thread info now. We'd like to do this in
+ * "require...", but that messes up attach.
+ */
+ clear_thread_info();
+
+ /* Tell the "rest of gdb" that the initial thread exists.
+ * This isn't really a hack. Other thread-based versions
+ * of gdb (e.g. gnu-nat.c) seem to do the same thing.
+ *
+ * Q: Why don't we also add this thread to the local
+ * list via "add_tthread"?
+ *
+ * A: Because we don't know the tid, and can't stop the
+ * the process safely to ask what it is. Anyway, we'll
+ * add it when it gets the EXEC event.
+ */
+ add_thread( pid ); /* in thread.c */
+
+ /* We can now set the child's ttrace event mask.
+ */
+ require_notification_of_exec_events (pid);
+
+ /* Tell ourselves that the process is running.
+ */
+ process_state = RUNNING;
+
+ /* Notify the child that it can exec. */
+ write (startup_semaphore.parent_channel[SEM_TALK],
+ &tc_magic_parent,
+ sizeof (tc_magic_parent));
+
+ /* Discard our copy of the semaphore. */
+ (void) close (startup_semaphore.parent_channel[SEM_LISTEN]);
+ (void) close (startup_semaphore.parent_channel[SEM_TALK]);
+ (void) close (startup_semaphore.child_channel[SEM_LISTEN]);
+ (void) close (startup_semaphore.child_channel[SEM_TALK]);
+}
+
+
+/*
+ * arrange for notification of all events by
+ * calling require_notification_of_events.
+ */
+void
+child_post_startup_inferior ( real_pid)
+ int real_pid;
+{
+ require_notification_of_events (real_pid);
+}
+
+/* From here on, we should expect tids rather than pids.
+ */
+static void
+hppa_enable_catch_fork (tid)
+ int tid;
+{
+ int tt_status;
+ ttevent_t ttrace_events;
+
+ /* Get the set of events that are currently enabled.
+ */
+ tt_status = call_ttrace (TT_PROC_GET_EVENT_MASK,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL );
+ if (errno)
+ perror_with_name ("ttrace");
+
+ /* Add forks to that set. */
+ ttrace_events.tte_events |= TTEVT_FORK;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "enable fork, tid is %d\n", tid );
+#endif
+
+ tt_status = call_ttrace (TT_PROC_SET_EVENT_MASK,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+ if (errno)
+ perror_with_name ("ttrace");
+}
+
+
+static void
+hppa_disable_catch_fork (tid)
+ int tid;
+{
+ int tt_status;
+ ttevent_t ttrace_events;
+
+ /* Get the set of events that are currently enabled.
+ */
+ tt_status = call_ttrace (TT_PROC_GET_EVENT_MASK,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+
+ /* Remove forks from that set. */
+ ttrace_events.tte_events &= ~TTEVT_FORK;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf("disable fork, tid is %d\n", tid );
+#endif
+
+ tt_status = call_ttrace (TT_PROC_SET_EVENT_MASK,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+}
+
+
+#if defined(CHILD_INSERT_FORK_CATCHPOINT)
+int
+child_insert_fork_catchpoint (tid)
+ int tid;
+{
+ /* Enable reporting of fork events from the kernel. */
+ /* ??rehrauer: For the moment, we're always enabling these events,
+ and just ignoring them if there's no catchpoint to catch them.
+ */
+ return 0;
+}
+#endif
+
+
+#if defined(CHILD_REMOVE_FORK_CATCHPOINT)
+int
+child_remove_fork_catchpoint (tid)
+ int tid;
+{
+ /* Disable reporting of fork events from the kernel. */
+ /* ??rehrauer: For the moment, we're always enabling these events,
+ and just ignoring them if there's no catchpoint to catch them.
+ */
+ return 0;
+}
+#endif
+
+
+static void
+hppa_enable_catch_vfork (tid)
+ int tid;
+{
+ int tt_status;
+ ttevent_t ttrace_events;
+
+ /* Get the set of events that are currently enabled.
+ */
+ tt_status = call_ttrace (TT_PROC_GET_EVENT_MASK,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+
+ /* Add vforks to that set. */
+ ttrace_events.tte_events |= TTEVT_VFORK;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf("enable vfork, tid is %d\n", tid );
+#endif
+
+ tt_status = call_ttrace (TT_PROC_SET_EVENT_MASK,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+}
+
+
+static void
+hppa_disable_catch_vfork (tid)
+ int tid;
+{
+ int tt_status;
+ ttevent_t ttrace_events;
+
+ /* Get the set of events that are currently enabled. */
+ tt_status = call_ttrace (TT_PROC_GET_EVENT_MASK,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+
+ /* Remove vforks from that set. */
+ ttrace_events.tte_events &= ~TTEVT_VFORK;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf("disable vfork, tid is %d\n", tid );
+#endif
+ tt_status = call_ttrace (TT_PROC_SET_EVENT_MASK,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+}
+
+
+#if defined(CHILD_INSERT_VFORK_CATCHPOINT)
+int
+child_insert_vfork_catchpoint (tid)
+ int tid;
+{
+ /* Enable reporting of vfork events from the kernel. */
+ /* ??rehrauer: For the moment, we're always enabling these events,
+ and just ignoring them if there's no catchpoint to catch them.
+ */
+ return 0;
+}
+#endif
+
+
+#if defined(CHILD_REMOVE_VFORK_CATCHPOINT)
+int
+child_remove_vfork_catchpoint (tid)
+ int tid;
+{
+ /* Disable reporting of vfork events from the kernel. */
+ /* ??rehrauer: For the moment, we're always enabling these events,
+ and just ignoring them if there's no catchpoint to catch them.
+ */
+ return 0;
+}
+#endif
+
+#if defined(CHILD_HAS_FORKED)
+
+/* Q: Do we need to map the returned process ID to a thread ID?
+ *
+ * A: I don't think so--here we want a _real_ pid. Any later
+ * operations will call "require_notification_of_events" and
+ * start the mapping.
+ */
+int
+child_has_forked (tid, childpid)
+ int tid;
+ int *childpid;
+{
+ int tt_status;
+ ttstate_t ttrace_state;
+ thread_info * tinfo;
+
+ /* Do we have cached thread state that we can consult? If so, use it. */
+ tinfo = find_thread_info (map_from_gdb_tid (tid));
+ if (tinfo != NULL) {
+ copy_ttstate_t (&ttrace_state, &tinfo->last_stop_state);
+ }
+
+ /* Nope, must read the thread's current state */
+ else
+ {
+ tt_status = call_ttrace (TT_LWP_GET_STATE,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_state,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_state),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+
+ if (tt_status < 0)
+ return 0;
+ }
+
+ if (ttrace_state.tts_event & TTEVT_FORK)
+ {
+ *childpid = ttrace_state.tts_u.tts_fork.tts_fpid;
+ return 1;
+ }
+
+ return 0;
+}
+#endif
+
+
+#if defined(CHILD_HAS_VFORKED)
+
+/* See child_has_forked for pid discussion.
+ */
+int
+child_has_vforked (tid, childpid)
+ int tid;
+ int * childpid;
+{
+ int tt_status;
+ ttstate_t ttrace_state;
+ thread_info * tinfo;
+
+ /* Do we have cached thread state that we can consult? If so, use it. */
+ tinfo = find_thread_info (map_from_gdb_tid (tid));
+ if (tinfo != NULL)
+ copy_ttstate_t (&ttrace_state, &tinfo->last_stop_state);
+
+ /* Nope, must read the thread's current state */
+ else
+ {
+ tt_status = call_ttrace (TT_LWP_GET_STATE,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_state,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_state),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+
+ if (tt_status < 0)
+ return 0;
+ }
+
+ if (ttrace_state.tts_event & TTEVT_VFORK)
+ {
+ *childpid = ttrace_state.tts_u.tts_fork.tts_fpid;
+ return 1;
+ }
+
+ return 0;
+}
+#endif
+
+
+#if defined(CHILD_CAN_FOLLOW_VFORK_PRIOR_TO_EXEC)
+int
+child_can_follow_vfork_prior_to_exec ()
+{
+ /* ttrace does allow this.
+
+ ??rehrauer: However, I had major-league problems trying to
+ convince wait_for_inferior to handle that case. Perhaps when
+ it is rewritten to grok multiple processes in an explicit way...
+ */
+ return 0;
+}
+#endif
+
+
+#if defined(CHILD_INSERT_EXEC_CATCHPOINT)
+int
+child_insert_exec_catchpoint (tid)
+ int tid;
+{
+ /* Enable reporting of exec events from the kernel. */
+ /* ??rehrauer: For the moment, we're always enabling these events,
+ and just ignoring them if there's no catchpoint to catch them.
+ */
+ return 0;
+}
+#endif
+
+
+#if defined(CHILD_REMOVE_EXEC_CATCHPOINT)
+int
+child_remove_exec_catchpoint (tid)
+ int tid;
+{
+ /* Disable reporting of execevents from the kernel. */
+ /* ??rehrauer: For the moment, we're always enabling these events,
+ and just ignoring them if there's no catchpoint to catch them.
+ */
+ return 0;
+}
+#endif
+
+
+#if defined(CHILD_HAS_EXECD)
+int
+child_has_execd (tid, execd_pathname)
+ int tid;
+ char ** execd_pathname;
+{
+ int tt_status;
+ ttstate_t ttrace_state;
+ thread_info * tinfo;
+
+ /* Do we have cached thread state that we can consult? If so, use it. */
+ tinfo = find_thread_info (map_from_gdb_tid (tid));
+ if (tinfo != NULL)
+ copy_ttstate_t (&ttrace_state, &tinfo->last_stop_state);
+
+ /* Nope, must read the thread's current state */
+ else
+ {
+ tt_status = call_ttrace (TT_LWP_GET_STATE,
+ tid,
+ (TTRACE_ARG_TYPE) &ttrace_state,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_state),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+
+ if (tt_status < 0)
+ return 0;
+ }
+
+ if (ttrace_state.tts_event & TTEVT_EXEC)
+ {
+ /* See child_pid_to_exec_file in this file: this is a macro.
+ */
+ char * exec_file = target_pid_to_exec_file (tid);
+
+ *execd_pathname = savestring (exec_file, strlen (exec_file));
+ return 1;
+ }
+
+ return 0;
+}
+#endif
+
+
+#if defined(CHILD_HAS_SYSCALL_EVENT)
+int
+child_has_syscall_event (pid, kind, syscall_id)
+ int pid;
+ enum target_waitkind * kind;
+ int * syscall_id;
+{
+ int tt_status;
+ ttstate_t ttrace_state;
+ thread_info * tinfo;
+
+ /* Do we have cached thread state that we can consult? If so, use it. */
+ tinfo = find_thread_info (map_from_gdb_tid (pid));
+ if (tinfo != NULL)
+ copy_ttstate_t (&ttrace_state, &tinfo->last_stop_state);
+
+ /* Nope, must read the thread's current state */
+ else
+ {
+ tt_status = call_ttrace (TT_LWP_GET_STATE,
+ pid,
+ (TTRACE_ARG_TYPE) &ttrace_state,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_state),
+ TT_NIL);
+
+ if (errno)
+ perror_with_name ("ttrace");
+
+ if (tt_status < 0)
+ return 0;
+ }
+
+ *kind = TARGET_WAITKIND_SPURIOUS; /* Until proven otherwise... */
+ *syscall_id = -1;
+
+ if (ttrace_state.tts_event & TTEVT_SYSCALL_ENTRY)
+ *kind = TARGET_WAITKIND_SYSCALL_ENTRY;
+ else if (ttrace_state.tts_event & TTEVT_SYSCALL_RETURN)
+ *kind = TARGET_WAITKIND_SYSCALL_RETURN;
+ else
+ return 0;
+
+ *syscall_id = ttrace_state.tts_scno;
+ return 1;
+}
+#endif
+
+
+
+#if defined(CHILD_THREAD_ALIVE)
+
+/* Check to see if the given thread is alive.
+ *
+ * We'll trust the thread list, as the more correct
+ * approach of stopping the process and spinning down
+ * the OS's thread list is _very_ expensive.
+ *
+ * May need a FIXME for that reason.
+ */
+int
+child_thread_alive (gdb_tid)
+ lwpid_t gdb_tid;
+{
+ lwpid_t tid;
+
+ /* This spins down the lists twice.
+ * Possible peformance improvement here!
+ */
+ tid = map_from_gdb_tid( gdb_tid );
+ return !is_terminated( tid );
+}
+
+#endif
+
+
+
+/* This function attempts to read the specified number of bytes from the
+ save_state_t that is our view into the hardware registers, starting at
+ ss_offset, and ending at ss_offset + sizeof_buf - 1
+
+ If this function succeeds, it deposits the fetched bytes into buf,
+ and returns 0.
+
+ If it fails, it returns a negative result. The contents of buf are
+ undefined it this function fails.
+ */
+int
+read_from_register_save_state (tid, ss_offset, buf, sizeof_buf)
+ int tid;
+ TTRACE_ARG_TYPE ss_offset;
+ char * buf;
+ int sizeof_buf;
+{
+ int tt_status;
+ register_value_t register_value = 0;
+
+ tt_status = call_ttrace (TT_LWP_RUREGS,
+ tid,
+ ss_offset,
+ (TTRACE_ARG_TYPE) sizeof_buf,
+ (TTRACE_ARG_TYPE) buf);
+
+ if( tt_status == 1 )
+ /* Map ttrace's version of success to our version.
+ * Sometime ttrace returns 0, but that's ok here.
+ */
+ return 0;
+
+ return tt_status;
+}
+
+
+/* This function attempts to write the specified number of bytes to the
+ save_state_t that is our view into the hardware registers, starting at
+ ss_offset, and ending at ss_offset + sizeof_buf - 1
+
+ If this function succeeds, it deposits the bytes in buf, and returns 0.
+
+ If it fails, it returns a negative result. The contents of the save_state_t
+ are undefined it this function fails.
+ */
+int
+write_to_register_save_state (tid, ss_offset, buf, sizeof_buf)
+ int tid;
+ TTRACE_ARG_TYPE ss_offset;
+ char * buf;
+ int sizeof_buf;
+{
+ int tt_status;
+ register_value_t register_value = 0;
+
+ tt_status = call_ttrace (TT_LWP_WUREGS,
+ tid,
+ ss_offset,
+ (TTRACE_ARG_TYPE) sizeof_buf,
+ (TTRACE_ARG_TYPE) buf);
+ return tt_status;
+}
+
+
+/* This function is a sop to the largeish number of direct calls
+ to call_ptrace that exist in other files. Rather than create
+ functions whose name abstracts away from ptrace, and change all
+ the present callers of call_ptrace, we'll do the expedient (and
+ perhaps only practical) thing.
+
+ Note HP-UX explicitly disallows a mix of ptrace & ttrace on a traced
+ process. Thus, we must translate all ptrace requests into their
+ process-specific, ttrace equivalents.
+ */
+int
+call_ptrace (pt_request, gdb_tid, addr, data)
+ int pt_request;
+ int gdb_tid;
+ PTRACE_ARG3_TYPE addr;
+ int data;
+{
+ ttreq_t tt_request;
+ TTRACE_ARG_TYPE tt_addr = (TTRACE_ARG_TYPE) addr;
+ TTRACE_ARG_TYPE tt_data = (TTRACE_ARG_TYPE) data;
+ TTRACE_ARG_TYPE tt_addr2 = TT_NIL;
+ int tt_status;
+ register_value_t register_value;
+ int read_buf;
+
+ /* Perform the necessary argument translation. Note that some
+ cases are funky enough in the ttrace realm that we handle them
+ very specially.
+ */
+ switch (pt_request) {
+ /* The following cases cannot conveniently be handled conveniently
+ by merely adjusting the ptrace arguments and feeding into the
+ generic call to ttrace at the bottom of this function.
+
+ Note that because all branches of this switch end in "return",
+ there's no need for any "break" statements.
+ */
+ case PT_SETTRC :
+ return parent_attach_all ();
+
+ case PT_RUREGS :
+ tt_status = read_from_register_save_state (gdb_tid,
+ tt_addr,
+ &register_value,
+ sizeof (register_value));
+ if (tt_status < 0)
+ return tt_status;
+ return register_value;
+
+ case PT_WUREGS :
+ register_value = (int) tt_data;
+ tt_status = write_to_register_save_state (gdb_tid,
+ tt_addr,
+ &register_value,
+ sizeof (register_value));
+ return tt_status;
+ break;
+
+ case PT_READ_I :
+ tt_status = call_ttrace (TT_PROC_RDTEXT, /* Implicit 4-byte xfer becomes block-xfer. */
+ gdb_tid,
+ tt_addr,
+ (TTRACE_ARG_TYPE) 4,
+ (TTRACE_ARG_TYPE) &read_buf);
+ if (tt_status < 0)
+ return tt_status;
+ return read_buf;
+
+ case PT_READ_D :
+ tt_status = call_ttrace (TT_PROC_RDDATA, /* Implicit 4-byte xfer becomes block-xfer. */
+ gdb_tid,
+ tt_addr,
+ (TTRACE_ARG_TYPE) 4,
+ (TTRACE_ARG_TYPE) &read_buf);
+ if (tt_status < 0)
+ return tt_status;
+ return read_buf;
+
+ case PT_ATTACH :
+ tt_status = call_real_ttrace (TT_PROC_ATTACH,
+ map_from_gdb_tid (gdb_tid),
+ (lwpid_t) TT_NIL,
+ tt_addr,
+ (TTRACE_ARG_TYPE) TT_VERSION,
+ tt_addr2);
+ if (tt_status < 0)
+ return tt_status;
+ return tt_status;
+
+ /* The following cases are handled by merely adjusting the ptrace
+ arguments and feeding into the generic call to ttrace.
+ */
+ case PT_DETACH :
+ tt_request = TT_PROC_DETACH;
+ break;
+
+ case PT_WRITE_I :
+ tt_request = TT_PROC_WRTEXT; /* Translates 4-byte xfer to block-xfer. */
+ tt_data = 4; /* This many bytes. */
+ tt_addr2 = (TTRACE_ARG_TYPE) &data; /* Address of xfer source. */
+ break;
+
+ case PT_WRITE_D :
+ tt_request = TT_PROC_WRDATA; /* Translates 4-byte xfer to block-xfer. */
+ tt_data = 4; /* This many bytes. */
+ tt_addr2 = (TTRACE_ARG_TYPE) &data; /* Address of xfer source. */
+ break;
+
+ case PT_RDTEXT :
+ tt_request = TT_PROC_RDTEXT;
+ break;
+
+ case PT_RDDATA :
+ tt_request = TT_PROC_RDDATA;
+ break;
+
+ case PT_WRTEXT :
+ tt_request = TT_PROC_WRTEXT;
+ break;
+
+ case PT_WRDATA :
+ tt_request = TT_PROC_WRDATA;
+ break;
+
+ case PT_CONTINUE :
+ tt_request = TT_PROC_CONTINUE;
+ break;
+
+ case PT_STEP :
+ tt_request = TT_LWP_SINGLE; /* Should not be making this request? */
+ break;
+
+ case PT_KILL :
+ tt_request = TT_PROC_EXIT;
+ break;
+
+ case PT_GET_PROCESS_PATHNAME :
+ tt_request = TT_PROC_GET_PATHNAME;
+ break;
+
+ default :
+ tt_request = pt_request; /* Let ttrace be the one to complain. */
+ break;
+ }
+
+ return call_ttrace (tt_request,
+ gdb_tid,
+ tt_addr,
+ tt_data,
+ tt_addr2);
+}
+
+/* Kill that pesky process!
+ */
+void
+kill_inferior ()
+{
+ int tid;
+ int wait_status;
+ thread_info * t;
+ thread_info **paranoia;
+ int para_count, i;
+
+ if (inferior_pid == 0)
+ return;
+
+ /* Walk the list of "threads", some of which are "pseudo threads",
+ aka "processes". For each that is NOT inferior_pid, stop it,
+ and detach it.
+
+ You see, we may not have just a single process to kill. If we're
+ restarting or quitting or detaching just after the inferior has
+ forked, then we've actually two processes to clean up.
+
+ But we can't just call target_mourn_inferior() for each, since that
+ zaps the target vector.
+ */
+
+ paranoia = (thread_info **) malloc( thread_head.count *
+ sizeof(thread_info *));
+ para_count = 0;
+
+ t = thread_head.head;
+ while (t) {
+
+ paranoia[ para_count ] = t;
+ for( i = 0; i < para_count; i++ ){
+ if( t->next == paranoia[i] ) {
+ warning( "Bad data in gdb's thread data; repairing." );
+ t->next = 0;
+ }
+ }
+ para_count++;
+
+ if (t->am_pseudo && (t->pid != inferior_pid))
+ {
+ /* TT_PROC_STOP doesn't require a subsequent ttrace_wait, as it
+ * generates no event.
+ */
+ call_ttrace (TT_PROC_STOP,
+ t->pid,
+ TT_NIL,
+ TT_NIL,
+ TT_NIL);
+
+ call_ttrace (TT_PROC_DETACH,
+ t->pid,
+ TT_NIL,
+ (TTRACE_ARG_TYPE) TARGET_SIGNAL_0,
+ TT_NIL);
+ }
+ t = t->next;
+ }
+
+ free( paranoia );
+
+ call_ttrace (TT_PROC_STOP,
+ inferior_pid,
+ TT_NIL,
+ TT_NIL,
+ TT_NIL);
+ target_mourn_inferior ();
+ clear_thread_info();
+}
+
+
+#ifndef CHILD_RESUME
+
+/* Sanity check a thread about to be continued.
+ */
+static void
+thread_dropping_event_check( p )
+ thread_info *p;
+{
+ if( !p->handled ) {
+ /*
+ * This seems to happen when we "next" over a
+ * "fork()" while following the parent. If it's
+ * the FORK event, that's ok. If it's a SIGNAL
+ * in the unfollowed child, that's ok to--but
+ * how can we know that's what's going on?
+ *
+ * FIXME!
+ */
+ if( p->have_state ) {
+ if( p->last_stop_state.tts_event == TTEVT_FORK ) {
+ /* Ok */
+ ;
+ }
+ else if( p->last_stop_state.tts_event == TTEVT_SIGNAL ) {
+ /* Ok, close eyes and let it happen.
+ */
+ ;
+ }
+ else {
+ /* This shouldn't happen--we're dropping a
+ * real event.
+ */
+ warning( "About to continue process %d, thread %d with unhandled event %s.",
+ p->pid, p->tid,
+ get_printable_name_of_ttrace_event(
+ p->last_stop_state.tts_event ));
+
+#ifdef PARANOIA
+ if( debug_on )
+ print_tthread( p );
+#endif
+ }
+ }
+ else {
+ /* No saved state, have to assume it failed.
+ */
+ warning( "About to continue process %d, thread %d with unhandled event.",
+ p->pid, p->tid );
+#ifdef PARANOIA
+ if( debug_on )
+ print_tthread( p );
+#endif
+ }
+ }
+
+} /* thread_dropping_event_check */
+
+/* Use a loop over the threads to continue all the threads but
+ * the one specified, which is to be stepped.
+ */
+static void
+threads_continue_all_but_one( gdb_tid, signal )
+ lwpid_t gdb_tid;
+ int signal;
+{
+ thread_info *p;
+ int thread_signal;
+ lwpid_t real_tid;
+ lwpid_t scan_tid;
+ ttstate_t state;
+ int real_pid;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Using loop over threads to step/resume with signals\n" );
+#endif
+
+ /* First update the thread list.
+ */
+ set_all_unseen();
+ real_tid = map_from_gdb_tid( gdb_tid );
+ real_pid = get_pid_for( real_tid );
+
+ scan_tid = get_process_first_stopped_thread_id( real_pid, &state );
+ while ( 0 != scan_tid ) {
+
+#ifdef THREAD_DEBUG
+ /* FIX: later should check state is stopped;
+ * state.tts_flags & TTS_STATEMASK == TTS_WASSUSPENDED
+ */
+ if( debug_on )
+ if( state.tts_flags & TTS_STATEMASK != TTS_WASSUSPENDED )
+ printf( "About to continue non-stopped thread %d\n", scan_tid );
+#endif
+
+ p = find_thread_info( scan_tid );
+ if( NULL == p ) {
+ add_tthread( real_pid, scan_tid );
+ p = find_thread_info( scan_tid );
+
+ /* This is either a newly-created thread or the
+ * result of a fork; in either case there's no
+ * actual event to worry about.
+ */
+ p->handled = 1;
+
+ if( state.tts_event != TTEVT_NONE ) {
+ /* Oops, do need to worry!
+ */
+ warning( "Unexpected thread with \"%s\" event.",
+ get_printable_name_of_ttrace_event( state.tts_event ));
+ }
+ }
+ else if( scan_tid != p->tid )
+ error( "Bad data in thread database." );
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ if( p->terminated )
+ printf( "Why are we continuing a dead thread?\n" );
+#endif
+
+ p->seen = 1;
+
+ scan_tid = get_process_next_stopped_thread_id( real_pid, &state );
+ }
+
+ /* Remove unseen threads.
+ */
+ update_thread_list();
+
+ /* Now run down the thread list and continue or step.
+ */
+ for( p = thread_head.head; p; p = p->next ) {
+
+ /* Sanity check.
+ */
+ thread_dropping_event_check( p );
+
+ /* Pass the correct signals along.
+ */
+ if( p->have_signal ) {
+ thread_signal = p->signal_value;
+ p->have_signal = 0;
+ }
+ else
+ thread_signal = 0;
+
+ if( p->tid != real_tid ) {
+ /*
+ * Not the thread of interest, so continue it
+ * as the user expects.
+ */
+ if( p->stepping_mode == DO_STEP ) {
+ /* Just step this thread.
+ */
+ call_ttrace(
+ TT_LWP_SINGLE,
+ p->tid,
+ TT_USE_CURRENT_PC,
+ (TTRACE_ARG_TYPE) target_signal_to_host( signal ),
+ TT_NIL );
+ }
+ else {
+ /* Regular continue (default case).
+ */
+ call_ttrace(
+ TT_LWP_CONTINUE,
+ p->tid,
+ TT_USE_CURRENT_PC,
+ (TTRACE_ARG_TYPE) target_signal_to_host( thread_signal ),
+ TT_NIL );
+ }
+ }
+ else {
+ /* Step the thread of interest.
+ */
+ call_ttrace(
+ TT_LWP_SINGLE,
+ real_tid,
+ TT_USE_CURRENT_PC,
+ (TTRACE_ARG_TYPE) target_signal_to_host( signal ),
+ TT_NIL );
+ }
+ } /* Loop over threads */
+} /* End threads_continue_all_but_one */
+
+/* Use a loop over the threads to continue all the threads.
+ * This is done when a signal must be sent to any of the threads.
+ */
+static void
+threads_continue_all_with_signals( gdb_tid, signal )
+ lwpid_t gdb_tid;
+ int signal;
+{
+ thread_info *p;
+ int thread_signal;
+ lwpid_t real_tid;
+ lwpid_t scan_tid;
+ ttstate_t state;
+ int real_pid;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Using loop over threads to resume with signals\n" );
+#endif
+
+ /* Scan and update thread list.
+ */
+ set_all_unseen();
+ real_tid = map_from_gdb_tid( gdb_tid );
+ real_pid = get_pid_for( real_tid );
+
+ scan_tid = get_process_first_stopped_thread_id( real_pid, &state );
+ while ( 0 != scan_tid ) {
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ if( state.tts_flags & TTS_STATEMASK != TTS_WASSUSPENDED )
+ warning( "About to continue non-stopped thread %d\n", scan_tid );
+#endif
+
+ p = find_thread_info( scan_tid );
+ if( NULL == p ) {
+ add_tthread( real_pid, scan_tid );
+ p = find_thread_info( scan_tid );
+
+ /* This is either a newly-created thread or the
+ * result of a fork; in either case there's no
+ * actual event to worry about.
+ */
+ p->handled = 1;
+
+ if( state.tts_event != TTEVT_NONE ) {
+ /* Oops, do need to worry!
+ */
+ warning( "Unexpected thread with \"%s\" event.",
+ get_printable_name_of_ttrace_event( state.tts_event ));
+ }
+ }
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ if( p->terminated )
+ printf( "Why are we continuing a dead thread? (1)\n" );
+#endif
+
+ p->seen = 1;
+
+ scan_tid = get_process_next_stopped_thread_id( real_pid, &state );
+ }
+
+ /* Remove unseen threads from our list.
+ */
+ update_thread_list();
+
+ /* Continue the threads.
+ */
+ for( p = thread_head.head; p; p = p->next ) {
+
+ /* Sanity check.
+ */
+ thread_dropping_event_check( p );
+
+ /* Pass the correct signals along.
+ */
+ if( p->tid == real_tid ) {
+ thread_signal = signal;
+ p->have_signal = 0;
+ }
+ else if( p->have_signal ) {
+ thread_signal = p->signal_value;
+ p->have_signal = 0;
+ }
+ else
+ thread_signal = 0;
+
+ if( p->stepping_mode == DO_STEP ) {
+ call_ttrace(
+ TT_LWP_SINGLE,
+ p->tid,
+ TT_USE_CURRENT_PC,
+ (TTRACE_ARG_TYPE) target_signal_to_host( signal ),
+ TT_NIL );
+ }
+ else {
+ /* Continue this thread (default case).
+ */
+ call_ttrace(
+ TT_LWP_CONTINUE,
+ p->tid,
+ TT_USE_CURRENT_PC,
+ (TTRACE_ARG_TYPE) target_signal_to_host( thread_signal ),
+ TT_NIL );
+ }
+ }
+} /* End threads_continue_all_with_signals */
+
+/* Step one thread only.
+ */
+static void
+thread_fake_step( tid, signal )
+ lwpid_t tid;
+ enum target_signal signal;
+{
+ thread_info *p;
+
+#ifdef THREAD_DEBUG
+ if( debug_on ) {
+ printf( "Doing a fake-step over a bpt, etc. for %d\n", tid );
+
+ if( is_terminated( tid ))
+ printf( "Why are we continuing a dead thread? (4)\n" );
+ }
+#endif
+
+ if( doing_fake_step )
+ warning( "Step while step already in progress." );
+
+ /* See if there's a saved signal value for this
+ * thread to be passed on, but no current signal.
+ */
+ p = find_thread_info( tid );
+ if( p != NULL ) {
+ if( p->have_signal && signal == NULL ) {
+ /* Pass on a saved signal.
+ */
+ signal = p->signal_value;
+ }
+
+ p->have_signal = 0;
+ }
+
+ if( !p->handled )
+ warning( "Internal error: continuing unhandled thread." );
+
+ call_ttrace( TT_LWP_SINGLE,
+ tid,
+ TT_USE_CURRENT_PC,
+ (TTRACE_ARG_TYPE) target_signal_to_host (signal),
+ TT_NIL );
+
+ /* Do bookkeeping so "call_ttrace_wait" knows it has to wait
+ * for this thread only, and clear any saved signal info.
+ */
+ doing_fake_step = 1;
+ fake_step_tid = tid;
+
+} /* End thread_fake_step */
+
+/* Continue one thread when a signal must be sent to it.
+ */
+static void
+threads_continue_one_with_signal( gdb_tid, signal )
+ lwpid_t gdb_tid;
+ int signal;
+{
+ thread_info *p;
+ lwpid_t real_tid;
+ int real_pid;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Continuing one thread with a signal\n" );
+#endif
+
+ real_tid = map_from_gdb_tid( gdb_tid );
+ real_pid = get_pid_for( real_tid );
+
+ p = find_thread_info( real_tid );
+ if( NULL == p ) {
+ add_tthread( real_pid, real_tid );
+ }
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ if( p->terminated )
+ printf( "Why are we continuing a dead thread? (2)\n" );
+#endif
+
+ if( !p->handled )
+ warning( "Internal error: continuing unhandled thread." );
+
+ p->have_signal = 0;
+
+ call_ttrace( TT_LWP_CONTINUE,
+ gdb_tid,
+ TT_USE_CURRENT_PC,
+ (TTRACE_ARG_TYPE) target_signal_to_host( signal ),
+ TT_NIL );
+}
+#endif
+
+#ifndef CHILD_RESUME
+
+/* Resume execution of the inferior process.
+ *
+ * This routine is in charge of setting the "handled" bits.
+ *
+ * If STEP is zero, continue it.
+ * If STEP is nonzero, single-step it.
+ *
+ * If SIGNAL is nonzero, give it that signal.
+ *
+ * If TID is -1, apply to all threads.
+ * If TID is not -1, apply to specified thread.
+ *
+ * STEP
+ * \ !0 0
+ * TID \________________________________________________
+ * |
+ * -1 | Step current Continue all threads
+ * | thread and (but which gets any
+ * | continue others signal?--We look at
+ * | "inferior_pid")
+ * |
+ * N | Step _this_ thread Continue _this_ thread
+ * | and leave others and leave others
+ * | stopped; internally stopped; used only for
+ * | used by gdb, never hardware watchpoints
+ * | a user command. and attach, never a
+ * | user command.
+ */
+void
+child_resume( gdb_tid, step, signal )
+ lwpid_t gdb_tid;
+ int step;
+ enum target_signal signal;
+{
+ int resume_all_threads;
+ lwpid_t tid;
+ process_state_t new_process_state;
+
+ resume_all_threads =
+ (gdb_tid == INFTTRACE_ALL_THREADS) ||
+ (vfork_in_flight);
+
+ if (resume_all_threads) {
+ /* Resume all threads, but first pick a tid value
+ * so we can get the pid when in call_ttrace doing
+ * the map.
+ */
+ if (vfork_in_flight)
+ tid = vforking_child_pid;
+ else
+ tid = map_from_gdb_tid( inferior_pid );
+ }
+ else
+ tid = map_from_gdb_tid( gdb_tid );
+
+#ifdef THREAD_DEBUG
+ if( debug_on ) {
+ if( more_events_left )
+ printf( "More events; " );
+
+ if( signal != 0 )
+ printf( "Sending signal %d; ", signal );
+
+ if( resume_all_threads ) {
+ if( step == 0 )
+ printf( "Continue process %d\n", tid );
+ else
+ printf( "Step/continue thread %d\n", tid );
+ }
+ else {
+ if( step == 0 )
+ printf( "Continue thread %d\n", tid );
+ else
+ printf( "Step just thread %d\n", tid );
+ }
+
+ if( vfork_in_flight )
+ printf( "Vfork in flight\n" );
+ }
+#endif
+
+ if( process_state == RUNNING )
+ warning( "Internal error in resume logic; doing resume or step anyway." );
+
+ if( !step /* Asked to continue... */
+ && resume_all_threads /* whole process.. */
+ && signal != 0 /* with a signal... */
+ && more_events_left > 0 ) { /* but we can't yet--save it! */
+
+ /* Continue with signal means we have to set the pending
+ * signal value for this thread.
+ */
+ thread_info *k;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Saving signal %d for thread %d\n", signal, tid );
+#endif
+
+ k = find_thread_info( tid );
+ if( k != NULL ) {
+ k->have_signal = 1;
+ k->signal_value = signal;
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ if( k->terminated )
+ printf( "Why are we continuing a dead thread? (3)\n" );
+#endif
+
+ }
+
+#ifdef THREAD_DEBUG
+ else if( debug_on ) {
+ printf( "No thread info for tid %d\n", tid );
+ }
+#endif
+ }
+
+ /* Are we faking this "continue" or "step"?
+ *
+ * We used to do steps by continuing all the threads for
+ * which the events had been handled already. While
+ * conceptually nicer (hides it all in a lower level), this
+ * can lead to starvation and a hang (e.g. all but one thread
+ * are unhandled at a breakpoint just before a "join" operation,
+ * and one thread is in the join, and the user wants to step that
+ * thread).
+ */
+ if( resume_all_threads /* Whole process, therefore user command */
+ && more_events_left > 0 ) { /* But we can't do this yet--fake it! */
+ thread_info *p;
+
+ if( !step ) {
+ /* No need to do any notes on a per-thread
+ * basis--we're done!
+ */
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "Faking a process resume.\n" );
+#endif
+
+ return;
+ }
+ else {
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "Faking a process step.\n" );
+#endif
+
+ }
+
+ p = find_thread_info( tid );
+ if( p == NULL ) {
+ warning( "No thread information for tid %d, 'next' command ignored.\n", tid );
+ return;
+ }
+ else {
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ if( p->terminated )
+ printf( "Why are we continuing a dead thread? (3.5)\n" );
+#endif
+
+ if( p->stepping_mode != DO_DEFAULT ) {
+ warning( "Step or continue command applied to thread which is already stepping or continuing; command ignored." );
+
+ return;
+ }
+
+ if( step )
+ p->stepping_mode = DO_STEP;
+ else
+ p->stepping_mode = DO_CONTINUE;
+
+ return;
+ } /* Have thread info */
+ } /* Must fake step or go */
+
+ /* Execept for fake-steps, from here on we know we are
+ * going to wind up with a running process which will
+ * need a real wait.
+ */
+ new_process_state = RUNNING;
+
+ /* An address of TT_USE_CURRENT_PC tells ttrace to continue from where
+ * it was. (If GDB wanted it to start some other way, we have already
+ * written a new PC value to the child.)
+ *
+ * If this system does not support PT_STEP, a higher level function will
+ * have called single_step() to transmute the step request into a
+ * continue request (by setting breakpoints on all possible successor
+ * instructions), so we don't have to worry about that here.
+ */
+ if (step) {
+ if( resume_all_threads ) {
+ /*
+ * Regular user step: other threads get a "continue".
+ */
+ threads_continue_all_but_one( tid, signal );
+ clear_all_handled();
+ clear_all_stepping_mode();
+ }
+
+ else {
+ /* "Fake step": gdb is stepping one thread over a
+ * breakpoint, watchpoint, or out of a library load
+ * event, etc. The rest just stay where they are.
+ *
+ * Also used when there are pending events: we really
+ * step the current thread, but leave the rest stopped.
+ * Users can't request this, but "wait_for_inferior"
+ * does--a lot!
+ */
+ thread_fake_step( tid, signal );
+
+ /* Clear the "handled" state of this thread, because
+ * we'll soon get a new event for it. Other events
+ * stay as they were.
+ */
+ clear_handled( tid );
+ clear_stepping_mode( tid );
+ new_process_state = FAKE_STEPPING;
+ }
+ }
+
+ else {
+ /* TT_LWP_CONTINUE can pass signals to threads,
+ * TT_PROC_CONTINUE can't. So if there are any
+ * signals to pass, we have to use the (slower)
+ * loop over the stopped threads.
+ *
+ * Equally, if we have to not continue some threads,
+ * due to saved events, we have to use the loop.
+ */
+ if( (signal != 0) || saved_signals_exist()) {
+ if( resume_all_threads ) {
+
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Doing a continue by loop of all threads\n" );
+#endif
+
+ threads_continue_all_with_signals( tid, signal );
+
+ clear_all_handled();
+ clear_all_stepping_mode();
+ }
+
+ else {
+#ifdef THREAD_DEBUG
+ printf( "Doing a continue w/signal of just thread %d\n", tid );
+#endif
+
+ threads_continue_one_with_signal( tid, signal );
+
+ /* Clear the "handled" state of this thread, because
+ * we'll soon get a new event for it. Other events
+ * can stay as they were.
+ */
+ clear_handled( tid );
+ clear_stepping_mode( tid );
+ }
+ }
+
+ else {
+ /* No signals to send.
+ */
+ if( resume_all_threads ) {
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Doing a continue by process of process %d\n", tid );
+#endif
+
+ if( more_events_left > 0 ) {
+ warning( "Losing buffered events on continue." );
+ more_events_left = 0;
+ }
+
+ call_ttrace( TT_PROC_CONTINUE,
+ tid,
+ TT_NIL,
+ TT_NIL,
+ TT_NIL );
+
+ clear_all_handled();
+ clear_all_stepping_mode();
+ }
+
+ else {
+#ifdef THREAD_DEBUG
+ if( debug_on ) {
+ printf( "Doing a continue of just thread %d\n", tid );
+ if( is_terminated( tid ))
+ printf( "Why are we continuing a dead thread? (5)\n" );
+ }
+#endif
+
+ call_ttrace( TT_LWP_CONTINUE,
+ tid,
+ TT_NIL,
+ TT_NIL,
+ TT_NIL );
+
+ /* Clear the "handled" state of this thread, because
+ * we'll soon get a new event for it. Other events
+ * can stay as they were.
+ */
+ clear_handled( tid );
+ clear_stepping_mode( tid );
+ }
+ }
+ }
+
+ process_state = new_process_state;
+
+#ifdef WAIT_BUFFER_DEBUG
+ if( debug_on )
+ printf( "Process set to %s\n",
+ get_printable_name_of_process_state (process_state) );
+#endif
+
+}
+#endif /* CHILD_RESUME */
+
+
+#ifdef ATTACH_DETACH
+/*
+ * Like it says.
+ *
+ * One worry is that we may not be attaching to "inferior_pid"
+ * and thus may not want to clear out our data. FIXME?
+ *
+ */
+static void
+update_thread_state_after_attach( pid, kind_of_go )
+ int pid;
+ attach_continue_t kind_of_go;
+{
+ int tt_status;
+ ttstate_t thread_state;
+ lwpid_t a_thread;
+ lwpid_t tid;
+
+ /* The process better be stopped.
+ */
+ if( process_state != STOPPED
+ && process_state != VFORKING )
+ warning( "Internal error attaching." );
+
+ /* Clear out old tthread info and start over. This has the
+ * side effect of ensuring that the TRAP is reported as being
+ * in the right thread (re-mapped from tid to pid).
+ *
+ * It's because we need to add the tthread _now_ that we
+ * need to call "clear_thread_info" _now_, and that's why
+ * "require_notification_of_events" doesn't clear the thread
+ * info (it's called later than this routine).
+ */
+ clear_thread_info();
+ a_thread = 0;
+
+ for (tid = get_process_first_stopped_thread_id (pid, &thread_state);
+ tid != 0;
+ tid = get_process_next_stopped_thread_id (pid, &thread_state))
+ {
+ thread_info *p;
+
+ if (a_thread == 0)
+ {
+ a_thread = tid;
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "Attaching to process %d, thread %d\n",
+ pid, a_thread );
+#endif
+ }
+
+ /* Tell ourselves and the "rest of gdb" that this thread
+ * exists.
+ *
+ * This isn't really a hack. Other thread-based versions
+ * of gdb (e.g. gnu-nat.c) seem to do the same thing.
+ *
+ * We don't need to do mapping here, as we know this
+ * is the first thread and thus gets the real pid
+ * (and is "inferior_pid").
+ *
+ * NOTE: it probably isn't the originating thread,
+ * but that doesn't matter (we hope!).
+ */
+ add_tthread( pid, tid );
+ p = find_thread_info( tid );
+ if( NULL == p ) /* ?We just added it! */
+ error( "Internal error adding a thread on attach." );
+
+ copy_ttstate_t( &p->last_stop_state, thread_state );
+ p->have_state = 1;
+
+ if( DO_ATTACH_CONTINUE == kind_of_go ) {
+ /*
+ * If we are going to CONTINUE afterwards,
+ * raising a SIGTRAP, don't bother trying to
+ * handle this event. But check first!
+ */
+ switch( p->last_stop_state.tts_event ) {
+
+ case TTEVT_NONE:
+ /* Ok to set this handled.
+ */
+ break;
+
+ default:
+ warning( "Internal error; skipping event %s on process %d, thread %d.",
+ get_printable_name_of_ttrace_event(
+ p->last_stop_state.tts_event ),
+ p->pid, p->tid);
+ }
+
+ set_handled( pid, tid );
+
+ }
+ else {
+ /* There will be no "continue" opertion, so the
+ * process remains stopped. Don't set any events
+ * handled except the "gimmies".
+ */
+ switch( p->last_stop_state.tts_event ) {
+
+ case TTEVT_NONE:
+ /* Ok to ignore this.
+ */
+ set_handled( pid, tid );
+ break;
+
+ case TTEVT_EXEC:
+ case TTEVT_FORK:
+ /* Expected "other" FORK or EXEC event from a
+ * fork or vfork.
+ */
+ break;
+
+ default:
+ printf( "Internal error: failed to handle event %s on process %d, thread %d.",
+ get_printable_name_of_ttrace_event(
+ p->last_stop_state.tts_event ),
+ p->pid, p->tid);
+ }
+ }
+
+ add_thread( tid ); /* in thread.c */
+ }
+
+#ifdef PARANOIA
+ if( debug_on )
+ print_tthreads();
+#endif
+
+ /* One mustn't call ttrace_wait() after attaching via ttrace,
+ 'cause the process is stopped already.
+
+ However, the upper layers of gdb's execution control will
+ want to wait after attaching (but not after forks, in
+ which case they will be doing a "target_resume", anticipating
+ a later TTEVT_EXEC or TTEVT_FORK event).
+
+ To make this attach() implementation more compatible with
+ others, we'll make the attached-to process raise a SIGTRAP.
+
+ Issue: this continues only one thread. That could be
+ dangerous if the thread is blocked--the process won't run
+ and no trap will be raised. FIX! (check state.tts_flags?
+ need one that's either TTS_WASRUNNING--but we've stopped
+ it and made it TTS_WASSUSPENDED. Hum...FIXME!)
+ */
+ if( DO_ATTACH_CONTINUE == kind_of_go ) {
+ tt_status = call_real_ttrace(
+ TT_LWP_CONTINUE,
+ pid,
+ a_thread,
+ TT_USE_CURRENT_PC,
+ (TTRACE_ARG_TYPE) target_signal_to_host (TARGET_SIGNAL_TRAP),
+ TT_NIL);
+ if (errno)
+ perror_with_name ("ttrace");
+
+ clear_handled( a_thread ); /* So TRAP will be reported. */
+
+ /* Now running.
+ */
+ process_state = RUNNING;
+ }
+
+ attach_flag = 1;
+}
+#endif /* ATTACH_DETACH */
+
+
+#ifdef ATTACH_DETACH
+/* Start debugging the process whose number is PID.
+ * (A _real_ pid).
+ */
+int
+attach( pid )
+ int pid;
+{
+ int tt_status;
+
+ tt_status = call_real_ttrace (
+ TT_PROC_ATTACH,
+ pid,
+ (lwpid_t) TT_NIL,
+ TT_NIL,
+ (TTRACE_ARG_TYPE) TT_VERSION,
+ TT_NIL);
+ if (errno)
+ perror_with_name ("ttrace attach");
+
+ /* If successful, the process is now stopped.
+ */
+ process_state = STOPPED;
+
+ /* Our caller ("attach_command" in "infcmd.c")
+ * expects to do a "wait_for_inferior" after
+ * the attach, so make sure the inferior is
+ * running when we're done.
+ */
+ update_thread_state_after_attach( pid, DO_ATTACH_CONTINUE );
+
+ return pid;
+}
+
+
+#if defined(CHILD_POST_ATTACH)
+void
+child_post_attach (pid)
+ int pid;
+{
+#ifdef THREAD_DEBUG
+ if( debug_on )
+ printf( "child-post-attach call\n" );
+#endif
+
+ require_notification_of_events (pid);
+}
+#endif
+
+
+/* Stop debugging the process whose number is PID
+ and continue it with signal number SIGNAL.
+ SIGNAL = 0 means just continue it.
+ */
+void
+detach( signal )
+ int signal;
+{
+ errno = 0;
+ call_ttrace (TT_PROC_DETACH,
+ inferior_pid,
+ TT_NIL,
+ (TTRACE_ARG_TYPE) signal,
+ TT_NIL);
+ attach_flag = 0;
+
+ clear_thread_info();
+
+ /* Process-state? */
+}
+#endif /* ATTACH_DETACH */
+
+
+/* Default the type of the ttrace transfer to int. */
+#ifndef TTRACE_XFER_TYPE
+#define TTRACE_XFER_TYPE int
+#endif
+
+void
+_initialize_kernel_u_addr ()
+{
+}
+
+#if !defined (CHILD_XFER_MEMORY)
+/* NOTE! I tried using TTRACE_READDATA, etc., to read and write memory
+ in the NEW_SUN_TTRACE case.
+ It ought to be straightforward. But it appears that writing did
+ not write the data that I specified. I cannot understand where
+ it got the data that it actually did write. */
+
+/* Copy LEN bytes to or from inferior's memory starting at MEMADDR
+ to debugger memory starting at MYADDR. Copy to inferior if
+ WRITE is nonzero.
+
+ Returns the length copied, which is either the LEN argument or zero.
+ This xfer function does not do partial moves, since child_ops
+ doesn't allow memory operations to cross below us in the target stack
+ anyway. */
+
+int
+child_xfer_memory (memaddr, myaddr, len, write, target)
+ CORE_ADDR memaddr;
+ char *myaddr;
+ int len;
+ int write;
+ struct target_ops *target; /* ignored */
+{
+ register int i;
+ /* Round starting address down to longword boundary. */
+ register CORE_ADDR addr = memaddr & - sizeof (TTRACE_XFER_TYPE);
+ /* Round ending address up; get number of longwords that makes. */
+ register int count
+ = (((memaddr + len) - addr) + sizeof (TTRACE_XFER_TYPE) - 1)
+ / sizeof (TTRACE_XFER_TYPE);
+ /* Allocate buffer of that many longwords. */
+ register TTRACE_XFER_TYPE *buffer
+ = (TTRACE_XFER_TYPE *) alloca (count * sizeof (TTRACE_XFER_TYPE));
+
+ if (write)
+ {
+ /* Fill start and end extra bytes of buffer with existing memory data. */
+
+ if (addr != memaddr || len < (int) sizeof (TTRACE_XFER_TYPE)) {
+ /* Need part of initial word -- fetch it. */
+ buffer[0] = call_ttrace (TT_LWP_RDTEXT,
+ inferior_pid,
+ (TTRACE_ARG_TYPE) addr,
+ TT_NIL,
+ TT_NIL);
+ }
+
+ if (count > 1) /* FIXME, avoid if even boundary */
+ {
+ buffer[count - 1] = call_ttrace (TT_LWP_RDTEXT,
+ inferior_pid,
+ ((TTRACE_ARG_TYPE)
+ (addr + (count - 1) * sizeof (TTRACE_XFER_TYPE))),
+ TT_NIL,
+ TT_NIL);
+ }
+
+ /* Copy data to be written over corresponding part of buffer */
+
+ memcpy ((char *) buffer + (memaddr & (sizeof (TTRACE_XFER_TYPE) - 1)),
+ myaddr,
+ len);
+
+ /* Write the entire buffer. */
+
+ for (i = 0; i < count; i++, addr += sizeof (TTRACE_XFER_TYPE))
+ {
+ errno = 0;
+ call_ttrace (TT_LWP_WRDATA,
+ inferior_pid,
+ (TTRACE_ARG_TYPE) addr,
+ (TTRACE_ARG_TYPE) buffer[i],
+ TT_NIL);
+ if (errno)
+ {
+ /* Using the appropriate one (I or D) is necessary for
+ Gould NP1, at least. */
+ errno = 0;
+ call_ttrace (TT_LWP_WRTEXT,
+ inferior_pid,
+ (TTRACE_ARG_TYPE) addr,
+ (TTRACE_ARG_TYPE) buffer[i],
+ TT_NIL);
+ }
+ if (errno)
+ return 0;
+ }
+ }
+ else
+ {
+ /* Read all the longwords */
+ for (i = 0; i < count; i++, addr += sizeof (TTRACE_XFER_TYPE))
+ {
+ errno = 0;
+ buffer[i] = call_ttrace (TT_LWP_RDTEXT,
+ inferior_pid,
+ (TTRACE_ARG_TYPE) addr,
+ TT_NIL,
+ TT_NIL);
+ if (errno)
+ return 0;
+ QUIT;
+ }
+
+ /* Copy appropriate bytes out of the buffer. */
+ memcpy (myaddr,
+ (char *) buffer + (memaddr & (sizeof (TTRACE_XFER_TYPE) - 1)),
+ len);
+ }
+ return len;
+}
+
+
+static void
+udot_info ()
+{
+ int udot_off; /* Offset into user struct */
+ int udot_val; /* Value from user struct at udot_off */
+ char mess[128]; /* For messages */
+
+ if (!target_has_execution)
+ {
+ error ("The program is not being run.");
+ }
+
+#if !defined (KERNEL_U_SIZE)
+
+ /* Adding support for this command is easy. Typically you just add a
+ routine, called "kernel_u_size" that returns the size of the user
+ struct, to the appropriate *-nat.c file and then add to the native
+ config file "#define KERNEL_U_SIZE kernel_u_size()" */
+ error ("Don't know how large ``struct user'' is in this version of gdb.");
+
+#else
+
+ for (udot_off = 0; udot_off < KERNEL_U_SIZE; udot_off += sizeof (udot_val))
+ {
+ if ((udot_off % 24) == 0)
+ {
+ if (udot_off > 0)
+ {
+ printf_filtered ("\n");
+ }
+ printf_filtered ("%04x:", udot_off);
+ }
+ udot_val = call_ttrace (TT_LWP_RUREGS,
+ inferior_pid,
+ (TTRACE_ARG_TYPE) udot_off,
+ TT_NIL,
+ TT_NIL);
+ if (errno != 0)
+ {
+ sprintf (mess, "\nreading user struct at offset 0x%x", udot_off);
+ perror_with_name (mess);
+ }
+ /* Avoid using nonportable (?) "*" in print specs */
+ printf_filtered (sizeof (int) == 4 ? " 0x%08x" : " 0x%16x", udot_val);
+ }
+ printf_filtered ("\n");
+
+#endif
+}
+#endif /* !defined (CHILD_XFER_MEMORY). */
+
+/* TTrace version of "target_pid_to_exec_file"
+ */
+char *
+child_pid_to_exec_file (tid)
+ int tid;
+{
+ static char exec_file_buffer[1024];
+ int tt_status;
+ CORE_ADDR top_of_stack;
+ char four_chars[4];
+ int name_index;
+ int i;
+ int done;
+ int saved_inferior_pid;
+
+ /* As of 10.x HP-UX, there's an explicit request to get the
+ *pathname.
+ */
+ tt_status = call_ttrace (TT_PROC_GET_PATHNAME,
+ tid,
+ (TTRACE_ARG_TYPE) exec_file_buffer,
+ (TTRACE_ARG_TYPE) sizeof (exec_file_buffer) - 1,
+ TT_NIL);
+ if (tt_status >= 0)
+ return exec_file_buffer;
+
+ /* ??rehrauer: The above request may or may not be broken. It
+ doesn't seem to work when I use it. But, it may be designed
+ to only work immediately after an exec event occurs. (I'm
+ waiting for COSL to explain.)
+
+ In any case, if it fails, try a really, truly amazingly gross
+ hack that DDE uses, of pawing through the process' data
+ segment to find the pathname.
+ */
+ top_of_stack = 0x7b03a000;
+ name_index = 0;
+ done = 0;
+
+ /* On the chance that pid != inferior_pid, set inferior_pid
+ to pid, so that (grrrr!) implicit uses of inferior_pid get
+ the right id.
+ */
+ saved_inferior_pid = inferior_pid;
+ inferior_pid = tid;
+
+ /* Try to grab a null-terminated string. */
+ while (! done) {
+ if (target_read_memory (top_of_stack, four_chars, 4) != 0)
+ {
+ inferior_pid = saved_inferior_pid;
+ return NULL;
+ }
+ for (i = 0; i < 4; i++) {
+ exec_file_buffer[name_index++] = four_chars[i];
+ done = (four_chars[i] == '\0');
+ if (done)
+ break;
+ }
+ top_of_stack += 4;
+ }
+
+ if (exec_file_buffer[0] == '\0')
+ {
+ inferior_pid = saved_inferior_pid;
+ return NULL;
+ }
+
+ inferior_pid = saved_inferior_pid;
+ return exec_file_buffer;
+}
+
+
+void
+pre_fork_inferior ()
+{
+ int status;
+
+ status = pipe (startup_semaphore.parent_channel);
+ if (status < 0) {
+ warning ("error getting parent pipe for startup semaphore");
+ return;
+ }
+
+ status = pipe (startup_semaphore.child_channel);
+ if (status < 0) {
+ warning ("error getting child pipe for startup semaphore");
+ return;
+ }
+}
+
+/* Called via #define REQUIRE_ATTACH from inftarg.c,
+ * ultimately from "follow_inferior_fork" in infrun.c,
+ * itself called from "resume".
+ *
+ * This seems to be intended to attach after a fork or
+ * vfork, while "attach" is used to attach to a pid
+ * given by the user. The check for an existing attach
+ * seems odd--it always fails in our test system.
+ */
+int
+hppa_require_attach (pid)
+ int pid;
+{
+ int tt_status;
+ CORE_ADDR pc;
+ CORE_ADDR pc_addr;
+ unsigned int regs_offset;
+ process_state_t old_process_state = process_state;
+
+ /* Are we already attached? There appears to be no explicit
+ * way to answer this via ttrace, so we try something which
+ * should be innocuous if we are attached. If that fails,
+ * then we assume we're not attached, and so attempt to make
+ * it so.
+ */
+ errno = 0;
+ tt_status = call_real_ttrace (TT_PROC_STOP,
+ pid,
+ (lwpid_t) TT_NIL,
+ (TTRACE_ARG_TYPE) TT_NIL,
+ (TTRACE_ARG_TYPE) TT_NIL,
+ TT_NIL);
+
+ if (errno)
+ {
+ /* No change to process-state!
+ */
+ errno = 0;
+ pid = attach (pid);
+ }
+ else
+ {
+ /* If successful, the process is now stopped. But if
+ * we're VFORKING, the parent is still running, so don't
+ * change the process state.
+ */
+ if( process_state != VFORKING )
+ process_state = STOPPED;
+
+ /* If we were already attached, you'd think that we
+ * would need to start going again--but you'd be wrong,
+ * as the fork-following code is actually in the middle
+ * of the "resume" routine in in "infrun.c" and so
+ * will (almost) immediately do a resume.
+ *
+ * On the other hand, if we are VFORKING, which means
+ * that the child and the parent share a process for a
+ * while, we know that "resume" won't be resuming
+ * until the child EXEC event is seen. But we still
+ * don't want to continue, as the event is already
+ * there waiting.
+ */
+ update_thread_state_after_attach( pid, DONT_ATTACH_CONTINUE );
+ } /* STOP succeeded */
+
+ return pid;
+}
+
+int
+hppa_require_detach (pid, signal)
+ int pid;
+ int signal;
+{
+ int tt_status;
+
+ /* If signal is non-zero, we must pass the signal on to the active
+ thread prior to detaching. We do this by continuing the threads
+ with the signal.
+ */
+ if (signal != 0)
+ {
+ errno = 0;
+ threads_continue_all_with_signals( pid, signal );
+ }
+
+ errno = 0;
+ tt_status = call_ttrace (TT_PROC_DETACH,
+ pid,
+ TT_NIL,
+ TT_NIL,
+ TT_NIL);
+
+ errno = 0; /* Ignore any errors. */
+
+ /* process_state? */
+
+ return pid;
+}
+
+/* Given the starting address of a memory page, hash it to a bucket in
+ the memory page dictionary.
+ */
+static int
+get_dictionary_bucket_of_page (page_start)
+ CORE_ADDR page_start;
+{
+ int hash;
+
+ hash = (page_start / memory_page_dictionary.page_size);
+ hash = hash % MEMORY_PAGE_DICTIONARY_BUCKET_COUNT;
+
+ return hash;
+}
+
+
+/* Given a memory page's starting address, get (i.e., find an existing
+ or create a new) dictionary entry for the page. The page will be
+ write-protected when this function returns, but may have a reference
+ count of 0 (if the page was newly-added to the dictionary).
+ */
+static memory_page_t *
+get_dictionary_entry_of_page (pid, page_start)
+ int pid;
+ CORE_ADDR page_start;
+{
+ int bucket;
+ memory_page_t * page = NULL;
+ memory_page_t * previous_page = NULL;
+
+ /* We're going to be using the dictionary now, than-kew. */
+ require_memory_page_dictionary (pid);
+
+ /* Try to find an existing dictionary entry for this page. Hash
+ on the page's starting address.
+ */
+ bucket = get_dictionary_bucket_of_page (page_start);
+ page = &memory_page_dictionary.buckets[bucket];
+ while (page != NULL)
+ {
+ if (page->page_start == page_start)
+ break;
+ previous_page = page;
+ page = page->next;
+ }
+
+ /* Did we find a dictionary entry for this page? If not, then
+ add it to the dictionary now.
+ */
+ if (page == NULL)
+ {
+ /* Create a new entry. */
+ page = (memory_page_t *) xmalloc (sizeof (memory_page_t));
+ page->page_start = page_start;
+ page->reference_count = 0;
+ page->next = NULL;
+ page->previous = NULL;
+
+ /* We'll write-protect the page now, if that's allowed. */
+ page->original_permissions = write_protect_page (pid, page_start);
+
+ /* Add the new entry to the dictionary. */
+ page->previous = previous_page;
+ previous_page->next = page;
+
+ memory_page_dictionary.page_count++;
+ }
+
+ return page;
+}
+
+
+static void
+remove_dictionary_entry_of_page (pid, page)
+ int pid;
+ memory_page_t * page;
+{
+ /* Restore the page's original permissions. */
+ unwrite_protect_page (pid, page->page_start, page->original_permissions);
+
+ /* Kick the page out of the dictionary. */
+ if (page->previous != NULL)
+ page->previous->next = page->next;
+ if (page->next != NULL)
+ page->next->previous = page->previous;
+
+ /* Just in case someone retains a handle to this after it's freed. */
+ page->page_start = (CORE_ADDR) 0;
+
+ memory_page_dictionary.page_count--;
+
+ free (page);
+}
+
+
+static void
+hppa_enable_syscall_events (pid)
+ int pid;
+{
+ int tt_status;
+ ttevent_t ttrace_events;
+
+ /* Get the set of events that are currently enabled. */
+ tt_status = call_ttrace (TT_PROC_GET_EVENT_MASK,
+ pid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+ if (errno)
+ perror_with_name ("ttrace");
+
+ /* Add syscall events to that set. */
+ ttrace_events.tte_events |= TTEVT_SYSCALL_ENTRY;
+ ttrace_events.tte_events |= TTEVT_SYSCALL_RETURN;
+
+ tt_status = call_ttrace (TT_PROC_SET_EVENT_MASK,
+ pid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+ if (errno)
+ perror_with_name ("ttrace");
+}
+
+
+static void
+hppa_disable_syscall_events (pid)
+ int pid;
+{
+ int tt_status;
+ ttevent_t ttrace_events;
+
+ /* Get the set of events that are currently enabled. */
+ tt_status = call_ttrace (TT_PROC_GET_EVENT_MASK,
+ pid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+ if (errno)
+ perror_with_name ("ttrace");
+
+ /* Remove syscall events from that set. */
+ ttrace_events.tte_events &= ~TTEVT_SYSCALL_ENTRY;
+ ttrace_events.tte_events &= ~TTEVT_SYSCALL_RETURN;
+
+ tt_status = call_ttrace (TT_PROC_SET_EVENT_MASK,
+ pid,
+ (TTRACE_ARG_TYPE) &ttrace_events,
+ (TTRACE_ARG_TYPE) sizeof (ttrace_events),
+ TT_NIL);
+ if (errno)
+ perror_with_name ("ttrace");
+}
+
+
+/* The address range beginning with START and ending with START+LEN-1
+ (inclusive) is to be watched via page-protection by a new watchpoint.
+ Set protection for all pages that overlap that range.
+
+ Note that our caller sets TYPE to:
+ 0 for a bp_hardware_watchpoint,
+ 1 for a bp_read_watchpoint,
+ 2 for a bp_access_watchpoint
+
+ (Yes, this is intentionally (though lord only knows why) different
+ from the TYPE that is passed to hppa_remove_hw_watchpoint.)
+ */
+int
+hppa_insert_hw_watchpoint (pid, start, len, type)
+ int pid;
+ CORE_ADDR start;
+ LONGEST len;
+ int type;
+{
+ CORE_ADDR page_start;
+ int dictionary_was_empty;
+ int page_size;
+ int page_id;
+ LONGEST range_size_in_pages;
+
+ if (type != 0)
+ error ("read or access hardware watchpoints not supported on HP-UX");
+
+ /* Examine all pages in the address range. */
+ require_memory_page_dictionary ();
+
+ dictionary_was_empty = (memory_page_dictionary.page_count == (LONGEST) 0);
+
+ page_size = memory_page_dictionary.page_size;
+ page_start = (start / page_size) * page_size;
+ range_size_in_pages = ((LONGEST) len + (LONGEST) page_size - 1) / (LONGEST) page_size;
+
+ for (page_id=0; page_id < range_size_in_pages; page_id++, page_start+=page_size)
+ {
+ memory_page_t * page;
+
+ /* This gets the page entered into the dictionary if it was
+ not already entered.
+ */
+ page = get_dictionary_entry_of_page (pid, page_start);
+ page->reference_count++;
+ }
+
+ /* Our implementation depends on seeing calls to kernel code, for the
+ following reason. Here we ask to be notified of syscalls.
+
+ When a protected page is accessed by user code, HP-UX raises a SIGBUS.
+ Fine.
+
+ But when kernel code accesses the page, it doesn't give a SIGBUS.
+ Rather, the system call that touched the page fails, with errno=EFAULT.
+ Not good for us.
+
+ We could accomodate this "feature" by asking to be notified of syscall
+ entries & exits; upon getting an entry event, disabling page-protections;
+ upon getting an exit event, reenabling page-protections and then checking
+ if any watchpoints triggered.
+
+ However, this turns out to be a real performance loser. syscalls are
+ usually a frequent occurrence. Having to unprotect-reprotect all watched
+ pages, and also to then read all watched memory locations and compare for
+ triggers, can be quite expensive.
+
+ Instead, we'll only ask to be notified of syscall exits. When we get
+ one, we'll check whether errno is set. If not, or if it's not EFAULT,
+ we can just continue the inferior.
+
+ If errno is set upon syscall exit to EFAULT, we must perform some fairly
+ hackish stuff to determine whether the failure really was due to a
+ page-protect trap on a watched location.
+ */
+ if (dictionary_was_empty)
+ hppa_enable_syscall_events (pid);
+
+ return 1;
+}
+
+
+/* The address range beginning with START and ending with START+LEN-1
+ (inclusive) was being watched via page-protection by a watchpoint
+ which has been removed. Remove protection for all pages that
+ overlap that range, which are not also being watched by other
+ watchpoints.
+ */
+int
+hppa_remove_hw_watchpoint (pid, start, len, type)
+ int pid;
+ CORE_ADDR start;
+ LONGEST len;
+ enum bptype type;
+{
+ CORE_ADDR page_start;
+ int dictionary_is_empty;
+ int page_size;
+ int page_id;
+ LONGEST range_size_in_pages;
+
+ if (type != 0)
+ error ("read or access hardware watchpoints not supported on HP-UX");
+
+ /* Examine all pages in the address range. */
+ require_memory_page_dictionary ();
+
+ page_size = memory_page_dictionary.page_size;
+ page_start = (start / page_size) * page_size;
+ range_size_in_pages = ((LONGEST) len + (LONGEST) page_size - 1) / (LONGEST) page_size;
+
+ for (page_id=0; page_id < range_size_in_pages; page_id++, page_start+=page_size)
+ {
+ memory_page_t * page;
+
+ page = get_dictionary_entry_of_page (pid, page_start);
+ page->reference_count--;
+
+ /* Was this the last reference of this page? If so, then we
+ must scrub the entry from the dictionary, and also restore
+ the page's original permissions.
+ */
+ if (page->reference_count == 0)
+ remove_dictionary_entry_of_page (pid, page);
+ }
+
+ dictionary_is_empty = (memory_page_dictionary.page_count == (LONGEST) 0);
+
+ /* If write protections are currently disallowed, then that implies that
+ wait_for_inferior believes that the inferior is within a system call.
+ Since we want to see both syscall entry and return, it's clearly not
+ good to disable syscall events in this state!
+
+ ??rehrauer: Yeah, it'd be better if we had a specific flag that said,
+ "inferior is between syscall events now". Oh well.
+ */
+ if (dictionary_is_empty && memory_page_dictionary.page_protections_allowed)
+ hppa_disable_syscall_events (pid);
+
+ return 1;
+}
+
+
+/* Could we implement a watchpoint of this type via our available
+ hardware support?
+
+ This query does not consider whether a particular address range
+ could be so watched, but just whether support is generally available
+ for such things. See hppa_range_profitable_for_hw_watchpoint for a
+ query that answers whether a particular range should be watched via
+ hardware support.
+ */
+int
+hppa_can_use_hw_watchpoint (type, cnt, ot)
+ enum bptype type;
+ int cnt;
+ enum bptype ot;
+{
+ return (type == bp_hardware_watchpoint);
+}
+
+
+/* Assuming we could set a hardware watchpoint on this address, do
+ we think it would be profitable ("a good idea") to do so? If not,
+ we can always set a regular (aka single-step & test) watchpoint
+ on the address...
+ */
+int
+hppa_range_profitable_for_hw_watchpoint (pid, start, len)
+ int pid;
+ CORE_ADDR start;
+ LONGEST len;
+{
+ int range_is_stack_based;
+ int range_is_accessible;
+ CORE_ADDR page_start;
+ int page_size;
+ int page;
+ LONGEST range_size_in_pages;
+
+ /* ??rehrauer: For now, say that all addresses are potentially
+ profitable. Possibly later we'll want to test the address
+ for "stackness"?
+ */
+ range_is_stack_based = 0;
+
+ /* If any page in the range is inaccessible, then we cannot
+ really use hardware watchpointing, even though our client
+ thinks we can. In that case, it's actually an error to
+ attempt to use hw watchpoints, so we'll tell our client
+ that the range is "unprofitable", and hope that they listen...
+ */
+ range_is_accessible = 1; /* Until proven otherwise. */
+
+ /* Examine all pages in the address range. */
+ errno = 0;
+ page_size = sysconf (_SC_PAGE_SIZE);
+
+ /* If we can't determine page size, we're hosed. Tell our
+ client it's unprofitable to use hw watchpoints for this
+ range.
+ */
+ if (errno || (page_size <= 0))
+ {
+ errno = 0;
+ return 0;
+ }
+
+ page_start = (start / page_size) * page_size;
+ range_size_in_pages = len / (LONGEST)page_size;
+
+ for (page=0; page < range_size_in_pages; page++, page_start+=page_size)
+ {
+ int tt_status;
+ int page_permissions;
+
+ /* Is this page accessible? */
+ errno = 0;
+ tt_status = call_ttrace (TT_PROC_GET_MPROTECT,
+ pid,
+ (TTRACE_ARG_TYPE) page_start,
+ TT_NIL,
+ (TTRACE_ARG_TYPE) &page_permissions);
+ if (errno || (tt_status < 0))
+ {
+ errno = 0;
+ range_is_accessible = 0;
+ break;
+ }
+
+ /* Yes, go for another... */
+ }
+
+ return (! range_is_stack_based && range_is_accessible);
+}
+
+
+char *
+hppa_pid_or_tid_to_str (id)
+ pid_t id;
+{
+ static char buf[100]; /* Static because address returned. */
+
+ /* Does this appear to be a process? If so, print it that way. */
+ if (is_process_id (id))
+ return hppa_pid_to_str (id);
+
+ /* Else, print both the GDB thread number and the system thread id. */
+ sprintf (buf, "thread %d (", pid_to_thread_id (id));
+ strcat (buf, hppa_tid_to_str (id));
+ strcat (buf, ")\0");
+
+ return buf;
+}
+
+
+/* If the current pid is not the pid this module reported
+ * from "ptrace_wait" with the most recent event, then the
+ * user has switched threads.
+ *
+ * If the last reported event was a breakpoint, then return
+ * the old thread id, else return 0.
+ */
+pid_t
+hppa_switched_threads( gdb_pid )
+ pid_t gdb_pid;
+{
+ if( gdb_pid == old_gdb_pid ) {
+ /*
+ * Core gdb is working with the same pid that it
+ * was before we reported the last event. This
+ * is ok: e.g. we reported hitting a thread-specific
+ * breakpoint, but we were reporting the wrong
+ * thread, so the core just ignored the event.
+ *
+ * No thread switch has happened.
+ */
+ return (pid_t) 0;
+ }
+ else if( gdb_pid == reported_pid ) {
+ /*
+ * Core gdb is working with the pid we reported, so
+ * any continue or step will be able to figure out
+ * that it needs to step over any hit breakpoints
+ * without our (i.e. PREPARE_TO_PROCEED's) help.
+ */
+ return (pid_t) 0;
+ }
+ else if( !reported_bpt ) {
+ /*
+ * The core switched, but we didn't just report a
+ * breakpoint, so there's no just-hit breakpoint
+ * instruction at "reported_pid"'s PC, and thus there
+ * is no need to step over it.
+ */
+ return (pid_t) 0;
+ }
+ else {
+ /* There's been a real switch, and we reported
+ * a hit breakpoint. Let "hppa_prepare_to_proceed"
+ * know, so it can see whether the breakpoint is
+ * still active.
+ */
+ return reported_pid;
+ }
+
+ /* Keep compiler happy with an obvious return at the end.
+ */
+ return (pid_t) 0;
+}
+
+void
+hppa_ensure_vforking_parent_remains_stopped (pid)
+ int pid;
+{
+ /* Nothing to do when using ttrace. Only the ptrace-based implementation
+ must do real work.
+ */
+}
+
+
+int
+hppa_resume_execd_vforking_child_to_get_parent_vfork ()
+{
+ return 0; /* No, the parent vfork is available now. */
+}
+
+
+
+void
+_initialize_infttrace ()
+{
+ /* Initialize the ttrace-based hardware watchpoint implementation. */
+ memory_page_dictionary.page_count = (LONGEST) -1;
+ memory_page_dictionary.page_protections_allowed = 1;
+
+ errno = 0;
+ memory_page_dictionary.page_size = sysconf (_SC_PAGE_SIZE);
+
+ if (errno || (memory_page_dictionary.page_size <= 0))
+ perror_with_name ("sysconf");
+}
+