606 lines
14 KiB
C
606 lines
14 KiB
C
/*
|
|
* This file is part of ixemul.library for the Amiga.
|
|
* Copyright (C) 1991, 1992 Markus M. Wild
|
|
* Changed to avoid buffer overflows by J. Hoehle and
|
|
* restricted output length for: str(n)cat, strlen
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the Free
|
|
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <clib/alib_protos.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <sys/tracecntl.h>
|
|
#include <signal.h>
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/ioctl_compat.h>
|
|
#include <sys/termios.h>
|
|
#include <exec/types.h>
|
|
#include <exec/execbase.h>
|
|
#include <proto/exec.h>
|
|
#include <libraries/dos.h>
|
|
#include "ixtrace.h"
|
|
|
|
#include <ix.h>
|
|
#include <proto/exec.h>
|
|
|
|
#ifdef __MORPHOS__
|
|
#define CreatePort(x,y) CreateMsgPort()
|
|
#define DeletePort DeleteMsgPort
|
|
#endif
|
|
|
|
#define OUT_WIDTH 80 /* big enough (>30) to hold first information */
|
|
|
|
static void print_call (FILE *output, struct trace_packet *tp);
|
|
static void show(struct trace_packet *tp, int in);
|
|
static void pshow(struct trace_packet *tp, int in);
|
|
|
|
int print_all = 0;
|
|
int skip_sigsetmask = 0;
|
|
int skip_calls = 0;
|
|
FILE *output;
|
|
char VERSION[] = "$VER: ixtrace 1.470 (14-Jun-97)";
|
|
int ctrlc = 0;
|
|
|
|
|
|
void
|
|
ctrlc_handler ()
|
|
{
|
|
ctrlc = 1;
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
char *logfile = "-";
|
|
int c, in = 0;
|
|
struct trace_packet tp;
|
|
|
|
memset ((char *) &tp, '\000', sizeof (tp));
|
|
signal (SIGINT, ctrlc_handler);
|
|
|
|
while ((c = getopt (argc, argv, "ailwvmzo:c:p:s:n:")) != EOF)
|
|
switch (c)
|
|
{
|
|
case 'a':
|
|
print_all = 1;
|
|
break;
|
|
|
|
case 'i':
|
|
in = 1;
|
|
break;
|
|
|
|
case 'l': /* list system calls */
|
|
{
|
|
int i;
|
|
|
|
for(i=1;i<=MAXCALLS;i=i+2)
|
|
{
|
|
fprintf(stdout,"(%3d) %-25s\t(%3d) %-25s\n",i,call_table[i].name,
|
|
i+1,call_table[i+1].name);
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case 'c': /* system call by name */
|
|
{
|
|
int i;
|
|
int notfound=1; /* Just to make sure that we know if call is found */
|
|
char *callname;
|
|
|
|
callname=optarg;
|
|
|
|
for(i=1;i<=MAXCALLS;i++)
|
|
{
|
|
if (!strcmp(callname, call_table[i].name))
|
|
{ tp.tp_syscall = i;
|
|
notfound=0;
|
|
break;
|
|
}
|
|
}
|
|
if (notfound)
|
|
{
|
|
fprintf(stderr,"system call [%s] is unknown to ixtrace\n",callname);
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'm':
|
|
skip_sigsetmask = 1;
|
|
break;
|
|
|
|
case 'o':
|
|
logfile = optarg;
|
|
break;
|
|
|
|
case 'p':
|
|
tp.tp_pid = atoi (optarg);
|
|
break;
|
|
|
|
case 'n':
|
|
skip_calls = atoi (optarg);
|
|
if (skip_calls < 0)
|
|
{
|
|
fprintf(stderr, "invalid argument for option -n\n");
|
|
exit(1);
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
if (!isdigit(optarg[0]))
|
|
{
|
|
fprintf(stderr, "The -s option requires a number\n");
|
|
exit(1);
|
|
}
|
|
tp.tp_syscall = atoi (optarg);
|
|
if (tp.tp_syscall > MAXCALLS)
|
|
{
|
|
fprintf(stderr, "System call number is out of range 1-%d\n",MAXCALLS);
|
|
exit(1);
|
|
}
|
|
break;
|
|
|
|
case 'w': /* Wipe out the calls you don't want */
|
|
{
|
|
int i;
|
|
char calls[80]="";
|
|
int notfound=1;
|
|
|
|
fprintf(stdout,"When done enter \"x\" by itself, followed by a [RETURN]\n");
|
|
do {
|
|
fprintf(stdout,"trace > ");
|
|
gets(calls);
|
|
if (!strcmp("x",calls)) break;
|
|
for(i=1;i<=MAXCALLS;i++)
|
|
{
|
|
if (!strcmp(calls, call_table[i].name))
|
|
{ call_table[i].interesting=0;
|
|
notfound=0;
|
|
break;
|
|
}
|
|
}
|
|
if (notfound) fprintf(stderr,"[%s] is unknown to ixtrace, try again\n"
|
|
, calls);
|
|
notfound=1;
|
|
} while(strcmp("x",calls)); /* A lil' over-kill */
|
|
|
|
}
|
|
break;
|
|
case 'z': /* you name the calls --in testing-- */
|
|
{
|
|
int i;
|
|
char calls[80]="";
|
|
int notfound=1;
|
|
|
|
/* Right now this is only the beginning, clear all systems calls.
|
|
In other words, make them all non-interesting. */
|
|
for(i=1;i<=MAXCALLS;i++)
|
|
{
|
|
call_table[i].interesting=0;
|
|
}
|
|
fprintf(stdout,"When done enter \"x\" by itself, followed by a [RETURN]\n");
|
|
do {
|
|
fprintf(stdout,"trace > ");
|
|
gets(calls);
|
|
if (!strcmp("x",calls)) break;
|
|
for(i=1;i<=MAXCALLS;i++)
|
|
{
|
|
if (!strcmp(calls, call_table[i].name))
|
|
{ call_table[i].interesting=1;
|
|
notfound=0;
|
|
break;
|
|
}
|
|
}
|
|
if (notfound) fprintf(stderr,"[%s] is unknown to ixtrace, try again\n"
|
|
, calls);
|
|
notfound=1;
|
|
} while(strcmp("x",calls)); /* A lil' over-kill */
|
|
|
|
}
|
|
break;
|
|
case 'v':
|
|
{
|
|
fprintf(stdout, "%s\n",VERSION+6); /* get rid of the first 7 chars */
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fprintf (stderr, "%s [-a] [-m] [-l] [-v] [-z] [-c syscall-name] [-n N] [-o logfile] [-p pid] [-s syscall-number]\n", argv[0]);
|
|
fprintf (stderr, " -a trace all calls (else __-calls are skipped)\n");
|
|
fprintf (stderr, " -m skip sigsetmask() calls (they're heavily used inside the library)\n");
|
|
fprintf (stderr, " -i trace entry to functions. Default is exit.\n");
|
|
fprintf (stderr, " -l list system calls (syscall #) [syscall name]\n");
|
|
fprintf (stderr, " -v version\n");
|
|
fprintf (stderr, " -w wipe out syscalls by name\n");
|
|
fprintf (stderr, " -z only trace the syscalls you want to trace\n");
|
|
fprintf (stderr, " -c only trace this syscall (by name)\n");
|
|
fprintf (stderr, " -n skip the first N traces\n");
|
|
fprintf (stderr, " -o log output to logfile (default is stdout)\n");
|
|
fprintf (stderr, " -p only trace process pid (default is to trace all processes)\n");
|
|
fprintf (stderr, " -s only trace this syscall (default is to trace all calls)\n");
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (logfile[0] == '-' && !logfile[1])
|
|
output = stdout;
|
|
else
|
|
output = fopen (logfile, "w");
|
|
|
|
if (!output)
|
|
{
|
|
perror ("fopen");
|
|
return 1;
|
|
}
|
|
show(&tp, in);
|
|
return 0;
|
|
}
|
|
|
|
static void show(struct trace_packet *tp, int in)
|
|
{
|
|
struct MsgPort *mp;
|
|
|
|
if ((mp = CreatePort (0, 0)))
|
|
{
|
|
tp->tp_tracer_port = mp;
|
|
if (ix_tracecntl (TRACE_INSTALL_HANDLER, tp) == 0)
|
|
{
|
|
while (!ctrlc)
|
|
{
|
|
struct Message *msg;
|
|
long sigs;
|
|
|
|
sigs = (1 << mp->mp_SigBit) | SIGBREAKF_CTRL_C;
|
|
ix_wait(&sigs);
|
|
while ((msg = GetMsg (mp)))
|
|
{
|
|
if (msg != (struct Message *)tp)
|
|
{
|
|
fprintf (stderr, "Got alien message! Don't do that ever again ;-)\n");
|
|
}
|
|
else
|
|
{
|
|
if (in)
|
|
tp->tp_action = TRACE_ACTION_JMP;
|
|
if (! tp->tp_is_entry || tp->tp_action == TRACE_ACTION_JMP)
|
|
print_call (output, tp);
|
|
}
|
|
Signal ((struct Task *) msg->mn_ReplyPort, /*SIGBREAKF_CTRL_E fixme, see tracecntl.c */);
|
|
}
|
|
if (sigs & SIGBREAKF_CTRL_C)
|
|
break;
|
|
}
|
|
ix_tracecntl (TRACE_REMOVE_HANDLER, tp);
|
|
}
|
|
else
|
|
perror ("ix_tracecntl");
|
|
|
|
DeletePort (mp);
|
|
}
|
|
else
|
|
perror ("CreatePort");
|
|
}
|
|
|
|
/* should help make things less mystic... */
|
|
#define TP_SCALL(tp) (tp->tp_argv[0])
|
|
|
|
/* this is only valid if !tp->tp_is_entry */
|
|
#define TP_RESULT(tp) (tp->tp_argv[1])
|
|
|
|
#define TP_ERROR(tp) (* tp->tp_errno)
|
|
|
|
/* tp_argv[2] is the return address */
|
|
/* tp_argv[3-6] are d0/d1/a0/a1 */
|
|
#define TP_FIRSTARG(tp) (tp->tp_argv[7])
|
|
|
|
void
|
|
print_call (FILE *output, struct trace_packet *tp)
|
|
{
|
|
char line[OUT_WIDTH+2]; /* for \n\0 */
|
|
char *argfield;
|
|
int space, len;
|
|
struct call *c;
|
|
|
|
space = sizeof (line) - 1;
|
|
len = sprintf (line, "$%lx: %c", (unsigned long) tp->tp_message.mn_ReplyPort,
|
|
tp->tp_is_entry ? '>' : '<');
|
|
argfield = line + len;
|
|
space -= len;
|
|
|
|
if (TP_SCALL (tp) > sizeof (call_table) / sizeof (struct call))
|
|
{
|
|
if (tp->tp_is_entry)
|
|
sprintf (argfield, "SYS_%d()\n", TP_SCALL (tp));
|
|
else
|
|
sprintf (argfield, "SYS_%d() = $%lx (%d)\n",
|
|
TP_SCALL (tp), (unsigned long) TP_RESULT (tp), TP_ERROR (tp));
|
|
}
|
|
else
|
|
{
|
|
c = call_table + TP_SCALL (tp);
|
|
|
|
if ((!print_all && !c->interesting) ||
|
|
(skip_sigsetmask && TP_SCALL (tp) == SYS_sigsetmask))
|
|
return;
|
|
|
|
/* we can write space-1 real characters in the buffer, \n is not counted */
|
|
c->func (argfield, space-1, c, tp);
|
|
}
|
|
|
|
if (skip_calls == 0)
|
|
fputs (line, output);
|
|
else
|
|
skip_calls--;
|
|
}
|
|
|
|
/* the program contained a bug due to the fact that
|
|
* when snprintf or vsnprintf are called with a zero or negative size,
|
|
* they return -1 as an error marker but not any length.
|
|
*/
|
|
|
|
static void
|
|
vp (char *buf, int len, struct call *c, struct trace_packet *tp)
|
|
{
|
|
int nl = snprintf (buf, len+1, "%s", c->name);
|
|
|
|
len -= nl; if (len <= 0) goto finish;
|
|
buf += nl;
|
|
if (tp->tp_is_entry || !c->rfmt[0])
|
|
vsnprintf (buf, len+1, c->fmt, (_BSD_VA_LIST_) & TP_FIRSTARG (tp));
|
|
else
|
|
{
|
|
nl = vsnprintf (buf, len+1, c->fmt, (_BSD_VA_LIST_) & TP_FIRSTARG (tp));
|
|
len -= nl; if (len <= 0) goto finish;
|
|
buf += nl;
|
|
nl = snprintf (buf, len+1, "=");
|
|
len -= nl; if (len <= 0) goto finish;
|
|
buf += nl;
|
|
nl = vsnprintf (buf, len+1, c->rfmt, (_BSD_VA_LIST_) & TP_RESULT (tp));
|
|
len -= nl; if (len <= 0) goto finish;
|
|
buf += nl;
|
|
nl = snprintf (buf, len+1, " (%d)", TP_ERROR (tp));
|
|
}
|
|
|
|
finish:
|
|
strcat (buf, "\n");
|
|
}
|
|
|
|
const char *
|
|
get_fcntl_cmd (int cmd)
|
|
{
|
|
switch (cmd)
|
|
{
|
|
case F_DUPFD:
|
|
return "F_DUPFD";
|
|
|
|
case F_GETFD:
|
|
return "F_GETFD";
|
|
|
|
case F_SETFD:
|
|
return "F_SETFD";
|
|
|
|
case F_GETFL:
|
|
return "F_GETFL";
|
|
|
|
case F_SETFL:
|
|
return "F_SETFL";
|
|
|
|
case F_GETOWN:
|
|
return "F_GETOWN";
|
|
|
|
case F_SETOWN:
|
|
return "F_SETOWN";
|
|
|
|
#ifdef F_GETLK
|
|
case F_GETLK:
|
|
return "F_GETLK";
|
|
#endif
|
|
|
|
#ifdef F_SETLK
|
|
case F_SETLK:
|
|
return "F_SETLK";
|
|
#endif
|
|
|
|
#ifdef F_SETLKW
|
|
case F_SETLKW:
|
|
return "F_SETLKW";
|
|
#endif
|
|
|
|
default:
|
|
return "F_unknown";
|
|
}
|
|
}
|
|
|
|
char *
|
|
get_open_mode (int mode)
|
|
{
|
|
static char buf[120];
|
|
|
|
switch (mode & O_ACCMODE)
|
|
{
|
|
case O_RDONLY:
|
|
strcpy (buf, "O_RDONLY");
|
|
break;
|
|
|
|
case O_WRONLY:
|
|
strcpy (buf, "O_WRONLY");
|
|
break;
|
|
|
|
case O_RDWR:
|
|
strcpy (buf, "O_RDWR");
|
|
break;
|
|
|
|
default:
|
|
strcpy (buf, "O_illegal");
|
|
break;
|
|
}
|
|
|
|
#define ADD(flag) \
|
|
if (mode & flag) strcat (buf, "|" #flag);
|
|
|
|
ADD (O_NONBLOCK);
|
|
ADD (O_APPEND);
|
|
ADD (O_SHLOCK);
|
|
ADD (O_EXLOCK);
|
|
ADD (O_ASYNC);
|
|
ADD (O_FSYNC);
|
|
ADD (O_CREAT);
|
|
ADD (O_TRUNC);
|
|
ADD (O_EXCL);
|
|
#undef ADD
|
|
|
|
return buf;
|
|
}
|
|
|
|
char *
|
|
get_ioctl_cmd (int cmd)
|
|
{
|
|
static char buf[12];
|
|
|
|
/* only deal with those commands that are really implemented somehow in
|
|
ixemul.library. The others are dummies anyway, so they don't matter */
|
|
|
|
switch (cmd)
|
|
{
|
|
case FIONREAD:
|
|
return "FIONREAD";
|
|
|
|
case FIONBIO:
|
|
return "FIONBIO";
|
|
|
|
case FIOASYNC:
|
|
/* not yet implemented, but important to know if some program tries
|
|
to use it ! */
|
|
return "FIOASYNC";
|
|
|
|
case TIOCGETA:
|
|
return "TIOCGETA";
|
|
|
|
case TIOCSETA:
|
|
return "TIOCSETA";
|
|
|
|
case TIOCSETAW:
|
|
return "TIOCSETAW";
|
|
|
|
case TIOCSETAF:
|
|
return "TIOCSETAF";
|
|
|
|
case TIOCGETP:
|
|
return "TIOCGETP";
|
|
|
|
case TIOCSETN:
|
|
return "TIOCSETN";
|
|
|
|
case TIOCSETP:
|
|
return "TIOCSETP";
|
|
|
|
case TIOCGWINSZ:
|
|
return "TIOCGWINSZ";
|
|
|
|
case TIOCOUTQ:
|
|
return "TIOCOUTQ";
|
|
|
|
case TIOCSWINSZ:
|
|
return "TIOCSWINSZ";
|
|
|
|
default:
|
|
sprintf (buf, "$%lx", (unsigned long) cmd);
|
|
return buf;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
vp_fcntl (char *buf, int len, struct call *c, struct trace_packet *tp)
|
|
{
|
|
int *argv = & TP_FIRSTARG (tp);
|
|
|
|
if (tp->tp_is_entry)
|
|
if (argv[1] == F_GETFL || argv[1] == F_SETFL)
|
|
snprintf (buf, len+1, "fcntl(%d, %s, %s)",
|
|
argv[0], get_fcntl_cmd (argv[1]), get_open_mode (argv[2]));
|
|
else
|
|
snprintf (buf, len+1, "fcntl(%d, %s, %d)",
|
|
argv[0], get_fcntl_cmd (argv[1]), argv[2]);
|
|
else
|
|
if (argv[1] == F_GETFL || argv[1] == F_SETFL)
|
|
snprintf (buf, len+1, "fcntl(%d, %s, %s) = %d (%d)",
|
|
argv[0], get_fcntl_cmd (argv[1]),
|
|
get_open_mode (argv[2]), TP_RESULT (tp), TP_ERROR (tp));
|
|
else
|
|
snprintf (buf, len+1, "fcntl(%d, %s, %d) = %d (%d)",
|
|
argv[0], get_fcntl_cmd (argv[1]), argv[2],
|
|
TP_RESULT (tp), TP_ERROR (tp));
|
|
|
|
strcat (buf, "\n");
|
|
}
|
|
|
|
|
|
static void
|
|
vp_ioctl (char *buf, int len, struct call *c, struct trace_packet *tp)
|
|
{
|
|
int *argv = & TP_FIRSTARG (tp);
|
|
|
|
if (tp->tp_is_entry)
|
|
snprintf (buf, len+1, "ioctl(%d, %s, $%lx)",
|
|
argv[0], get_ioctl_cmd (argv[1]), argv[2]);
|
|
else
|
|
snprintf (buf, len+1, "ioctl(%d, %s, $%lx) = %d (%d)",
|
|
argv[0], get_ioctl_cmd (argv[1]), argv[2],
|
|
TP_RESULT (tp), TP_ERROR (tp));
|
|
|
|
strcat (buf, "\n");
|
|
}
|
|
|
|
|
|
static void
|
|
vp_open (char *buf, int len, struct call *c, struct trace_packet *tp)
|
|
{
|
|
int *argv = & TP_FIRSTARG (tp);
|
|
|
|
if (tp->tp_is_entry)
|
|
snprintf (buf, len+1, "open(\"%s\", %s)", argv[0], get_open_mode (argv[1]));
|
|
else
|
|
snprintf (buf, len+1, "open(\"%s\", %s) = %d (%d)", argv[0],
|
|
get_open_mode (argv[1]), TP_RESULT (tp), TP_ERROR (tp));
|
|
|
|
strcat (buf, "\n");
|
|
}
|
|
|
|
static void
|
|
vp_pipe (char *buf, int len, struct call *c, struct trace_packet *tp)
|
|
{
|
|
int *argv = & TP_FIRSTARG (tp);
|
|
|
|
if (tp->tp_is_entry)
|
|
snprintf (buf, len+1, "pipe($%lx)", argv[0]);
|
|
else
|
|
{
|
|
int *pv = (int *) argv[0];
|
|
|
|
snprintf (buf, len+1, "pipe([%d, %d]) = %d (%d)",
|
|
pv[0], pv[1], TP_RESULT (tp), TP_ERROR (tp));
|
|
}
|
|
|
|
strcat (buf, "\n");
|
|
}
|