mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-01 05:55:23 +08:00
5675 lines
158 KiB
C
5675 lines
158 KiB
C
|
/* Low level Unix child interface to ttrace, for GDB when running under HP-UX.
|
|||
|
Copyright 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996
|
|||
|
Free Software Foundation, Inc.
|
|||
|
|
|||
|
This file is part of GDB.
|
|||
|
|
|||
|
This program is free software; you can redistribute it and/or modify
|
|||
|
it under the terms of the GNU General Public License as published by
|
|||
|
the Free Software Foundation; either version 2 of the License, or
|
|||
|
(at your option) any later version.
|
|||
|
|
|||
|
This program is distributed in the hope that it will be useful,
|
|||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
GNU General Public License for more details.
|
|||
|
|
|||
|
You should have received a copy of the GNU General Public License
|
|||
|
along with this program; if not, write to the Free Software
|
|||
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
|
|||
|
|
|||
|
#include "defs.h"
|
|||
|
#include "frame.h"
|
|||
|
#include "inferior.h"
|
|||
|
#include "target.h"
|
|||
|
#include "gdb_string.h"
|
|||
|
#include "wait.h"
|
|||
|
#include "command.h"
|
|||
|
|
|||
|
/* Some hackery to work around a use of the #define name NO_FLAGS
|
|||
|
* in both gdb and HPUX (bfd.h and /usr/include/machine/vmparam.h).
|
|||
|
*/
|
|||
|
#ifdef NO_FLAGS
|
|||
|
#define INFTTRACE_TEMP_HACK NO_FLAGS
|
|||
|
#undef NO_FLAGS
|
|||
|
#endif
|
|||
|
|
|||
|
#ifdef USG
|
|||
|
#include <sys/types.h>
|
|||
|
#endif
|
|||
|
|
|||
|
#include <sys/param.h>
|
|||
|
#include <sys/dir.h>
|
|||
|
#include <signal.h>
|
|||
|
#include <sys/ioctl.h>
|
|||
|
|
|||
|
#include <sys/ttrace.h>
|
|||
|
#ifdef HAVE_UNISTD_H
|
|||
|
#include <unistd.h>
|
|||
|
#endif
|
|||
|
#include <sys/mman.h>
|
|||
|
|
|||
|
#ifndef NO_PTRACE_H
|
|||
|
#ifdef PTRACE_IN_WRONG_PLACE
|
|||
|
#include <ptrace.h>
|
|||
|
#else
|
|||
|
#include <sys/ptrace.h>
|
|||
|
#endif
|
|||
|
#endif /* NO_PTRACE_H */
|
|||
|
|
|||
|
/* Second half of the hackery above. Non-ANSI C, so
|
|||
|
* we can't use "#error", alas.
|
|||
|
*/
|
|||
|
#ifdef NO_FLAGS
|
|||
|
#if (NO_FLAGS != INFTTRACE_TEMP_HACK )
|
|||
|
/* #error "Hackery to remove warning didn't work right" */
|
|||
|
#else
|
|||
|
/* Ok, new def'n of NO_FLAGS is same as old one; no action needed. */
|
|||
|
#endif
|
|||
|
#else
|
|||
|
/* #error "Didn't get expected re-definition of NO_FLAGS" */
|
|||
|
#define NO_FLAGS INFTTRACE_TEMP_HACK
|
|||
|
#endif
|
|||
|
|
|||
|
#if !defined (PT_SETTRC)
|
|||
|
#define PT_SETTRC 0 /* Make process traceable by parent */
|
|||
|
#endif
|
|||
|
#if !defined (PT_READ_I)
|
|||
|
#define PT_READ_I 1 /* Read word from text space */
|
|||
|
#endif
|
|||
|
#if !defined (PT_READ_D)
|
|||
|
#define PT_READ_D 2 /* Read word from data space */
|
|||
|
#endif
|
|||
|
#if !defined (PT_READ_U)
|
|||
|
#define PT_READ_U 3 /* Read word from kernel user struct */
|
|||
|
#endif
|
|||
|
#if !defined (PT_WRITE_I)
|
|||
|
#define PT_WRITE_I 4 /* Write word to text space */
|
|||
|
#endif
|
|||
|
#if !defined (PT_WRITE_D)
|
|||
|
#define PT_WRITE_D 5 /* Write word to data space */
|
|||
|
#endif
|
|||
|
#if !defined (PT_WRITE_U)
|
|||
|
#define PT_WRITE_U 6 /* Write word to kernel user struct */
|
|||
|
#endif
|
|||
|
#if !defined (PT_CONTINUE)
|
|||
|
#define PT_CONTINUE 7 /* Continue after signal */
|
|||
|
#endif
|
|||
|
#if !defined (PT_STEP)
|
|||
|
#define PT_STEP 9 /* Set flag for single stepping */
|
|||
|
#endif
|
|||
|
#if !defined (PT_KILL)
|
|||
|
#define PT_KILL 8 /* Send child a SIGKILL signal */
|
|||
|
#endif
|
|||
|
|
|||
|
#ifndef PT_ATTACH
|
|||
|
#define PT_ATTACH PTRACE_ATTACH
|
|||
|
#endif
|
|||
|
#ifndef PT_DETACH
|
|||
|
#define PT_DETACH PTRACE_DETACH
|
|||
|
#endif
|
|||
|
|
|||
|
#include "gdbcore.h"
|
|||
|
#ifndef NO_SYS_FILE
|
|||
|
#include <sys/file.h>
|
|||
|
#endif
|
|||
|
|
|||
|
/* This semaphore is used to coordinate the child and parent processes
|
|||
|
after a fork(), and before an exec() by the child. See parent_attach_all
|
|||
|
for details.
|
|||
|
*/
|
|||
|
typedef struct {
|
|||
|
int parent_channel[2]; /* Parent "talks" to [1], child "listens" to [0] */
|
|||
|
int child_channel[2]; /* Child "talks" to [1], parent "listens" to [0] */
|
|||
|
} startup_semaphore_t;
|
|||
|
|
|||
|
#define SEM_TALK (1)
|
|||
|
#define SEM_LISTEN (0)
|
|||
|
|
|||
|
static startup_semaphore_t startup_semaphore;
|
|||
|
|
|||
|
/* See can_touch_threads_of_process for details. */
|
|||
|
static int vforking_child_pid = 0;
|
|||
|
static int vfork_in_flight = 0;
|
|||
|
|
|||
|
/* To support PREPARE_TO_PROCEED (hppa_prepare_to_proceed).
|
|||
|
*/
|
|||
|
static pid_t old_gdb_pid = 0;
|
|||
|
static pid_t reported_pid = 0;
|
|||
|
static int reported_bpt = 0;
|
|||
|
|
|||
|
/* 1 if ok as results of a ttrace or ttrace_wait call, 0 otherwise.
|
|||
|
*/
|
|||
|
#define TT_OK( _status, _errno ) \
|
|||
|
(((_status) == 1) && ((_errno) == 0))
|
|||
|
|
|||
|
#define TTRACE_ARG_TYPE uint64_t
|
|||
|
|
|||
|
/* When supplied as the "addr" operand, ttrace interprets this
|
|||
|
to mean, "from the current address".
|
|||
|
*/
|
|||
|
#define TT_USE_CURRENT_PC ((TTRACE_ARG_TYPE) TT_NOPC)
|
|||
|
|
|||
|
/* When supplied as the "addr", "data" or "addr2" operand for most
|
|||
|
requests, ttrace interprets this to mean, "pay no heed to this
|
|||
|
argument".
|
|||
|
*/
|
|||
|
#define TT_NIL ((TTRACE_ARG_TYPE) TT_NULLARG)
|
|||
|
|
|||
|
/* This is capable of holding the value of a 32-bit register. The
|
|||
|
value is always left-aligned in the buffer; i.e., [0] contains
|
|||
|
the most-significant byte of the register's value, and [sizeof(reg)]
|
|||
|
contains the least-significant value.
|
|||
|
|
|||
|
??rehrauer: Yes, this assumes that an int is 32-bits on HP-UX, and
|
|||
|
that registers are 32-bits on HP-UX. The latter assumption changes
|
|||
|
with PA2.0.
|
|||
|
*/
|
|||
|
typedef int register_value_t;
|
|||
|
|
|||
|
/********************************************************************
|
|||
|
|
|||
|
How this works:
|
|||
|
|
|||
|
1. Thread numbers
|
|||
|
|
|||
|
The rest of GDB sees threads as being things with different
|
|||
|
"pid" (process id) values. See "thread.c" for details. The
|
|||
|
separate threads will be seen and reacted to if infttrace passes
|
|||
|
back different pid values (for _events_). See wait_for_inferior
|
|||
|
in inftarg.c.
|
|||
|
|
|||
|
So infttrace is going to use thread ids externally, pretending
|
|||
|
they are process ids, and keep track internally so that it can
|
|||
|
use the real process id (and thread id) when calling ttrace.
|
|||
|
|
|||
|
The data structure that supports this is a linked list of the
|
|||
|
current threads. Since at some date infttrace will have to
|
|||
|
deal with multiple processes, each list element records its
|
|||
|
corresponding pid, rather than having a single global.
|
|||
|
|
|||
|
Note that the list is only approximately current; that's ok, as
|
|||
|
it's up to date when we need it (we hope!). Also, it can contain
|
|||
|
dead threads, as there's no harm if it does.
|
|||
|
|
|||
|
The approach taken here is to bury the translation from external
|
|||
|
to internal inside "call_ttrace" and a few other places.
|
|||
|
|
|||
|
There are some wrinkles:
|
|||
|
|
|||
|
o When GDB forks itself to create the debug target process,
|
|||
|
there's only a pid of 0 around in the child, so the
|
|||
|
TT_PROC_SETTRC operation uses a more direct call to ttrace;
|
|||
|
Similiarly, the initial setting of the event mask happens
|
|||
|
early as well, and so is also special-cased, and an attach
|
|||
|
uses a real pid;
|
|||
|
|
|||
|
o We define an unthreaded application as having a "pseudo"
|
|||
|
thread;
|
|||
|
|
|||
|
o To keep from confusing the rest of GDB, we don't switch
|
|||
|
the PID for the pseudo thread to a TID. A table will help:
|
|||
|
|
|||
|
Rest of GDB sees these PIDs: pid tid1 tid2 tid3 ...
|
|||
|
|
|||
|
Our thread list stores: pid pid pid pid ...
|
|||
|
tid0 tid1 tid2 tid3
|
|||
|
|
|||
|
Ttrace sees these TIDS: tid0 tid1 tid2 tid3 ...
|
|||
|
|
|||
|
Both pid and tid0 will map to tid0, as there are infttrace.c-internal
|
|||
|
calls to ttrace using tid0.
|
|||
|
|
|||
|
2. Step and Continue
|
|||
|
|
|||
|
Since we're implementing the "stop the world" model, sub-model
|
|||
|
"other threads run during step", we have some stuff to do:
|
|||
|
|
|||
|
o User steps require continuing all threads other than the
|
|||
|
one the user is stepping;
|
|||
|
|
|||
|
o Internal debugger steps (such as over a breakpoint or watchpoint,
|
|||
|
but not out of a library load thunk) require stepping only
|
|||
|
the selected thread; this means that we have to report the
|
|||
|
step finish on that thread, which can lead to complications;
|
|||
|
|
|||
|
o When a thread is created, it is created running, rather
|
|||
|
than stopped--so we have to stop it.
|
|||
|
|
|||
|
The OS doesn't guarantee the stopped thread list will be stable,
|
|||
|
no does it guarantee where on the stopped thread list a thread
|
|||
|
that is single-stepped will wind up: it's possible that it will
|
|||
|
be off the list for a while, it's possible the step will complete
|
|||
|
and it will be re-posted to the end...
|
|||
|
|
|||
|
This means we have to scan the stopped thread list, build up
|
|||
|
a work-list, and then run down the work list; we can't do the
|
|||
|
step/continue during the scan.
|
|||
|
|
|||
|
3. Buffering events
|
|||
|
|
|||
|
Then there's the issue of waiting for an event. We do this by
|
|||
|
noticing how many events are reported at the end of each wait.
|
|||
|
From then on, we "fake" all resumes and steps, returning instantly,
|
|||
|
and don't do another wait. Once all pending events are reported,
|
|||
|
we can really resume again.
|
|||
|
|
|||
|
To keep this hidden, all the routines which know about tids and
|
|||
|
pids or real events and simulated ones are static (file-local).
|
|||
|
|
|||
|
This code can make lots of calls to ttrace, in particular it
|
|||
|
can spin down the list of thread states more than once. If this
|
|||
|
becomes a performance hit, the spin could be done once and the
|
|||
|
various "tsp" blocks saved, keeping all later spins in this
|
|||
|
process.
|
|||
|
|
|||
|
The O/S doesn't promise to keep the list straight, and so we must
|
|||
|
re-scan a lot. By observation, it looks like a single-step/wait
|
|||
|
puts the stepped thread at the end of the list but doesn't change
|
|||
|
it otherwise.
|
|||
|
|
|||
|
****************************************************************
|
|||
|
*/
|
|||
|
|
|||
|
/* Uncomment these to turn on various debugging output */
|
|||
|
/* #define THREAD_DEBUG */
|
|||
|
/* #define WAIT_BUFFER_DEBUG */
|
|||
|
/* #define PARANOIA */
|
|||
|
|
|||
|
|
|||
|
#define INFTTRACE_ALL_THREADS (-1)
|
|||
|
#define INFTTRACE_STEP (1)
|
|||
|
#define INFTTRACE_CONTINUE (0)
|
|||
|
|
|||
|
/* FIX: this is used in inftarg.c/child_wait, in a hack.
|
|||
|
*/
|
|||
|
extern int not_same_real_pid;
|
|||
|
|
|||
|
/* This is used to count buffered events.
|
|||
|
*/
|
|||
|
static unsigned int more_events_left = 0;
|
|||
|
|
|||
|
/* Process state.
|
|||
|
*/
|
|||
|
typedef enum process_state_enum {
|
|||
|
STOPPED,
|
|||
|
FAKE_STEPPING,
|
|||
|
FAKE_CONTINUE, /* For later use */
|
|||
|
RUNNING,
|
|||
|
FORKING,
|
|||
|
VFORKING
|
|||
|
} process_state_t;
|
|||
|
|
|||
|
static process_state_t process_state = STOPPED;
|
|||
|
|
|||
|
/* User-specified stepping modality.
|
|||
|
*/
|
|||
|
typedef enum stepping_mode_enum {
|
|||
|
DO_DEFAULT, /* ...which is a continue! */
|
|||
|
DO_STEP,
|
|||
|
DO_CONTINUE
|
|||
|
} stepping_mode_t;
|
|||
|
|
|||
|
/* Action to take on an attach, depends on
|
|||
|
* what kind (user command, fork, vfork).
|
|||
|
*
|
|||
|
* At the moment, this is either:
|
|||
|
*
|
|||
|
* o continue with a SIGTRAP signal, or
|
|||
|
*
|
|||
|
* o leave stopped.
|
|||
|
*/
|
|||
|
typedef enum attach_continue_enum {
|
|||
|
DO_ATTACH_CONTINUE,
|
|||
|
DONT_ATTACH_CONTINUE
|
|||
|
} attach_continue_t;
|
|||
|
|
|||
|
/* This flag is true if we are doing a step-over-bpt
|
|||
|
* with buffered events. We will have to be sure to
|
|||
|
* report the right thread, as otherwise the spaghetti
|
|||
|
* code in "infrun.c/wait_for_inferior" will get
|
|||
|
* confused.
|
|||
|
*/
|
|||
|
static int doing_fake_step = 0;
|
|||
|
static lwpid_t fake_step_tid = 0;
|
|||
|
|
|||
|
|
|||
|
/****************************************************
|
|||
|
* Thread information structure routines and types. *
|
|||
|
****************************************************
|
|||
|
*/
|
|||
|
typedef
|
|||
|
struct thread_info_struct
|
|||
|
{
|
|||
|
int am_pseudo; /* This is a pseudo-thread for the process. */
|
|||
|
int pid; /* Process ID */
|
|||
|
lwpid_t tid; /* Thread ID */
|
|||
|
int handled; /* 1 if a buffered event was handled. */
|
|||
|
int seen; /* 1 if this thread was seen on a traverse. */
|
|||
|
int terminated; /* 1 if thread has terminated. */
|
|||
|
int have_signal; /* 1 if signal to be sent */
|
|||
|
enum target_signal signal_value; /* Signal to send */
|
|||
|
int have_start; /* 1 if alternate starting address */
|
|||
|
stepping_mode_t stepping_mode; /* Whether to step or continue */
|
|||
|
CORE_ADDR start; /* Where to start */
|
|||
|
int have_state; /* 1 if the event state has been set */
|
|||
|
ttstate_t last_stop_state;/* The most recently-waited event for this thread. */
|
|||
|
struct thread_info_struct
|
|||
|
*next; /* All threads are linked via this field. */
|
|||
|
struct thread_info_struct
|
|||
|
*next_pseudo; /* All pseudo-threads are linked via this field. */
|
|||
|
} thread_info;
|
|||
|
|
|||
|
typedef
|
|||
|
struct thread_info_header_struct
|
|||
|
{
|
|||
|
int count;
|
|||
|
thread_info *head;
|
|||
|
thread_info *head_pseudo;
|
|||
|
|
|||
|
} thread_info_header;
|
|||
|
|
|||
|
static thread_info_header thread_head = { 0, NULL, NULL };
|
|||
|
static thread_info_header deleted_threads = { 0, NULL, NULL };
|
|||
|
|
|||
|
static saved_real_pid = 0;
|
|||
|
|
|||
|
|
|||
|
/*************************************************
|
|||
|
* Debugging support functions *
|
|||
|
*************************************************
|
|||
|
*/
|
|||
|
CORE_ADDR
|
|||
|
get_raw_pc( ttid )
|
|||
|
lwpid_t ttid;
|
|||
|
{
|
|||
|
unsigned long pc_val;
|
|||
|
int offset;
|
|||
|
int res;
|
|||
|
|
|||
|
offset = register_addr( PC_REGNUM, U_REGS_OFFSET );
|
|||
|
res = read_from_register_save_state(
|
|||
|
ttid,
|
|||
|
(TTRACE_ARG_TYPE) offset,
|
|||
|
(char *) &pc_val,
|
|||
|
sizeof( pc_val ));
|
|||
|
if( res <= 0 ) {
|
|||
|
return (CORE_ADDR) pc_val;
|
|||
|
}
|
|||
|
else {
|
|||
|
return (CORE_ADDR) 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static char *
|
|||
|
get_printable_name_of_stepping_mode( mode )
|
|||
|
stepping_mode_t mode;
|
|||
|
{
|
|||
|
switch( mode ) {
|
|||
|
case DO_DEFAULT: return "DO_DEFAULT";
|
|||
|
case DO_STEP: return "DO_STEP";
|
|||
|
case DO_CONTINUE: return "DO_CONTINUE";
|
|||
|
default: return "?unknown mode?";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* This function returns a pointer to a string describing the
|
|||
|
* ttrace event being reported.
|
|||
|
*/
|
|||
|
char *
|
|||
|
get_printable_name_of_ttrace_event (event)
|
|||
|
ttevents_t event;
|
|||
|
{
|
|||
|
/* This enumeration is "gappy", so don't use a table. */
|
|||
|
switch (event) {
|
|||
|
|
|||
|
case TTEVT_NONE:
|
|||
|
return "TTEVT_NONE";
|
|||
|
case TTEVT_SIGNAL:
|
|||
|
return "TTEVT_SIGNAL";
|
|||
|
case TTEVT_FORK:
|
|||
|
return "TTEVT_FORK";
|
|||
|
case TTEVT_EXEC:
|
|||
|
return "TTEVT_EXEC";
|
|||
|
case TTEVT_EXIT:
|
|||
|
return "TTEVT_EXIT";
|
|||
|
case TTEVT_VFORK:
|
|||
|
return "TTEVT_VFORK";
|
|||
|
case TTEVT_SYSCALL_RETURN:
|
|||
|
return "TTEVT_SYSCALL_RETURN";
|
|||
|
case TTEVT_LWP_CREATE:
|
|||
|
return "TTEVT_LWP_CREATE";
|
|||
|
case TTEVT_LWP_TERMINATE:
|
|||
|
return "TTEVT_LWP_TERMINATE";
|
|||
|
case TTEVT_LWP_EXIT:
|
|||
|
return "TTEVT_LWP_EXIT";
|
|||
|
case TTEVT_LWP_ABORT_SYSCALL:
|
|||
|
return "TTEVT_LWP_ABORT_SYSCALL";
|
|||
|
case TTEVT_SYSCALL_ENTRY:
|
|||
|
return "TTEVT_SYSCALL_ENTRY";
|
|||
|
case TTEVT_SYSCALL_RESTART:
|
|||
|
return "TTEVT_SYSCALL_RESTART";
|
|||
|
default :
|
|||
|
return "?new event?";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* This function translates the ttrace request enumeration into
|
|||
|
* a character string that is its printable (aka "human readable")
|
|||
|
* name.
|
|||
|
*/
|
|||
|
char *
|
|||
|
get_printable_name_of_ttrace_request (request)
|
|||
|
ttreq_t request;
|
|||
|
{
|
|||
|
if (!IS_TTRACE_REQ (request))
|
|||
|
return "?bad req?";
|
|||
|
|
|||
|
/* This enumeration is "gappy", so don't use a table. */
|
|||
|
switch (request) {
|
|||
|
case TT_PROC_SETTRC :
|
|||
|
return "TT_PROC_SETTRC";
|
|||
|
case TT_PROC_ATTACH :
|
|||
|
return "TT_PROC_ATTACH";
|
|||
|
case TT_PROC_DETACH :
|
|||
|
return "TT_PROC_DETACH";
|
|||
|
case TT_PROC_RDTEXT :
|
|||
|
return "TT_PROC_RDTEXT";
|
|||
|
case TT_PROC_WRTEXT :
|
|||
|
return "TT_PROC_WRTEXT";
|
|||
|
case TT_PROC_RDDATA :
|
|||
|
return "TT_PROC_RDDATA";
|
|||
|
case TT_PROC_WRDATA :
|
|||
|
return "TT_PROC_WRDATA";
|
|||
|
case TT_PROC_STOP :
|
|||
|
return "TT_PROC_STOP";
|
|||
|
case TT_PROC_CONTINUE :
|
|||
|
return "TT_PROC_CONTINUE";
|
|||
|
case TT_PROC_GET_PATHNAME :
|
|||
|
return "TT_PROC_GET_PATHNAME";
|
|||
|
case TT_PROC_GET_EVENT_MASK :
|
|||
|
return "TT_PROC_GET_EVENT_MASK";
|
|||
|
case TT_PROC_SET_EVENT_MASK :
|
|||
|
return "TT_PROC_SET_EVENT_MASK";
|
|||
|
case TT_PROC_GET_FIRST_LWP_STATE :
|
|||
|
return "TT_PROC_GET_FIRST_LWP_STATE";
|
|||
|
case TT_PROC_GET_NEXT_LWP_STATE :
|
|||
|
return "TT_PROC_GET_NEXT_LWP_STATE";
|
|||
|
case TT_PROC_EXIT :
|
|||
|
return "TT_PROC_EXIT";
|
|||
|
case TT_PROC_GET_MPROTECT :
|
|||
|
return "TT_PROC_GET_MPROTECT";
|
|||
|
case TT_PROC_SET_MPROTECT :
|
|||
|
return "TT_PROC_SET_MPROTECT";
|
|||
|
case TT_PROC_SET_SCBM :
|
|||
|
return "TT_PROC_SET_SCBM";
|
|||
|
case TT_LWP_STOP :
|
|||
|
return "TT_LWP_STOP";
|
|||
|
case TT_LWP_CONTINUE :
|
|||
|
return "TT_LWP_CONTINUE";
|
|||
|
case TT_LWP_SINGLE :
|
|||
|
return "TT_LWP_SINGLE";
|
|||
|
case TT_LWP_RUREGS :
|
|||
|
return "TT_LWP_RUREGS";
|
|||
|
case TT_LWP_WUREGS :
|
|||
|
return "TT_LWP_WUREGS";
|
|||
|
case TT_LWP_GET_EVENT_MASK :
|
|||
|
return "TT_LWP_GET_EVENT_MASK";
|
|||
|
case TT_LWP_SET_EVENT_MASK :
|
|||
|
return "TT_LWP_SET_EVENT_MASK";
|
|||
|
case TT_LWP_GET_STATE :
|
|||
|
return "TT_LWP_GET_STATE";
|
|||
|
default :
|
|||
|
return "?new req?";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* This function translates the process state enumeration into
|
|||
|
* a character string that is its printable (aka "human readable")
|
|||
|
* name.
|
|||
|
*/
|
|||
|
static char *
|
|||
|
get_printable_name_of_process_state (process_state)
|
|||
|
process_state_t process_state;
|
|||
|
{
|
|||
|
switch (process_state) {
|
|||
|
case STOPPED:
|
|||
|
return "STOPPED";
|
|||
|
case FAKE_STEPPING:
|
|||
|
return "FAKE_STEPPING";
|
|||
|
case RUNNING:
|
|||
|
return "RUNNING";
|
|||
|
case FORKING:
|
|||
|
return "FORKING";
|
|||
|
case VFORKING:
|
|||
|
return "VFORKING";
|
|||
|
default:
|
|||
|
return "?some unknown state?";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Set a ttrace thread state to a safe, initial state.
|
|||
|
*/
|
|||
|
static void
|
|||
|
clear_ttstate_t (tts)
|
|||
|
ttstate_t * tts;
|
|||
|
{
|
|||
|
tts->tts_pid = 0;
|
|||
|
tts->tts_lwpid = 0;
|
|||
|
tts->tts_user_tid = 0;
|
|||
|
tts->tts_event = TTEVT_NONE;
|
|||
|
}
|
|||
|
|
|||
|
/* Copy ttrace thread state TTS_FROM into TTS_TO.
|
|||
|
*/
|
|||
|
static void
|
|||
|
copy_ttstate_t (tts_to, tts_from)
|
|||
|
ttstate_t * tts_to;
|
|||
|
ttstate_t * tts_from;
|
|||
|
{
|
|||
|
memcpy ((char *) tts_to, (char *) tts_from, sizeof (*tts_to));
|
|||
|
}
|
|||
|
|
|||
|
/* Are there any live threads we know about?
|
|||
|
*/
|
|||
|
static int
|
|||
|
any_thread_records()
|
|||
|
{
|
|||
|
return( thread_head.count > 0 );
|
|||
|
}
|
|||
|
|
|||
|
/* Create, fill in and link in a thread descriptor.
|
|||
|
*/
|
|||
|
static thread_info *
|
|||
|
create_thread_info (pid, tid)
|
|||
|
int pid;
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info * new_p;
|
|||
|
thread_info * p;
|
|||
|
int thread_count_of_pid;
|
|||
|
|
|||
|
new_p = malloc( sizeof( thread_info ));
|
|||
|
new_p->pid = pid;
|
|||
|
new_p->tid = tid;
|
|||
|
new_p->have_signal = 0;
|
|||
|
new_p->have_start = 0;
|
|||
|
new_p->have_state = 0;
|
|||
|
clear_ttstate_t( &new_p->last_stop_state );
|
|||
|
new_p->am_pseudo = 0;
|
|||
|
new_p->handled = 0;
|
|||
|
new_p->seen = 0;
|
|||
|
new_p->terminated = 0;
|
|||
|
new_p->next = NULL;
|
|||
|
new_p->next_pseudo = NULL;
|
|||
|
new_p->stepping_mode = DO_DEFAULT;
|
|||
|
|
|||
|
if( 0 == thread_head.count ) {
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "First thread, pid %d tid %d!\n", pid, tid );
|
|||
|
#endif
|
|||
|
saved_real_pid = inferior_pid;
|
|||
|
}
|
|||
|
else {
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Subsequent thread, pid %d tid %d\n", pid, tid );
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
/* Another day, another thread...
|
|||
|
*/
|
|||
|
thread_head.count++;
|
|||
|
|
|||
|
/* The new thread always goes at the head of the list.
|
|||
|
*/
|
|||
|
new_p->next = thread_head.head;
|
|||
|
thread_head.head = new_p;
|
|||
|
|
|||
|
/* Is this the "pseudo" thread of a process? It is if there's
|
|||
|
* no other thread for this process on the list. (Note that this
|
|||
|
* accomodates multiple processes, such as we see even for simple
|
|||
|
* cases like forking "non-threaded" programs.)
|
|||
|
*/
|
|||
|
p = thread_head.head;
|
|||
|
thread_count_of_pid = 0;
|
|||
|
while (p)
|
|||
|
{
|
|||
|
if (p->pid == new_p->pid)
|
|||
|
thread_count_of_pid++;
|
|||
|
p = p->next;
|
|||
|
}
|
|||
|
|
|||
|
/* Did we see any other threads for this pid? (Recall that we just
|
|||
|
* added this thread to the list...)
|
|||
|
*/
|
|||
|
if (thread_count_of_pid == 1)
|
|||
|
{
|
|||
|
new_p->am_pseudo = 1;
|
|||
|
new_p->next_pseudo = thread_head.head_pseudo;
|
|||
|
thread_head.head_pseudo = new_p;
|
|||
|
}
|
|||
|
|
|||
|
return new_p;
|
|||
|
}
|
|||
|
|
|||
|
/* Get rid of our thread info.
|
|||
|
*/
|
|||
|
static void
|
|||
|
clear_thread_info ()
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
thread_info *q;
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Clearing all thread info\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
p = thread_head.head;
|
|||
|
while( p ) {
|
|||
|
q = p;
|
|||
|
p = p->next;
|
|||
|
free( q );
|
|||
|
}
|
|||
|
|
|||
|
thread_head.head = NULL;
|
|||
|
thread_head.head_pseudo = NULL;
|
|||
|
thread_head.count = 0;
|
|||
|
|
|||
|
p = deleted_threads.head;
|
|||
|
while( p ) {
|
|||
|
q = p;
|
|||
|
p = p->next;
|
|||
|
free( q );
|
|||
|
}
|
|||
|
|
|||
|
deleted_threads.head = NULL;
|
|||
|
deleted_threads.head_pseudo = NULL;
|
|||
|
deleted_threads.count = 0;
|
|||
|
|
|||
|
/* No threads, so can't have pending events.
|
|||
|
*/
|
|||
|
more_events_left = 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Given a tid, find the thread block for it.
|
|||
|
*/
|
|||
|
static thread_info *
|
|||
|
find_thread_info (tid)
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
if( p->tid == tid ) {
|
|||
|
return p;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
for( p = deleted_threads.head; p; p = p->next ) {
|
|||
|
if( p->tid == tid ) {
|
|||
|
return p;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/* For any but the pseudo thread, this maps to the
|
|||
|
* thread ID. For the pseudo thread, if you pass either
|
|||
|
* the thread id or the PID, you get the pseudo thread ID.
|
|||
|
*
|
|||
|
* We have to be prepared for core gdb to ask about
|
|||
|
* deleted threads. We do the map, but we don't like it.
|
|||
|
*/
|
|||
|
static lwpid_t
|
|||
|
map_from_gdb_tid( gdb_tid )
|
|||
|
lwpid_t gdb_tid;
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
/* First assume gdb_tid really is a tid, and try to find a
|
|||
|
* matching entry on the threads list.
|
|||
|
*/
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
if( p->tid == gdb_tid )
|
|||
|
return gdb_tid;
|
|||
|
}
|
|||
|
|
|||
|
/* It doesn't appear to be a tid; perhaps it's really a pid?
|
|||
|
* Try to find a "pseudo" thread entry on the threads list.
|
|||
|
*/
|
|||
|
for (p = thread_head.head_pseudo; p != NULL; p = p->next_pseudo)
|
|||
|
{
|
|||
|
if (p->pid == gdb_tid)
|
|||
|
return p->tid;
|
|||
|
}
|
|||
|
|
|||
|
/* Perhaps it's the tid of a deleted thread we may still
|
|||
|
* have some knowledge of?
|
|||
|
*/
|
|||
|
for( p = deleted_threads.head; p; p = p-> next ) {
|
|||
|
if( p->tid == gdb_tid )
|
|||
|
return gdb_tid;
|
|||
|
}
|
|||
|
|
|||
|
/* Or perhaps it's the pid of a deleted process we may still
|
|||
|
* have knowledge of?
|
|||
|
*/
|
|||
|
for (p = deleted_threads.head_pseudo; p != NULL; p = p->next_pseudo)
|
|||
|
{
|
|||
|
if (p->pid == gdb_tid)
|
|||
|
return p->tid;
|
|||
|
}
|
|||
|
|
|||
|
return 0; /* Error? */
|
|||
|
}
|
|||
|
|
|||
|
/* Map the other way: from a real tid to the
|
|||
|
* "pid" known by core gdb. This tid may be
|
|||
|
* for a thread that just got deleted, so we
|
|||
|
* also need to consider deleted threads.
|
|||
|
*/
|
|||
|
static lwpid_t
|
|||
|
map_to_gdb_tid( real_tid )
|
|||
|
lwpid_t real_tid;
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
if( p->tid == real_tid ) {
|
|||
|
if( p->am_pseudo )
|
|||
|
return p->pid;
|
|||
|
else
|
|||
|
return real_tid;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
for( p = deleted_threads.head; p; p = p-> next ) {
|
|||
|
if( p->tid == real_tid )
|
|||
|
if( p->am_pseudo )
|
|||
|
return p->pid; /* Error? */
|
|||
|
else
|
|||
|
return real_tid;
|
|||
|
}
|
|||
|
|
|||
|
return 0; /* Error? Never heard of this thread! */
|
|||
|
}
|
|||
|
|
|||
|
/* Do any threads have saved signals?
|
|||
|
*/
|
|||
|
static int
|
|||
|
saved_signals_exist ()
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
if( p->have_signal ) {
|
|||
|
return 1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Is this the tid for the zero-th thread?
|
|||
|
*/
|
|||
|
static int
|
|||
|
is_pseudo_thread (tid)
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info *p = find_thread_info( tid );
|
|||
|
if( NULL == p || p->terminated )
|
|||
|
return 0;
|
|||
|
else
|
|||
|
return p->am_pseudo;
|
|||
|
}
|
|||
|
|
|||
|
/* Is this thread terminated?
|
|||
|
*/
|
|||
|
static int
|
|||
|
is_terminated (tid)
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info *p = find_thread_info( tid );
|
|||
|
|
|||
|
if( NULL != p )
|
|||
|
return p->terminated;
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Is this pid a real PID or a TID?
|
|||
|
*/
|
|||
|
static int
|
|||
|
is_process_id (pid)
|
|||
|
int pid;
|
|||
|
{
|
|||
|
lwpid_t tid;
|
|||
|
thread_info * tinfo;
|
|||
|
pid_t this_pid;
|
|||
|
int this_pid_count;
|
|||
|
|
|||
|
/* What does PID really represent?
|
|||
|
*/
|
|||
|
tid = map_from_gdb_tid (pid);
|
|||
|
if (tid <= 0)
|
|||
|
return 0; /* Actually, is probably an error... */
|
|||
|
|
|||
|
tinfo = find_thread_info (tid);
|
|||
|
|
|||
|
/* Does it appear to be a true thread?
|
|||
|
*/
|
|||
|
if (! tinfo->am_pseudo)
|
|||
|
return 0;
|
|||
|
|
|||
|
/* Else, it looks like it may be a process. See if there's any other
|
|||
|
* threads with the same process ID, though. If there are, then TID
|
|||
|
* just happens to be the first thread of several for this process.
|
|||
|
*/
|
|||
|
this_pid = tinfo->pid;
|
|||
|
this_pid_count = 0;
|
|||
|
for (tinfo = thread_head.head; tinfo; tinfo = tinfo->next)
|
|||
|
{
|
|||
|
if (tinfo->pid == this_pid)
|
|||
|
this_pid_count++;
|
|||
|
}
|
|||
|
|
|||
|
return (this_pid_count == 1);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Add a thread to our info. Prevent duplicate entries.
|
|||
|
*/
|
|||
|
static thread_info *
|
|||
|
add_tthread (pid, tid)
|
|||
|
int pid;
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
p = find_thread_info( tid );
|
|||
|
if( NULL == p )
|
|||
|
p = create_thread_info( pid, tid );
|
|||
|
|
|||
|
return p;
|
|||
|
}
|
|||
|
|
|||
|
/* Notice that a thread was deleted.
|
|||
|
*/
|
|||
|
static void
|
|||
|
del_tthread (tid)
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
thread_info *chase;
|
|||
|
|
|||
|
if( thread_head.count <= 0 ) {
|
|||
|
error( "Internal error in thread database." );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
chase = NULL;
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
if( p->tid == tid ) {
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Delete here: %d \n", tid );
|
|||
|
#endif
|
|||
|
|
|||
|
if( p->am_pseudo ) {
|
|||
|
/*
|
|||
|
* Deleting a main thread is ok if we're doing
|
|||
|
* a parent-follow on a child; this is odd but
|
|||
|
* not wrong. It apparently _doesn't_ happen
|
|||
|
* on the child-follow, as we don't just delete
|
|||
|
* the pseudo while keeping the rest of the
|
|||
|
* threads around--instead, we clear out the whole
|
|||
|
* thread list at once.
|
|||
|
*/
|
|||
|
thread_info *q;
|
|||
|
thread_info *q_chase;
|
|||
|
|
|||
|
q_chase = NULL;
|
|||
|
for( q = thread_head.head_pseudo; q; q = q -> next ) {
|
|||
|
if( q == p ) {
|
|||
|
/* Remove from pseudo list.
|
|||
|
*/
|
|||
|
if( q_chase == NULL )
|
|||
|
thread_head.head_pseudo = p->next_pseudo;
|
|||
|
else
|
|||
|
q_chase-> next = p->next_pseudo;
|
|||
|
}
|
|||
|
else
|
|||
|
q_chase = q;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Remove from live list.
|
|||
|
*/
|
|||
|
thread_head.count--;
|
|||
|
|
|||
|
if( NULL == chase )
|
|||
|
thread_head.head = p->next;
|
|||
|
else
|
|||
|
chase->next = p->next;
|
|||
|
|
|||
|
/* Add to deleted thread list.
|
|||
|
*/
|
|||
|
p->next = deleted_threads.head;
|
|||
|
deleted_threads.head = p;
|
|||
|
deleted_threads.count++;
|
|||
|
if( p->am_pseudo ) {
|
|||
|
p->next_pseudo = deleted_threads.head_pseudo;
|
|||
|
deleted_threads.head_pseudo = p;
|
|||
|
}
|
|||
|
p->terminated = 1;
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
else
|
|||
|
chase = p;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Get the pid for this tid. (Has to be a real TID!).
|
|||
|
*/
|
|||
|
static int
|
|||
|
get_pid_for (tid)
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
if( p->tid == tid ) {
|
|||
|
return p->pid;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
for( p = deleted_threads.head; p; p = p->next ) {
|
|||
|
if( p->tid == tid ) {
|
|||
|
return p->pid;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Note that this thread's current event has been handled.
|
|||
|
*/
|
|||
|
static void
|
|||
|
set_handled( pid, tid )
|
|||
|
int pid;
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
p = find_thread_info( tid );
|
|||
|
if( NULL == p )
|
|||
|
p = add_tthread( pid, tid );
|
|||
|
|
|||
|
p->handled = 1;
|
|||
|
}
|
|||
|
|
|||
|
/* Was this thread's current event handled?
|
|||
|
*/
|
|||
|
static int
|
|||
|
was_handled( tid )
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
p = find_thread_info( tid );
|
|||
|
if( NULL != p )
|
|||
|
return p->handled;
|
|||
|
|
|||
|
return 0; /* New threads have not been handled */
|
|||
|
}
|
|||
|
|
|||
|
/* Set this thread to unhandled.
|
|||
|
*/
|
|||
|
static void
|
|||
|
clear_handled( tid )
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info * p;
|
|||
|
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "clear_handled %d\n", (int) tid );
|
|||
|
#endif
|
|||
|
|
|||
|
p = find_thread_info (tid);
|
|||
|
if (p == NULL)
|
|||
|
error ("Internal error: No thread state to clear?");
|
|||
|
|
|||
|
p->handled = 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Set all threads to unhandled.
|
|||
|
*/
|
|||
|
static void
|
|||
|
clear_all_handled ()
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "clear_all_handled\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
p->handled = 0;
|
|||
|
}
|
|||
|
|
|||
|
for( p = deleted_threads.head; p; p = p->next ) {
|
|||
|
p->handled = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Set this thread to default stepping mode.
|
|||
|
*/
|
|||
|
static void
|
|||
|
clear_stepping_mode( tid )
|
|||
|
lwpid_t tid;
|
|||
|
{
|
|||
|
thread_info * p;
|
|||
|
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "clear_stepping_mode %d\n", (int) tid );
|
|||
|
#endif
|
|||
|
|
|||
|
p = find_thread_info (tid);
|
|||
|
if (p == NULL)
|
|||
|
error ("Internal error: No thread state to clear?");
|
|||
|
|
|||
|
p->stepping_mode = DO_DEFAULT;
|
|||
|
}
|
|||
|
|
|||
|
/* Set all threads to do default continue on resume.
|
|||
|
*/
|
|||
|
static void
|
|||
|
clear_all_stepping_mode ()
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "clear_all_stepping_mode\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
p->stepping_mode = DO_DEFAULT;
|
|||
|
}
|
|||
|
|
|||
|
for( p = deleted_threads.head; p; p = p->next ) {
|
|||
|
p->stepping_mode = DO_DEFAULT;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Set all threads to unseen on this pass.
|
|||
|
*/
|
|||
|
static void
|
|||
|
set_all_unseen ()
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
p->seen = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#if (defined( THREAD_DEBUG ) || defined( PARANOIA ))
|
|||
|
/* debugging routine.
|
|||
|
*/
|
|||
|
static void
|
|||
|
print_tthread (p)
|
|||
|
thread_info * p;
|
|||
|
{
|
|||
|
printf( " Thread pid %d, tid %d", p->pid, p->tid );
|
|||
|
if( p->have_state )
|
|||
|
printf( ", event is %s",
|
|||
|
get_printable_name_of_ttrace_event( p->last_stop_state.tts_event ));
|
|||
|
|
|||
|
if( p->am_pseudo )
|
|||
|
printf( ", pseudo thread" );
|
|||
|
|
|||
|
if( p->have_signal )
|
|||
|
printf( ", have signal 0x%x", p->signal_value );
|
|||
|
|
|||
|
if( p->have_start )
|
|||
|
printf( ", have start at 0x%x", p->start );
|
|||
|
|
|||
|
printf( ", step is %s", get_printable_name_of_stepping_mode( p->stepping_mode ));
|
|||
|
|
|||
|
if( p->handled )
|
|||
|
printf( ", handled" );
|
|||
|
else
|
|||
|
printf( ", not handled" );
|
|||
|
|
|||
|
if( p->seen )
|
|||
|
printf( ", seen" );
|
|||
|
else
|
|||
|
printf( ", not seen" );
|
|||
|
|
|||
|
printf( "\n" );
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
print_tthreads ()
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
if( thread_head.count == 0 )
|
|||
|
printf( "Thread list is empty\n" );
|
|||
|
else {
|
|||
|
printf( "Thread list has " );
|
|||
|
if( thread_head.count == 1 )
|
|||
|
printf( "1 entry:\n" );
|
|||
|
else
|
|||
|
printf( "%d entries:\n", thread_head.count );
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
print_tthread (p);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if( deleted_threads.count == 0 )
|
|||
|
printf( "Deleted thread list is empty\n" );
|
|||
|
else {
|
|||
|
printf( "Deleted thread list has " );
|
|||
|
if( deleted_threads.count == 1 )
|
|||
|
printf( "1 entry:\n" );
|
|||
|
else
|
|||
|
printf( "%d entries:\n", deleted_threads.count );
|
|||
|
|
|||
|
for( p = deleted_threads.head; p; p = p->next ) {
|
|||
|
print_tthread (p);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
/* Update the thread list based on the "seen" bits.
|
|||
|
*/
|
|||
|
static void
|
|||
|
update_thread_list ()
|
|||
|
{
|
|||
|
thread_info *p;
|
|||
|
thread_info *chase;
|
|||
|
|
|||
|
chase = NULL;
|
|||
|
for( p = thread_head.head; p; p = p->next ) {
|
|||
|
/* Is this an "unseen" thread which really happens to be a process?
|
|||
|
If so, is it inferior_pid and is a vfork in flight? If yes to
|
|||
|
all, then DON'T REMOVE IT! We're in the midst of moving a vfork
|
|||
|
operation, which is a multiple step thing, to the point where we
|
|||
|
can touch the parent again. We've most likely stopped to examine
|
|||
|
the child at a late stage in the vfork, and if we're not following
|
|||
|
the child, we'd best not treat the parent as a dead "thread"...
|
|||
|
*/
|
|||
|
if( (!p->seen) && p->am_pseudo && vfork_in_flight
|
|||
|
&& (p->pid != vforking_child_pid))
|
|||
|
p->seen = 1;
|
|||
|
|
|||
|
if( !p->seen ) {
|
|||
|
/* Remove this one
|
|||
|
*/
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Delete unseen thread: %d \n", p->tid );
|
|||
|
#endif
|
|||
|
del_tthread( p->tid );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/************************************************
|
|||
|
* O/S call wrappers *
|
|||
|
************************************************
|
|||
|
*/
|
|||
|
|
|||
|
/* This function simply calls ttrace with the given arguments.
|
|||
|
* It exists so that all calls to ttrace are isolated. All
|
|||
|
* parameters should be as specified by "man 2 ttrace".
|
|||
|
*
|
|||
|
* No other "raw" calls to ttrace should exist in this module.
|
|||
|
*/
|
|||
|
static int
|
|||
|
call_real_ttrace( request, pid, tid, addr, data, addr2 )
|
|||
|
ttreq_t request;
|
|||
|
pid_t pid;
|
|||
|
lwpid_t tid;
|
|||
|
TTRACE_ARG_TYPE addr, data, addr2;
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
|
|||
|
errno = 0;
|
|||
|
tt_status = ttrace( request, pid, tid, addr, data, addr2 );
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if (errno) {
|
|||
|
/* Don't bother for a known benign error: if you ask for the
|
|||
|
* first thread state, but there is only one thread and it's
|
|||
|
* not stopped, ttrace complains.
|
|||
|
*
|
|||
|
* We have this inside the #ifdef because our caller will do
|
|||
|
* this check for real.
|
|||
|
*/
|
|||
|
if( request != TT_PROC_GET_FIRST_LWP_STATE
|
|||
|
|| errno != EPROTO ) {
|
|||
|
if( debug_on )
|
|||
|
printf( "TT fail for %s, with pid %d, tid %d, status %d \n",
|
|||
|
get_printable_name_of_ttrace_request (request),
|
|||
|
pid, tid, tt_status );
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
#if 0
|
|||
|
/* ??rehrauer: It would probably be most robust to catch and report
|
|||
|
* failed requests here. However, some clients of this interface
|
|||
|
* seem to expect to catch & deal with them, so we'd best not.
|
|||
|
*/
|
|||
|
if (errno) {
|
|||
|
strcpy (reason_for_failure, "ttrace (");
|
|||
|
strcat (reason_for_failure, get_printable_name_of_ttrace_request (request));
|
|||
|
strcat (reason_for_failure, ")");
|
|||
|
printf( "ttrace error, errno = %d\n", errno );
|
|||
|
perror_with_name (reason_for_failure);
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
return tt_status;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* This function simply calls ttrace_wait with the given arguments.
|
|||
|
* It exists so that all calls to ttrace_wait are isolated.
|
|||
|
*
|
|||
|
* No "raw" calls to ttrace_wait should exist elsewhere.
|
|||
|
*/
|
|||
|
static int
|
|||
|
call_real_ttrace_wait( pid, tid, option, tsp, tsp_size )
|
|||
|
int pid;
|
|||
|
lwpid_t tid;
|
|||
|
ttwopt_t option;
|
|||
|
ttstate_t *tsp;
|
|||
|
size_t tsp_size;
|
|||
|
{
|
|||
|
int ttw_status;
|
|||
|
thread_info * tinfo = NULL;
|
|||
|
|
|||
|
errno = 0;
|
|||
|
ttw_status = ttrace_wait (pid, tid, option, tsp, tsp_size);
|
|||
|
|
|||
|
if (errno) {
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "TW fail with pid %d, tid %d \n", pid, tid );
|
|||
|
#endif
|
|||
|
|
|||
|
perror_with_name ("ttrace wait");
|
|||
|
}
|
|||
|
|
|||
|
return ttw_status;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* A process may have one or more kernel threads, of which all or
|
|||
|
none may be stopped. This function returns the ID of the first
|
|||
|
kernel thread in a stopped state, or 0 if none are stopped.
|
|||
|
|
|||
|
This function can be used with get_process_next_stopped_thread_id
|
|||
|
to iterate over the IDs of all stopped threads of this process.
|
|||
|
*/
|
|||
|
static lwpid_t
|
|||
|
get_process_first_stopped_thread_id (pid, thread_state)
|
|||
|
int pid;
|
|||
|
ttstate_t * thread_state;
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
|
|||
|
tt_status = call_real_ttrace (
|
|||
|
TT_PROC_GET_FIRST_LWP_STATE,
|
|||
|
(pid_t) pid,
|
|||
|
(lwpid_t) TT_NIL,
|
|||
|
(TTRACE_ARG_TYPE) thread_state,
|
|||
|
(TTRACE_ARG_TYPE) sizeof (*thread_state),
|
|||
|
TT_NIL);
|
|||
|
|
|||
|
if (errno) {
|
|||
|
if( errno == EPROTO) {
|
|||
|
/* This is an error we can handle: there isn't any stopped
|
|||
|
* thread. This happens when we're re-starting the application
|
|||
|
* and it has only one thread. GET_NEXT handles the case of
|
|||
|
* no more stopped threads well; GET_FIRST doesn't. (A ttrace
|
|||
|
* "feature".)
|
|||
|
*/
|
|||
|
tt_status = 1;
|
|||
|
errno = 0;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
else
|
|||
|
perror_with_name ("ttrace");
|
|||
|
}
|
|||
|
|
|||
|
if( tt_status < 0 )
|
|||
|
/* Failed somehow.
|
|||
|
*/
|
|||
|
return 0;
|
|||
|
|
|||
|
return thread_state->tts_lwpid;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* This function returns the ID of the "next" kernel thread in a
|
|||
|
stopped state, or 0 if there are none. "Next" refers to the
|
|||
|
thread following that of the last successful call to this
|
|||
|
function or to get_process_first_stopped_thread_id, using
|
|||
|
the value of thread_state returned by that call.
|
|||
|
|
|||
|
This function can be used with get_process_first_stopped_thread_id
|
|||
|
to iterate over the IDs of all stopped threads of this process.
|
|||
|
*/
|
|||
|
static lwpid_t
|
|||
|
get_process_next_stopped_thread_id (pid, thread_state)
|
|||
|
int pid;
|
|||
|
ttstate_t * thread_state;
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
|
|||
|
tt_status = call_real_ttrace (
|
|||
|
TT_PROC_GET_NEXT_LWP_STATE,
|
|||
|
(pid_t) pid,
|
|||
|
(lwpid_t) TT_NIL,
|
|||
|
(TTRACE_ARG_TYPE) thread_state,
|
|||
|
(TTRACE_ARG_TYPE) sizeof (*thread_state),
|
|||
|
TT_NIL);
|
|||
|
if (errno)
|
|||
|
perror_with_name ("ttrace");
|
|||
|
|
|||
|
if (tt_status < 0)
|
|||
|
/* Failed
|
|||
|
*/
|
|||
|
return 0;
|
|||
|
|
|||
|
else if( tt_status == 0 ) {
|
|||
|
/* End of list, no next state. Don't return the
|
|||
|
* tts_lwpid, as it's a meaningless "240".
|
|||
|
*
|
|||
|
* This is an HPUX "feature".
|
|||
|
*/
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
return thread_state->tts_lwpid;
|
|||
|
}
|
|||
|
|
|||
|
/* ??rehrauer: Eventually this function perhaps should be calling
|
|||
|
pid_to_thread_id. However, that function currently does nothing
|
|||
|
for HP-UX. Even then, I'm not clear whether that function
|
|||
|
will return a "kernel" thread ID, or a "user" thread ID. If
|
|||
|
the former, we can just call it here. If the latter, we must
|
|||
|
map from the "user" tid to a "kernel" tid.
|
|||
|
|
|||
|
NOTE: currently not called.
|
|||
|
*/
|
|||
|
static lwpid_t
|
|||
|
get_active_tid_of_pid (pid)
|
|||
|
int pid;
|
|||
|
{
|
|||
|
ttstate_t thread_state;
|
|||
|
|
|||
|
return get_process_first_stopped_thread_id (pid, &thread_state);
|
|||
|
}
|
|||
|
|
|||
|
/* This function returns 1 if tt_request is a ttrace request that
|
|||
|
* operates upon all threads of a (i.e., the entire) process.
|
|||
|
*/
|
|||
|
int
|
|||
|
is_process_ttrace_request (tt_request)
|
|||
|
ttreq_t tt_request;
|
|||
|
{
|
|||
|
return IS_TTRACE_PROCREQ (tt_request);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* This function translates a thread ttrace request into
|
|||
|
* the equivalent process request for a one-thread process.
|
|||
|
*/
|
|||
|
static ttreq_t
|
|||
|
make_process_version( request )
|
|||
|
ttreq_t request;
|
|||
|
{
|
|||
|
if (!IS_TTRACE_REQ (request)) {
|
|||
|
error( "Internal error, bad ttrace request made\n" );
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
switch (request) {
|
|||
|
case TT_LWP_STOP :
|
|||
|
return TT_PROC_STOP;
|
|||
|
|
|||
|
case TT_LWP_CONTINUE :
|
|||
|
return TT_PROC_CONTINUE;
|
|||
|
|
|||
|
case TT_LWP_GET_EVENT_MASK :
|
|||
|
return TT_PROC_GET_EVENT_MASK;
|
|||
|
|
|||
|
case TT_LWP_SET_EVENT_MASK :
|
|||
|
return TT_PROC_SET_EVENT_MASK;
|
|||
|
|
|||
|
case TT_LWP_SINGLE :
|
|||
|
case TT_LWP_RUREGS :
|
|||
|
case TT_LWP_WUREGS :
|
|||
|
case TT_LWP_GET_STATE :
|
|||
|
return -1; /* No equivalent */
|
|||
|
|
|||
|
default :
|
|||
|
return request;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* This function translates the "pid" used by the rest of
|
|||
|
* gdb to a real pid and a tid. It then calls "call_real_ttrace"
|
|||
|
* with the given arguments.
|
|||
|
*
|
|||
|
* In general, other parts of this module should call this
|
|||
|
* function when they are dealing with external users, who only
|
|||
|
* have tids to pass (but they call it "pid" for historical
|
|||
|
* reasons).
|
|||
|
*/
|
|||
|
static int
|
|||
|
call_ttrace( request, gdb_tid, addr, data, addr2 )
|
|||
|
ttreq_t request;
|
|||
|
int gdb_tid;
|
|||
|
TTRACE_ARG_TYPE addr, data, addr2;
|
|||
|
{
|
|||
|
lwpid_t real_tid;
|
|||
|
int real_pid;
|
|||
|
ttreq_t new_request;
|
|||
|
int tt_status;
|
|||
|
char reason_for_failure [100]; /* Arbitrary size, should be big enough. */
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
int is_interesting = 0;
|
|||
|
|
|||
|
if( TT_LWP_RUREGS == request ) {
|
|||
|
is_interesting = 1; /* Adjust code here as desired */
|
|||
|
}
|
|||
|
|
|||
|
if( is_interesting && 0 && debug_on ) {
|
|||
|
if( !is_process_ttrace_request( request )) {
|
|||
|
printf( "TT: Thread request, tid is %d", gdb_tid );
|
|||
|
printf( "== SINGLE at %x", addr );
|
|||
|
}
|
|||
|
else {
|
|||
|
printf( "TT: Process request, tid is %d\n", gdb_tid );
|
|||
|
printf( "==! SINGLE at %x", addr );
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
/* The initial SETTRC and SET_EVENT_MASK calls (and all others
|
|||
|
* which happen before any threads get set up) should go
|
|||
|
* directly to "call_real_ttrace", so they don't happen here.
|
|||
|
*
|
|||
|
* But hardware watchpoints do a SET_EVENT_MASK, so we can't
|
|||
|
* rule them out....
|
|||
|
*/
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( request == TT_PROC_SETTRC && debug_on )
|
|||
|
printf( "Unexpected call for TT_PROC_SETTRC\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
/* Sometimes we get called with a bogus tid (e.g., if a
|
|||
|
* thread has terminated, we return 0; inftarg later asks
|
|||
|
* whether the thread has exited/forked/vforked).
|
|||
|
*/
|
|||
|
if( gdb_tid == 0 )
|
|||
|
{
|
|||
|
errno = ESRCH; /* ttrace's response would probably be "No such process". */
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/* All other cases should be able to expect that there are
|
|||
|
* thread records.
|
|||
|
*/
|
|||
|
if( !any_thread_records()) {
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
warning ("No thread records for ttrace call");
|
|||
|
#endif
|
|||
|
errno = ESRCH; /* ttrace's response would be "No such process". */
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/* OK, now the task is to translate the incoming tid into
|
|||
|
* a pid/tid pair.
|
|||
|
*/
|
|||
|
real_tid = map_from_gdb_tid( gdb_tid );
|
|||
|
real_pid = get_pid_for( real_tid );
|
|||
|
|
|||
|
/* Now check the result. "Real_pid" is NULL if our list
|
|||
|
* didn't find it. We have some tricks we can play to fix
|
|||
|
* this, however.
|
|||
|
*/
|
|||
|
if( 0 == real_pid ) {
|
|||
|
ttstate_t thread_state;
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "No saved pid for tid %d\n", gdb_tid );
|
|||
|
#endif
|
|||
|
|
|||
|
if( is_process_ttrace_request( request )) {
|
|||
|
|
|||
|
/* Ok, we couldn't get a tid. Try to translate to
|
|||
|
* the equivalent process operation. We expect this
|
|||
|
* NOT to happen, so this is a desparation-type
|
|||
|
* move. It can happen if there is an internal
|
|||
|
* error and so no "wait()" call is ever done.
|
|||
|
*/
|
|||
|
new_request = make_process_version( request );
|
|||
|
if( new_request == -1 ) {
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "...and couldn't make process version of thread operation\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
/* Use hacky saved pid, which won't always be correct
|
|||
|
* in the multi-process future. Use tid as thread,
|
|||
|
* probably dooming this to failure. FIX!
|
|||
|
*/
|
|||
|
if( saved_real_pid != 0 ) {
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "...using saved pid %d\n", saved_real_pid );
|
|||
|
#endif
|
|||
|
|
|||
|
real_pid = saved_real_pid;
|
|||
|
real_tid = gdb_tid;
|
|||
|
}
|
|||
|
|
|||
|
else
|
|||
|
error( "Unable to perform thread operation" );
|
|||
|
}
|
|||
|
|
|||
|
else {
|
|||
|
/* Sucessfully translated this to a process request,
|
|||
|
* which needs no thread value.
|
|||
|
*/
|
|||
|
real_pid = gdb_tid;
|
|||
|
real_tid = 0;
|
|||
|
request = new_request;
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on ) {
|
|||
|
printf( "Translated thread request to process request\n" );
|
|||
|
if( saved_real_pid == 0 )
|
|||
|
printf( "...but there's no saved pid\n" );
|
|||
|
|
|||
|
else {
|
|||
|
if( gdb_tid != saved_real_pid )
|
|||
|
printf( "...but have the wrong pid (%d rather than %d)\n",
|
|||
|
gdb_tid, saved_real_pid );
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
} /* Translated to a process request */
|
|||
|
} /* Is a process request */
|
|||
|
|
|||
|
else {
|
|||
|
/* We have to have a thread. Ooops.
|
|||
|
*/
|
|||
|
error( "Thread request with no threads (%s)",
|
|||
|
get_printable_name_of_ttrace_request( request ));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Ttrace doesn't like to see tid values on process requests,
|
|||
|
* even if we have the right one.
|
|||
|
*/
|
|||
|
if (is_process_ttrace_request (request)) {
|
|||
|
real_tid = 0;
|
|||
|
}
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( is_interesting && 0 && debug_on ) {
|
|||
|
printf( " now tid %d, pid %d\n", real_tid, real_pid );
|
|||
|
printf( " request is %s\n", get_printable_name_of_ttrace_request (request));
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
/* Finally, the (almost) real call.
|
|||
|
*/
|
|||
|
tt_status = call_real_ttrace (request, real_pid, real_tid, addr, data, addr2);
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if(is_interesting && debug_on ) {
|
|||
|
if( !TT_OK( tt_status, errno )
|
|||
|
&& !(tt_status == 0 & errno == 0))
|
|||
|
printf( " got error (errno==%d, status==%d)\n", errno, tt_status );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
return tt_status;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Stop all the threads of a process.
|
|||
|
*
|
|||
|
* NOTE: use of TT_PROC_STOP can cause a thread with a real event
|
|||
|
* to get a TTEVT_NONE event, discarding the old event. Be
|
|||
|
* very careful, and only call TT_PROC_STOP when you mean it!
|
|||
|
*/
|
|||
|
static void
|
|||
|
stop_all_threads_of_process( real_pid )
|
|||
|
pid_t real_pid;
|
|||
|
{
|
|||
|
int ttw_status;
|
|||
|
|
|||
|
ttw_status = call_real_ttrace (TT_PROC_STOP,
|
|||
|
(pid_t) real_pid,
|
|||
|
(lwpid_t) TT_NIL,
|
|||
|
(TTRACE_ARG_TYPE) TT_NIL,
|
|||
|
(TTRACE_ARG_TYPE) TT_NIL,
|
|||
|
TT_NIL );
|
|||
|
if (errno)
|
|||
|
perror_with_name ("ttrace stop of other threads");
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Under some circumstances, it's unsafe to attempt to stop, or even
|
|||
|
query the state of, a process' threads.
|
|||
|
|
|||
|
In ttrace-based HP-UX, an example is a vforking child process. The
|
|||
|
vforking parent and child are somewhat fragile, w/r/t what we can do
|
|||
|
what we can do to them with ttrace, until after the child exits or
|
|||
|
execs, or until the parent's vfork event is delivered. Until that
|
|||
|
time, we must not try to stop the process' threads, or inquire how
|
|||
|
many there are, or even alter its data segments, or it typically dies
|
|||
|
with a SIGILL. Sigh.
|
|||
|
|
|||
|
This function returns 1 if this stopped process, and the event that
|
|||
|
we're told was responsible for its current stopped state, cannot safely
|
|||
|
have its threads examined.
|
|||
|
*/
|
|||
|
#define CHILD_VFORKED(evt,pid) \
|
|||
|
(((evt) == TTEVT_VFORK) && ((pid) != inferior_pid))
|
|||
|
#define CHILD_URPED(evt,pid) \
|
|||
|
((((evt) == TTEVT_EXEC) || ((evt) == TTEVT_EXIT)) && ((pid) != vforking_child_pid))
|
|||
|
#define PARENT_VFORKED(evt,pid) \
|
|||
|
(((evt) == TTEVT_VFORK) && ((pid) == inferior_pid))
|
|||
|
|
|||
|
static int
|
|||
|
can_touch_threads_of_process (pid, stopping_event)
|
|||
|
int pid;
|
|||
|
ttevents_t stopping_event;
|
|||
|
{
|
|||
|
if (CHILD_VFORKED (stopping_event, pid))
|
|||
|
{
|
|||
|
vforking_child_pid = pid;
|
|||
|
vfork_in_flight = 1;
|
|||
|
}
|
|||
|
|
|||
|
else if (vfork_in_flight &&
|
|||
|
(PARENT_VFORKED (stopping_event, pid) ||
|
|||
|
CHILD_URPED (stopping_event, pid)))
|
|||
|
{
|
|||
|
vfork_in_flight = 0;
|
|||
|
vforking_child_pid = 0;
|
|||
|
}
|
|||
|
|
|||
|
return ! vfork_in_flight;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* If we can find an as-yet-unhandled thread state of a
|
|||
|
* stopped thread of this process return 1 and set "tsp".
|
|||
|
* Return 0 if we can't.
|
|||
|
*
|
|||
|
* If this function is used when the threads of PIS haven't
|
|||
|
* been stopped, undefined behaviour is guaranteed!
|
|||
|
*/
|
|||
|
static int
|
|||
|
select_stopped_thread_of_process (pid, tsp)
|
|||
|
int pid;
|
|||
|
ttstate_t * tsp;
|
|||
|
{
|
|||
|
lwpid_t candidate_tid, tid;
|
|||
|
ttstate_t candidate_tstate, tstate;
|
|||
|
|
|||
|
/* If we're not allowed to touch the process now, then just
|
|||
|
* return the current value of *TSP.
|
|||
|
*
|
|||
|
* This supports "vfork". It's ok, really, to double the
|
|||
|
* current event (the child EXEC, we hope!).
|
|||
|
*/
|
|||
|
if (! can_touch_threads_of_process (pid, tsp->tts_event))
|
|||
|
return 1;
|
|||
|
|
|||
|
/* Decide which of (possibly more than one) events to
|
|||
|
* return as the first one. We scan them all so that
|
|||
|
* we always return the result of a fake-step first.
|
|||
|
*/
|
|||
|
candidate_tid = 0;
|
|||
|
for (tid = get_process_first_stopped_thread_id (pid, &tstate);
|
|||
|
tid != 0;
|
|||
|
tid = get_process_next_stopped_thread_id (pid, &tstate))
|
|||
|
{
|
|||
|
/* TTEVT_NONE events are uninteresting to our clients. They're
|
|||
|
* an artifact of our "stop the world" model--the thread is
|
|||
|
* stopped because we stopped it.
|
|||
|
*/
|
|||
|
if (tstate.tts_event == TTEVT_NONE) {
|
|||
|
set_handled( pid, tstate.tts_lwpid );
|
|||
|
}
|
|||
|
|
|||
|
/* Did we just single-step a single thread, without letting any
|
|||
|
* of the others run? Is this an event for that thread?
|
|||
|
*
|
|||
|
* If so, we believe our client would prefer to see this event
|
|||
|
* over any others. (Typically the client wants to just push
|
|||
|
* one thread a little farther forward, and then go around
|
|||
|
* checking for what all threads are doing.)
|
|||
|
*/
|
|||
|
else if (doing_fake_step && (tstate.tts_lwpid == fake_step_tid))
|
|||
|
{
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
/* It's possible here to see either a SIGTRAP (due to
|
|||
|
* successful completion of a step) or a SYSCALL_ENTRY
|
|||
|
* (due to a step completion with active hardware
|
|||
|
* watchpoints).
|
|||
|
*/
|
|||
|
if( debug_on )
|
|||
|
printf( "Ending fake step with tid %d, state %s\n",
|
|||
|
tstate.tts_lwpid,
|
|||
|
get_printable_name_of_ttrace_event( tstate.tts_event ));
|
|||
|
#endif
|
|||
|
|
|||
|
/* Remember this one, and throw away any previous
|
|||
|
* candidate.
|
|||
|
*/
|
|||
|
candidate_tid = tstate.tts_lwpid;
|
|||
|
candidate_tstate = tstate;
|
|||
|
}
|
|||
|
|
|||
|
#ifdef FORGET_DELETED_BPTS
|
|||
|
|
|||
|
/* We can't just do this, as if we do, and then wind
|
|||
|
* up the loop with no unhandled events, we need to
|
|||
|
* handle that case--the appropriate reaction is to
|
|||
|
* just continue, but there's no easy way to do that.
|
|||
|
*
|
|||
|
* Better to put this in the ttrace_wait call--if, when
|
|||
|
* we fake a wait, we update our events based on the
|
|||
|
* breakpoint_here_pc call and find there are no more events,
|
|||
|
* then we better continue and so on.
|
|||
|
*
|
|||
|
* Or we could put it in the next/continue fake.
|
|||
|
* But it has to go in the buffering code, not in the
|
|||
|
* real go/wait code.
|
|||
|
*/
|
|||
|
else if( (TTEVT_SIGNAL == tstate.tts_event)
|
|||
|
&& (5 == tstate.tts_u.tts_signal.tts_signo)
|
|||
|
&& (0 != get_raw_pc( tstate.tts_lwpid ))
|
|||
|
&& ! breakpoint_here_p( get_raw_pc( tstate.tts_lwpid )) ) {
|
|||
|
/*
|
|||
|
* If the user deleted a breakpoint while this
|
|||
|
* breakpoint-hit event was buffered, we can forget
|
|||
|
* it now.
|
|||
|
*/
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Forgetting deleted bp hit for thread %d\n",
|
|||
|
tstate.tts_lwpid );
|
|||
|
#endif
|
|||
|
|
|||
|
set_handled( pid, tstate.tts_lwpid );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
/* Else, is this the first "unhandled" event? If so,
|
|||
|
* we believe our client wants to see it (if we don't
|
|||
|
* see a fake-step later on in the scan).
|
|||
|
*/
|
|||
|
else if( !was_handled( tstate.tts_lwpid ) && candidate_tid == 0 ) {
|
|||
|
candidate_tid = tstate.tts_lwpid;
|
|||
|
candidate_tstate = tstate;
|
|||
|
}
|
|||
|
|
|||
|
/* This is either an event that has already been "handled",
|
|||
|
* and thus we believe is uninteresting to our client, or we
|
|||
|
* already have a candidate event. Ignore it...
|
|||
|
*/
|
|||
|
}
|
|||
|
|
|||
|
/* What do we report?
|
|||
|
*/
|
|||
|
if( doing_fake_step ) {
|
|||
|
if( candidate_tid == fake_step_tid ) {
|
|||
|
/* Fake step.
|
|||
|
*/
|
|||
|
tstate = candidate_tstate;
|
|||
|
}
|
|||
|
else {
|
|||
|
warning( "Internal error: fake-step failed to complete." );
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
else if( candidate_tid != 0 ) {
|
|||
|
/* Found a candidate unhandled event.
|
|||
|
*/
|
|||
|
tstate = candidate_tstate;
|
|||
|
}
|
|||
|
else if( tid != 0 ) {
|
|||
|
warning( "Internal error in call of ttrace_wait." );
|
|||
|
return 0;
|
|||
|
}
|
|||
|
else {
|
|||
|
warning ("Internal error: no unhandled thread event to select");
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
copy_ttstate_t (tsp, &tstate);
|
|||
|
return 1;
|
|||
|
} /* End of select_stopped_thread_of_process */
|
|||
|
|
|||
|
#ifdef PARANOIA
|
|||
|
/* Check our internal thread data against the real thing.
|
|||
|
*/
|
|||
|
static void
|
|||
|
check_thread_consistency( real_pid )
|
|||
|
pid_t real_pid;
|
|||
|
{
|
|||
|
int tid; /* really lwpid_t */
|
|||
|
ttstate_t tstate;
|
|||
|
thread_info *p;
|
|||
|
|
|||
|
/* Spin down the O/S list of threads, checking that they
|
|||
|
* match what we've got.
|
|||
|
*/
|
|||
|
for (tid = get_process_first_stopped_thread_id( real_pid, &tstate );
|
|||
|
tid != 0;
|
|||
|
tid = get_process_next_stopped_thread_id( real_pid, &tstate )) {
|
|||
|
|
|||
|
p = find_thread_info( tid );
|
|||
|
|
|||
|
if( NULL == p ) {
|
|||
|
warning( "No internal thread data for thread %d.", tid );
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if( !p->seen ) {
|
|||
|
warning( "Inconsistent internal thread data for thread %d.", tid );
|
|||
|
}
|
|||
|
|
|||
|
if( p->terminated ) {
|
|||
|
warning( "Thread %d is not terminated, internal error.", tid );
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#define TT_COMPARE( fld ) \
|
|||
|
tstate.fld != p->last_stop_state.fld
|
|||
|
|
|||
|
if( p->have_state ) {
|
|||
|
if( TT_COMPARE( tts_pid )
|
|||
|
|| TT_COMPARE( tts_lwpid )
|
|||
|
|| TT_COMPARE( tts_user_tid )
|
|||
|
|| TT_COMPARE( tts_event )
|
|||
|
|| TT_COMPARE( tts_flags )
|
|||
|
|| TT_COMPARE( tts_scno )
|
|||
|
|| TT_COMPARE( tts_scnargs )) {
|
|||
|
warning( "Internal thread data for thread %d is wrong.", tid );
|
|||
|
continue;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endif /* PARANOIA */
|
|||
|
|
|||
|
|
|||
|
/* This function wraps calls to "call_real_ttrace_wait" so
|
|||
|
* that a actual wait is only done when all pending events
|
|||
|
* have been reported.
|
|||
|
*
|
|||
|
* Note that typically it is called with a pid of "0", i.e.
|
|||
|
* the "don't care" value.
|
|||
|
*
|
|||
|
* Return value is the status of the pseudo wait.
|
|||
|
*/
|
|||
|
static int
|
|||
|
call_ttrace_wait( pid, option, tsp, tsp_size )
|
|||
|
int pid;
|
|||
|
ttwopt_t option;
|
|||
|
ttstate_t *tsp;
|
|||
|
size_t tsp_size;
|
|||
|
{
|
|||
|
/* This holds the actual, for-real, true process ID.
|
|||
|
*/
|
|||
|
static int real_pid;
|
|||
|
|
|||
|
/* As an argument to ttrace_wait, zero pid
|
|||
|
* means "Any process", and zero tid means
|
|||
|
* "Any thread of the specified process".
|
|||
|
*/
|
|||
|
int wait_pid = 0;
|
|||
|
lwpid_t wait_tid = 0;
|
|||
|
lwpid_t real_tid;
|
|||
|
|
|||
|
int ttw_status = 0; /* To be returned */
|
|||
|
|
|||
|
thread_info * tinfo = NULL;
|
|||
|
|
|||
|
if( pid != 0 ) {
|
|||
|
/* Unexpected case.
|
|||
|
*/
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "TW: Pid to wait on is %d\n", pid );
|
|||
|
#endif
|
|||
|
|
|||
|
if( !any_thread_records())
|
|||
|
error( "No thread records for ttrace call w. specific pid" );
|
|||
|
|
|||
|
/* OK, now the task is to translate the incoming tid into
|
|||
|
* a pid/tid pair.
|
|||
|
*/
|
|||
|
real_tid = map_from_gdb_tid( pid );
|
|||
|
real_pid = get_pid_for( real_tid );
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "==TW: real pid %d, real tid %d\n", real_pid, real_tid );
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Sanity checks and set-up.
|
|||
|
* Process State
|
|||
|
*
|
|||
|
* Stopped Running Fake-step (v)Fork
|
|||
|
* \________________________________________
|
|||
|
* |
|
|||
|
* No buffered events | error wait wait wait
|
|||
|
* |
|
|||
|
* Buffered events | debuffer error wait debuffer (?)
|
|||
|
*
|
|||
|
*/
|
|||
|
if( more_events_left == 0 ) {
|
|||
|
|
|||
|
if( process_state == RUNNING ) {
|
|||
|
/* OK--normal call of ttrace_wait with no buffered events.
|
|||
|
*/
|
|||
|
;
|
|||
|
}
|
|||
|
else if( process_state == FAKE_STEPPING ) {
|
|||
|
/* Ok--call of ttrace_wait to support
|
|||
|
* fake stepping with no buffered events.
|
|||
|
*
|
|||
|
* But we better be fake-stepping!
|
|||
|
*/
|
|||
|
if( !doing_fake_step ) {
|
|||
|
warning( "Inconsistent thread state." );
|
|||
|
}
|
|||
|
}
|
|||
|
else if( (process_state == FORKING)
|
|||
|
|| (process_state == VFORKING)) {
|
|||
|
/* Ok--there are two processes, so waiting
|
|||
|
* for the second while the first is stopped
|
|||
|
* is ok. Handled bits stay as they were.
|
|||
|
*/
|
|||
|
;
|
|||
|
}
|
|||
|
else if( process_state == STOPPED ) {
|
|||
|
warning( "Process not running at wait call." );
|
|||
|
}
|
|||
|
else
|
|||
|
/* No known state.
|
|||
|
*/
|
|||
|
warning( "Inconsistent process state." );
|
|||
|
}
|
|||
|
|
|||
|
else {
|
|||
|
/* More events left
|
|||
|
*/
|
|||
|
if( process_state == STOPPED ) {
|
|||
|
/* OK--buffered events being unbuffered.
|
|||
|
*/
|
|||
|
;
|
|||
|
}
|
|||
|
else if( process_state == RUNNING ) {
|
|||
|
/* An error--shouldn't have buffered events
|
|||
|
* when running.
|
|||
|
*/
|
|||
|
warning( "Trying to continue with buffered events:" );
|
|||
|
}
|
|||
|
else if( process_state == FAKE_STEPPING ) {
|
|||
|
/*
|
|||
|
* Better be fake-stepping!
|
|||
|
*/
|
|||
|
if( !doing_fake_step ) {
|
|||
|
warning( "Losing buffered thread events!\n" );
|
|||
|
}
|
|||
|
}
|
|||
|
else if( (process_state == FORKING)
|
|||
|
|| (process_state == VFORKING)) {
|
|||
|
/* Ok--there are two processes, so waiting
|
|||
|
* for the second while the first is stopped
|
|||
|
* is ok. Handled bits stay as they were.
|
|||
|
*/
|
|||
|
;
|
|||
|
}
|
|||
|
else
|
|||
|
warning( "Process in unknown state with buffered events." );
|
|||
|
}
|
|||
|
|
|||
|
/* Sometimes we have to wait for a particular thread
|
|||
|
* (if we're stepping over a bpt). In that case, we
|
|||
|
* _know_ it's going to complete the single-step we
|
|||
|
* asked for (because we're only doing the step under
|
|||
|
* certain very well-understood circumstances), so it
|
|||
|
* can't block.
|
|||
|
*/
|
|||
|
if( doing_fake_step ) {
|
|||
|
wait_tid = fake_step_tid;
|
|||
|
wait_pid = get_pid_for( fake_step_tid );
|
|||
|
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Doing a wait after a fake-step for %d, pid %d\n",
|
|||
|
wait_tid, wait_pid );
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
if( more_events_left == 0 /* No buffered events, need real ones. */
|
|||
|
|| process_state != STOPPED ) {
|
|||
|
/* If there are no buffered events, and so we need
|
|||
|
* real ones, or if we are FORKING, VFORKING,
|
|||
|
* FAKE_STEPPING or RUNNING, and thus have to do
|
|||
|
* a real wait, then do a real wait.
|
|||
|
*/
|
|||
|
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
/* Normal case... */
|
|||
|
if( debug_on )
|
|||
|
printf( "TW: do it for real; pid %d, tid %d\n", wait_pid, wait_tid );
|
|||
|
#endif
|
|||
|
|
|||
|
/* The actual wait call.
|
|||
|
*/
|
|||
|
ttw_status = call_real_ttrace_wait( wait_pid, wait_tid, option, tsp, tsp_size);
|
|||
|
|
|||
|
/* Note that the routines we'll call will be using "call_real_ttrace",
|
|||
|
* not "call_ttrace", and thus need the real pid rather than the pseudo-tid
|
|||
|
* the rest of the world uses (which is actually the tid).
|
|||
|
*/
|
|||
|
real_pid = tsp->tts_pid;
|
|||
|
|
|||
|
/* For most events: Stop the world!
|
|||
|
*
|
|||
|
* It's sometimes not safe to stop all threads of a process.
|
|||
|
* Sometimes it's not even safe to ask for the thread state
|
|||
|
* of a process!
|
|||
|
*/
|
|||
|
if (can_touch_threads_of_process (real_pid, tsp->tts_event))
|
|||
|
{
|
|||
|
/* If we're really only stepping a single thread, then don't
|
|||
|
* try to stop all the others -- we only do this single-stepping
|
|||
|
* business when all others were already stopped...and the stop
|
|||
|
* would mess up other threads' events.
|
|||
|
*
|
|||
|
* Similiarly, if there are other threads with events,
|
|||
|
* don't do the stop.
|
|||
|
*/
|
|||
|
if( !doing_fake_step ) {
|
|||
|
if( more_events_left > 0 )
|
|||
|
warning( "Internal error in stopping process" );
|
|||
|
|
|||
|
stop_all_threads_of_process (real_pid);
|
|||
|
|
|||
|
/* At this point, we could scan and update_thread_list(),
|
|||
|
* and only use the local list for the rest of the
|
|||
|
* module! We'd get rid of the scans in the various
|
|||
|
* continue routines (adding one in attach). It'd
|
|||
|
* be great--UPGRADE ME!
|
|||
|
*/
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#ifdef PARANOIA
|
|||
|
else if( debug_on ) {
|
|||
|
if( more_events_left > 0 )
|
|||
|
printf( "== Can't stop process; more events!\n" );
|
|||
|
else
|
|||
|
printf( "== Can't stop process!\n" );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
process_state = STOPPED;
|
|||
|
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Process set to STOPPED\n" );
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
else {
|
|||
|
/* Fake a call to ttrace_wait. The process must be
|
|||
|
* STOPPED, as we aren't going to do any wait.
|
|||
|
*/
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "TW: fake it\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
if( process_state != STOPPED ) {
|
|||
|
warning( "Process not stopped at wait call, in state '%s'.\n",
|
|||
|
get_printable_name_of_process_state( process_state ));
|
|||
|
}
|
|||
|
|
|||
|
if( doing_fake_step )
|
|||
|
error( "Internal error in stepping over breakpoint" );
|
|||
|
|
|||
|
ttw_status = 0; /* Faking it is always successful! */
|
|||
|
} /* End of fake or not? if */
|
|||
|
|
|||
|
/* Pick an event to pass to our caller. Be paranoid.
|
|||
|
*/
|
|||
|
if( !select_stopped_thread_of_process( real_pid, tsp ))
|
|||
|
warning( "Can't find event, using previous event." );
|
|||
|
|
|||
|
else if( tsp->tts_event == TTEVT_NONE )
|
|||
|
warning( "Internal error: no thread has a real event." );
|
|||
|
|
|||
|
else if( doing_fake_step ) {
|
|||
|
if( fake_step_tid != tsp->tts_lwpid )
|
|||
|
warning( "Internal error in stepping over breakpoint." );
|
|||
|
|
|||
|
/* This wait clears the (current) fake-step if there was one.
|
|||
|
*/
|
|||
|
doing_fake_step = 0;
|
|||
|
fake_step_tid = 0;
|
|||
|
}
|
|||
|
|
|||
|
/* We now have a correct tsp and ttw_status for the thread
|
|||
|
* which we want to report. So it's "handled"! This call
|
|||
|
* will add it to our list if it's not there already.
|
|||
|
*/
|
|||
|
set_handled( real_pid, tsp->tts_lwpid );
|
|||
|
|
|||
|
/* Save a copy of the ttrace state of this thread, in our local
|
|||
|
thread descriptor.
|
|||
|
|
|||
|
This caches the state. The implementation of queries like
|
|||
|
target_has_execd can then use this cached state, rather than
|
|||
|
be forced to make an explicit ttrace call to get it.
|
|||
|
|
|||
|
(Guard against the condition that this is the first time we've
|
|||
|
waited on, i.e., seen this thread, and so haven't yet entered
|
|||
|
it into our list of threads.)
|
|||
|
*/
|
|||
|
tinfo = find_thread_info (tsp->tts_lwpid);
|
|||
|
if (tinfo != NULL) {
|
|||
|
copy_ttstate_t (&tinfo->last_stop_state, tsp);
|
|||
|
tinfo->have_state = 1;
|
|||
|
}
|
|||
|
|
|||
|
return ttw_status;
|
|||
|
} /* call_ttrace_wait */
|
|||
|
|
|||
|
#if defined(CHILD_REPORTED_EXEC_EVENTS_PER_EXEC_CALL)
|
|||
|
int
|
|||
|
child_reported_exec_events_per_exec_call ()
|
|||
|
{
|
|||
|
return 1; /* ttrace reports the event once per call. */
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/* Our implementation of hardware watchpoints involves making memory
|
|||
|
pages write-protected. We must remember a page's original permissions,
|
|||
|
and we must also know when it is appropriate to restore a page's
|
|||
|
permissions to its original state.
|
|||
|
|
|||
|
We use a "dictionary" of hardware-watched pages to do this. Each
|
|||
|
hardware-watched page is recorded in the dictionary. Each page's
|
|||
|
dictionary entry contains the original permissions and a reference
|
|||
|
count. Pages are hashed into the dictionary by their start address.
|
|||
|
|
|||
|
When hardware watchpoint is set on page X for the first time, page X
|
|||
|
is added to the dictionary with a reference count of 1. If other
|
|||
|
hardware watchpoints are subsequently set on page X, its reference
|
|||
|
count is incremented. When hardware watchpoints are removed from
|
|||
|
page X, its reference count is decremented. If a page's reference
|
|||
|
count drops to 0, it's permissions are restored and the page's entry
|
|||
|
is thrown out of the dictionary.
|
|||
|
*/
|
|||
|
typedef struct memory_page {
|
|||
|
CORE_ADDR page_start;
|
|||
|
int reference_count;
|
|||
|
int original_permissions;
|
|||
|
struct memory_page * next;
|
|||
|
struct memory_page * previous;
|
|||
|
} memory_page_t;
|
|||
|
|
|||
|
#define MEMORY_PAGE_DICTIONARY_BUCKET_COUNT 128
|
|||
|
|
|||
|
static struct {
|
|||
|
LONGEST page_count;
|
|||
|
int page_size;
|
|||
|
int page_protections_allowed;
|
|||
|
/* These are just the heads of chains of actual page descriptors. */
|
|||
|
memory_page_t buckets [MEMORY_PAGE_DICTIONARY_BUCKET_COUNT];
|
|||
|
} memory_page_dictionary;
|
|||
|
|
|||
|
|
|||
|
static void
|
|||
|
require_memory_page_dictionary ()
|
|||
|
{
|
|||
|
int i;
|
|||
|
|
|||
|
/* Is the memory page dictionary ready for use? If so, we're done. */
|
|||
|
if (memory_page_dictionary.page_count >= (LONGEST) 0)
|
|||
|
return;
|
|||
|
|
|||
|
/* Else, initialize it. */
|
|||
|
memory_page_dictionary.page_count = (LONGEST) 0;
|
|||
|
|
|||
|
for (i=0; i<MEMORY_PAGE_DICTIONARY_BUCKET_COUNT; i++)
|
|||
|
{
|
|||
|
memory_page_dictionary.buckets[i].page_start = (CORE_ADDR) 0;
|
|||
|
memory_page_dictionary.buckets[i].reference_count = 0;
|
|||
|
memory_page_dictionary.buckets[i].next = NULL;
|
|||
|
memory_page_dictionary.buckets[i].previous = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static void
|
|||
|
retire_memory_page_dictionary ()
|
|||
|
{
|
|||
|
memory_page_dictionary.page_count = (LONGEST) -1;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Write-protect the memory page that starts at this address.
|
|||
|
|
|||
|
Returns the original permissions of the page.
|
|||
|
*/
|
|||
|
static int
|
|||
|
write_protect_page (pid, page_start)
|
|||
|
int pid;
|
|||
|
CORE_ADDR page_start;
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
int original_permissions;
|
|||
|
int new_permissions;
|
|||
|
|
|||
|
tt_status = call_ttrace (TT_PROC_GET_MPROTECT,
|
|||
|
pid,
|
|||
|
(TTRACE_ARG_TYPE) page_start,
|
|||
|
TT_NIL,
|
|||
|
(TTRACE_ARG_TYPE) &original_permissions);
|
|||
|
if (errno || (tt_status < 0))
|
|||
|
{
|
|||
|
return 0; /* What else can we do? */
|
|||
|
}
|
|||
|
|
|||
|
/* We'll also write-protect the page now, if that's allowed. */
|
|||
|
if (memory_page_dictionary.page_protections_allowed)
|
|||
|
{
|
|||
|
new_permissions = original_permissions & ~PROT_WRITE;
|
|||
|
tt_status = call_ttrace (TT_PROC_SET_MPROTECT,
|
|||
|
pid,
|
|||
|
(TTRACE_ARG_TYPE) page_start,
|
|||
|
(TTRACE_ARG_TYPE) memory_page_dictionary.page_size,
|
|||
|
(TTRACE_ARG_TYPE) new_permissions);
|
|||
|
if (errno || (tt_status < 0))
|
|||
|
{
|
|||
|
return 0; /* What else can we do? */
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return original_permissions;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Unwrite-protect the memory page that starts at this address, restoring
|
|||
|
(what we must assume are) its original permissions.
|
|||
|
*/
|
|||
|
static void
|
|||
|
unwrite_protect_page (pid, page_start, original_permissions)
|
|||
|
int pid;
|
|||
|
CORE_ADDR page_start;
|
|||
|
int original_permissions;
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
|
|||
|
tt_status = call_ttrace (TT_PROC_SET_MPROTECT,
|
|||
|
pid,
|
|||
|
(TTRACE_ARG_TYPE) page_start,
|
|||
|
(TTRACE_ARG_TYPE) memory_page_dictionary.page_size,
|
|||
|
(TTRACE_ARG_TYPE) original_permissions);
|
|||
|
if (errno || (tt_status < 0))
|
|||
|
{
|
|||
|
return; /* What else can we do? */
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Memory page-protections are used to implement "hardware" watchpoints
|
|||
|
on HP-UX.
|
|||
|
|
|||
|
For every memory page that is currently being watched (i.e., that
|
|||
|
presently should be write-protected), write-protect it.
|
|||
|
*/
|
|||
|
void
|
|||
|
hppa_enable_page_protection_events (pid)
|
|||
|
int pid;
|
|||
|
{
|
|||
|
int bucket;
|
|||
|
|
|||
|
memory_page_dictionary.page_protections_allowed = 1;
|
|||
|
|
|||
|
for (bucket=0; bucket<MEMORY_PAGE_DICTIONARY_BUCKET_COUNT; bucket++)
|
|||
|
{
|
|||
|
memory_page_t * page;
|
|||
|
|
|||
|
page = memory_page_dictionary.buckets[bucket].next;
|
|||
|
while (page != NULL)
|
|||
|
{
|
|||
|
page->original_permissions = write_protect_page (pid, page->page_start);
|
|||
|
page = page->next;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Memory page-protections are used to implement "hardware" watchpoints
|
|||
|
on HP-UX.
|
|||
|
|
|||
|
For every memory page that is currently being watched (i.e., that
|
|||
|
presently is or should be write-protected), un-write-protect it.
|
|||
|
*/
|
|||
|
void
|
|||
|
hppa_disable_page_protection_events (pid)
|
|||
|
int pid;
|
|||
|
{
|
|||
|
int bucket;
|
|||
|
|
|||
|
for (bucket=0; bucket<MEMORY_PAGE_DICTIONARY_BUCKET_COUNT; bucket++)
|
|||
|
{
|
|||
|
memory_page_t * page;
|
|||
|
|
|||
|
page = memory_page_dictionary.buckets[bucket].next;
|
|||
|
while (page != NULL)
|
|||
|
{
|
|||
|
unwrite_protect_page (pid, page->page_start, page->original_permissions);
|
|||
|
page = page->next;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
memory_page_dictionary.page_protections_allowed = 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Count the number of outstanding events. At this
|
|||
|
* point, we have selected one thread and its event
|
|||
|
* as the one to be "reported" upwards to core gdb.
|
|||
|
* That thread is already marked as "handled".
|
|||
|
*
|
|||
|
* Note: we could just scan our own thread list. FIXME!
|
|||
|
*/
|
|||
|
static int
|
|||
|
count_unhandled_events( real_pid, real_tid )
|
|||
|
int real_pid;
|
|||
|
lwpid_t real_tid;
|
|||
|
{
|
|||
|
ttstate_t tstate;
|
|||
|
lwpid_t ttid;
|
|||
|
int events_left;
|
|||
|
|
|||
|
/* Ok, find out how many threads have real events to report.
|
|||
|
*/
|
|||
|
events_left = 0;
|
|||
|
ttid = get_process_first_stopped_thread_id( real_pid, &tstate );
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on ) {
|
|||
|
if( ttid == 0 )
|
|||
|
printf( "Process %d has no threads\n", real_pid );
|
|||
|
else
|
|||
|
printf( "Process %d has these threads:\n", real_pid );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
while (ttid > 0 ) {
|
|||
|
if( tstate.tts_event != TTEVT_NONE
|
|||
|
&& !was_handled( ttid )) {
|
|||
|
/* TTEVT_NONE implies we just stopped it ourselves
|
|||
|
* because we're the stop-the-world guys, so it's
|
|||
|
* not an event from our point of view.
|
|||
|
*
|
|||
|
* If "was_handled" is true, this is an event we
|
|||
|
* already handled, so don't count it.
|
|||
|
*
|
|||
|
* Note that we don't count the thread with the
|
|||
|
* currently-reported event, as it's already marked
|
|||
|
* as handled.
|
|||
|
*/
|
|||
|
events_left++;
|
|||
|
}
|
|||
|
|
|||
|
#if defined( THREAD_DEBUG ) || defined( WAIT_BUFFER_DEBUG )
|
|||
|
if( debug_on ) {
|
|||
|
if( ttid == real_tid )
|
|||
|
printf( "*" ); /* Thread we're reporting */
|
|||
|
else
|
|||
|
printf( " " );
|
|||
|
|
|||
|
if( tstate.tts_event != TTEVT_NONE )
|
|||
|
printf( "+" ); /* Thread with a real event */
|
|||
|
else
|
|||
|
printf( " " );
|
|||
|
|
|||
|
if( was_handled( ttid ))
|
|||
|
printf( "h" ); /* Thread has been handled */
|
|||
|
else
|
|||
|
printf( " " );
|
|||
|
|
|||
|
printf( " %d, with event %s", ttid,
|
|||
|
get_printable_name_of_ttrace_event( tstate.tts_event ));
|
|||
|
|
|||
|
if( tstate.tts_event == TTEVT_SIGNAL
|
|||
|
&& 5 == tstate.tts_u.tts_signal.tts_signo ) {
|
|||
|
CORE_ADDR pc_val;
|
|||
|
|
|||
|
pc_val = get_raw_pc( ttid );
|
|||
|
|
|||
|
if( pc_val > 0 )
|
|||
|
printf( " breakpoint at 0x%x\n", pc_val );
|
|||
|
else
|
|||
|
printf( " bpt, can't fetch pc.\n" );
|
|||
|
}
|
|||
|
else
|
|||
|
printf( "\n" );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
ttid = get_process_next_stopped_thread_id (real_pid, &tstate);
|
|||
|
}
|
|||
|
|
|||
|
#if defined( THREAD_DEBUG ) || defined( WAIT_BUFFER_DEBUG )
|
|||
|
if( debug_on )
|
|||
|
if( events_left > 0 )
|
|||
|
printf( "There are thus %d pending events\n", events_left );
|
|||
|
#endif
|
|||
|
|
|||
|
return events_left;
|
|||
|
}
|
|||
|
|
|||
|
/* This function is provided as a sop to clients that are calling
|
|||
|
* ptrace_wait to wait for a process to stop. (see the
|
|||
|
* implementation of child_wait.) Return value is the pid for
|
|||
|
* the event that ended the wait.
|
|||
|
*
|
|||
|
* Note: used by core gdb and so uses the pseudo-pid (really tid).
|
|||
|
*/
|
|||
|
int
|
|||
|
ptrace_wait (pid, status)
|
|||
|
int pid;
|
|||
|
int *status;
|
|||
|
{
|
|||
|
ttstate_t tsp;
|
|||
|
int ttwait_return;
|
|||
|
int real_pid;
|
|||
|
ttstate_t state;
|
|||
|
lwpid_t real_tid;
|
|||
|
int return_pid;
|
|||
|
|
|||
|
/* The ptrace implementation of this also ignores pid.
|
|||
|
*/
|
|||
|
*status = 0;
|
|||
|
|
|||
|
ttwait_return = call_ttrace_wait( 0, TTRACE_WAITOK, &tsp, sizeof (tsp) );
|
|||
|
if (ttwait_return < 0)
|
|||
|
{
|
|||
|
/* ??rehrauer: It appears that if our inferior exits and we
|
|||
|
haven't asked for exit events, that we're not getting any
|
|||
|
indication save a negative return from ttrace_wait and an
|
|||
|
errno set to ESRCH?
|
|||
|
*/
|
|||
|
if (errno == ESRCH)
|
|||
|
{
|
|||
|
*status = 0; /* WIFEXITED */
|
|||
|
return inferior_pid;
|
|||
|
}
|
|||
|
|
|||
|
warning( "Call of ttrace_wait returned with errno %d.",
|
|||
|
errno );
|
|||
|
*status = ttwait_return;
|
|||
|
return inferior_pid;
|
|||
|
}
|
|||
|
|
|||
|
real_pid = tsp.tts_pid;
|
|||
|
real_tid = tsp.tts_lwpid;
|
|||
|
|
|||
|
/* One complication is that the "tts_event" structure has
|
|||
|
* a set of flags, and more than one can be set. So we
|
|||
|
* either have to force an order (as we do here), or handle
|
|||
|
* more than one flag at a time.
|
|||
|
*/
|
|||
|
if (tsp.tts_event & TTEVT_LWP_CREATE) {
|
|||
|
|
|||
|
/* Unlike what you might expect, this event is reported in
|
|||
|
* the _creating_ thread, and the _created_ thread (whose tid
|
|||
|
* we have) is still running. So we have to stop it. This
|
|||
|
* has already been done in "call_ttrace_wait", but should we
|
|||
|
* ever abandon the "stop-the-world" model, here's the command
|
|||
|
* to use:
|
|||
|
*
|
|||
|
* call_ttrace( TT_LWP_STOP, real_tid, TT_NIL, TT_NIL, TT_NIL );
|
|||
|
*
|
|||
|
* Note that this would depend on being called _after_ "add_tthread"
|
|||
|
* below for the tid-to-pid translation to be done in "call_ttrace".
|
|||
|
*/
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "New thread: pid %d, tid %d, creator tid %d\n",
|
|||
|
real_pid, tsp.tts_u.tts_thread.tts_target_lwpid,
|
|||
|
real_tid );
|
|||
|
#endif
|
|||
|
|
|||
|
/* Now we have to return the tid of the created thread, not
|
|||
|
* the creating thread, or "wait_for_inferior" won't know we
|
|||
|
* have a new "process" (thread). Plus we should record it
|
|||
|
* right, too.
|
|||
|
*/
|
|||
|
real_tid = tsp.tts_u.tts_thread.tts_target_lwpid;
|
|||
|
|
|||
|
add_tthread( real_pid, real_tid );
|
|||
|
}
|
|||
|
|
|||
|
else if( (tsp.tts_event & TTEVT_LWP_TERMINATE )
|
|||
|
|| (tsp.tts_event & TTEVT_LWP_EXIT) ) {
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Thread dies: %d\n", real_tid );
|
|||
|
#endif
|
|||
|
|
|||
|
del_tthread( real_tid );
|
|||
|
}
|
|||
|
|
|||
|
else if (tsp.tts_event & TTEVT_EXEC) {
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Pid %d has zero'th thread %d; inferior pid is %d\n",
|
|||
|
real_pid, real_tid, inferior_pid );
|
|||
|
#endif
|
|||
|
|
|||
|
add_tthread( real_pid, real_tid );
|
|||
|
}
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
else if( debug_on ) {
|
|||
|
printf( "Process-level event %s, using tid %d\n",
|
|||
|
get_printable_name_of_ttrace_event( tsp.tts_event ),
|
|||
|
real_tid );
|
|||
|
|
|||
|
/* OK to do this, as "add_tthread" won't add
|
|||
|
* duplicate entries. Also OK not to do it,
|
|||
|
* as this event isn't one which can change the
|
|||
|
* thread state.
|
|||
|
*/
|
|||
|
add_tthread( real_pid, real_tid );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
/* How many events are left to report later?
|
|||
|
* In a non-stop-the-world model, this isn't needed.
|
|||
|
*
|
|||
|
* Note that it's not always safe to query the thread state of a process,
|
|||
|
* which is what count_unhandled_events does. (If unsafe, we're left with
|
|||
|
* no other resort than to assume that no more events remain...)
|
|||
|
*/
|
|||
|
if (can_touch_threads_of_process (real_pid, tsp.tts_event))
|
|||
|
more_events_left = count_unhandled_events( real_pid, real_tid );
|
|||
|
|
|||
|
else {
|
|||
|
if( more_events_left > 0 )
|
|||
|
warning( "Vfork or fork causing loss of %d buffered events.",
|
|||
|
more_events_left );
|
|||
|
|
|||
|
more_events_left = 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Attempt to translate the ttrace_wait-returned status into the
|
|||
|
ptrace equivalent.
|
|||
|
|
|||
|
??rehrauer: This is somewhat fragile. We really ought to rewrite
|
|||
|
clients that expect to pick apart a ptrace wait status, to use
|
|||
|
something a little more abstract.
|
|||
|
*/
|
|||
|
if ( (tsp.tts_event & TTEVT_EXEC)
|
|||
|
|| (tsp.tts_event & TTEVT_FORK)
|
|||
|
|| (tsp.tts_event & TTEVT_VFORK))
|
|||
|
{
|
|||
|
/* Forks come in pairs (parent and child), so core gdb
|
|||
|
* will do two waits. Be ready to notice this.
|
|||
|
*/
|
|||
|
if (tsp.tts_event & TTEVT_FORK)
|
|||
|
{
|
|||
|
process_state = FORKING;
|
|||
|
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Process set to FORKING\n" );
|
|||
|
#endif
|
|||
|
}
|
|||
|
else if (tsp.tts_event & TTEVT_VFORK)
|
|||
|
{
|
|||
|
process_state = VFORKING;
|
|||
|
|
|||
|
#ifdef WAIT_BUFFER_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Process set to VFORKING\n" );
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
/* Make an exec or fork look like a breakpoint. Definitely a hack,
|
|||
|
but I don't think non HP-UX-specific clients really carefully
|
|||
|
inspect the first events they get after inferior startup, so
|
|||
|
it probably almost doesn't matter what we claim this is.
|
|||
|
*/
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "..a process 'event'\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
/* Also make fork and exec events look like bpts, so they can be caught.
|
|||
|
*/
|
|||
|
*status = 0177 | (_SIGTRAP << 8);
|
|||
|
}
|
|||
|
|
|||
|
/* Special-cases: We ask for syscall entry and exit events to implement
|
|||
|
"fast" (aka "hardware") watchpoints.
|
|||
|
|
|||
|
When we get a syscall entry, we want to disable page-protections,
|
|||
|
and resume the inferior; this isn't an event we wish for
|
|||
|
wait_for_inferior to see. Note that we must resume ONLY the
|
|||
|
thread that reported the syscall entry; we don't want to allow
|
|||
|
other threads to run with the page protections off, as they might
|
|||
|
then be able to write to watch memory without it being caught.
|
|||
|
|
|||
|
When we get a syscall exit, we want to reenable page-protections,
|
|||
|
but we don't want to resume the inferior; this is an event we wish
|
|||
|
wait_for_inferior to see. Make it look like the signal we normally
|
|||
|
get for a single-step completion. This should cause wait_for_inferior
|
|||
|
to evaluate whether any watchpoint triggered.
|
|||
|
|
|||
|
Or rather, that's what we'd LIKE to do for syscall exit; we can't,
|
|||
|
due to some HP-UX "features". Some syscalls have problems with
|
|||
|
write-protections on some pages, and some syscalls seem to have
|
|||
|
pending writes to those pages at the time we're getting the return
|
|||
|
event. So, we'll single-step the inferior to get out of the syscall,
|
|||
|
and then reenable protections.
|
|||
|
|
|||
|
Note that we're intentionally allowing the syscall exit case to
|
|||
|
fall through into the succeeding cases, as sometimes we single-
|
|||
|
step out of one syscall only to immediately enter another...
|
|||
|
*/
|
|||
|
else if ((tsp.tts_event & TTEVT_SYSCALL_ENTRY)
|
|||
|
|| (tsp.tts_event & TTEVT_SYSCALL_RETURN))
|
|||
|
{
|
|||
|
/* Make a syscall event look like a breakpoint. Same comments
|
|||
|
as for exec & fork events.
|
|||
|
*/
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "..a syscall 'event'\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
/* Also make syscall events look like bpts, so they can be caught.
|
|||
|
*/
|
|||
|
*status = 0177 | (_SIGTRAP << 8);
|
|||
|
}
|
|||
|
|
|||
|
else if ((tsp.tts_event & TTEVT_LWP_CREATE)
|
|||
|
|| (tsp.tts_event & TTEVT_LWP_TERMINATE)
|
|||
|
|| (tsp.tts_event & TTEVT_LWP_EXIT))
|
|||
|
{
|
|||
|
/* Make a thread event look like a breakpoint. Same comments
|
|||
|
* as for exec & fork events.
|
|||
|
*/
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "..a thread 'event'\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
/* Also make thread events look like bpts, so they can be caught.
|
|||
|
*/
|
|||
|
*status = 0177 | (_SIGTRAP << 8);
|
|||
|
}
|
|||
|
|
|||
|
else if ((tsp.tts_event & TTEVT_EXIT))
|
|||
|
{ /* WIFEXITED */
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "..an exit\n" );
|
|||
|
#endif
|
|||
|
|
|||
|
/* Prevent rest of gdb from thinking this is
|
|||
|
* a new thread if for some reason it's never
|
|||
|
* seen the main thread before.
|
|||
|
*/
|
|||
|
inferior_pid = map_to_gdb_tid( real_tid ); /* HACK, FIX */
|
|||
|
|
|||
|
*status = 0 | (tsp.tts_u.tts_exit.tts_exitcode);
|
|||
|
}
|
|||
|
|
|||
|
else if (tsp.tts_event & TTEVT_SIGNAL)
|
|||
|
{ /* WIFSTOPPED */
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "..a signal, %d\n", tsp.tts_u.tts_signal.tts_signo );
|
|||
|
#endif
|
|||
|
|
|||
|
*status = 0177 | (tsp.tts_u.tts_signal.tts_signo << 8);
|
|||
|
}
|
|||
|
|
|||
|
else
|
|||
|
{ /* !WIFSTOPPED */
|
|||
|
|
|||
|
/* This means the process or thread terminated. But we should've
|
|||
|
caught an explicit exit/termination above. So warn (this is
|
|||
|
really an internal error) and claim the process or thread
|
|||
|
terminated with a SIGTRAP.
|
|||
|
*/
|
|||
|
|
|||
|
warning ("process_wait: unknown process state");
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Process-level event %s, using tid %d\n",
|
|||
|
get_printable_name_of_ttrace_event( tsp.tts_event ),
|
|||
|
real_tid );
|
|||
|
#endif
|
|||
|
|
|||
|
*status = _SIGTRAP;
|
|||
|
}
|
|||
|
|
|||
|
target_post_wait (tsp.tts_pid, *status);
|
|||
|
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Done waiting, pid is %d, tid %d\n", real_pid, real_tid );
|
|||
|
#endif
|
|||
|
|
|||
|
/* All code external to this module uses the tid, but calls
|
|||
|
* it "pid". There's some tweaking so that the outside sees
|
|||
|
* the first thread as having the same number as the starting
|
|||
|
* pid.
|
|||
|
*/
|
|||
|
return_pid = map_to_gdb_tid( real_tid );
|
|||
|
|
|||
|
/* Remember this for later use in "hppa_prepare_to_proceed".
|
|||
|
*/
|
|||
|
old_gdb_pid = inferior_pid;
|
|||
|
reported_pid = return_pid;
|
|||
|
reported_bpt = ((tsp.tts_event & TTEVT_SIGNAL) && (5 == tsp.tts_u.tts_signal.tts_signo));
|
|||
|
|
|||
|
if( real_tid == 0 || return_pid == 0 ) {
|
|||
|
warning( "Internal error: process-wait failed." );
|
|||
|
}
|
|||
|
|
|||
|
return return_pid;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* This function causes the caller's process to be traced by its
|
|||
|
parent. This is intended to be called after GDB forks itself,
|
|||
|
and before the child execs the target. Despite the name, it
|
|||
|
is called by the child.
|
|||
|
|
|||
|
Note that HP-UX ttrace is rather funky in how this is done.
|
|||
|
If the parent wants to get the initial exec event of a child,
|
|||
|
it must set the ttrace event mask of the child to include execs.
|
|||
|
(The child cannot do this itself.) This must be done after the
|
|||
|
child is forked, but before it execs.
|
|||
|
|
|||
|
To coordinate the parent and child, we implement a semaphore using
|
|||
|
pipes. After SETTRC'ing itself, the child tells the parent that
|
|||
|
it is now traceable by the parent, and waits for the parent's
|
|||
|
acknowledgement. The parent can then set the child's event mask,
|
|||
|
and notify the child that it can now exec.
|
|||
|
|
|||
|
(The acknowledgement by parent happens as a result of a call to
|
|||
|
child_acknowledge_created_inferior.)
|
|||
|
*/
|
|||
|
int
|
|||
|
parent_attach_all ()
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
|
|||
|
/* We need a memory home for a constant, to pass it to ttrace.
|
|||
|
The value of the constant is arbitrary, so long as both
|
|||
|
parent and child use the same value. Might as well use the
|
|||
|
"magic" constant provided by ttrace...
|
|||
|
*/
|
|||
|
uint64_t tc_magic_child = TT_VERSION;
|
|||
|
uint64_t tc_magic_parent = 0;
|
|||
|
|
|||
|
tt_status = call_real_ttrace (
|
|||
|
TT_PROC_SETTRC,
|
|||
|
(int) TT_NIL,
|
|||
|
(lwpid_t) TT_NIL,
|
|||
|
TT_NIL,
|
|||
|
(TTRACE_ARG_TYPE) TT_VERSION,
|
|||
|
TT_NIL );
|
|||
|
|
|||
|
if (tt_status < 0)
|
|||
|
return tt_status;
|
|||
|
|
|||
|
/* Notify the parent that we're potentially ready to exec(). */
|
|||
|
write (startup_semaphore.child_channel[SEM_TALK],
|
|||
|
&tc_magic_child,
|
|||
|
sizeof (tc_magic_child));
|
|||
|
|
|||
|
/* Wait for acknowledgement from the parent. */
|
|||
|
read (startup_semaphore.parent_channel[SEM_LISTEN],
|
|||
|
&tc_magic_parent,
|
|||
|
sizeof (tc_magic_parent));
|
|||
|
|
|||
|
if (tc_magic_child != tc_magic_parent)
|
|||
|
warning ("mismatched semaphore magic");
|
|||
|
|
|||
|
/* Discard our copy of the semaphore. */
|
|||
|
(void) close (startup_semaphore.parent_channel[SEM_LISTEN]);
|
|||
|
(void) close (startup_semaphore.parent_channel[SEM_TALK]);
|
|||
|
(void) close (startup_semaphore.child_channel[SEM_LISTEN]);
|
|||
|
(void) close (startup_semaphore.child_channel[SEM_TALK]);
|
|||
|
|
|||
|
return tt_status;
|
|||
|
}
|
|||
|
|
|||
|
/* Despite being file-local, this routine is dealing with
|
|||
|
* actual process IDs, not thread ids. That's because it's
|
|||
|
* called before the first "wait" call, and there's no map
|
|||
|
* yet from tids to pids.
|
|||
|
*
|
|||
|
* When it is called, a forked child is running, but waiting on
|
|||
|
* the semaphore. If you stop the child and re-start it,
|
|||
|
* things get confused, so don't do that! An attached child is
|
|||
|
* stopped.
|
|||
|
*
|
|||
|
* Since this is called after either attach or run, we
|
|||
|
* have to be the common part of both.
|
|||
|
*/
|
|||
|
static void
|
|||
|
require_notification_of_events ( real_pid )
|
|||
|
int real_pid;
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
ttevent_t notifiable_events;
|
|||
|
|
|||
|
lwpid_t tid;
|
|||
|
ttstate_t thread_state;
|
|||
|
|
|||
|
#ifdef THREAD_DEBUG
|
|||
|
if( debug_on )
|
|||
|
printf( "Require notif, pid is %d\n", real_pid );
|
|||
|
#endif
|
|||
|
|
|||
|
/* Temporary HACK: tell inftarg.c/child_wait to not
|
|||
|
* loop until pids are the same.
|
|||
|
*/
|
|||
|
not_same_real_pid = 0;
|
|||
|
|
|||
|
sigemptyset (¬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;
|
|||
|
}
|
|||
|
|
|||
|
/* Given the starting address of a memory page, hash it to a bucket in
|
|||
|
the memory page dictionary.
|
|||
|
*/
|
|||
|
static int
|
|||
|
get_dictionary_bucket_of_page (page_start)
|
|||
|
CORE_ADDR page_start;
|
|||
|
{
|
|||
|
int hash;
|
|||
|
|
|||
|
hash = (page_start / memory_page_dictionary.page_size);
|
|||
|
hash = hash % MEMORY_PAGE_DICTIONARY_BUCKET_COUNT;
|
|||
|
|
|||
|
return hash;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Given a memory page's starting address, get (i.e., find an existing
|
|||
|
or create a new) dictionary entry for the page. The page will be
|
|||
|
write-protected when this function returns, but may have a reference
|
|||
|
count of 0 (if the page was newly-added to the dictionary).
|
|||
|
*/
|
|||
|
static memory_page_t *
|
|||
|
get_dictionary_entry_of_page (pid, page_start)
|
|||
|
int pid;
|
|||
|
CORE_ADDR page_start;
|
|||
|
{
|
|||
|
int bucket;
|
|||
|
memory_page_t * page = NULL;
|
|||
|
memory_page_t * previous_page = NULL;
|
|||
|
|
|||
|
/* We're going to be using the dictionary now, than-kew. */
|
|||
|
require_memory_page_dictionary (pid);
|
|||
|
|
|||
|
/* Try to find an existing dictionary entry for this page. Hash
|
|||
|
on the page's starting address.
|
|||
|
*/
|
|||
|
bucket = get_dictionary_bucket_of_page (page_start);
|
|||
|
page = &memory_page_dictionary.buckets[bucket];
|
|||
|
while (page != NULL)
|
|||
|
{
|
|||
|
if (page->page_start == page_start)
|
|||
|
break;
|
|||
|
previous_page = page;
|
|||
|
page = page->next;
|
|||
|
}
|
|||
|
|
|||
|
/* Did we find a dictionary entry for this page? If not, then
|
|||
|
add it to the dictionary now.
|
|||
|
*/
|
|||
|
if (page == NULL)
|
|||
|
{
|
|||
|
/* Create a new entry. */
|
|||
|
page = (memory_page_t *) xmalloc (sizeof (memory_page_t));
|
|||
|
page->page_start = page_start;
|
|||
|
page->reference_count = 0;
|
|||
|
page->next = NULL;
|
|||
|
page->previous = NULL;
|
|||
|
|
|||
|
/* We'll write-protect the page now, if that's allowed. */
|
|||
|
page->original_permissions = write_protect_page (pid, page_start);
|
|||
|
|
|||
|
/* Add the new entry to the dictionary. */
|
|||
|
page->previous = previous_page;
|
|||
|
previous_page->next = page;
|
|||
|
|
|||
|
memory_page_dictionary.page_count++;
|
|||
|
}
|
|||
|
|
|||
|
return page;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static void
|
|||
|
remove_dictionary_entry_of_page (pid, page)
|
|||
|
int pid;
|
|||
|
memory_page_t * page;
|
|||
|
{
|
|||
|
/* Restore the page's original permissions. */
|
|||
|
unwrite_protect_page (pid, page->page_start, page->original_permissions);
|
|||
|
|
|||
|
/* Kick the page out of the dictionary. */
|
|||
|
if (page->previous != NULL)
|
|||
|
page->previous->next = page->next;
|
|||
|
if (page->next != NULL)
|
|||
|
page->next->previous = page->previous;
|
|||
|
|
|||
|
/* Just in case someone retains a handle to this after it's freed. */
|
|||
|
page->page_start = (CORE_ADDR) 0;
|
|||
|
|
|||
|
memory_page_dictionary.page_count--;
|
|||
|
|
|||
|
free (page);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static void
|
|||
|
hppa_enable_syscall_events (pid)
|
|||
|
int pid;
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
ttevent_t ttrace_events;
|
|||
|
|
|||
|
/* Get the set of events that are currently enabled. */
|
|||
|
tt_status = call_ttrace (TT_PROC_GET_EVENT_MASK,
|
|||
|
pid,
|
|||
|
(TTRACE_ARG_TYPE) &ttrace_events,
|
|||
|
(TTRACE_ARG_TYPE) sizeof (ttrace_events),
|
|||
|
TT_NIL);
|
|||
|
if (errno)
|
|||
|
perror_with_name ("ttrace");
|
|||
|
|
|||
|
/* Add syscall events to that set. */
|
|||
|
ttrace_events.tte_events |= TTEVT_SYSCALL_ENTRY;
|
|||
|
ttrace_events.tte_events |= TTEVT_SYSCALL_RETURN;
|
|||
|
|
|||
|
tt_status = call_ttrace (TT_PROC_SET_EVENT_MASK,
|
|||
|
pid,
|
|||
|
(TTRACE_ARG_TYPE) &ttrace_events,
|
|||
|
(TTRACE_ARG_TYPE) sizeof (ttrace_events),
|
|||
|
TT_NIL);
|
|||
|
if (errno)
|
|||
|
perror_with_name ("ttrace");
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static void
|
|||
|
hppa_disable_syscall_events (pid)
|
|||
|
int pid;
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
ttevent_t ttrace_events;
|
|||
|
|
|||
|
/* Get the set of events that are currently enabled. */
|
|||
|
tt_status = call_ttrace (TT_PROC_GET_EVENT_MASK,
|
|||
|
pid,
|
|||
|
(TTRACE_ARG_TYPE) &ttrace_events,
|
|||
|
(TTRACE_ARG_TYPE) sizeof (ttrace_events),
|
|||
|
TT_NIL);
|
|||
|
if (errno)
|
|||
|
perror_with_name ("ttrace");
|
|||
|
|
|||
|
/* Remove syscall events from that set. */
|
|||
|
ttrace_events.tte_events &= ~TTEVT_SYSCALL_ENTRY;
|
|||
|
ttrace_events.tte_events &= ~TTEVT_SYSCALL_RETURN;
|
|||
|
|
|||
|
tt_status = call_ttrace (TT_PROC_SET_EVENT_MASK,
|
|||
|
pid,
|
|||
|
(TTRACE_ARG_TYPE) &ttrace_events,
|
|||
|
(TTRACE_ARG_TYPE) sizeof (ttrace_events),
|
|||
|
TT_NIL);
|
|||
|
if (errno)
|
|||
|
perror_with_name ("ttrace");
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* The address range beginning with START and ending with START+LEN-1
|
|||
|
(inclusive) is to be watched via page-protection by a new watchpoint.
|
|||
|
Set protection for all pages that overlap that range.
|
|||
|
|
|||
|
Note that our caller sets TYPE to:
|
|||
|
0 for a bp_hardware_watchpoint,
|
|||
|
1 for a bp_read_watchpoint,
|
|||
|
2 for a bp_access_watchpoint
|
|||
|
|
|||
|
(Yes, this is intentionally (though lord only knows why) different
|
|||
|
from the TYPE that is passed to hppa_remove_hw_watchpoint.)
|
|||
|
*/
|
|||
|
int
|
|||
|
hppa_insert_hw_watchpoint (pid, start, len, type)
|
|||
|
int pid;
|
|||
|
CORE_ADDR start;
|
|||
|
LONGEST len;
|
|||
|
int type;
|
|||
|
{
|
|||
|
CORE_ADDR page_start;
|
|||
|
int dictionary_was_empty;
|
|||
|
int page_size;
|
|||
|
int page_id;
|
|||
|
LONGEST range_size_in_pages;
|
|||
|
|
|||
|
if (type != 0)
|
|||
|
error ("read or access hardware watchpoints not supported on HP-UX");
|
|||
|
|
|||
|
/* Examine all pages in the address range. */
|
|||
|
require_memory_page_dictionary ();
|
|||
|
|
|||
|
dictionary_was_empty = (memory_page_dictionary.page_count == (LONGEST) 0);
|
|||
|
|
|||
|
page_size = memory_page_dictionary.page_size;
|
|||
|
page_start = (start / page_size) * page_size;
|
|||
|
range_size_in_pages = ((LONGEST) len + (LONGEST) page_size - 1) / (LONGEST) page_size;
|
|||
|
|
|||
|
for (page_id=0; page_id < range_size_in_pages; page_id++, page_start+=page_size)
|
|||
|
{
|
|||
|
memory_page_t * page;
|
|||
|
|
|||
|
/* This gets the page entered into the dictionary if it was
|
|||
|
not already entered.
|
|||
|
*/
|
|||
|
page = get_dictionary_entry_of_page (pid, page_start);
|
|||
|
page->reference_count++;
|
|||
|
}
|
|||
|
|
|||
|
/* Our implementation depends on seeing calls to kernel code, for the
|
|||
|
following reason. Here we ask to be notified of syscalls.
|
|||
|
|
|||
|
When a protected page is accessed by user code, HP-UX raises a SIGBUS.
|
|||
|
Fine.
|
|||
|
|
|||
|
But when kernel code accesses the page, it doesn't give a SIGBUS.
|
|||
|
Rather, the system call that touched the page fails, with errno=EFAULT.
|
|||
|
Not good for us.
|
|||
|
|
|||
|
We could accomodate this "feature" by asking to be notified of syscall
|
|||
|
entries & exits; upon getting an entry event, disabling page-protections;
|
|||
|
upon getting an exit event, reenabling page-protections and then checking
|
|||
|
if any watchpoints triggered.
|
|||
|
|
|||
|
However, this turns out to be a real performance loser. syscalls are
|
|||
|
usually a frequent occurrence. Having to unprotect-reprotect all watched
|
|||
|
pages, and also to then read all watched memory locations and compare for
|
|||
|
triggers, can be quite expensive.
|
|||
|
|
|||
|
Instead, we'll only ask to be notified of syscall exits. When we get
|
|||
|
one, we'll check whether errno is set. If not, or if it's not EFAULT,
|
|||
|
we can just continue the inferior.
|
|||
|
|
|||
|
If errno is set upon syscall exit to EFAULT, we must perform some fairly
|
|||
|
hackish stuff to determine whether the failure really was due to a
|
|||
|
page-protect trap on a watched location.
|
|||
|
*/
|
|||
|
if (dictionary_was_empty)
|
|||
|
hppa_enable_syscall_events (pid);
|
|||
|
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* The address range beginning with START and ending with START+LEN-1
|
|||
|
(inclusive) was being watched via page-protection by a watchpoint
|
|||
|
which has been removed. Remove protection for all pages that
|
|||
|
overlap that range, which are not also being watched by other
|
|||
|
watchpoints.
|
|||
|
*/
|
|||
|
int
|
|||
|
hppa_remove_hw_watchpoint (pid, start, len, type)
|
|||
|
int pid;
|
|||
|
CORE_ADDR start;
|
|||
|
LONGEST len;
|
|||
|
enum bptype type;
|
|||
|
{
|
|||
|
CORE_ADDR page_start;
|
|||
|
int dictionary_is_empty;
|
|||
|
int page_size;
|
|||
|
int page_id;
|
|||
|
LONGEST range_size_in_pages;
|
|||
|
|
|||
|
if (type != 0)
|
|||
|
error ("read or access hardware watchpoints not supported on HP-UX");
|
|||
|
|
|||
|
/* Examine all pages in the address range. */
|
|||
|
require_memory_page_dictionary ();
|
|||
|
|
|||
|
page_size = memory_page_dictionary.page_size;
|
|||
|
page_start = (start / page_size) * page_size;
|
|||
|
range_size_in_pages = ((LONGEST) len + (LONGEST) page_size - 1) / (LONGEST) page_size;
|
|||
|
|
|||
|
for (page_id=0; page_id < range_size_in_pages; page_id++, page_start+=page_size)
|
|||
|
{
|
|||
|
memory_page_t * page;
|
|||
|
|
|||
|
page = get_dictionary_entry_of_page (pid, page_start);
|
|||
|
page->reference_count--;
|
|||
|
|
|||
|
/* Was this the last reference of this page? If so, then we
|
|||
|
must scrub the entry from the dictionary, and also restore
|
|||
|
the page's original permissions.
|
|||
|
*/
|
|||
|
if (page->reference_count == 0)
|
|||
|
remove_dictionary_entry_of_page (pid, page);
|
|||
|
}
|
|||
|
|
|||
|
dictionary_is_empty = (memory_page_dictionary.page_count == (LONGEST) 0);
|
|||
|
|
|||
|
/* If write protections are currently disallowed, then that implies that
|
|||
|
wait_for_inferior believes that the inferior is within a system call.
|
|||
|
Since we want to see both syscall entry and return, it's clearly not
|
|||
|
good to disable syscall events in this state!
|
|||
|
|
|||
|
??rehrauer: Yeah, it'd be better if we had a specific flag that said,
|
|||
|
"inferior is between syscall events now". Oh well.
|
|||
|
*/
|
|||
|
if (dictionary_is_empty && memory_page_dictionary.page_protections_allowed)
|
|||
|
hppa_disable_syscall_events (pid);
|
|||
|
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Could we implement a watchpoint of this type via our available
|
|||
|
hardware support?
|
|||
|
|
|||
|
This query does not consider whether a particular address range
|
|||
|
could be so watched, but just whether support is generally available
|
|||
|
for such things. See hppa_range_profitable_for_hw_watchpoint for a
|
|||
|
query that answers whether a particular range should be watched via
|
|||
|
hardware support.
|
|||
|
*/
|
|||
|
int
|
|||
|
hppa_can_use_hw_watchpoint (type, cnt, ot)
|
|||
|
enum bptype type;
|
|||
|
int cnt;
|
|||
|
enum bptype ot;
|
|||
|
{
|
|||
|
return (type == bp_hardware_watchpoint);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Assuming we could set a hardware watchpoint on this address, do
|
|||
|
we think it would be profitable ("a good idea") to do so? If not,
|
|||
|
we can always set a regular (aka single-step & test) watchpoint
|
|||
|
on the address...
|
|||
|
*/
|
|||
|
int
|
|||
|
hppa_range_profitable_for_hw_watchpoint (pid, start, len)
|
|||
|
int pid;
|
|||
|
CORE_ADDR start;
|
|||
|
LONGEST len;
|
|||
|
{
|
|||
|
int range_is_stack_based;
|
|||
|
int range_is_accessible;
|
|||
|
CORE_ADDR page_start;
|
|||
|
int page_size;
|
|||
|
int page;
|
|||
|
LONGEST range_size_in_pages;
|
|||
|
|
|||
|
/* ??rehrauer: For now, say that all addresses are potentially
|
|||
|
profitable. Possibly later we'll want to test the address
|
|||
|
for "stackness"?
|
|||
|
*/
|
|||
|
range_is_stack_based = 0;
|
|||
|
|
|||
|
/* If any page in the range is inaccessible, then we cannot
|
|||
|
really use hardware watchpointing, even though our client
|
|||
|
thinks we can. In that case, it's actually an error to
|
|||
|
attempt to use hw watchpoints, so we'll tell our client
|
|||
|
that the range is "unprofitable", and hope that they listen...
|
|||
|
*/
|
|||
|
range_is_accessible = 1; /* Until proven otherwise. */
|
|||
|
|
|||
|
/* Examine all pages in the address range. */
|
|||
|
errno = 0;
|
|||
|
page_size = sysconf (_SC_PAGE_SIZE);
|
|||
|
|
|||
|
/* If we can't determine page size, we're hosed. Tell our
|
|||
|
client it's unprofitable to use hw watchpoints for this
|
|||
|
range.
|
|||
|
*/
|
|||
|
if (errno || (page_size <= 0))
|
|||
|
{
|
|||
|
errno = 0;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
page_start = (start / page_size) * page_size;
|
|||
|
range_size_in_pages = len / (LONGEST)page_size;
|
|||
|
|
|||
|
for (page=0; page < range_size_in_pages; page++, page_start+=page_size)
|
|||
|
{
|
|||
|
int tt_status;
|
|||
|
int page_permissions;
|
|||
|
|
|||
|
/* Is this page accessible? */
|
|||
|
errno = 0;
|
|||
|
tt_status = call_ttrace (TT_PROC_GET_MPROTECT,
|
|||
|
pid,
|
|||
|
(TTRACE_ARG_TYPE) page_start,
|
|||
|
TT_NIL,
|
|||
|
(TTRACE_ARG_TYPE) &page_permissions);
|
|||
|
if (errno || (tt_status < 0))
|
|||
|
{
|
|||
|
errno = 0;
|
|||
|
range_is_accessible = 0;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
/* Yes, go for another... */
|
|||
|
}
|
|||
|
|
|||
|
return (! range_is_stack_based && range_is_accessible);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
char *
|
|||
|
hppa_pid_or_tid_to_str (id)
|
|||
|
pid_t id;
|
|||
|
{
|
|||
|
static char buf[100]; /* Static because address returned. */
|
|||
|
|
|||
|
/* Does this appear to be a process? If so, print it that way. */
|
|||
|
if (is_process_id (id))
|
|||
|
return hppa_pid_to_str (id);
|
|||
|
|
|||
|
/* Else, print both the GDB thread number and the system thread id. */
|
|||
|
sprintf (buf, "thread %d (", pid_to_thread_id (id));
|
|||
|
strcat (buf, hppa_tid_to_str (id));
|
|||
|
strcat (buf, ")\0");
|
|||
|
|
|||
|
return buf;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* If the current pid is not the pid this module reported
|
|||
|
* from "ptrace_wait" with the most recent event, then the
|
|||
|
* user has switched threads.
|
|||
|
*
|
|||
|
* If the last reported event was a breakpoint, then return
|
|||
|
* the old thread id, else return 0.
|
|||
|
*/
|
|||
|
pid_t
|
|||
|
hppa_switched_threads( gdb_pid )
|
|||
|
pid_t gdb_pid;
|
|||
|
{
|
|||
|
if( gdb_pid == old_gdb_pid ) {
|
|||
|
/*
|
|||
|
* Core gdb is working with the same pid that it
|
|||
|
* was before we reported the last event. This
|
|||
|
* is ok: e.g. we reported hitting a thread-specific
|
|||
|
* breakpoint, but we were reporting the wrong
|
|||
|
* thread, so the core just ignored the event.
|
|||
|
*
|
|||
|
* No thread switch has happened.
|
|||
|
*/
|
|||
|
return (pid_t) 0;
|
|||
|
}
|
|||
|
else if( gdb_pid == reported_pid ) {
|
|||
|
/*
|
|||
|
* Core gdb is working with the pid we reported, so
|
|||
|
* any continue or step will be able to figure out
|
|||
|
* that it needs to step over any hit breakpoints
|
|||
|
* without our (i.e. PREPARE_TO_PROCEED's) help.
|
|||
|
*/
|
|||
|
return (pid_t) 0;
|
|||
|
}
|
|||
|
else if( !reported_bpt ) {
|
|||
|
/*
|
|||
|
* The core switched, but we didn't just report a
|
|||
|
* breakpoint, so there's no just-hit breakpoint
|
|||
|
* instruction at "reported_pid"'s PC, and thus there
|
|||
|
* is no need to step over it.
|
|||
|
*/
|
|||
|
return (pid_t) 0;
|
|||
|
}
|
|||
|
else {
|
|||
|
/* There's been a real switch, and we reported
|
|||
|
* a hit breakpoint. Let "hppa_prepare_to_proceed"
|
|||
|
* know, so it can see whether the breakpoint is
|
|||
|
* still active.
|
|||
|
*/
|
|||
|
return reported_pid;
|
|||
|
}
|
|||
|
|
|||
|
/* Keep compiler happy with an obvious return at the end.
|
|||
|
*/
|
|||
|
return (pid_t) 0;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
hppa_ensure_vforking_parent_remains_stopped (pid)
|
|||
|
int pid;
|
|||
|
{
|
|||
|
/* Nothing to do when using ttrace. Only the ptrace-based implementation
|
|||
|
must do real work.
|
|||
|
*/
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
int
|
|||
|
hppa_resume_execd_vforking_child_to_get_parent_vfork ()
|
|||
|
{
|
|||
|
return 0; /* No, the parent vfork is available now. */
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
void
|
|||
|
_initialize_infttrace ()
|
|||
|
{
|
|||
|
/* Initialize the ttrace-based hardware watchpoint implementation. */
|
|||
|
memory_page_dictionary.page_count = (LONGEST) -1;
|
|||
|
memory_page_dictionary.page_protections_allowed = 1;
|
|||
|
|
|||
|
errno = 0;
|
|||
|
memory_page_dictionary.page_size = sysconf (_SC_PAGE_SIZE);
|
|||
|
|
|||
|
if (errno || (memory_page_dictionary.page_size <= 0))
|
|||
|
perror_with_name ("sysconf");
|
|||
|
}
|
|||
|
|