5600 lines
141 KiB
C
5600 lines
141 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,
|
||
1998, 1999, 2000, 2001, 2003, 2004 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 "gdb_wait.h"
|
||
#include "command.h"
|
||
#include "gdbthread.h"
|
||
#include "infttrace.h"
|
||
|
||
/* We need pstat functionality so that we can get the exec file
|
||
for a process we attach to.
|
||
|
||
According to HP, we should use the 64bit interfaces, so we
|
||
define _PSTAT64 to achieve this. */
|
||
#define _PSTAT64
|
||
#include <sys/pstat.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
|
||
|
||
#include <sys/param.h>
|
||
#include <sys/dir.h>
|
||
#include <signal.h>
|
||
#include <sys/ioctl.h>
|
||
|
||
#include <sys/ttrace.h>
|
||
#include <sys/mman.h>
|
||
|
||
#ifndef NO_PTRACE_H
|
||
#ifdef PTRACE_IN_WRONG_PLACE
|
||
#include <ptrace.h>
|
||
#else
|
||
#include <sys/ptrace.h>
|
||
#endif
|
||
#endif /* NO_PTRACE_H */
|
||
|
||
/* Second half of the hackery above. Non-ANSI C, so
|
||
* we can't use "#error", alas.
|
||
*/
|
||
#ifdef NO_FLAGS
|
||
#if (NO_FLAGS != INFTTRACE_TEMP_HACK )
|
||
/* #error "Hackery to remove warning didn't work right" */
|
||
#else
|
||
/* Ok, new def'n of NO_FLAGS is same as old one; no action needed. */
|
||
#endif
|
||
#else
|
||
/* #error "Didn't get expected re-definition of NO_FLAGS" */
|
||
#define NO_FLAGS INFTTRACE_TEMP_HACK
|
||
#endif
|
||
|
||
#if !defined (PT_SETTRC)
|
||
#define PT_SETTRC 0 /* Make process traceable by parent */
|
||
#endif
|
||
#if !defined (PT_READ_I)
|
||
#define PT_READ_I 1 /* Read word from text space */
|
||
#endif
|
||
#if !defined (PT_READ_D)
|
||
#define PT_READ_D 2 /* Read word from data space */
|
||
#endif
|
||
#if !defined (PT_READ_U)
|
||
#define PT_READ_U 3 /* Read word from kernel user struct */
|
||
#endif
|
||
#if !defined (PT_WRITE_I)
|
||
#define PT_WRITE_I 4 /* Write word to text space */
|
||
#endif
|
||
#if !defined (PT_WRITE_D)
|
||
#define PT_WRITE_D 5 /* Write word to data space */
|
||
#endif
|
||
#if !defined (PT_WRITE_U)
|
||
#define PT_WRITE_U 6 /* Write word to kernel user struct */
|
||
#endif
|
||
#if !defined (PT_CONTINUE)
|
||
#define PT_CONTINUE 7 /* Continue after signal */
|
||
#endif
|
||
#if !defined (PT_STEP)
|
||
#define PT_STEP 9 /* Set flag for single stepping */
|
||
#endif
|
||
#if !defined (PT_KILL)
|
||
#define PT_KILL 8 /* Send child a SIGKILL signal */
|
||
#endif
|
||
|
||
#ifndef PT_ATTACH
|
||
#define PT_ATTACH PTRACE_ATTACH
|
||
#endif
|
||
#ifndef PT_DETACH
|
||
#define PT_DETACH PTRACE_DETACH
|
||
#endif
|
||
|
||
#include "gdbcore.h"
|
||
#ifdef HAVE_SYS_FILE_H
|
||
#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;
|
||
|
||
/* 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 ptid_t saved_real_ptid;
|
||
|
||
|
||
/*************************************************
|
||
* Debugging support functions *
|
||
*************************************************
|
||
*/
|
||
CORE_ADDR
|
||
get_raw_pc (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 (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 (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 (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_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 (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 (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 (void)
|
||
{
|
||
return (thread_head.count > 0);
|
||
}
|
||
|
||
/* Create, fill in and link in a thread descriptor.
|
||
*/
|
||
static thread_info *
|
||
create_thread_info (int pid, lwpid_t tid)
|
||
{
|
||
thread_info *new_p;
|
||
thread_info *p;
|
||
int thread_count_of_pid;
|
||
|
||
new_p = xmalloc (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_ptid = inferior_ptid;
|
||
}
|
||
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 (void)
|
||
{
|
||
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;
|
||
xfree (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;
|
||
xfree (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 (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 (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 (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 (void)
|
||
{
|
||
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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (void)
|
||
{
|
||
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 (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 (void)
|
||
{
|
||
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 (void)
|
||
{
|
||
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 (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 (void)
|
||
{
|
||
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 (void)
|
||
{
|
||
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_ptid 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 (ttreq_t request, pid_t pid, lwpid_t tid, TTRACE_ARG_TYPE addr,
|
||
TTRACE_ARG_TYPE data, TTRACE_ARG_TYPE 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 (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 (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 (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 (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 (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 (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 (ttreq_t request, int gdb_tid, TTRACE_ARG_TYPE addr,
|
||
TTRACE_ARG_TYPE data, TTRACE_ARG_TYPE 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 (! ptid_equal (saved_real_ptid, null_ptid))
|
||
{
|
||
#ifdef THREAD_DEBUG
|
||
if (debug_on)
|
||
printf ("...using saved pid %d\n",
|
||
PIDGET (saved_real_ptid));
|
||
#endif
|
||
|
||
real_pid = PIDGET (saved_real_ptid);
|
||
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 (ptid_equal (saved_real_ptid, null_ptid))
|
||
printf ("...but there's no saved pid\n");
|
||
|
||
else
|
||
{
|
||
if (gdb_tid != PIDGET (saved_real_ptid))
|
||
printf ("...but have the wrong pid (%d rather than %d)\n",
|
||
gdb_tid, PIDGET (saved_real_ptid));
|
||
}
|
||
}
|
||
#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 (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) != PIDGET (inferior_ptid)))
|
||
#define CHILD_URPED(evt,pid) \
|
||
((((evt) == TTEVT_EXEC) || ((evt) == TTEVT_EXIT)) && ((pid) != vforking_child_pid))
|
||
#define PARENT_VFORKED(evt,pid) \
|
||
(((evt) == TTEVT_VFORK) && ((pid) == PIDGET (inferior_ptid)))
|
||
|
||
static int
|
||
can_touch_threads_of_process (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 (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 (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 (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
|
||
hpux_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 (void)
|
||
{
|
||
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 (void)
|
||
{
|
||
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 (void)
|
||
{
|
||
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 (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 (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 (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 (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 (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 (ptid_t ptid, 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 PIDGET (inferior_ptid);
|
||
}
|
||
|
||
warning ("Call of ttrace_wait returned with errno %d.",
|
||
errno);
|
||
*status = ttwait_return;
|
||
return PIDGET (inferior_ptid);
|
||
}
|
||
|
||
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, PIDGET (inferior_ptid));
|
||
#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_ptid = pid_to_ptid (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 (pid_to_ptid (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);
|
||
|
||
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 p1, PTRACE_ARG3_TYPE p2, int p3)
|
||
{
|
||
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 (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) & notifiable_events,
|
||
(TTRACE_ARG_TYPE) sizeof (notifiable_events),
|
||
TT_NIL);
|
||
}
|
||
|
||
static void
|
||
require_notification_of_exec_events (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) & notifiable_events,
|
||
(TTRACE_ARG_TYPE) sizeof (notifiable_events),
|
||
TT_NIL);
|
||
}
|
||
|
||
|
||
/* This function is called by the parent process, with pid being the
|
||
* ID of the child process, after the debugger has forked.
|
||
*/
|
||
void
|
||
child_acknowledge_created_inferior (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_to_ptid (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 (ptid_t ptid)
|
||
{
|
||
require_notification_of_events (PIDGET (ptid));
|
||
}
|
||
|
||
/* From here on, we should expect tids rather than pids.
|
||
*/
|
||
static void
|
||
hppa_enable_catch_fork (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 (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 (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 (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 (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 (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 (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 (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
|
||
|
||
/* 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
|
||
hpux_has_forked (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;
|
||
}
|
||
|
||
/* See hpux_has_forked for pid discussion.
|
||
*/
|
||
int
|
||
hpux_has_vforked (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;
|
||
}
|
||
|
||
|
||
#if defined(CHILD_INSERT_EXEC_CATCHPOINT)
|
||
int
|
||
child_insert_exec_catchpoint (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 (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
|
||
|
||
|
||
int
|
||
hpux_has_execd (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;
|
||
}
|
||
|
||
|
||
int
|
||
hpux_has_syscall_event (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;
|
||
}
|
||
|
||
|
||
|
||
#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 (ptid_t ptid)
|
||
{
|
||
lwpid_t gdb_tid = PIDGET (ptid);
|
||
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 (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 (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 (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 (0, 0, 0);
|
||
|
||
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 (void)
|
||
{
|
||
int tid;
|
||
int wait_status;
|
||
thread_info *t;
|
||
thread_info **paranoia;
|
||
int para_count, i;
|
||
|
||
if (PIDGET (inferior_ptid) == 0)
|
||
return;
|
||
|
||
/* Walk the list of "threads", some of which are "pseudo threads",
|
||
aka "processes". For each that is NOT inferior_ptid, 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 **) xmalloc (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 != PIDGET (inferior_ptid)))
|
||
{
|
||
call_ttrace (TT_PROC_EXIT,
|
||
t->pid,
|
||
TT_NIL,
|
||
TT_NIL,
|
||
TT_NIL);
|
||
}
|
||
t = t->next;
|
||
}
|
||
|
||
xfree (paranoia);
|
||
|
||
call_ttrace (TT_PROC_EXIT,
|
||
PIDGET (inferior_ptid),
|
||
TT_NIL,
|
||
TT_NIL,
|
||
TT_NIL);
|
||
target_mourn_inferior ();
|
||
clear_thread_info ();
|
||
}
|
||
|
||
|
||
#ifndef DEPRECATED_CHILD_RESUME
|
||
|
||
/* Sanity check a thread about to be continued.
|
||
*/
|
||
static void
|
||
thread_dropping_event_check (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 (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 (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 (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 == TARGET_SIGNAL_0)
|
||
{
|
||
/* 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 (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 DEPRECATED_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_ptid")
|
||
* |
|
||
* 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 (ptid_t ptid, int step, enum target_signal signal)
|
||
{
|
||
int resume_all_threads;
|
||
lwpid_t tid;
|
||
process_state_t new_process_state;
|
||
lwpid_t gdb_tid = PIDGET (ptid);
|
||
|
||
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 (PIDGET (inferior_ptid));
|
||
}
|
||
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.
|
||
Therefore, we really can't use TT_PROC_CONTINUE here.
|
||
|
||
Consider a process which stopped due to signal which gdb decides
|
||
to handle and not pass on to the inferior. In that case we must
|
||
clear the pending signal by restarting the inferior using
|
||
TT_LWP_CONTINUE and pass zero as the signal number. Else the
|
||
pending signal will be passed to the inferior. interrupt.exp
|
||
in the testsuite does this precise thing and fails due to the
|
||
unwanted signal delivery to the inferior. */
|
||
/* drow/2002-12-05: However, note that we must use TT_PROC_CONTINUE
|
||
if we are tracing a vfork. */
|
||
if (vfork_in_flight)
|
||
{
|
||
call_ttrace (TT_PROC_CONTINUE, tid, TT_NIL, TT_NIL, TT_NIL);
|
||
clear_all_handled ();
|
||
clear_all_stepping_mode ();
|
||
}
|
||
else 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
|
||
will 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 /* DEPRECATED_CHILD_RESUME */
|
||
|
||
/*
|
||
* Like it says.
|
||
*
|
||
* One worry is that we may not be attaching to "inferior_ptid"
|
||
* and thus may not want to clear out our data. FIXME?
|
||
*
|
||
*/
|
||
static void
|
||
update_thread_state_after_attach (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_ptid").
|
||
*
|
||
* 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 (pid_to_ptid (pid)); /* 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;
|
||
}
|
||
|
||
|
||
/* Start debugging the process whose number is PID.
|
||
* (A _real_ pid).
|
||
*/
|
||
int
|
||
attach (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 (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 (int signal)
|
||
{
|
||
errno = 0;
|
||
call_ttrace (TT_PROC_DETACH,
|
||
PIDGET (inferior_ptid),
|
||
TT_NIL,
|
||
(TTRACE_ARG_TYPE) signal,
|
||
TT_NIL);
|
||
attach_flag = 0;
|
||
|
||
clear_thread_info ();
|
||
|
||
/* Process-state? */
|
||
}
|
||
|
||
|
||
/* Default the type of the ttrace transfer to int. */
|
||
#ifndef TTRACE_XFER_TYPE
|
||
#define TTRACE_XFER_TYPE int
|
||
#endif
|
||
|
||
void
|
||
_initialize_kernel_u_addr (void)
|
||
{
|
||
}
|
||
|
||
#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. TARGET is ignored.
|
||
|
||
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 (CORE_ADDR memaddr, char *myaddr, int len, int write,
|
||
struct mem_attrib *attrib,
|
||
struct target_ops *target)
|
||
{
|
||
int i;
|
||
/* Round starting address down to longword boundary. */
|
||
CORE_ADDR addr = memaddr & -(CORE_ADDR) sizeof (TTRACE_XFER_TYPE);
|
||
/* Round ending address up; get number of longwords that makes. */
|
||
int count
|
||
= (((memaddr + len) - addr) + sizeof (TTRACE_XFER_TYPE) - 1)
|
||
/ sizeof (TTRACE_XFER_TYPE);
|
||
/* Allocate buffer of that many longwords. */
|
||
/* FIXME (alloca): This code, cloned from infptrace.c, is unsafe
|
||
because it uses alloca to allocate a buffer of arbitrary size.
|
||
For very large xfers, this could crash GDB's stack. */
|
||
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,
|
||
PIDGET (inferior_ptid),
|
||
(TTRACE_ARG_TYPE) addr,
|
||
TT_NIL,
|
||
TT_NIL);
|
||
}
|
||
|
||
if (count > 1) /* FIXME, avoid if even boundary */
|
||
{
|
||
buffer[count - 1] = call_ttrace (TT_LWP_RDTEXT,
|
||
PIDGET (inferior_ptid),
|
||
((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,
|
||
PIDGET (inferior_ptid),
|
||
(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,
|
||
PIDGET (inferior_ptid),
|
||
(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,
|
||
PIDGET (inferior_ptid),
|
||
(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 (void)
|
||
{
|
||
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,
|
||
PIDGET (inferior_ptid),
|
||
(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 (int tid)
|
||
{
|
||
int tt_status;
|
||
static char exec_file_buffer[1024];
|
||
pid_t pid;
|
||
static struct pst_status buf;
|
||
|
||
/* On various versions of hpux11, this may fail due to a supposed
|
||
kernel bug. We have alternate methods to get this information
|
||
(ie pstat). */
|
||
tt_status = call_ttrace (TT_PROC_GET_PATHNAME,
|
||
tid,
|
||
(uint64_t) exec_file_buffer,
|
||
sizeof (exec_file_buffer) - 1,
|
||
0);
|
||
if (tt_status >= 0)
|
||
return exec_file_buffer;
|
||
|
||
/* Try to get process information via pstat and extract the filename
|
||
from the pst_cmd field within the pst_status structure. */
|
||
if (pstat_getproc (&buf, sizeof (struct pst_status), 0, tid) != -1)
|
||
{
|
||
char *p = buf.pst_cmd;
|
||
|
||
while (*p && *p != ' ')
|
||
p++;
|
||
*p = 0;
|
||
|
||
return (buf.pst_cmd);
|
||
}
|
||
|
||
return (NULL);
|
||
}
|
||
|
||
void
|
||
pre_fork_inferior (void)
|
||
{
|
||
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 from child_follow_fork in hppah-nat.c.
|
||
*
|
||
* 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 (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 (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 (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 (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 ();
|
||
|
||
/* 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 (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--;
|
||
|
||
xfree (page);
|
||
}
|
||
|
||
|
||
static void
|
||
hppa_enable_syscall_events (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 (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 (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 (int pid, CORE_ADDR start, LONGEST len, int 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 (int type, int cnt, int 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 (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 (ptid_t ptid)
|
||
{
|
||
static char buf[100]; /* Static because address returned. */
|
||
pid_t id = PIDGET (ptid);
|
||
|
||
/* Does this appear to be a process? If so, print it that way. */
|
||
if (is_process_id (id))
|
||
return child_pid_to_str (ptid);
|
||
|
||
/* Else, print both the GDB thread number and the system thread id. */
|
||
sprintf (buf, "thread %d (", pid_to_thread_id (ptid));
|
||
strcat (buf, hppa_tid_to_str (ptid));
|
||
strcat (buf, ")\0");
|
||
|
||
return buf;
|
||
}
|
||
|
||
|
||
void
|
||
hppa_ensure_vforking_parent_remains_stopped (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 (void)
|
||
{
|
||
return 0; /* No, the parent vfork is available now. */
|
||
}
|
||
|
||
|
||
/* Write a register as a 64bit value. This may be necessary if the
|
||
native OS is too braindamaged to allow some (or all) registers to
|
||
be written in 32bit hunks such as hpux11 and the PC queue registers.
|
||
|
||
This is horribly gross and disgusting. */
|
||
|
||
int
|
||
ttrace_write_reg_64 (int gdb_tid, CORE_ADDR dest_addr, CORE_ADDR src_addr)
|
||
{
|
||
pid_t pid;
|
||
lwpid_t tid;
|
||
int tt_status;
|
||
|
||
tid = map_from_gdb_tid (gdb_tid);
|
||
pid = get_pid_for (tid);
|
||
|
||
errno = 0;
|
||
tt_status = ttrace (TT_LWP_WUREGS,
|
||
pid,
|
||
tid,
|
||
(TTRACE_ARG_TYPE) dest_addr,
|
||
8,
|
||
(TTRACE_ARG_TYPE) src_addr );
|
||
|
||
#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 (TT_LWP_WUREGS),
|
||
pid, tid, tt_status );
|
||
}
|
||
}
|
||
#endif
|
||
|
||
return tt_status;
|
||
}
|
||
|
||
void
|
||
_initialize_infttrace (void)
|
||
{
|
||
/* 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);
|
||
|
||
/* We do a lot of casts from pointers to TTRACE_ARG_TYPE; make sure
|
||
this is okay. */
|
||
if (sizeof (TTRACE_ARG_TYPE) < sizeof (void *))
|
||
internal_error (__FILE__, __LINE__, "failed internal consistency check");
|
||
|
||
if (errno || (memory_page_dictionary.page_size <= 0))
|
||
perror_with_name ("sysconf");
|
||
}
|