From 9e9a114f06c905e161ecc38cc4dc8af9b94ebb1e Mon Sep 17 00:00:00 2001 From: sef Date: Sat, 6 Dec 1997 05:23:12 +0000 Subject: Truss program. Requires procfs. --- usr.bin/truss/Makefile | 23 ++++ usr.bin/truss/amd64-fbsd32.c | 281 ++++++++++++++++++++++++++++++++++++++++++ usr.bin/truss/amd64-linux32.c | 221 +++++++++++++++++++++++++++++++++ usr.bin/truss/i386-fbsd.c | 281 ++++++++++++++++++++++++++++++++++++++++++ usr.bin/truss/i386-linux.c | 221 +++++++++++++++++++++++++++++++++ usr.bin/truss/i386.conf | 10 ++ usr.bin/truss/i386linux.conf | 10 ++ usr.bin/truss/main.c | 201 ++++++++++++++++++++++++++++++ usr.bin/truss/setup.c | 125 +++++++++++++++++++ usr.bin/truss/syscall.h | 43 +++++++ usr.bin/truss/syscalls.c | 194 +++++++++++++++++++++++++++++ usr.bin/truss/truss.1 | 57 +++++++++ 12 files changed, 1667 insertions(+) create mode 100644 usr.bin/truss/Makefile create mode 100644 usr.bin/truss/amd64-fbsd32.c create mode 100644 usr.bin/truss/amd64-linux32.c create mode 100644 usr.bin/truss/i386-fbsd.c create mode 100644 usr.bin/truss/i386-linux.c create mode 100644 usr.bin/truss/i386.conf create mode 100644 usr.bin/truss/i386linux.conf create mode 100644 usr.bin/truss/main.c create mode 100644 usr.bin/truss/setup.c create mode 100644 usr.bin/truss/syscall.h create mode 100644 usr.bin/truss/syscalls.c create mode 100644 usr.bin/truss/truss.1 (limited to 'usr.bin/truss') diff --git a/usr.bin/truss/Makefile b/usr.bin/truss/Makefile new file mode 100644 index 0000000..a34501a0 --- /dev/null +++ b/usr.bin/truss/Makefile @@ -0,0 +1,23 @@ +PROG= truss +SRCS= main.c setup.c i386-fbsd.c i386-linux.c \ + syscalls.c linux_syscalls.h syscalls.h +CFLAGS+= -I${.CURDIR} -I. +CLEANFILES+=i386l-syscalls.master syscalls.master linux_syscalls.h syscalls.h + +.SUFFIXES: .master + +i386l-syscalls.master: ${.CURDIR}/../../sys/i386/linux/syscalls.master + cp ${.ALLSRC} i386l-syscalls.master + +linux_syscalls.h: i386l-syscalls.master + /bin/sh ${.CURDIR}/../../sys/kern/makesyscalls.sh i386l-syscalls.master \ + ${.CURDIR}/i386linux.conf + +syscalls.master: ${.CURDIR}/../../sys/kern/syscalls.master + cp ${.ALLSRC} syscalls.master + +syscalls.h: syscalls.master + /bin/sh ${.CURDIR}/../../sys/kern/makesyscalls.sh syscalls.master \ + ${.CURDIR}/i386.conf + +.include diff --git a/usr.bin/truss/amd64-fbsd32.c b/usr.bin/truss/amd64-fbsd32.c new file mode 100644 index 0000000..be1d78f --- /dev/null +++ b/usr.bin/truss/amd64-fbsd32.c @@ -0,0 +1,281 @@ +/* + * FreeBSD/386-specific system call handling. This is probably the most + * complex part of the entire truss program, although I've got lots of + * it handled relatively cleanly now. The system call names are generated + * automatically, thanks to /usr/src/sys/kern/syscalls.master. The + * names used for the various structures are confusing, I sadly admit. + */ +/* + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syscall.h" + +static int fd = -1; +static int cpid = -1; +extern int Procfd; + +extern FILE *outfile; +#include "syscalls.h" + +static int nsyscalls = sizeof(syscallnames) / sizeof(syscallnames[0]); + +/* + * This is what this particular file uses to keep track of a system call. + * It is probably not quite sufficient -- I can probably use the same + * structure for the various syscall personalities, and I also probably + * need to nest system calls (for signal handlers). + * + * 'struct syscall' describes the system call; it may be NULL, however, + * if we don't know about this particular system call yet. + */ +static struct freebsd_syscall { + struct syscall *sc; + char *name; + int number; + unsigned long *args; + int nargs; /* number of arguments -- *not* number of words! */ + char **s_args; /* the printable arguments */ +} fsc; + +/* Clear up and free parts of the fsc structure. */ +static inline void +clear_fsc() { + if (fsc.args) { + free(fsc.args); + } + if (fsc.s_args) { + int i; + for (i = 0; i < fsc.nargs; i++) + if (fsc.s_args[i]) + free(fsc.s_args[i]); + free(fsc.s_args); + } + memset(&fsc, 0, sizeof(fsc)); +} + +/* + * Called when a process has entered a system call. nargs is the + * number of words, not number of arguments (a necessary distinction + * in some cases). Note that if the STOPEVENT() code in i386/i386/trap.c + * is ever changed these functions need to keep up. + */ + +void +i386_syscall_entry(int pid, int nargs) { + char buf[32]; + struct reg regs = { 0 }; + int syscall; + int i; + int memfd; + unsigned int parm_offset; + struct syscall *sc; + + if (fd == -1 || pid != cpid) { + sprintf(buf, "/proc/%d/regs", pid); + fd = open(buf, O_RDWR); + if (fd == -1) { + fprintf(outfile, "-- CANNOT READ REGISTERS --\n"); + return; + } + cpid = pid; + } + + clear_fsc(); + lseek(fd, 0L, 0); + i = read(fd, ®s, sizeof(regs)); + parm_offset = regs.r_esp + sizeof(int); + + /* + * FreeBSD has two special kinds of system call redirctions -- + * SYS_syscall, and SYS___syscall. The former is the old syscall() + * routine, basicly; the latter is for quad-aligned arguments. + */ + syscall = regs.r_eax; + switch (syscall) { + case SYS_syscall: + lseek(Procfd, parm_offset, SEEK_SET); + read(Procfd, &syscall, sizeof(int)); + parm_offset += sizeof(int); + break; + case SYS___syscall: + lseek(Procfd, parm_offset, SEEK_SET); + read(Procfd, &syscall, sizeof(int)); + parm_offset += sizeof(quad_t); + break; + } + + fsc.number = syscall; + fsc.name = + (syscall < 0 || syscall > nsyscalls) ? NULL : syscallnames[syscall]; + if (!fsc.name) { + fprintf(outfile, "-- UNKNOWN SYSCALL %d --\n", syscall); + } + + if (nargs == 0) + return; + + fsc.args = malloc((1+nargs) * sizeof(unsigned long)); + lseek(Procfd, parm_offset, SEEK_SET); + if (read(Procfd, fsc.args, nargs * sizeof(unsigned long)) == -1) + return; + + sc = get_syscall(fsc.name); + if (sc) { + fsc.nargs = sc->nargs; + } else { +#if DEBUG + fprintf(outfile, "unknown syscall %s -- setting args to %d\n", + fsc.name, nargs); +#endif + fsc.nargs = nargs; + } + + fsc.s_args = malloc((1+fsc.nargs) * sizeof(char*)); + memset(fsc.s_args, 0, fsc.nargs * sizeof(char*)); + fsc.sc = sc; + + /* + * At this point, we set up the system call arguments. + * We ignore any OUT ones, however -- those are arguments that + * are set by the system call, and so are probably meaningless + * now. This doesn't currently support arguments that are + * passed in *and* out, however. + */ + + if (fsc.name) { + char *tmp; + +#if DEBUG + fprintf(stderr, "syscall %s(", fsc.name); +#endif + for (i = 0; i < fsc.nargs; i++) { +#if DEBUG + fprintf(stderr, "0x%x%s", + sc + ? fsc.args[sc->args[i].offset] + : fsc.args[i], + i < (fsc.nargs -1) ? "," : ""); +#endif + if (sc && !(sc->args[i].type & OUT)) { + fsc.s_args[i] = print_arg(Procfd, &sc->args[i], fsc.args); + } + } +#if DEBUG + fprintf(stderr, ")\n"); +#endif + } + +#if DEBUG + fprintf(outfile, "\n"); +#endif + + /* + * Some system calls should be printed out before they are done -- + * execve() and exit(), for example, never return. Possibly change + * this to work for any system call that doesn't have an OUT + * parameter? + */ + + if (!strcmp(fsc.name, "execve") || !strcmp(fsc.name, "exit")) { + print_syscall(outfile, fsc.name, fsc.nargs, fsc.s_args); + } + + return; +} + +/* + * And when the system call is done, we handle it here. + * Currently, no attempt is made to ensure that the system calls + * match -- this needs to be fixed (and is, in fact, why S_SCX includes + * the sytem call number instead of, say, an error status). + */ + +void +i386_syscall_exit(int pid, int syscall) { + char buf[32]; + struct reg regs; + int retval; + int i; + int errorp; + struct syscall *sc; + + if (fd == -1 || pid != cpid) { + sprintf(buf, "/proc/%d/regs", pid); + fd = open(buf, O_RDONLY); + if (fd == -1) { + fprintf(outfile, "-- CANNOT READ REGISTERS --\n"); + return; + } + cpid = pid; + } + + lseek(fd, 0L, 0); + if (read(fd, ®s, sizeof(regs)) != sizeof(regs)) + return; + retval = regs.r_eax; + errorp = !!(regs.r_eflags & PSL_C); + + /* + * This code, while simpler than the initial versions I used, could + * stand some significant cleaning. + */ + + sc = fsc.sc; + if (!sc) { + for (i = 0; i < fsc.nargs; i++) { + fsc.s_args[i] = malloc(12); + sprintf(fsc.s_args[i], "0x%x", fsc.args[i]); + } + } else { + /* + * Here, we only look for arguments that have OUT masked in -- + * otherwise, they were handled in the syscall_entry function. + */ + for (i = 0; i < sc->nargs; i++) { + char *temp; + if (sc->args[i].type & OUT) { + /* + * If an error occurred, than don't bothe getting the data; + * it may not be valid. + */ + if (errorp) { + temp = malloc(12); + sprintf(temp, "0x%x", fsc.args[sc->args[i].offset]); + } else { + temp = print_arg(Procfd, &sc->args[i], fsc.args); + } + fsc.s_args[i] = temp; + } + } + } + + /* + * It would probably be a good idea to merge the error handling, + * but that complicates things considerably. + */ + + print_syscall(outfile, fsc.name, fsc.nargs, fsc.s_args); + if (errorp) { + fprintf(outfile, "errno %d '%s'\n", retval, strerror(retval)); + } else { + fprintf(outfile, "returns %d (0x%x)\n", retval, retval); + } + clear_fsc(); + + return; +} diff --git a/usr.bin/truss/amd64-linux32.c b/usr.bin/truss/amd64-linux32.c new file mode 100644 index 0000000..8a732a1 --- /dev/null +++ b/usr.bin/truss/amd64-linux32.c @@ -0,0 +1,221 @@ +/* + * Linux/i386-specific system call handling. Given how much of this code + * is taken from the freebsd equivalent, I can probably put even more of + * it in support routines that can be used by any personality support. + */ +/* + * $Id$ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syscall.h" + +static int fd = -1; +static int cpid = -1; +extern int Procfd; + +extern FILE *outfile; +#include "linux_syscalls.h" + +static int nsyscalls = + sizeof(linux_syscallnames) / sizeof(linux_syscallnames[0]); + +/* See the comment in i386-fbsd.c about this structure. */ +static struct linux_syscall { + struct syscall *sc; + char *name; + int number; + unsigned long args[5]; + int nargs; /* number of arguments -- *not* number of words! */ + char **s_args; /* the printable arguments */ +} lsc; + +static inline void +clear_lsc() { + if (lsc.s_args) { + int i; + for (i = 0; i < lsc.nargs; i++) + if (lsc.s_args[i]) + free(lsc.s_args[i]); + free(lsc.s_args); + } + memset(&lsc, 0, sizeof(lsc)); +} + +void +i386_linux_syscall_entry(int pid, int nargs) { + char buf[32]; + struct reg regs = { 0 }; + int syscall; + int i; + int memfd; + struct syscall *sc; + + if (fd == -1 || pid != cpid) { + sprintf(buf, "/proc/%d/regs", pid); + fd = open(buf, O_RDWR); + if (fd == -1) { + fprintf(outfile, "-- CANNOT READ REGISTERS --\n"); + return; + } + cpid = pid; + } + + clear_lsc(); + lseek(fd, 0L, 0); + i = read(fd, ®s, sizeof(regs)); + syscall = regs.r_eax; + + lsc.number = syscall; + lsc.name = + (syscall < 0 || syscall > nsyscalls) ? NULL : linux_syscallnames[syscall]; + if (!lsc.name) { + fprintf (outfile, "-- UNKNOWN SYSCALL %d\n", syscall); + } + + if (nargs == 0) + return; + + /* + * Linux passes syscall arguments in registers, not + * on the stack. Fortunately, we've got access to the + * register set. Note that we don't bother checking the + * number of arguments. And what does linux do for syscalls + * that have more than five arguments? + */ + + lsc.args[0] = regs.r_ebx; + lsc.args[1] = regs.r_ecx; + lsc.args[2] = regs.r_edx; + lsc.args[3] = regs.r_esi; + lsc.args[4] = regs.r_edi; + + sc = get_syscall(lsc.name); + if (sc) { + lsc.nargs = sc->nargs; + } else { +#ifdef DEBUG + fprintf(outfile, "unknown syscall %s -- setting args to %d\n", + lsc.name, nargs); +#endif + lsc.nargs = nargs; + } + + lsc.s_args = malloc((1+lsc.nargs) * sizeof(char*)); + memset(lsc.s_args, 0, lsc.nargs * sizeof(char*)); + lsc.sc = sc; + + if (lsc.name) { + char *tmp; + +#ifdef DEBUG + fprintf(stderr, "syscall %s(", lsc.name); +#endif + for (i = 0; i < lsc.nargs ; i++) { +#ifdef DEBUG + fprintf(stderr, "0x%x%s", + sc ? + lsc.args[sc->args[i].offset] + : lsc.args[i], + i < (lsc.nargs - 1) ? "," : ""); +#endif + if (sc && !(sc->args[i].type & OUT)) { + lsc.s_args[i] = print_arg(Procfd, &sc->args[i], lsc.args); + } + } +#ifdef DEBUG + fprintf(stderr, ")\n"); +#endif + } + + if (!strcmp(lsc.name, "linux_execve") || !strcmp(lsc.name, "exit")) { + print_syscall(outfile, lsc.name, lsc.nargs, lsc.s_args); + } + + return; +} + +/* + * Linux syscalls return negative errno's, we do positive and map them + */ +int bsd_to_linux_errno[] = { + -0, -1, -2, -3, -4, -5, -6, -7, -8, -9, + -10, -35, -12, -13, -14, -15, -16, -17, -18, -19, + -20, -21, -22, -23, -24, -25, -26, -27, -28, -29, + -30, -31, -32, -33, -34, -11,-115,-114, -88, -89, + -90, -91, -92, -93, -94, -95, -96, -97, -98, -99, + -100,-101,-102,-103,-104,-105,-106,-107,-108,-109, + -110,-111, -40, -36,-112,-113, -39, -11, -87,-122, + -116, -66, -6, -6, -6, -6, -6, -37, -38, -9, + -6, +}; + +void +i386_linux_syscall_exit(int pid, int syscall) { + char buf[32]; + struct reg regs; + int retval; + int i; + int errorp; + struct syscall *sc; + + if (fd == -1 || pid != cpid) { + sprintf(buf, "/proc/%d/regs", pid); + fd = open(buf, O_RDONLY); + if (fd == -1) { + fprintf(outfile, "-- CANNOT READ REGISTERS --\n"); + return; + } + cpid = pid; + } + + lseek(fd, 0L, 0); + if (read(fd, ®s, sizeof(regs)) != sizeof(regs)) + return; + + retval = regs.r_eax; + errorp = !!(regs.r_eflags & PSL_C); + + sc = lsc.sc; + if (!sc) { + for (i = 0; i < lsc.nargs; i++) { + lsc.s_args[i] = malloc(12); + sprintf(lsc.s_args[i], "0x%x", lsc.args[i]); + } + } else { + for (i = 0; i < sc->nargs; i++) { + char *temp; + if (sc->args[i].type & OUT) { + if (errorp) { + temp = malloc(12); + sprintf(temp, "0x%x", lsc.args[sc->args[i].offset]); + } else { + temp = print_arg(Procfd, &sc->args[i], lsc.args); + } + lsc.s_args[i] = temp; + } + } + } + print_syscall(outfile, lsc.name, lsc.nargs, lsc.s_args); + if (errorp) { + for (i = 0; i < sizeof(bsd_to_linux_errno) / sizeof(int); i++) + if (retval == bsd_to_linux_errno[i]) + break; + fprintf(outfile, "errno %d '%s'\n", retval, strerror(i)); + } else { + fprintf(outfile, "returns %d (0x%x)\n", retval, retval); + } + clear_lsc(); + return; +} diff --git a/usr.bin/truss/i386-fbsd.c b/usr.bin/truss/i386-fbsd.c new file mode 100644 index 0000000..be1d78f --- /dev/null +++ b/usr.bin/truss/i386-fbsd.c @@ -0,0 +1,281 @@ +/* + * FreeBSD/386-specific system call handling. This is probably the most + * complex part of the entire truss program, although I've got lots of + * it handled relatively cleanly now. The system call names are generated + * automatically, thanks to /usr/src/sys/kern/syscalls.master. The + * names used for the various structures are confusing, I sadly admit. + */ +/* + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syscall.h" + +static int fd = -1; +static int cpid = -1; +extern int Procfd; + +extern FILE *outfile; +#include "syscalls.h" + +static int nsyscalls = sizeof(syscallnames) / sizeof(syscallnames[0]); + +/* + * This is what this particular file uses to keep track of a system call. + * It is probably not quite sufficient -- I can probably use the same + * structure for the various syscall personalities, and I also probably + * need to nest system calls (for signal handlers). + * + * 'struct syscall' describes the system call; it may be NULL, however, + * if we don't know about this particular system call yet. + */ +static struct freebsd_syscall { + struct syscall *sc; + char *name; + int number; + unsigned long *args; + int nargs; /* number of arguments -- *not* number of words! */ + char **s_args; /* the printable arguments */ +} fsc; + +/* Clear up and free parts of the fsc structure. */ +static inline void +clear_fsc() { + if (fsc.args) { + free(fsc.args); + } + if (fsc.s_args) { + int i; + for (i = 0; i < fsc.nargs; i++) + if (fsc.s_args[i]) + free(fsc.s_args[i]); + free(fsc.s_args); + } + memset(&fsc, 0, sizeof(fsc)); +} + +/* + * Called when a process has entered a system call. nargs is the + * number of words, not number of arguments (a necessary distinction + * in some cases). Note that if the STOPEVENT() code in i386/i386/trap.c + * is ever changed these functions need to keep up. + */ + +void +i386_syscall_entry(int pid, int nargs) { + char buf[32]; + struct reg regs = { 0 }; + int syscall; + int i; + int memfd; + unsigned int parm_offset; + struct syscall *sc; + + if (fd == -1 || pid != cpid) { + sprintf(buf, "/proc/%d/regs", pid); + fd = open(buf, O_RDWR); + if (fd == -1) { + fprintf(outfile, "-- CANNOT READ REGISTERS --\n"); + return; + } + cpid = pid; + } + + clear_fsc(); + lseek(fd, 0L, 0); + i = read(fd, ®s, sizeof(regs)); + parm_offset = regs.r_esp + sizeof(int); + + /* + * FreeBSD has two special kinds of system call redirctions -- + * SYS_syscall, and SYS___syscall. The former is the old syscall() + * routine, basicly; the latter is for quad-aligned arguments. + */ + syscall = regs.r_eax; + switch (syscall) { + case SYS_syscall: + lseek(Procfd, parm_offset, SEEK_SET); + read(Procfd, &syscall, sizeof(int)); + parm_offset += sizeof(int); + break; + case SYS___syscall: + lseek(Procfd, parm_offset, SEEK_SET); + read(Procfd, &syscall, sizeof(int)); + parm_offset += sizeof(quad_t); + break; + } + + fsc.number = syscall; + fsc.name = + (syscall < 0 || syscall > nsyscalls) ? NULL : syscallnames[syscall]; + if (!fsc.name) { + fprintf(outfile, "-- UNKNOWN SYSCALL %d --\n", syscall); + } + + if (nargs == 0) + return; + + fsc.args = malloc((1+nargs) * sizeof(unsigned long)); + lseek(Procfd, parm_offset, SEEK_SET); + if (read(Procfd, fsc.args, nargs * sizeof(unsigned long)) == -1) + return; + + sc = get_syscall(fsc.name); + if (sc) { + fsc.nargs = sc->nargs; + } else { +#if DEBUG + fprintf(outfile, "unknown syscall %s -- setting args to %d\n", + fsc.name, nargs); +#endif + fsc.nargs = nargs; + } + + fsc.s_args = malloc((1+fsc.nargs) * sizeof(char*)); + memset(fsc.s_args, 0, fsc.nargs * sizeof(char*)); + fsc.sc = sc; + + /* + * At this point, we set up the system call arguments. + * We ignore any OUT ones, however -- those are arguments that + * are set by the system call, and so are probably meaningless + * now. This doesn't currently support arguments that are + * passed in *and* out, however. + */ + + if (fsc.name) { + char *tmp; + +#if DEBUG + fprintf(stderr, "syscall %s(", fsc.name); +#endif + for (i = 0; i < fsc.nargs; i++) { +#if DEBUG + fprintf(stderr, "0x%x%s", + sc + ? fsc.args[sc->args[i].offset] + : fsc.args[i], + i < (fsc.nargs -1) ? "," : ""); +#endif + if (sc && !(sc->args[i].type & OUT)) { + fsc.s_args[i] = print_arg(Procfd, &sc->args[i], fsc.args); + } + } +#if DEBUG + fprintf(stderr, ")\n"); +#endif + } + +#if DEBUG + fprintf(outfile, "\n"); +#endif + + /* + * Some system calls should be printed out before they are done -- + * execve() and exit(), for example, never return. Possibly change + * this to work for any system call that doesn't have an OUT + * parameter? + */ + + if (!strcmp(fsc.name, "execve") || !strcmp(fsc.name, "exit")) { + print_syscall(outfile, fsc.name, fsc.nargs, fsc.s_args); + } + + return; +} + +/* + * And when the system call is done, we handle it here. + * Currently, no attempt is made to ensure that the system calls + * match -- this needs to be fixed (and is, in fact, why S_SCX includes + * the sytem call number instead of, say, an error status). + */ + +void +i386_syscall_exit(int pid, int syscall) { + char buf[32]; + struct reg regs; + int retval; + int i; + int errorp; + struct syscall *sc; + + if (fd == -1 || pid != cpid) { + sprintf(buf, "/proc/%d/regs", pid); + fd = open(buf, O_RDONLY); + if (fd == -1) { + fprintf(outfile, "-- CANNOT READ REGISTERS --\n"); + return; + } + cpid = pid; + } + + lseek(fd, 0L, 0); + if (read(fd, ®s, sizeof(regs)) != sizeof(regs)) + return; + retval = regs.r_eax; + errorp = !!(regs.r_eflags & PSL_C); + + /* + * This code, while simpler than the initial versions I used, could + * stand some significant cleaning. + */ + + sc = fsc.sc; + if (!sc) { + for (i = 0; i < fsc.nargs; i++) { + fsc.s_args[i] = malloc(12); + sprintf(fsc.s_args[i], "0x%x", fsc.args[i]); + } + } else { + /* + * Here, we only look for arguments that have OUT masked in -- + * otherwise, they were handled in the syscall_entry function. + */ + for (i = 0; i < sc->nargs; i++) { + char *temp; + if (sc->args[i].type & OUT) { + /* + * If an error occurred, than don't bothe getting the data; + * it may not be valid. + */ + if (errorp) { + temp = malloc(12); + sprintf(temp, "0x%x", fsc.args[sc->args[i].offset]); + } else { + temp = print_arg(Procfd, &sc->args[i], fsc.args); + } + fsc.s_args[i] = temp; + } + } + } + + /* + * It would probably be a good idea to merge the error handling, + * but that complicates things considerably. + */ + + print_syscall(outfile, fsc.name, fsc.nargs, fsc.s_args); + if (errorp) { + fprintf(outfile, "errno %d '%s'\n", retval, strerror(retval)); + } else { + fprintf(outfile, "returns %d (0x%x)\n", retval, retval); + } + clear_fsc(); + + return; +} diff --git a/usr.bin/truss/i386-linux.c b/usr.bin/truss/i386-linux.c new file mode 100644 index 0000000..8a732a1 --- /dev/null +++ b/usr.bin/truss/i386-linux.c @@ -0,0 +1,221 @@ +/* + * Linux/i386-specific system call handling. Given how much of this code + * is taken from the freebsd equivalent, I can probably put even more of + * it in support routines that can be used by any personality support. + */ +/* + * $Id$ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syscall.h" + +static int fd = -1; +static int cpid = -1; +extern int Procfd; + +extern FILE *outfile; +#include "linux_syscalls.h" + +static int nsyscalls = + sizeof(linux_syscallnames) / sizeof(linux_syscallnames[0]); + +/* See the comment in i386-fbsd.c about this structure. */ +static struct linux_syscall { + struct syscall *sc; + char *name; + int number; + unsigned long args[5]; + int nargs; /* number of arguments -- *not* number of words! */ + char **s_args; /* the printable arguments */ +} lsc; + +static inline void +clear_lsc() { + if (lsc.s_args) { + int i; + for (i = 0; i < lsc.nargs; i++) + if (lsc.s_args[i]) + free(lsc.s_args[i]); + free(lsc.s_args); + } + memset(&lsc, 0, sizeof(lsc)); +} + +void +i386_linux_syscall_entry(int pid, int nargs) { + char buf[32]; + struct reg regs = { 0 }; + int syscall; + int i; + int memfd; + struct syscall *sc; + + if (fd == -1 || pid != cpid) { + sprintf(buf, "/proc/%d/regs", pid); + fd = open(buf, O_RDWR); + if (fd == -1) { + fprintf(outfile, "-- CANNOT READ REGISTERS --\n"); + return; + } + cpid = pid; + } + + clear_lsc(); + lseek(fd, 0L, 0); + i = read(fd, ®s, sizeof(regs)); + syscall = regs.r_eax; + + lsc.number = syscall; + lsc.name = + (syscall < 0 || syscall > nsyscalls) ? NULL : linux_syscallnames[syscall]; + if (!lsc.name) { + fprintf (outfile, "-- UNKNOWN SYSCALL %d\n", syscall); + } + + if (nargs == 0) + return; + + /* + * Linux passes syscall arguments in registers, not + * on the stack. Fortunately, we've got access to the + * register set. Note that we don't bother checking the + * number of arguments. And what does linux do for syscalls + * that have more than five arguments? + */ + + lsc.args[0] = regs.r_ebx; + lsc.args[1] = regs.r_ecx; + lsc.args[2] = regs.r_edx; + lsc.args[3] = regs.r_esi; + lsc.args[4] = regs.r_edi; + + sc = get_syscall(lsc.name); + if (sc) { + lsc.nargs = sc->nargs; + } else { +#ifdef DEBUG + fprintf(outfile, "unknown syscall %s -- setting args to %d\n", + lsc.name, nargs); +#endif + lsc.nargs = nargs; + } + + lsc.s_args = malloc((1+lsc.nargs) * sizeof(char*)); + memset(lsc.s_args, 0, lsc.nargs * sizeof(char*)); + lsc.sc = sc; + + if (lsc.name) { + char *tmp; + +#ifdef DEBUG + fprintf(stderr, "syscall %s(", lsc.name); +#endif + for (i = 0; i < lsc.nargs ; i++) { +#ifdef DEBUG + fprintf(stderr, "0x%x%s", + sc ? + lsc.args[sc->args[i].offset] + : lsc.args[i], + i < (lsc.nargs - 1) ? "," : ""); +#endif + if (sc && !(sc->args[i].type & OUT)) { + lsc.s_args[i] = print_arg(Procfd, &sc->args[i], lsc.args); + } + } +#ifdef DEBUG + fprintf(stderr, ")\n"); +#endif + } + + if (!strcmp(lsc.name, "linux_execve") || !strcmp(lsc.name, "exit")) { + print_syscall(outfile, lsc.name, lsc.nargs, lsc.s_args); + } + + return; +} + +/* + * Linux syscalls return negative errno's, we do positive and map them + */ +int bsd_to_linux_errno[] = { + -0, -1, -2, -3, -4, -5, -6, -7, -8, -9, + -10, -35, -12, -13, -14, -15, -16, -17, -18, -19, + -20, -21, -22, -23, -24, -25, -26, -27, -28, -29, + -30, -31, -32, -33, -34, -11,-115,-114, -88, -89, + -90, -91, -92, -93, -94, -95, -96, -97, -98, -99, + -100,-101,-102,-103,-104,-105,-106,-107,-108,-109, + -110,-111, -40, -36,-112,-113, -39, -11, -87,-122, + -116, -66, -6, -6, -6, -6, -6, -37, -38, -9, + -6, +}; + +void +i386_linux_syscall_exit(int pid, int syscall) { + char buf[32]; + struct reg regs; + int retval; + int i; + int errorp; + struct syscall *sc; + + if (fd == -1 || pid != cpid) { + sprintf(buf, "/proc/%d/regs", pid); + fd = open(buf, O_RDONLY); + if (fd == -1) { + fprintf(outfile, "-- CANNOT READ REGISTERS --\n"); + return; + } + cpid = pid; + } + + lseek(fd, 0L, 0); + if (read(fd, ®s, sizeof(regs)) != sizeof(regs)) + return; + + retval = regs.r_eax; + errorp = !!(regs.r_eflags & PSL_C); + + sc = lsc.sc; + if (!sc) { + for (i = 0; i < lsc.nargs; i++) { + lsc.s_args[i] = malloc(12); + sprintf(lsc.s_args[i], "0x%x", lsc.args[i]); + } + } else { + for (i = 0; i < sc->nargs; i++) { + char *temp; + if (sc->args[i].type & OUT) { + if (errorp) { + temp = malloc(12); + sprintf(temp, "0x%x", lsc.args[sc->args[i].offset]); + } else { + temp = print_arg(Procfd, &sc->args[i], lsc.args); + } + lsc.s_args[i] = temp; + } + } + } + print_syscall(outfile, lsc.name, lsc.nargs, lsc.s_args); + if (errorp) { + for (i = 0; i < sizeof(bsd_to_linux_errno) / sizeof(int); i++) + if (retval == bsd_to_linux_errno[i]) + break; + fprintf(outfile, "errno %d '%s'\n", retval, strerror(i)); + } else { + fprintf(outfile, "returns %d (0x%x)\n", retval, retval); + } + clear_lsc(); + return; +} diff --git a/usr.bin/truss/i386.conf b/usr.bin/truss/i386.conf new file mode 100644 index 0000000..20407cb --- /dev/null +++ b/usr.bin/truss/i386.conf @@ -0,0 +1,10 @@ +sysnames="syscalls.h" +sysproto="/dev/null" +sysproto_h="/dev/null" +syshdr="/dev/null" +syssw="/dev/null" +syshide="/dev/null" +syscallprefix="SYS_" +switchname="sysent" +namesname="syscallnames" + diff --git a/usr.bin/truss/i386linux.conf b/usr.bin/truss/i386linux.conf new file mode 100644 index 0000000..118150d --- /dev/null +++ b/usr.bin/truss/i386linux.conf @@ -0,0 +1,10 @@ +sysnames="linux_syscalls.h" +sysproto="/dev/null" +sysproto_h="/dev/null" +syshdr="/dev/null" +syssw="/dev/null" +syshide="/dev/null" +syscallprefix="SYS_" +switchname="sysent" +namesname="linux_syscallnames" + diff --git a/usr.bin/truss/main.c b/usr.bin/truss/main.c new file mode 100644 index 0000000..94d97f3 --- /dev/null +++ b/usr.bin/truss/main.c @@ -0,0 +1,201 @@ +/* + * The main module for truss. Suprisingly simple, but, then, the other + * files handle the bulk of the work. And, of course, the kernel has to + * do a lot of the work :). + */ +/* + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int setup_and_wait(char **); +extern int start_tracing(int, int); +extern void i386_syscall_entry(int, int); +extern void i386_syscall_exit(int, int); +extern void i386_linux_syscall_entry(int, int); +extern void i386_linux_syscall_exit(int, int); + +/* + * These should really be parameterized -- I don't like having globals, + * but this is the easiest way, right now, to deal with them. + */ + +int pid = 0; +int nosigs = 0; +FILE *outfile = stderr; +char *prog; +int Procfd; +char progtype[50]; /* OS and type of executable */ + +static inline void +usage(void) { + fprintf(stderr, "usage: %s [-o ] [-S] { [-p ] | " + "[ ] }\n", prog); + exit(1); +} + +struct ex_types { + char *type; + void (*enter_syscall)(int, int); + void (*exit_syscall)(int, int); +} ex_types[] = { + { "FreeBSD a.out", i386_syscall_entry, i386_syscall_exit }, + { "Linux ELF", i386_linux_syscall_entry, i386_linux_syscall_exit }, + { 0, 0, 0 }, +}; + +/* + * Set the execution type. This is called after every exec, and when + * a process is first monitored. The procfs pseudo-file "etype" has + * the execution module type -- see /proc/curproc/etype for an example. + */ + +static struct ex_types * +set_etype() { + struct ex_types *funcs; + char etype[24]; + char progtype[32]; + int fd; + + sprintf(etype, "/proc/%d/etype", pid); + if ((fd = open(etype, O_RDONLY)) == -1) { + strcpy(progtype, "FreeBSD a.out"); + } else { + int len = read(fd, progtype, sizeof(progtype)); + progtype[len-1] = '\0'; + close(etype); + } + + for (funcs = ex_types; funcs->type; funcs++) + if (!strcmp(funcs->type, progtype)) + break; + + return funcs; +} + +main(int ac, char **av) { + int mask; + int c; + int i; + char **command; + struct procfs_status pfs; + char etype[25]; + struct ex_types *funcs; + int fd; + int in_exec = 0; + + prog = av[0]; + + while ((c = getopt(ac, av, "p:o:S")) != EOF) { + switch (c) { + case 'p': /* specified pid */ + pid = atoi(optarg); + break; + case 'o': /* Specified output file */ + if ((outfile = fopen(optarg, "w")) == NULL) { + fprintf (stderr, "%s: cannot open %s\n", av[0], optarg); + exit(1); + } + break; + case 'S': /* Don't trace signals */ + nosigs = 1; + break; + default: + usage(); + } + } + + ac -= optind; av += optind; + if (ac && pid != 0) + usage(); + + /* + * If truss starts the process itself, it will ignore some signals -- + * they should be passed off to the process, which may or may not + * exit. If, however, we are examining an already-running process, + * then we restore the event mask on these same signals. + */ + + if (pid == 0) { /* Start a command ourselves */ + command = av; + pid = setup_and_wait(command); + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + } else { + extern void restore_proc(int); + signal(SIGINT, restore_proc); + signal(SIGTERM, restore_proc); + signal(SIGQUIT, restore_proc); + } + + + /* + * At this point, if we started the process, it is stopped waiting to + * be woken up, either in exit() or in execve(). + */ + + Procfd = start_tracing(pid, S_EXEC | S_SCE | S_SCX | S_CORE | S_EXIT | + (nosigs ? 0 : S_SIG)); + pfs.why = 0; + + funcs = set_etype(); + /* + * At this point, it's a simple loop, waiting for the process to + * stop, finding out why, printing out why, and then continuing it. + * All of the grunt work is done in the support routines. + */ + + do { + int val = 0; + + if (ioctl(Procfd, PIOCWAIT, &pfs) == -1) + perror("PIOCWAIT top of loop"); + else { + switch(i = pfs.why) { + case S_SCE: + funcs->enter_syscall(pid, pfs.val); + break; + case S_SCX: + /* + * This is so we don't get two messages for an exec -- one + * for the S_EXEC, and one for the syscall exit. It also, + * conveniently, ensures that the first message printed out + * isn't the return-from-syscall used to create the process. + */ + + if (in_exec) { + in_exec = 0; + break; + } + funcs->exit_syscall(pid, pfs.val); + break; + case S_SIG: + fprintf(outfile, "SIGNAL %d\n", pfs.val); + break; + case S_EXIT: + fprintf (outfile, "process exit, rval = %d\n", pfs.val); + break; + case S_EXEC: + funcs = set_etype(); + in_exec = 1; + break; + default: + fprintf (outfile, "Process stopped because of: %d\n", i); + break; + } + } + if (ioctl(Procfd, PIOCCONT, &val) == -1) + perror("PIOCCONT"); + } while (pfs.why != S_EXIT); + return 0; +} diff --git a/usr.bin/truss/setup.c b/usr.bin/truss/setup.c new file mode 100644 index 0000000..ca6692b --- /dev/null +++ b/usr.bin/truss/setup.c @@ -0,0 +1,125 @@ +/* + * Various setup functions for truss. Not the cleanest-written code, + * I'm afraid. + */ +/* + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int evflags = 0; + +/* + * setup_and_wait() is called to start a process. All it really does + * is vfork(), set itself up to stop on exec or exit, and then exec + * the given command. At that point, the child process stops, and + * the parent can wake up and deal with it. + */ + +int +setup_and_wait(char *command[]) { + struct procfs_status pfs; + char buf[32]; + int fd; + int pid; + extern char *prog; + int flags; + + pid = vfork(); + if (pid == -1) { + err(1, "vfork failed\n"); + } + if (pid == 0) { /* Child */ + int mask = S_EXEC | S_EXIT; + fd = open("/proc/curproc/mem", O_WRONLY); + if (fd == -1) + err(2, "cannot open /proc/curproc/mem: %s\n", strerror(errno)); + if (ioctl(fd, PIOCBIS, &mask) == -1) + err(3, "PIOCBIS: %s\n", strerror(errno)); + execvp(command[0], command); + mask = ~0; + ioctl(fd, PIOCBIC, &mask); + err(4, "execvp %s", command[0]); + } + /* Only in the parent here */ + + if (waitpid(pid, NULL, WNOHANG) != 0) { + /* + * Process exited before it got to us -- meaning the exec failed + * miserably -- so we just quietly exit. + */ + exit(1); + } + + sprintf(buf, "/proc/%d/mem", pid); + if ((fd = open(buf, O_RDWR)) == -1) + err(5, "cannot open %s: %s\n", buf, strerror(errno)); + if (ioctl(fd, PIOCWAIT, &pfs) == -1) + err(6, "PIOCWAIT: %s\n", strerror(errno)); + if (pfs.why == S_EXIT) { + int zero = 0; + fprintf(stderr, "process exited before exec'ing\n"); + ioctl(fd, PIOCCONT, &zero); + wait(0); + exit(7); + } + close(fd); + return pid; +} + +/* + * start_tracing picks up where setup_and_wait() dropped off -- namely, + * it sets the event mask for the given process id. Called for both + * monitoring an existing process and when we create our own. + */ + +int +start_tracing(int pid, int flags) { + int fd; + char buf[32]; + struct procfs_status tmp; + sprintf(buf, "/proc/%d/mem", pid); + fd = open(buf, O_RDWR); + if (fd == -1) + err(8, "cannot open %s", buf); + + if (ioctl(fd, PIOCSTATUS, &tmp) == -1) { + err(10, "cannot get procfs status struct"); + } + evflags = tmp.events; + + if (ioctl(fd, PIOCBIS, &flags) == -1) + err(9, "cannot set procfs event bit mask"); + + return fd; +} + +/* + * Restore a process back to it's pre-truss state. + * Called for SIGINT, SIGTERM, SIGQUIT. This only + * applies if truss was told to monitor an already-existing + * process. + */ +void +restore_proc(int signo) { + extern int Procfd; + int i; + + i = ~0; + ioctl(Procfd, PIOCBIC, &i); + if (evflags) + ioctl(Procfd, PIOCBIS, &evflags); + exit(0); +} diff --git a/usr.bin/truss/syscall.h b/usr.bin/truss/syscall.h new file mode 100644 index 0000000..74f8334 --- /dev/null +++ b/usr.bin/truss/syscall.h @@ -0,0 +1,43 @@ +/* + * System call arguments come in several flavours: + * Hex -- values that should be printed in hex (addresses) + * Octal -- Same as above, but octal + * Int -- normal integer values (file descriptors, for example) + * String -- pointers to sensible data. Note that we treat read() and + * write() arguments as such, even though they may *not* be + * printable data. + * Ptr -- pointer to some specific structure. Just print as hex for now. + * Quad -- a double-word value. e.g., lseek(int, offset_t, int) + * Stat -- a pointer to a stat buffer. Currently unused. + * + * In addition, the pointer types (String, Ptr) may have OUT masked in -- + * this means that the data is set on *return* from the system call -- or + * IN (meaning that the data is passed *into* the system call). + */ +/* + * $Id$ + */ + +enum Argtype { None = 1, Hex, Octal, Int, String, Ptr, Stat, Quad }; + +#define ARG_MASK 0xff +#define OUT 0x100 +#define IN /*0x20*/0 + +struct syscall_args { + enum Argtype type; + int offset; +}; + +struct syscall { + char *name; + int ret_type; /* 0, 1, or 2 return values */ + int nargs; /* actual number of meaningful arguments */ + /* Hopefully, no syscalls with > 10 args */ + struct syscall_args args[10]; +}; + +struct syscall *get_syscall(const char*); +char *get_string(int, void*, int); +char *print_arg(int, struct syscall_args *, unsigned long*); +void print_syscall(FILE *, const char *, int, char **); diff --git a/usr.bin/truss/syscalls.c b/usr.bin/truss/syscalls.c new file mode 100644 index 0000000..98dadca --- /dev/null +++ b/usr.bin/truss/syscalls.c @@ -0,0 +1,194 @@ +/* + * This file has routines used to print out system calls and their + * arguments. + */ +/* + * $Id$ + */ + +#include +#include +#include +#include +#include +#include "syscall.h" + +/* + * This should probably be in its own file. + */ + +struct syscall syscalls[] = { + { "readlink", 1, 3, + { { String, 0 } , { String | OUT, 1 }, { Int, 2 }}}, + { "lseek", 2, 3, + { { Int, 0 }, {Quad, 2 }, { Int, 4 }}}, + { "mmap", 2, 6, + { { Hex, 0 }, {Int, 1}, {Hex, 2}, {Hex, 3}, {Int, 4}, {Quad, 6}}}, + { "open", 1, 3, + { { String | IN, 0} , { Int, 1}, {Octal, 2}}}, + { "linux_open", 1, 3, + { { String, 0 }, { Int, 1}, { Octal, 2 }}}, + { "close", 1, 1, { { Int, 0 } } }, + { "fstat", 1, 2, + { { Int, 0}, {Ptr | OUT , 1 }}}, + { "stat", 1, 2, + { { String | IN, 0 }, { Ptr | OUT, 1 }}}, + { "linux_newstat", 1, 2, + { { String | IN, 0 }, { Ptr | OUT, 1 }}}, + { "linux_newfstat", 1, 2, + { { Int, 0 }, { Ptr | OUT, 1 }}}, + { "write", 1, 3, + { { Int, 0}, { Ptr | IN, 1 }, { Int, 2 }}}, + { "break", 1, 1, { { Hex, 0 }}}, + { "exit", 0, 1, { { Hex, 0 }}}, + { 0, 0, 0, { 0, 0 } }, +}; + +/* + * If/when the list gets big, it might be desirable to do it + * as a hash table or binary search. + */ + +struct syscall * +get_syscall(const char *name) { + struct syscall *sc = syscalls; + + while (sc->name) { + if (!strcmp(name, sc->name)) + return sc; + sc++; + } + return NULL; +} + +/* + * get_string + * Copy a string from the process. Note that it is + * expected to be a C string, but if max is set, it will + * only get that much. + */ + +char * +get_string(int procfd, void *offset, int max) { + char *buf, *tmp; + int size, len, c; + FILE *p; + + if ((p = fdopen(procfd, "r")) == NULL) { + perror("fdopen"); + exit(1); + } + buf = malloc( size = (max ? max : 64 ) ); + len = 0; + fseek(p, (long)offset, SEEK_SET); + while ((c = fgetc(p)) != EOF) { + buf[len++] = c; + if (c == 0 || len == max) { + buf[len] = 0; + break; + } + if (len == size) { + char *tmp = buf; + tmp = realloc(buf, size+64); + if (tmp == NULL) { + buf[len] = 0; + return buf; + } + size += 64; + } + } + return buf; +} + + +/* + * Gag. This is really unportable. Multiplication is more portable. + * But slower, from the code I saw. + */ + +static long long +make_quad(unsigned long p1, unsigned long p2) { + union { + long long ll; + unsigned long l[2]; + } t; + t.l[0] = p1; + t.l[1] = p2; + return t.ll; +} + + +/* + * print_arg + * Converts a syscall argument into a string. Said string is + * allocated via malloc(), so needs to be free()'d. The file + * descriptor is for the process' memory (via /proc), and is used + * to get any data (where the argument is a pointer). sc is + * a pointer to the syscall description (see above); args is + * an array of all of the system call arguments. + */ + +char * +print_arg(int fd, struct syscall_args *sc, unsigned long *args) { + char *tmp; + switch (sc->type & ARG_MASK) { + case Hex: + tmp = malloc(12); + sprintf(tmp, "0x%x", args[sc->offset]); + break; + case Octal: + tmp = malloc(13); + sprintf(tmp, "0%o", args[sc->offset]); + break; + case Int: + tmp = malloc(12); + sprintf(tmp, "%d", args[sc->offset]); + break; + case String: + { + char *tmp2; + tmp2 = get_string(fd, (void*)args[sc->offset], 0); + tmp = malloc(strlen(tmp2) + 3); + sprintf(tmp, "\"%s\"", tmp2); + free(tmp2); + } + break; + case Quad: + { + unsigned long long t; + unsigned long l1, l2; + l1 = args[sc->offset]; + l2 = args[sc->offset+1]; + t = make_quad(l1, l2); + tmp = malloc(24); + sprintf(tmp, "0x%qx", t); + break; + } + case Ptr: + tmp = malloc(12); + sprintf(tmp, "0x%x", args[sc->offset]); + break; + } + return tmp; +} + +/* + * print_syscall + * Print (to outfile) the system call and its arguments. Note that + * nargs is the number of arguments (not the number of words; this is + * potentially confusing, I know). + */ + +void +print_syscall(FILE *outfile, const char *name, int nargs, char **s_args) { + int i; + fprintf(outfile, "syscall %s(", name); + for (i = 0; i < nargs; i++) { + if (s_args[i]) + fprintf(outfile, "%s", s_args[i]); + else + fprintf(outfile, ""); + fprintf(outfile, "%s", i < (nargs - 1) ? "," : ""); + } + fprintf(outfile, ")\n\t"); +} diff --git a/usr.bin/truss/truss.1 b/usr.bin/truss/truss.1 new file mode 100644 index 0000000..878398e --- /dev/null +++ b/usr.bin/truss/truss.1 @@ -0,0 +1,57 @@ +.Dd Nov 23, 1997 +.Dt TRUSS 1 +.Os FreeBSD +.Sh NAME +.Nm \&truss +.Nd trace system calls +.Sh Synopsis +.Nm \&truss +.Op Fl S +.Op Fl p Ar pid +.Op Fl o Ar file +command +.Sh DESCRIPTION +.Nm \&truss +traces the system calls called by the specified process or program. +Output is to the specified output file, or standard error by default. +It does this by stopping and restarting the process being monitored via +.Xr procfs 5 . +.Pp +The options are as follows: +.Bl -tag -width command +.It Fl S +Do not display information about signals received by the process. +(Normally, +.Nm \&ps +displays signal as well as system call events.) +.It Fl p +Follow the process specified by +.Ar pid +instead of a new command. +.It Fl o +Print the output to the specified file instead of standard error. +.It Ar command +Execute +.Ar command +and trace the system calls of it. +(The +.Fl p +and +.Ar command +options are mutually exclusive.) +.Sh EXAMPLES +# Follow the system calls used in echoing "hello" +.Dl $ truss /bin/echo hello +# Do the same, but put the output into a file +.Dl $ truss -o /tmp/truss.out /bin/echo hello +# Follow an already-running process +.Dl $ truss -p 1 +.Sh SEE ALSO +.Xr procfs 5 , +.Xr ktrace 1 , +.Xr kdump 1 +.Sh HISTORY +The +.Nm truss +command was written by Sean Eric Fagan for FreeBSD; it was modeled after +similar commands available for System V Release 4 and SunOS. -- cgit v1.1