[comp.sources.misc] v02i094: trapfile - a file-opening logger

aj@zyx.UUCP (Arndt Jonasson) (04/10/88)

comp.sources.misc: Volume 2, Issue 94
Submitted-By: "Arndt Jonasson" <aj@zyx.UUCP>
Archive-Name: trapfile

[The "options" package was posted to comp.unix.wizards a week or so ago,
there may be more documentation for it there.

WARNING:  This program assumes BSD system calls and is otherwise machine and
OS dependent -- suffice it to say that it uses ptrace (if you don't know what
that means, you probably don't want to touch this program) to trap system
calls.  ++bsa]

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by arndt at lynx on Sun Apr 10 02:07:41 1988
#
# This archive contains:
#	README		Makefile.BSD	Makefile.hpux	options.c	
#	options.h	trapfile.1	trapfile.c	
#

LANG=""; export LANG

echo x - README
cat >README <<'@EOF'
This packet contains the source for the 'trapfile' program, which runs
a program as a subprocess while reporting to the user any calls the
subprocess makes to the system calls 'open' and 'close'. To parse its
command line arguments, 'trapfile' uses my 'options' library, which
can be used by itself and is portable (whereas 'trapfile' is not).

trapfile.c		source for trapfile
trapfile.1		manual page for trapfile (use nroff -man)
options.h		include file for options parsing
options.c		source code for options parser
Makefile.BSD		makefile for 4.2BSD on Vax
Makefile.hpux		makefile for HP-UX s300 and s800

No manual page for 'options' yet, sorry. Look at how 'trapfile' uses
it.

Copy the appropriate makefile to Makefile and just type 'make'.  If
your system is not either HP-UX or 4.2BSD, you are on your own. Try
anyway, and if you succeed, I would be happy if you mailed your
changes to me. 4.3BSD just might work directly, and there should be
no very difficult changes for Sun-3.

me = Arndt Jonasson, aj@zyx.SE (uucp:  ...!uunet!mcvax!enea!zyx!aj)
@EOF

chmod 664 README

echo x - Makefile.BSD
cat >Makefile.BSD <<'@EOF'
trapfile:	options.a trapfile.o
		cc $(CFLAGS) -o trapfile trapfile.o options.a -ltermcap

trapfile.o:	options.h trapfile.c
		cc $(CFLAGS) -c -DBSD trapfile.c

options.o:	options.h options.c
		cc $(CFLAGS) -c -Dstrchr=index options.c

options.a:	options.o
		ar r options.a options.o
		ranlib options.a
@EOF

chmod 664 Makefile.BSD

echo x - Makefile.hpux
cat >Makefile.hpux <<'@EOF'
trapfile:	trapfile.o options.a
		cc $(CFLAGS) -o trapfile trapfile.o options.a -lcurses

trapfile.o:	trapfile.c options.h
		cc $(CFLAGS) -c trapfile.c

options.o:	options.h options.c
		cc $(CFLAGS) -c options.c

options.a:	options.o
		ar r options.a options.o
@EOF

chmod 664 Makefile.hpux

echo x - options.c
cat >options.c <<'@EOF'
/*
  Prerelease 0.2 of the command line option parser, 28 Mar 88.
  options.c

  Copyright Arndt Jonasson, 1988. Please send enhancements and corrections
  back to the author.

  Author: Arndt Jonasson, Zyx Sweden
  aj@zyx.SE	(...<backbone>!mcvax!enea!zyx!aj)

  No manual page yet.
*/

#include "options.h"

extern void exit ();		/* '#define void' if your compiler doesn't */
				/* have it.*/

char *O_programname = NULL;

static char *usage_string = NULL;
static int option_error;

static fatal (msg)
char *msg;
{
   fprintf (stderr, "option parsing failure: %s\n", msg);
   exit (1);
}

static char *basename (str)
char *str;
{
   char *p1, *p2;
   extern char *strchr ();

   p2 = str;
   while ((p1 = strchr (p2, '/')) != NULL)
   {
      if (p1[1] == '\0')
      {
	 p1[0] = '\0';
	 return p2;
      }
      p2 = p1 + 1;
   }
   return p2;
}

static char *match (prefix, str)
char *prefix, *str;
{
   int n = strlen (prefix);

   if (strncmp (prefix, str, n) == 0)
      return str + n;
   else
      return NULL;
}

O_usage ()
{
   if (usage_string != NULL)
   {
      if (O_programname == NULL)
	 fprintf (stderr, "valid options: %s\n", usage_string);
      else
	 fprintf (stderr, "usage: %s %s\n", O_programname, usage_string);
   }
   else
   {
      if (O_programname != NULL)
	 fprintf (stderr, "%s: ", O_programname);
      fprintf (stderr, "invalid usage\n");
   }

   exit (1);
}

int O_atoi (str)
char *str;
{
   char c;
   int base = 10;
   int res = 0;
   int negative = 1;
   
/*
  No check for overflow.
*/

   c = str[0];
   
   if (*str == '-')
   {
      negative = -1;
      str++;
   }

   if (*str == '0')
   {
      if (*++str == 'x')
      {
	 str++;
	 base = 16;
      }
      else
	 base = 8;
   }
   
   if (*str == '\0' && (negative != -1 || base != 8)) /* kludgie */
   {
      option_error = 1;
      return 0;
   }

   for (;;)
   {
      switch (c = *str++)
      {
       case '8':
       case '9':
	 if (base == 8)
	 {
	    option_error = 1;
	    return 0;
	 }
       case '0':
       case '1':
       case '2':
       case '3':
       case '4':
       case '5':
       case '6':
       case '7':
	 res = base * res + c - '0';
	 break;
       case 'a':
       case 'b':
       case 'c':
       case 'd':
       case 'e':
       case 'f':
	 if (base == 16)
	    res = base * res + c - 'a' + 10;
	 else
	 {
	    option_error = 1;
	    return 0;
	 }
	 break;
       case '\0':
	 return (negative * res);
	 /* NOTREACHED */
	 break;
       default:
	 option_error = 1;
	 return 0;
      }
   }
}

double O_atof (s)
char *s;
{
/*
  No error-checking currently.
*/
   extern double atof ();

   return atof (s);
}

char *O_strid (s)
char *s;
{
   return s;
}

char O_chrid (s)
char *s;
{
   return s[0];
}

static get_it (p, arg, c)
Option *p;
char *arg;
char c;
{
   option_error = 0;

   switch ((p->flags & 037))
   {
    case O_INT:
      *p->ip = (*p->ipf) (arg);
      if (option_error)
      {
	 fprintf (stderr,
		  "Invalid integer '%s' given to the -%c option\n", arg, c);
	 O_usage ();
      }
      break;
    case O_STR:
      *p->cp = (*p->cpf) (arg);
      break;
    case O_DBL:
      *p->dp = (*p->dpf) (arg);
      if (option_error)
      {
	 fprintf (stderr,
      "Invalid floating-point number '%s' given to the -%c option\n", arg, c);
	 O_usage ();
      }
      break;
    case O_CHR:
      *p->tp = (*p->tpf) (arg);
      break;
    case O_MUL:
      (*p->table_p)[p->data++] = arg;
      break;
    default:
      fatal ("invalid option type");
   }
}

int O_parse_options (desc, argc, argv)
Option *desc;
int argc;
char **argv;
{
   static char *empty_table[] = {NULL};
   int first_char, multiple_error, multiple_add, hyphen_is_arg;
   int min, max, i, remaining;
   Option *p, *default_p = NULL;
   char *cp, *dir, *arg;
   char c;

   multiple_error = 1;
   multiple_add = 0;
   hyphen_is_arg = 1;
   min = 0;
   max = -1;

   for (p = desc; p->flags != O_END; p++)
   {
      p->data = 0;

      if (p->flags == O_DIR)
      {
	 dir = p->directive;
	 if (cp = match ("usage: ", dir))
	    usage_string = cp;
	 else if (cp = match ("multiple: ", dir))
	 {
	    multiple_error = (strcmp ("error", cp) == 0);
	    multiple_add = (strcmp ("add", cp) == 0);
	 }
	 else if (cp = match ("remaining: ", dir))
	 {
	    int n;
	    char dummy;

	    n = sscanf (cp, "%d%c%d", &min, &dummy, &max);
	    if (n == 1)
	       max = min;
	    else if (n == 2)
	       max = -1;
	 }
	 else
	    fatal ("unknown option directive");
      }
      else if (p->flags == O_MUL)
	 *p->table_p = (char **) malloc (argc * sizeof (char *));
      else if (p->flags == O_INT && p->c == '\0')
      {
	 p->c = -1;
	 default_p = p;
      }
      else if (p->flags == O_FLG && p->c == '\0')
	 hyphen_is_arg = 0;

      if (p->flags == O_FLG)
	 *p->ip = 0;
   }

   O_programname = basename (argv[0]);

   for (i = 1; i < argc; i++)
   {
      arg = argv[i];
      if (arg[0] != '-' || (arg[1] == '\0' && hyphen_is_arg))
	 break;

      first_char = 1;

      while ((c = *++arg) != '\0' || first_char)
      {
	 for (p = desc; p->flags != O_END; p++)
	    if (p->c == c)
	    {
	       if (p->flags & 040)
	       {
		  fprintf (stderr, "The -%c option was given twice\n", c);
		  O_usage ();
	       }

	       if (multiple_error && p->flags != O_MUL)
		  p->flags |= 040;

	       if ((p->flags & 037) == O_FLG)
	       {

		  if (multiple_add)
		     *(p->ip) += 1;
		  else
		     *(p->ip) = 1;
		  if (c == '\0')
		     goto next_argument;
		  else
		     goto next_character;
	       }

	       arg += 1;
	       if (arg[0] == '\0')
	       {
		  if (++i == argc)
		  {
		     fprintf (stderr,
			      "The -%c option requires an argument\n", c);
		     O_usage ();
		  }
		  arg = argv[i];
	       }
	       get_it (p, arg, c);
	       goto next_argument;
	    }
	 if ((p = default_p) != NULL && first_char)
	 {
	    *p->ip = (*p->ipf) (arg);
	    if (!option_error)
	       goto next_argument;
	 }
	 fprintf (stderr, "There is no -%c option\n", c);
	 O_usage ();

	 /* NOTREACHED*/

       next_character:
	 first_char = 0;
      }
    next_argument: ;
   }

   for (p = desc; p->flags != O_END; p++)
      if ((p->flags & 037) == O_MUL)
      {
	 if (p->data == 0)
	    (*p->table_p = empty_table);
	 else
	 {
	    (*p->table_p)[p->data++] = NULL;
	    *p->table_p = (char **) realloc (*p->table_p,
					     p->data * sizeof (char *));
	 }
      }

   remaining = argc - i;
   if (remaining < min || max != -1 && remaining > max)
   {
      if (max == -1)
	 fprintf (stderr, "At least %d non-option argument%s required\n",
		  min, min == 1 ? " is" : "s are");
      else if (max == 0)
	 fprintf (stderr, "No non-option arguments are allowed\n");
      else if (min == max)
	 fprintf (stderr, "Exactly %d non-option argument%s required\n",
		  min, min == 1 ? " is" : "s are");
      else
	 fprintf (stderr, "%d to %d non-option arguments are required\n",
		  min, max);
      O_usage ();
   }
   return i;
}
@EOF

chmod 664 options.c

echo x - options.h
cat >options.h <<'@EOF'
/*
  Prerelease 0.2 of the command line option parser, 28 Mar 88.
  options.h

  Copyright Arndt Jonasson, 1988. Please send enhancements and corrections
  back to the author.

  Author: Arndt Jonasson, Zyx Sweden
  aj@zyx.SE	(...<backbone>!mcvax!enea!zyx!aj)

  No manual page yet.
*/

#include <stdio.h>

typedef struct
{
   char c;
   int flags;
   char **cp;
   char *(*cpf) ();
   int *ip;
   int (*ipf) ();
   double *dp;
   double (*dpf) ();
   char *tp;
   char (*tpf) ();
   char ***table_p;
   char *directive;
   int data;
} Option;

extern int O_atoi ();
extern double O_atof ();
extern char O_chrid ();

extern char *O_strid ();
extern char *O_programname;

#define O_FLG	0
#define O_INT	1
#define O_STR	2
#define O_DBL	3
#define O_END	4
#define O_DIR	5
#define O_CHR	6
#define O_MUL	7


#define O_flg(c, ip)	{c, O_FLG, 0, 0, &ip, 0, 0, 0, 0, 0, 0, 0}
#define O_int(c, ip)	{c, O_INT, 0, 0, &ip, O_atoi, 0, 0, 0, 0, 0, 0}
#define O_str(c, cp)	{c, O_STR, &cp, O_strid, 0, 0, 0, 0, 0, 0, 0, 0}
#define O_dbl(c, dp)	{c, O_DBL, 0, 0, 0, 0, &dp, O_atof, 0, 0, 0, 0}
#define O_end		{-1, O_END, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define O_directive(str) {-1, O_DIR, 0, 0, 0, 0, 0, 0, 0, 0, 0, str}
#define O_chr(c, cp)	{c, O_CHR, 0, 0, 0, 0, 0, 0, &cp, O_chrid, 0, 0}
#define O_mul(c, cpp)	{c, O_MUL, 0, 0, 0, 0, 0, 0, 0, 0, &cpp, 0}

#define O_Empty			'\0'

#define usage()			O_usage ()
@EOF

chmod 664 options.h

echo x - trapfile.1
cat >trapfile.1 <<'@EOF'
.TH TRAPFILE 1 local ""
.SH NAME
trapfile - trap all file opening calls in a program
.SH SYNOPSIS
.B trapfile
[options] command [args ...]
.SH DESCRIPTION
.I trapfile
runs an application program in an inferior fork, trapping all calls
the application makes to the system calls
.I open
and
.IR close ,
and reporting them on standard error. The report consists of the name
of the system call and its arguments. If the call was successful, the return
value is shown (except for
.IR close,
which always returns 0 when successful), otherwise an error message,
as defined by
.IR perror (3).
In order to let subprocesses of the application run correctly,
.I trapfile
must also trap calls to
.I fork
(and
.IR vfork ,
if it exists). Thus, these system calls will be reported too.
.PP
On some systems, for complicated reasons, it doesn't work to single-step a
call to
.I fork
(or
.IR vfork ).
Instead
.I trapfile
lets the subprocess run free for a short while. When this happens, only
the call itself will be reported, not the result (whether successful
or not).
.PP 
The following options exist:
.TP 11
.BR -l
Don't run the program; only print out all system calls in it to standard
output, and the
locations they would be in if the program were run.
.TP
.BR -s
Report signals sent to the application as well as system calls.
.TP
.BI -w " time"
When the application running under
.I trapfile
spawns a child of its own,
.I trapfile
can no longer write breakpoints into the address space of the application,
since the system will have made the core image shared and thus non-writable
(unless the application is of the old non-shared type, see
.IR ld (1).
In this situation,
.I trapfile
reports why it is waiting, and by default sleeps until the child of
the application has done an exec or exited, at which time the application's
address space will be writable again. If the
.B \-w
option is used,
.I time
indicates how many seconds
.I trapfile
is to wait before continuing to run. In this case, no more system call
reports will be
issued, since the application is now running free. Signal reporting, if enabled
with the
.B \-s
option, will still be done.
.TP
.B -i
Turn off interrupts for
.IR trapfile .
Normally, interrupt, hangup and quit signals kill both
.I trapfile
and its subprocess.
.TP
.B -d
When
.I trapfile
looks for system calls in the program file, it normally only searches
the code area (text segment) for system calls. This is sufficient for
the majority of all programs. With the
.B \-d
option, the entire file is searched.
.TP
.B -r
Display the reports given by
.I trapfile
in reverse video on the terminal, if possible.
.TP
.BI -T " term"
Use the terminal definition for
.IR term ,
instead of the one given by the environment variable TERM. This is only
useful together with the
.B \-r
option.
.TP
.BI -o " logfile"
Outputs the reports to the file
.I logfile
instead of standard error.
.SH WARNING
Since
.I trapfile
makes very heavy use of the SIGTRAP signal, the application is not likely
to run correctly if it uses that signal for any purpose.
.SH ENVIRONMENT VARIABLES
.TP 11
TERM
The name of the terminal database entry. Used for obtaining the escape
sequences for reverse video when using the
.B \-r
option, unless overridden by the
.B \-T
option.
.TP
TMPDIR
If
.I trapfile
doesn't have write access to the application program file,
it has to make a temporary copy; otherwise it won't be able to set any
breakpoints in the program image. The temporary copy is by default put in the
directory
.BR /tmp ,
unless overridden by the TMPDIR environment variable.
Setting TMPDIR only works in systems that have the
.B tempnam(3)
library routine.
.SH FILES
.TP 11
/tmp/trapfXXXXXX
Temporary writable copy of the application.
.SH SYSTEM DEPENDENCIES
.I trapfile
currently works for HP-UX s300 (MC68020), HP-UX s800 (HP Precision
Architecture) and 4.2BSD (Vax-11/750). Due to the small granularity in
the Vax (system calls instructions are only two bytes and may start at
any address), the possibility of finding what looks like a system
call but isn't, and thus trapping the wrong location, is larger on a Vax.

Of these three systems, only the HP-UX s300 can single-step calls to
.IR fork .
.SH SEE ALSO
adb(1), ptrace(2), perror(3), tempnam(3).
.SH BUGS
.I trapfile
doesn't look at the PATH environment variable when opening the application
file. Thus, the full pathname of the application must be given.
.PP
In a program containing several instances of one of the trapped system
calls (\fIopen\fR,
.IR close ,
.I fork
or
.IR vfork ),
only the first occurrence will be trapped; the others are ignored.
.PP
If the application receives a signal during a
.I fork
call on a system where single-stepping a
.I fork
can't be done, the newly created child process may not run correctly.
.SH DIAGNOSTICS
"couldn't remove temporary file XXXXXX"
.IP
The temporary copy of the application program couldn't be removed.
See the discussion of TMPDIR.
.PP
"child killed by signal NNN"
.br
"child killed by signal NNN (core dumped)"
.IP
The application was killed by a signal, and possibly a core dump was
produced.
.PP
"TERM not set; can't use reverse video"
.br
"terminal type XXX not known; can't use reverse video"
.br
"terminal type XXX doesn't support reverse video"
.IP
The
.B \-r
option was requested, but reverse video can't be used, for
the named reason. The program will ignore the
.B \-r
option.
.PP
"the application is setuid and may not run correctly"
.IP
Since traced programs do not honour the setuid/setgid bit, a
program that needs a special user identity to run correctly may
fail when run under
.IR trapfile .
.PP
"couldn't exec XXX"
.IP
The application exists, but couldn't be started.
.PP
"couldn't open XXX for writing, using stderr"
.IP
The logfile given with the
.B \-o
option couldn't be opened.
.PP
"warning, multiple occurrences of trap NN"
.IP
Only the first of several occurrences of a system call will be 
trapped; the rest will be ignored. This can cause file openings
not to be reported, or worse, a child process of the application
to terminate prematurely.
.PP
"Waiting for child's child to exit or exec"
.br
"No more system calls will be made"
.IP
See the discussion of the
.B \-w
option.
.PP
These messages are followed by a standard error printout if relevant (as
printed by
.IR perror (3).
.PP
Messages containing the word
.I fatal
indicate some internal error in
.IR trapfile ,
caused either by a bug, or by external circumstances preventing
.I trapfile
from continuing.
.SH AUTHOR
Arndt Jonasson
@EOF

chmod 664 trapfile.1

echo x - trapfile.c
cat >trapfile.c <<'@EOF'
/*
  Author: Arndt Jonasson, Zyx Sweden AB
  Mail address: aj@zyx.SE	(uucp: <backbone>!mcvax!enea!zyx!aj)
  Date: March 20, 1988
  Version 1.0

  This program arose from a question in the news group comp.unix.questions
  (or was it comp.unix.wizards?) - someone wanted to know whether the
  functionality of TOPS-20's "Set Trap File-Openings" command existed in
  Unix. The answer is "well, yes, in a way" and this program is the result.

  It is very dependent on the 'ptrace(2)' system call and knows a few
  system- and processor-dependent things about what a system call looks
  like.

  Assuming that 'ptrace(2)' exists and that the registers can be accessed,
  it shouldn't be too hard to port.
*/

/*
  Bad style:

  We use goto.

  We deliberately use a preprocessor definition with no () around it,
  but I won't fix that - it's too convenient. Just watch out for it.

  We assume sizeof(int) = 4.
*/

/*
  All the different preprocessor symbols are quite intertangled; e.g.
  BSD currently means not only 4.2BSD Unix, but the Vax processor.
  hp9000s300 implies the MC68020 processor. It's too soon to untangle
  this until this program has been ported to more systems.

  Currently used preprocessor symbols:

  hpux				HP-UX system.
  hp9000s300, hp9000s800	Imply hpux.
  BSD				4.2BSD.

  BSD currently implies Vax processor. hp9000s800 implies HP PA, and
  hp9000s300 implies MC68020.

  BSD and hpux are mutually exclusive. So are hp9000s300 and hp9000s800.
*/

/*
  What does a system call look like?

  General assumptions: system calls have a uniform calling sequence on
  any given machine, with the arguments in known places. When a system
  call returns, either the result is in a certain register, or an
  error value defined by <errno.h> is in that same register. There
  exists an instruction that will generate a SIGTRAP for the process
  executing it.

  ==================
  HP-UX s300, 68020:
  ==================

  MOVQ		&0x5,%d0	0x7005
or
  MOV.W		&0xA5,%d0	0x303c
				0xA5
or
  MOV.L		&0xA5,%d0	0x203c
  				0x0
				0xA5
followed by
  TRAP		&0		0x4e40

  Top of stack is return pc, then come the arguments.
  If the call failed, 0x10000 in the status flag word is set, and errno is
  in d0. If the call was successful, the result is typically in d0.

  The trap is transformed into a 'trap &1' (0x4e41).

  =================
  HP-UX s800, HPPA:
  =================
  
  ldil		-40000000,r1
  ble		4(sr7,r1)	0xe420e008
  ldo		0xA5(r0),r22	0x341600e0

  Arguments are passed in arg0, arg1, etc.
  If the call failed, r22 is 1 with errno in ret0. Otherwise r22 is 0,
  and the return value is in ret0. The 'ble' jumps into execute-only
  kernel space, and there the real trap instruction lies. Can't put
  a breakpoint there, of course.

  The ble is supplanted with a 'break 4,8' (0x10004).

  ============
  4.2BSD, Vax:
  ============

  chmk		$4		0x04bc
OR
  chmk		$42		0x8fbc
				0x42

  Arguments are passed on the stack, but the system call is typically
  called via the 'calls' instruction that pushes five additional
  words on the stack before the return pc. We have to adjust the sp
  accordingly. Return value in R0.

  The chmk is replaced by a 'bpt' (0x803).
*/

#include "options.h"		/* My option parser. */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>

#include	<machine/reg.h>
#ifdef hpux
# define PS	16		/* These two are not defined in reg.h */
# define PC	16 + 2		/* WARNING: BIG KLUDGE (but how convenient) */
#endif

#ifdef hpux
# include <sys/ptrace.h>
#else
# define	PT_SETTRC	0
# define	PT_RIUSER	1
# define	PT_RDUSER	2
# define	PT_RUAREA	3
# define	PT_WIUSER	4
# define	PT_WDUSER	5
# define	PT_WUAREA	6
# define	PT_CONTIN	7
# define	PT_EXIT		8
# define	PT_SINGLE	9
#endif

/*
  System call numbers. Some systems define them in an include file, some
  don't. The numbers are the same on all the systems where this program
  currently runs.
*/

#ifndef hpux
# define SYS_FORK	0x2
# define SYS_OPEN	0x5
# define SYS_CLOSE	0x6
#endif

#ifndef hp9000s300
# define SYS_VFORK	0x42
#endif

#include <sys/param.h>
#include <sys/dir.h>

#ifdef hp9000s800
# include <filehdr.h>
# include <aouthdr.h>
# include <spacehdr.h>
# include <scnhdr.h>
#else
# ifdef BSD
#  include <a.out.h>
# endif
# include <sys/user.h>
#endif

/*
  Unix system incompatibility.
*/

#ifndef BSD
# include <string.h>
#else
# include <strings.h>
# define strchr		index
#endif

#include <errno.h>
extern char *sys_errlist[];	/* And pray tell me, why aren't */
extern int sys_nerr;		/* these defined in <errno.h>? */
extern void perror ();
#ifdef BSD
extern int errno;		/* sigh ... */
#endif

extern int tgetent ();		/* For obtaining information from the */
extern char *tgetstr ();	/* terminfo or termcap database. */

extern void _exit (), exit ();
extern unsigned long sleep ();
extern unsigned short geteuid (), getegid ();
extern char *getenv ();

char *signame[] =
{
   "NAL 0",
   "HUP", "INT", "QUIT", "ILL", "TRAP",
   "IOT", "EMT", "FPE", "KILL", "BUS",
   "SEGV", "SYS", "PIPE", "ALRM", "TERM",
#ifdef BSD
   "URG", "STOP", "TSTP", "CONT", "CHLD",
   "TTIN", "TTOU", "IO", "XCPU", "XFSZ",
   "VTALRM", "PROF"
#else
   "USR1", "USR2", "CLD", "PWR", "VTALRM",
   "PROF", "IO", "WINDOW", "STOP", "TSTP",
   "CONT", "TTIN", "TTOU", "URG"
#endif
};

/*
  Approximate - it's only important that the number be large enough.
*/

#ifdef BSD
# define SYS_n	153
#else
# define SYS_n	239
#endif

typedef struct
{
   int nr;			/* The entry's index in the 'calls' array. */
   enum {Absent, Present, Bpt} type; /* Status of this system call. */
   int old_word;		/* Old contents before breakpoint was set. */
   int new_word;		/* Breakpoint instruction. */
   int address;			/* Address in subprocess's address space. */
}
system_call;
system_call calls[SYS_n];	/* Array of all system calls in the */
				/* application, indexed by trap number. */

#define Check(d, msg)	if (!(d)) fatal (msg, 1)

#define with_reverse_video(x)		do { \
					     fputs (rev_on, log); \
					     x \
					     fputs (rev_off, log); \
					   } while (0)

char *tempfile = NULL;		/* If application executable isn't writable, */
				/* this is the name of the temporary file. */
char *term = NULL;		/* Terminal type, set by the -T option. */
FILE *log;			/* Name of logfile, set by the -o option. */
char *rev_on, *rev_off;		/* Control sequences to turn on/off reverse */
				/* video on the terminal. Set to "" when no */
				/* such capability is available or desired. */
int report_signals = 0;		/* If non-zero, report signals. Set by the */
				/* -s option. */
int sigm = 0;			/* Mask of pending signals. */


/*
  Return to our superior, with the given exit status. The log file is closed
  and the temporary copy removed, if one was made.
*/

cleanup_and_exit (status)
int status;
{
   int s;

   fclose (log);

   if (tempfile != NULL)
   {
      s = unlink (tempfile);
      if (s == -1 && errno != ENOENT)
	 fprintf (stderr, "%s: couldn't remove temporary file %s\n",
		  O_programname, tempfile);
   }
   exit (status);
}

/*
  Report a fatal error on stderr. If 'perr' is set, 'errno' contains useful
  information, so print it using 'perror'. Then exit with status 1.
*/

fatal (msg, perr)
char *msg;
int perr;
{
   fprintf (stderr, "%s: fatal - %s", O_programname, msg);
   if (perr)
   {
      fprintf (stderr, " - ");
      perror ("");
   }
   else
      fprintf (stderr, "\n");
   cleanup_and_exit (1);
} 

/*
  From a pathname, extract the last component.
*/

char *basename (str)
char *str;
{
   char *p1, *p2;

   p2 = str;
   while ((p1 = strchr (p2, '/')) != NULL)
      p2 = p1 + 1;
   return p2;
}

/* 
  If 'sig' is non-zero, it is a pending signal for the subprocess
  'pid'. Report it to the user if desired. Then continue the subprocess,
  giving it the signal.
*/

report_signal_and_continue (pid, sig)
int pid, sig;
{
   int s;

   if (report_signals && sig > 0)
      with_reverse_video
	 ({
	    fprintf (log, "SIG%s\n", signame[sig]);
	 });

   s = ptrace (PT_CONTIN, pid, 1, sig);
   Check (s != -1, "couldn't continue subprocess after signal");
}

/*
  Let the subprocess 'pid' run until it either exits or encounters the signal
  'sig'. The value -1 for 'sig' means return on all signals. The return
  value is the signal that the subprocess received, unless it was a SIGTRAP,
  in which case the return value is 0.
*/

int do_wait (pid, sig)
int pid, sig;
{
   int s, status, lo, hi;

/*
  o If 'wait' returns some other pid than the one we expect, try again.
    When using pipes in sh, such things can happen.
  o If the child exited normally, so do we, with the same exit status.
*/

   for (;;)
   {
      s = wait (&status);

      if (s == -1)
      {
	 if (errno == EINTR)
	    continue;
	 else
	    fatal ("wait failed", 1);
      }

      if (s != pid)
	 continue;

      lo = (status) & 0377;
      hi = ((status) >> 8) & 0377;

      if (lo == 0177)
      {
	 if (hi == SIGTRAP)
	    return 0;

	 if (sig == -1)
	 {
	    sigm |= (1 << hi);
	    return hi;
	 }

	 report_signal_and_continue (pid, hi);
	 continue;
      }

      if (lo == 0)
	 cleanup_and_exit (hi);

      if (hi == 0)
      {
	 fprintf (stderr, "%s: child killed by signal %d%s\n",
		  O_programname,
		  lo & 0177,
		  (lo & 0200) ? "(core dumped)" : "");
	 cleanup_and_exit (lo);
      }
   }
}

/*
  Read a 16-bit chunk from the file 'f'. Depending on the processor, the
  first byte will be either the low or the high byte.
*/

unsigned int get_chunk (f)
FILE *f;
{
   static int first = 1;
   static int saved;

   if (!first)
   {
      first = 1;
#ifdef BSD
      return (saved >> 16) & 0xffff;
#else
      return (saved & 0xffff);
#endif
   }
   else
   {
      first = 0;
      saved = getw (f);
#ifdef BSD
      return (saved & 0xffff);
#else
      return (saved >> 16) & 0xffff;
#endif
   }
}

/*
  Given an address in the application's code area, the system call at that
  address is returned, or NULL if it wasn't found.
*/

system_call *find_system_call (adr)
int adr;
{
   int i;

   for (i = 0; i < SYS_n; i++)
      if (calls[i].address == adr)
	 return &calls[i];

   return NULL;
}

/*
  Obtain the sequences for turning reverse video on and off. If that fails,
  they are set to the empty string.
*/

setup_standout_strings (on, off)
char **on, **off;
{
   int s;
   char *r_on, *r_off;
   static char termbuf[1024], databuf[1024];
   char *scratch = databuf;

   *on = *off = "";

   if (term == NULL)
      term = getenv ("TERM");
   if (term == NULL)
   {
      fprintf (stderr, "%s: TERM not set; can't use reverse video\n",
	       O_programname);
      return;
   }
   s = tgetent (termbuf, term);
   if (s != 1)
   {
      fprintf (stderr,
	       "%s: terminal type \"%s\" not known; can't use reverse video\n",
	       O_programname, term);
      return;
   }
   r_on = tgetstr ("so", &scratch);
   r_off = tgetstr ("se", &scratch);
   if (r_on == 0 || r_off == 0)
   {
      fprintf (stderr,
	       "%s: terminal type \"%s\" doesn't support reverse video\n",
	       O_programname, term);
      return;
   }
   *on = r_on;
   *off = r_off;
}

/*
  If the application is setuid or setgid to something other than the current
  user's id, give a warning.
*/

warn_if_setuid (file)
char *file;
{
   int s;
   struct stat b;

   s = stat (file, &b);
   Check (s == 0, "couldn't stat program file");
   if (((b.st_mode & S_ISUID) && geteuid () != b.st_uid)
       ||
       ((b.st_mode & S_ISGID) && getegid () != b.st_gid))
   {
      fprintf (stderr,
	       "%s: the application is set%cid and may not work correctly\n",
	       O_programname, (b.st_mode & S_ISUID) ? 'u' : 'g');
   }
}

/*
  Like 'perror', but to an arbitrary file stream and without the newline.
*/

print_error (f, err)
FILE *f;
int err;
{
   if (err < 0 || err > sys_nerr)
      fprintf (f, " Error %d", err);
   else
      fprintf (f, " %s", sys_errlist[err]);
}

/* 
  Report the result of a system call. 'print_p' tells whether to print the
  result if the call succeeded (useless in the case of 'close', for instance).
  'flags' is some value that says whether the system call succeeded or not.
  Mostly it is the processor's status register. What bits in it are relevant
  is processor-specific.
*/

print_result (f, val, flags, print_p)
FILE *f;
int val, flags, print_p;
{
#ifdef hp9000s800
   if (flags != 0)
#endif
#ifdef BSD
   if (flags & 1)
#endif
#ifdef hp9000s300
   if (flags & 0x10000)
#endif
      print_error (f, val);
   else if (print_p)
      fprintf (f, " = %d", val);

   fprintf (f, "\n");
}

/*
  The nice 'tempnam' function doesn't exist in 4.2BSD, so we use what
  we have, i.e. 'mktemp'.
*/

char *tempfile_name ()
{
   char *s;

#ifndef BSD
   extern char *tempnam ();
   s = tempnam ("/tmp", "trapf");
#else
   extern char *mktemp ();
   s = mktemp ("/tmp/trapfXXXXXX");
#endif
   return s;
}

/*
  Start the application in an inferior fork (the fork will already have been
  done). 'f' is the logfile - if not stderr, the application shouldn't see
  it.
*/

do_exec (f, file, argv)
FILE *f;
char *file;
char **argv;
{
   if (f != stderr)
      close (fileno (f));

   (void) signal (SIGINT, SIG_DFL);
   (void) signal (SIGQUIT, SIG_DFL);
   argv[0] = basename (argv[0]);
   (void) execv (file, argv);
   fatal ("couldn't exec program", 1);
}

/*
  Obtain the base of the register area in the u area of the application.
  Apparently, this changes when the application is running, so it has
  to be done each time the application has been continued or single-stepped.

  In HP-UX s300, the register area lies somewhat after the user area proper,
  and is accessed in the same way. All we need to know is the address of that
  area, and the address of the start of the user area, so we can give a
  relative address to 'ptrace'. That relative address is given by
  <machine/reg.h>, but not for pc and ps. ps is immediately after SP, and
  since it is only two bytes, pc can't be defined as an index into the
  u.u_ar0 array.

  In 4.2BSD Vax, the above remains valid, except that pc and pc are indeed
  defined in reg.h.

  In HP-UX s800, the register area lies somewhere totally different, and is
  not accessed through the user area. There is a special request P_RUREGS
  for reading the registers.
*/

#ifndef hp9000s800
int get_reg_base (pid)
{
   int base, minus;
   struct user u;

#define u_offset(a)		((char *) a - (char *) &u)

   base = ptrace (PT_RUAREA, pid, u_offset (&u.u_ar0), 0);
   Check (base != -1, "couldn't get user area register block pointer");
   minus = ptrace (PT_RUAREA, pid, u_offset (&u.u_ap), 0) - u_offset (u.u_arg);
   Check (minus != -1, "couldn't get user area u_ap");

   return base - minus;
}
#endif

/*
  Read a structure from a file into memory, complain if it failed.
*/

#define slurp(f, x)	if (fread (&(x), sizeof (x), 1, f) != 1) \
   				fatal ("slurp", 1)

/*
  From the file stream 'f', open to an executable file, obtain the offset
  of the code in the file, the offset of the code in the address space when
  the program is run, and the size of the code area.
*/

get_offsets (f, file, code, size)
int *file, *code, *size;
FILE *f;
{
#ifdef hp9000s800

/*
  In s800, the code always starts at 0x800 in the process's address space.
  The start of code in the file can be obtained by examination of the
  header.
*/

   struct header hdr;
   struct som_exec_auxhdr auxhdr;
   int n, i;
	
   slurp (f, hdr);

   fseek (f, hdr.aux_header_location, 0);
   slurp (f, auxhdr);
   if (auxhdr.som_auxhdr.type != HPUX_AUX_ID)
      fatal ("not right header type", 0);

   *file = auxhdr.exec_tfile;
   *code = 0x800;
   *size = auxhdr.exec_tsize;
#else

/*
  In s300 and BSD, the code always starts at 0 in the process's address space.
  Depending on the kind of executable, the start of the code in the
  file is different.
*/

   struct exec hdr;

#ifdef hpux
# define N_TXTOFF	TEXT_OFFSET
#endif

   slurp (f, hdr);

   if (N_BADMAG (hdr))
      fatal ("unknown magic number", 0);

   *file = N_TXTOFF (hdr);
   *code = 0;
   *size = hdr.a_text;
#endif
}

/*
  Return index of lowest set bit in *maskp. Return 0 if *maskp is 0. Lowest
  bit in *maskp will never be set, since that would mean signal 0. If that
  signal occurs, something is seriously wrong.
*/

int pick_first_signal (maskp)
unsigned int *maskp;
{
   unsigned int m = *maskp;
   int i = 0;

   if (m == 0)
      return 0;

   for (;;)
   {
      i += 1;
      m >>= 1;
      if (m & 1)
      {
	 *maskp ^= (1 << i);
	 return i;
      }
   }
}

main (argc, argv)
int argc;
char **argv;
{
#ifdef hp9000s800
   struct save_state state;
# define s_offset(a)	((char *) &state.a - (char *) &state)
#endif

   int sig;
   int address;
   char *string_arg;
   char **command;
   system_call *p;
#ifndef hp9000s800
   int reg_base;
   int sp;
#endif
   int trapno;
   int pid, i;
   FILE *f;
   int *ip;
   int bytes;
   unsigned int w1, w2;
   int val, arg1, arg2, arg3, res;
   int s;
   int pc, flags;
   int c_offset, f_offset, c_size;
   char *file;
   int n;

   static int ignore_signals = 0, reverse_video = 0, look_only = 0;
   static int search_data = 0;
   static char *logname = NULL;
   static int wait_for_child = -1;

   static Option desc[] = {
      O_flg ('d', search_data),
      O_flg ('i', ignore_signals),
      O_flg ('s', report_signals),
      O_flg ('r', reverse_video),
      O_flg ('l', look_only),
      O_str ('o', logname),
      O_str ('T', term),
      O_int ('w', wait_for_child),
      O_directive ("remaining: 1-infinity"),
      O_directive
       ("usage: [-isrld] [-T term] [-o logfile] [-w time] command [args ...]"),
      O_end,
   };

   n = O_parse_options (desc, argc, argv);

   command = argv + n;

   file = command[0];

/*
  Set up the log file.
*/

   if (logname != NULL)
   {
      log = fopen (logname, "w");
      if (log == NULL)
      {
	 fprintf (stderr,
		  "%s: couldn't open %s for writing, using stderr\n",
		  O_programname, logname);
	 log = stderr;
      }
   }
   else
      log = stderr;

/*
  Set up reverse video.
*/

   if (reverse_video)
      setup_standout_strings (&rev_on, &rev_off);

/*
  If we are going to run the application, it has to be writable, otherwise
  no breakpoint can be set. So make a temporary copy if the application
  isn't writable.
*/

   if (!look_only)
   {
      warn_if_setuid (file);

      s = open (file, O_WRONLY, 0); /* Try to open for writing. */
      if (s == -1)		/* Couldn't, so make a writable copy. */
      {
	 char buf[1024];

	 tempfile = tempfile_name ();
	 sprintf (buf, "cp %s %s && chmod u+w %s", file, tempfile, tempfile);
	 s = system (buf);
	 if (s != 0)
	    fatal ("couldn't make a temporary copy", 0);
	 file = tempfile;
      }
      else
	 (void) close (s);
   }

   f = fopen (file, "r");
   Check (f != 0, "couldn't open the program file");

/*
  Get pointers to where the code starts in the file, and in core.
  Set the file pointer so that we start reading the first code
  instruction from the file.
*/

   get_offsets (f, &f_offset, &c_offset, &c_size);
   s = fseek (f, (long) f_offset, 0);
   Check (s != -1, "couldn't fseek the program file");

/*
  Initialize the array of system calls.
*/

   for (i = 0; i < SYS_n; i++)
   {
      calls[i].type = Absent;
      calls[i].nr = i;
   }

/*
  Read through the executable file, with the core address 'bytes' starting
  at the address where the code will be loaded when run.
*/

   w1 = 0;
   bytes = c_offset;

   for (;;)
   {
      if (!search_data && bytes > c_offset + c_size)
	 break;

      bytes += 2;

      w2 = get_chunk (f);
      if (feof (f))
	 break;

      trapno = -1;

#ifdef hp9000s300
      if (((w1 & ~0xff) == 0x7000 || (w1 & ~0xff) == 0x0)
	  &&
	  w2 == 0x4e40)
      {
	 trapno = (w1 & 0xff);
	 address = bytes - 2;
      }
#endif
#ifdef hp9000s800
      if (w1 == 0xe420 && w2 == 0xe008)
      {
	 bytes += 2;
	 w1 = get_chunk (f);
	 bytes += 2;
	 w2 = get_chunk (f);

	 if (w1 == 0x3416)
	 {
	    trapno = (w2 >> 1);
	    address = bytes - 8;
	 }
      }
#endif
#ifdef BSD
      if ((w2 & 0xff) == 0xbc)
      {
	 unsigned int saved_w1 = w1;

	 trapno = (w2 >> 8) & 0xff;
	 address = bytes - 2;
	 if (trapno == 0x8f)
	 {
	    bytes += 2;
	    w2 = get_chunk (f);
	    trapno = w2;
	 }
	 else if (trapno == 0 || trapno > 0x3f)
	    trapno = -1;

	 bytes += 2;
	 w2 = get_chunk (f);
	 if (trapno != -1
	     && (w2 & 0xff) != 0x1f /* all except exit, getppid and geteuid */
	     && (w2 & 0xff) != 0x1e /* vfork and wait3 */
	     && (saved_w1 != 0)) /* all except brk, sbrk, ptrace */
	   trapno = -2;
      }
				/* Instructions may start on odd addresses */
				/* on a Vax. */
      if (trapno == -1)
      {
	 unsigned int saved_w1 = w1;

	 if ((w1 & 0xff00) == 0xbc00)
	 {
	    trapno = (w2 & 0xff);
	    address = bytes - 3;
	    if (trapno == 0x8f)
	    {
	       w1 = w2;
	       bytes += 2;
	       w2 = get_chunk (f);
	       trapno = ((w2 & 0xff) << 8) + ((w1 >> 8) & 0xff);
	    }
	    else if (trapno == 0 || trapno > 0x3f)
	       trapno = -1;

	    if ((saved_w1 & 0xff) != 0
		&& (w2 & 0xff00) != 0x1e00
		&& (w2 & 0xff00) != 0x1f00)
	      trapno = -1;
	 }
      }
#endif
      if (trapno > 0 && trapno < SYS_n)
      {
	 if (calls[trapno].type == Present && !look_only
	    && (i == SYS_OPEN
		|| i == SYS_CLOSE
		|| i == SYS_FORK
		|| i == SYS_VFORK))
	 {
	    fprintf (stderr, "%s: warning, multiple occurrences of trap %#x\n",
		     O_programname, trapno);
	 }
	 calls[trapno].address = address;
	 calls[trapno].type = Present;
	 if (look_only)
	    printf ("system call %#x at %#x\n", trapno, address);
      }
      w1 = w2;
   }
   fclose (f);

   if (look_only)		/* If only listing system calls, we are */
      exit (0);			/* done now. */

   fflush (stdout);		/* Cause child's stdio buffers to start */
   fflush (stdout);		/* out clean, in case the child wants to */
   fflush (stderr);		/* report an error. */

   if (ignore_signals)		/* Ignore signals, if the user told us to. */
   {
      (void) signal (SIGHUP, SIG_IGN);
      (void) signal (SIGINT, SIG_IGN);
      (void) signal (SIGQUIT, SIG_IGN);
   }

   pid = fork ();		/* vfork won't do. Bug in hpux. */
   Check (pid != -1, "couldn't fork");

   if (pid == 0)
   {
      (void) ptrace (PT_SETTRC, 0, 0, 0); /* Shouldn't fail */
      do_exec (log, file, command);
      /* NOTREACHED */
   }

   (void) do_wait (pid, SIGTRAP);
				/* Wait for the 'exec' to happen, */
   for (i = 0; i < SYS_n; i++)	/* then start putting in our breakpoints. */
   {
      if (i != SYS_OPEN
	  && i != SYS_CLOSE
	  && i != SYS_FORK
	  && i != SYS_VFORK)
	 continue;

      if (calls[i].type == Present)
      {
	 s = ptrace (PT_RIUSER, pid, calls[i].address, 0);
	 Check (s != -1, "couldn't read trap word");
	 calls[i].old_word = s;

#ifdef BSD
	 calls[i].new_word = (s & 0xffff0000) | 0x803;
#endif
#ifdef hp9000s300
	 calls[i].new_word = 0x4e410000 + (s & 0xffff);
#endif
#ifdef hp9000s800
	 calls[i].new_word = 0x10004;
#endif

	 calls[i].type = Bpt;

	 s = ptrace (PT_WIUSER, pid, calls[i].address, calls[i].new_word);
	 Check (s != -1, "couldn't write new trap word");
      }
   }

/*
  The rest of the 'main' function is within this loop. The basic flow
  of control is:

  1) Subprocess is in the stopped state.
  2) Continue it, passing along any pending signal that occurred during the
  last loop.
  3) Wait until it hits another breakpoint (which will cause a SIGTRAP signal
  and stop the subprocess).
  4) Find what system call is at the location of the breakpoint.
  5) If none (spurious signal), just go back to step 1.
  6) Find the arguments to the system call.
  7) Put back the real system call.
  8) Single-step the system call.
  9) Get the result of the system call (including error status).
  10) Report the whole system call.
  11) Put back the breakpoint and come back to step 1.
*/

   for (;;)
   {
/* 1 */
      report_signal_and_continue (pid, pick_first_signal (&sigm));
/* 2 */
      (void) do_wait (pid, SIGTRAP);

/* 3 */
#ifdef hp9000s800
      pc = ptrace (PT_RUREGS, pid, s_offset (ss_pcoq_head), 0);
      pc &= ~3;			/* remove privilege level */
#else
      reg_base = get_reg_base (pid);
      pc = ptrace (PT_RUAREA, pid, reg_base + 4*PC, 0);
# ifdef hp9000s300
      pc -= 2;
# endif
#endif

/* 4 */
      p = find_system_call (pc);

/* 5 */
      if (p == 0)
	 continue;		/* Spurious SIGTRAP, just ignore it. */

/* 6 */
#ifdef hp9000s800
      arg1 = ptrace (PT_RUREGS, pid, s_offset (ss_arg0), 0);
      arg2 = ptrace (PT_RUREGS, pid, s_offset (ss_arg1), 0);
      arg3 = ptrace (PT_RUREGS, pid, s_offset (ss_arg2), 0);
#else
      reg_base = get_reg_base (pid); /* unnecessary, really, since it was */
				     /* done above. */
      sp = ptrace (PT_RUAREA, pid, reg_base + 4*SP, 0);
# ifdef BSD
      sp += 4*5;		/* adjustment for junk on stack */
# endif
      arg1 = ptrace (PT_RDUSER, pid, sp+4, 0);
      arg2 = ptrace (PT_RDUSER, pid, sp+8, 0);
      arg3 = ptrace (PT_RDUSER, pid, sp+12, 0);
#endif

      if (p->nr == SYS_OPEN)
      {
	 static char buf[1024];
	 int adjusted_arg1 = arg1 & ~3;
	 int offset = (arg1 & 3);
#ifdef BSD
	 int mask = (0x01010101 >> (32 - 8 * offset));
#else
	 int mask = (0x01010101 >> (8 * offset)) ^ 0x01010101;
#endif

	 ip = (int *) buf;
	 for (i = 0; i < 100; i++)
	 {
	    val = ptrace (PT_RDUSER, pid, adjusted_arg1, 0);
	    Check (val != -1, "couldn't read string from subprocess");

/*
  Not all systems allow address arguments to 'ptrace' to be odd, so we
  do this to get things right. The Vax does allow it, but it's easier to
  special-case 'mask' than the whole loop.
*/

	    if (i == 0)
	       val |= mask;
	    *ip++ = val;
	    if (((val & 0xff000000) == 0)
		| ((val & 0x00ff0000) == 0)
		| ((val & 0x0000ff00) == 0)
		| ((val & 0x000000ff) == 0))
	       break;
	    adjusted_arg1 += 4;
	 }
	 *ip = 0;
	 string_arg = buf + offset;
      }

/*
  Put back the true system call in order to run it.

  If the call is a kind of fork, we have to remove all the breakpoints;
  otherwise the child process would die on SIGTRAP as soon as it tried
  to use one of the system calls that we have put breakpoints in.
*/

/* 7 */
      if (p->nr == SYS_FORK || p->nr == SYS_VFORK)
      {
	 for (i = 0; i < SYS_n; i++)
	    if (calls[i].type == Bpt)
	    {
	       s = ptrace (PT_WIUSER,
			   pid, calls[i].address, calls[i].old_word);
	       Check (s != -1, "couldn't write back old trap words");
	    }
      }
      else
      {
	 s = ptrace (PT_WIUSER, pid, p->address, p->old_word);
	 Check (s != -1, "couldn't write back old trap word");
      }

/*
  Now run the system call by single-stepping it.

  Some systems lose when the 'fork' or 'vfork' system call is
  single-stepped over; the child immediately gets a SIGTRAP. The only
  solution to this problem is to continue the process instead of
  single-stepping it, then stopping it as soon as possible with a
  'kill' call. After this, we jump down to the label 'put_back',
  and put back all our breakpoints. We may miss some system calls that
  we should have reported in this way, but that can't be helped.
*/

/* 8 */
#if defined(hp9000s800) | defined(BSD)

/*
  To avoid anomalies (such as reporting the fork call after the child
  has printed things on our terminal), we do the reporting here already.
  Unfortunately, we can't report the pid of the child process.
*/

      if (p->nr == SYS_FORK || p->nr == SYS_VFORK)
      {
	 with_reverse_video
	    ({
	       fprintf (log, "%sfork()\n", p->nr == SYS_FORK ? "" : "v");
	    });

/*
  There is a slight chance here that a signal is stopping the process just
  before the system call is done. In that case, the result won't show up
  correctly, and in the case of fork, the child process loses. The only
  way to detect that is to look at the pc. We don't do that yet.
*/

	 s = ptrace (PT_CONTIN, pid, pc, 0);
	 kill (pid, SIGTRAP);
	 sig = do_wait (pid, -1);
	 goto put_back;
      }
#endif

/*
  On some systems, the correct data don't show up in the registers until
  we have single-stepped more than once.
*/

#ifdef hp9000s800
# define EXTRA_STEPS	2	/* Once for the instruction after the jump */
				/* (yes, this is a RISC), once for the */
				/* actual trap somewhere in the protected */
				/* kernel code. */
#endif
#ifdef hp9000s300
# define EXTRA_STEPS	1	/* Scheduling anomaly, perhaps? */
#endif
#ifdef BSD
# define EXTRA_STEPS	0	/* No problem. */
#endif

      for (i = 0; i < 1 + EXTRA_STEPS;)
      {
	 s = ptrace (PT_SINGLE, pid, pc, 0);
	 Check (s != -1, "couldn't singlestep 1");
	 pc = 1;
	 sig = do_wait (pid, -1);
	 if (sig == 0)
	    i += 1;
      }

/*
  Pick up the return value of the system call from the right register, and
  the flag word that says whether the call succeeded. This flag word will
  be decoded by the function 'print_result'.
*/

/* 9 */
#ifdef hp9000s800
      flags = ptrace (PT_RUREGS, pid, s_offset (ss_gr22), 0);
      res = ptrace (PT_RUREGS, pid, s_offset (ss_ret0), 0);
#else
      reg_base = get_reg_base (pid);
      flags = ptrace (PT_RUAREA, pid, reg_base + 4*PS, 0);
      res = ptrace (PT_RUAREA, pid, reg_base + 4*R0, 0);
#endif

/*
  Now the system call is complete; report it along with its arguments,
  and whether it succeeded or failed.
*/

/* 10 */
      with_reverse_video
	 ({
	    if (p->nr == SYS_OPEN)
	    {
	       if ((arg2 & O_CREAT) != 0)
		  fprintf (log, "open(\"%s\", %#x, %#o)",
			   string_arg, arg2, arg3);
	       else
		  fprintf (log, "open(\"%s\", %#x)", string_arg, arg2);
	       print_result (log, res, flags, 1);
	    }
	    else if (p->nr == SYS_CLOSE)
	    {
	       fprintf (log, "close(%d)", arg1);
	       print_result (log, res, flags, 0);
	    }
	    else if (p->nr == SYS_FORK || p->nr == SYS_VFORK)
	    {
	       fprintf (log, "%sfork()", p->nr == SYS_FORK ? "" : "v");
	       print_result (log, res, flags, 1);
	    }
	 });

    put_back:

/*
  If we previously removed all our breakpoints before single-stepping a
  fork or vfork, now put them back again.

  If the child hasn't had time to exec or exit, there are now two processes
  sharing the same code area, and the system consequently prohibits us from
  writing into it. Wait either until we can write, or until the user-supplied
  timeout expires.

  If not fork or vfork, just put back the one system call that was called.
*/

/* 11 */
      if (p->nr == SYS_FORK || p->nr == SYS_VFORK)
      {
	 int waiting = 0;

       retry:
	 for (i = 0; i < SYS_n; i++)
	    if (calls[i].type == Bpt)
	    {
	       s = ptrace (PT_WIUSER,
			   pid, calls[i].address, calls[i].new_word);
	       if (s == -1)
	       {
		  if (wait_for_child == waiting)
		  {
		     with_reverse_video
			({
			   fprintf (log,
			       "No more system call reports will be made\n");
			});
		     break;
		  }

		  if (waiting == 0)
		     with_reverse_video
			({
			   fprintf (log,
			      "Waiting for child's child to exit or exec\n");
			});

		  sleep (1);
		  waiting += 1;
		  goto retry;
	       }
	    }
      }
      else
      {
	 s = ptrace (PT_WIUSER, pid, p->address, p->new_word);
	 Check (s != -1, "couldn't write new trap word");
      }
   }
}
@EOF

chmod 664 trapfile.c

exit 0
-- 
Arndt Jonasson, ZYX Sweden AB, Styrmansgatan 6, 114 54 Stockholm, Sweden
email address:	 aj@zyx.SE	or	<backbone>!mcvax!enea!zyx!aj