diff options
author | David Taylor <taylor@redhat.com> | 1998-12-31 21:58:30 +0000 |
---|---|---|
committer | David Taylor <taylor@redhat.com> | 1998-12-31 21:58:30 +0000 |
commit | 65b07ddca8832033e0e102c3a2a0d9f9f5922a9d (patch) | |
tree | 7fc8cee254f271f4cc57e64bcb23576fa121e706 /gdb/infttrace.c | |
parent | c450a7fe3f5214f42118a04639074d0e3883582c (diff) | |
download | gdb-65b07ddca8832033e0e102c3a2a0d9f9f5922a9d.zip gdb-65b07ddca8832033e0e102c3a2a0d9f9f5922a9d.tar.gz gdb-65b07ddca8832033e0e102c3a2a0d9f9f5922a9d.tar.bz2 |
all remaining *.c *.h files from hp merge.
Diffstat (limited to 'gdb/infttrace.c')
-rw-r--r-- | gdb/infttrace.c | 5709 |
1 files changed, 5709 insertions, 0 deletions
diff --git a/gdb/infttrace.c b/gdb/infttrace.c new file mode 100644 index 0000000..e23f163 --- /dev/null +++ b/gdb/infttrace.c @@ -0,0 +1,5709 @@ +/* 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> +#include <sys/unistd.h> +#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 + * proc_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 +proc_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 (¬ifiable_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) ¬ifiable_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 (¬ifiable_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) ¬ifiable_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, + ®ister_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, + ®ister_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; +} + +#if defined(HPPA_GET_PROCESS_EVENTS) +process_event_vector +hppa_get_process_events (pid, wait_status, must_continue_pid_after) + int pid; + int wait_status; + int * must_continue_pid_after; +{ + int tt_status; + ttstate_t ttrace_state; + process_event_vector events = PEVT_NONE; + + /* This is always 1 with ptrace. */ + *must_continue_pid_after = 0; + + errno = 0; + 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; + + if (ttrace_state.tts_event & TTEVT_SIGNAL) + events |= PEVT_SIGNAL; + if (ttrace_state.tts_event & TTEVT_FORK) + events |= PEVT_FORK; + if (ttrace_state.tts_event & TTEVT_VFORK) + events |= PEVT_VFORK; + if (ttrace_state.tts_event & TTEVT_EXEC) + events |= PEVT_EXEC; + if (ttrace_state.tts_event & TTEVT_EXIT) + events |= PEVT_EXIT; + + return events; +} +#endif /* HPPA_GET_PROCESS_EVENTS */ + + +/* 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); +} + + +/* 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); +} + + +/* 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 "proc_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"); +} + |