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