NetBSD/usr.bin/ktruss/dump.c

829 lines
18 KiB
C

/* $NetBSD: dump.c,v 1.48 2021/05/01 18:07:52 rillig Exp $ */
/*-
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1988, 1993\
The Regents of the University of California. All rights reserved.");
#endif /* not lint */
#ifndef lint
#if 0
static char sccsid[] = "@(#)kdump.c 8.4 (Berkeley) 4/28/95";
#endif
__RCSID("$NetBSD: dump.c,v 1.48 2021/05/01 18:07:52 rillig Exp $");
#endif /* not lint */
#include <sys/param.h>
#define _KERNEL
#include <sys/errno.h>
#undef _KERNEL
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/ktrace.h>
#include <sys/ptrace.h>
#include <sys/queue.h>
#include <err.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <vis.h>
#include "ktrace.h"
#include "misc.h"
#include "setemul.h"
int timestamp, decimal, fancy = 1, tail, maxdata;
int width; /* Keep track of current columns. */
#include <sys/syscall.h>
static const char *const ptrace_ops[] = {
PT_STRINGS
};
#ifdef PT_MACHDEP_STRINGS
static const char * const ptrace_machdep_ops[] = { PT_MACHDEP_STRINGS };
#endif
struct ktr_entry {
TAILQ_ENTRY(ktr_entry) kte_list;
struct ktr_header kte_kth;
};
TAILQ_HEAD(kteq, ktr_entry) ktependq = TAILQ_HEAD_INITIALIZER(ktependq);
void argprint(const char *, register_t **, int *);
void dumpheader(struct ktr_header *);
int dumprecord(int, FILE *);
void flushpendq(struct ktr_entry *);
int fread_tail(void *, int, int, FILE *);
void genioprint(struct ktr_header *);
struct ktr_entry *
getpendq(struct ktr_header *, int, struct kteq *);
struct ktr_entry *
getrecord(FILE *);
void indent(int);
void ioctldecode(u_long);
void ktrcsw(struct ktr_entry *);
void ktremul(struct ktr_entry *);
void ktrgenio(struct ktr_entry *);
void ktrnamei(struct ktr_entry *);
void ktrpsig(struct ktr_entry *);
void ktrsyscall(struct ktr_entry *);
void ktrsysret(struct ktr_entry *);
void nameiargprint(const char *, struct ktr_header *, register_t **, int *);
void nameiprint(struct ktr_header *);
void newline(void);
void putpendq(struct ktr_entry *);
void syscallnameprint(int);
void syscallprint(struct ktr_header *);
void sysretprint(struct ktr_header *);
int xwprintf(const char *, ...) __printflike(1, 2);
void *xrealloc(void *, size_t *, size_t);
int
xwprintf(const char *fmt, ...)
{
va_list ap;
int w;
va_start(ap, fmt);
w = vprintf(fmt, ap);
if (w == -1)
warn("vprintf");
else
width += w;
va_end(ap);
return (w);
}
void
newline(void)
{
if (width > 0) {
printf("\n");
width = 0;
}
}
void
indent(int col)
{
while (width < col)
if (xwprintf(" ") < 0)
break;
}
void *
xrealloc(void *p, size_t *siz, size_t req)
{
if (*siz < req) {
if (*siz == 0)
*siz = 1;
while (*siz < req)
*siz <<= 1;
p = realloc(p, *siz);
if (p == NULL)
err(EXIT_FAILURE, "realloc: %lu bytes",
(u_long)*siz);
}
return (p);
}
struct ktr_entry *
getrecord(FILE *fp)
{
struct ktr_entry *kte;
struct ktr_header *kth;
char *cp;
size_t siz, len;
siz = 0;
kte = xrealloc(NULL, &siz, sizeof(struct ktr_entry));
kth = &kte->kte_kth;
if (fread_tail(kth, sizeof(struct ktr_header), 1, fp) == 0) {
free(kte);
return (NULL);
}
if (kth->ktr_len < 0)
errx(EXIT_FAILURE, "bogus length 0x%x", kth->ktr_len);
len = kth->ktr_len;
if (len > 0) {
/* + 1 to ensure room for NUL terminate */
kte = xrealloc(kte, &siz, sizeof(struct ktr_entry) + len + 1);
if (fread_tail(cp = (char *)(&kte->kte_kth + 1),
len, 1, fp) == 0)
errx(EXIT_FAILURE, "data too short");
cp[len] = 0;
}
return (kte);
}
#define KTE_TYPE(kte) ((kte)->kte_kth.ktr_type)
#define KTE_PID(kte) ((kte)->kte_kth.ktr_pid)
#define KTE_LID(kte) ((kte)->kte_kth.ktr_lid)
#define KTE_MATCH(kte, type, pid, lid) \
(KTE_TYPE(kte) == (type) && KTE_PID(kte) == (pid) && \
KTE_LID(kte) == (lid))
void
putpendq(struct ktr_entry *kte)
{
TAILQ_INSERT_TAIL(&ktependq, kte, kte_list);
}
void
flushpendq(struct ktr_entry *us)
{
struct ktr_entry *kte, *kte_next;
int pid = KTE_PID(us), lid = KTE_LID(us);
for (kte = TAILQ_FIRST(&ktependq); kte != NULL; kte = kte_next) {
kte_next = TAILQ_NEXT(kte, kte_list);
if (KTE_PID(kte) == pid || KTE_LID(kte) == lid) {
TAILQ_REMOVE(&ktependq, kte, kte_list);
free(kte);
}
}
}
struct ktr_entry *
getpendq(struct ktr_header *us, int type, struct kteq *kteq)
{
struct ktr_entry *kte, *kte_next;
int pid = us->ktr_pid, lid = us->ktr_lid;
if (kteq != NULL)
TAILQ_INIT(kteq);
for (kte = TAILQ_FIRST(&ktependq); kte != NULL; kte = kte_next) {
kte_next = TAILQ_NEXT(kte, kte_list);
if (KTE_MATCH(kte, type, pid, lid)) {
TAILQ_REMOVE(&ktependq, kte, kte_list);
if (kteq != NULL)
TAILQ_INSERT_TAIL(kteq, kte, kte_list);
else
break;
}
}
return (kteq ? TAILQ_FIRST(kteq) : kte);
}
int
dumprecord(int trpoints, FILE *fp)
{
struct ktr_entry *kte;
struct ktr_header *kth;
kte = getrecord(fp);
if (kte == NULL)
return (0);
kth = &kte->kte_kth;
if ((trpoints & (1 << kth->ktr_type)) == 0) {
free(kte);
goto out;
}
/* Update context to match currently processed record. */
ectx_sanify(kth->ktr_pid);
switch (kth->ktr_type) {
case KTR_SYSCALL:
ktrsyscall(kte);
break;
case KTR_SYSRET:
ktrsysret(kte);
break;
case KTR_NAMEI:
putpendq(kte);
break;
case KTR_GENIO:
putpendq(kte);
break;
case KTR_PSIG:
ktrpsig(kte);
break;
case KTR_CSW:
ktrcsw(kte);
break;
case KTR_EMUL:
putpendq(kte);
break;
default:
/*
* XXX: Other types added recently.
*/
free(kte);
break;
}
newline();
out:
return (1);
}
void
dumpfile(const char *file, int fd, int trpoints)
{
FILE *fp;
if (file == NULL || *file == 0) {
if ((fp = fdopen(fd, "r")) == NULL)
err(EXIT_FAILURE, "fdopen(%d)", fd);
} else if (strcmp(file, "-") == 0)
fp = stdin;
else if ((fp = fopen(file, "r")) == NULL)
err(EXIT_FAILURE, "fopen: %s", file);
for (width = 0; dumprecord(trpoints, fp) != 0;)
if (tail)
(void)fflush(stdout);
newline();
/*
* XXX: Dump pending KTR_SYSCALL if any?
*/
}
int
fread_tail(void *buf, int size, int num, FILE *fp)
{
int i;
while ((i = fread(buf, size, num, fp)) == 0 && tail) {
(void)sleep(1);
clearerr(fp);
}
return (i);
}
void
dumpheader(struct ktr_header *kth)
{
union timeholder {
struct timeval tv;
struct timespec ts;
};
static union timeholder prevtime;
union timeholder temp;
temp.tv.tv_sec = temp.tv.tv_usec = 0;
xwprintf("%6d ", kth->ktr_pid);
if (kth->ktr_version > KTRFAC_VERSION(KTRFACv0))
xwprintf("%6d ", kth->ktr_lid);
xwprintf("%-8.*s ", MAXCOMLEN, kth->ktr_comm);
if (timestamp) {
if (timestamp == 2) {
switch (kth->ktr_version) {
case KTRFAC_VERSION(KTRFACv0):
if (prevtime.tv.tv_sec == 0)
temp.tv.tv_sec = temp.tv.tv_usec = 0;
else
timersub(&kth->ktr_otv,
&prevtime.tv, &temp.tv);
prevtime.tv.tv_sec = kth->ktr_otv.tv_sec;
prevtime.tv.tv_usec = kth->ktr_otv.tv_usec;
break;
case KTRFAC_VERSION(KTRFACv1):
if (prevtime.ts.tv_sec == 0)
temp.ts.tv_sec = temp.ts.tv_nsec = 0;
else
timespecsub(&kth->ktr_time,
&prevtime.ts, &temp.ts);
prevtime.ts.tv_sec = kth->ktr_ots.tv_sec;
prevtime.ts.tv_nsec = kth->ktr_ots.tv_nsec;
break;
case KTRFAC_VERSION(KTRFACv2):
if (prevtime.ts.tv_sec == 0)
temp.ts.tv_sec = temp.ts.tv_nsec = 0;
else
timespecsub(&kth->ktr_time,
&prevtime.ts, &temp.ts);
prevtime.ts.tv_sec = kth->ktr_ts.tv_sec;
prevtime.ts.tv_nsec = kth->ktr_ts.tv_nsec;
break;
}
} else {
switch (kth->ktr_version) {
case KTRFAC_VERSION(KTRFACv0):
temp.tv.tv_sec = kth->ktr_otv.tv_sec;
temp.tv.tv_usec = kth->ktr_otv.tv_usec;
break;
case KTRFAC_VERSION(KTRFACv1):
temp.ts.tv_sec = kth->ktr_ots.tv_sec;
temp.ts.tv_nsec = kth->ktr_ots.tv_nsec;
break;
case KTRFAC_VERSION(KTRFACv2):
temp.ts.tv_sec = kth->ktr_ts.tv_sec;
temp.ts.tv_nsec = kth->ktr_ts.tv_nsec;
break;
}
}
if (kth->ktr_version == KTRFAC_VERSION(KTRFACv0))
xwprintf("%lld.%06ld ",
(long long)temp.tv.tv_sec, (long)temp.tv.tv_usec);
else
xwprintf("%lld.%09ld ",
(long long)temp.ts.tv_sec, (long)temp.ts.tv_nsec);
}
}
void
ioctldecode(u_long cmd)
{
char dirbuf[4], *dir = dirbuf;
if (cmd & IOC_OUT)
*dir++ = 'W';
if (cmd & IOC_IN)
*dir++ = 'R';
*dir = '\0';
xwprintf(decimal ? ", _IO%s('%c',%ld" : ", _IO%s('%c',%#lx",
dirbuf, (int) ((cmd >> 8) & 0xff), cmd & 0xff);
if ((cmd & IOC_VOID) == 0)
xwprintf(decimal ? ",%ld)" : ",%#lx)",
(cmd >> 16) & 0xff);
else
xwprintf(")");
}
void
nameiargprint(const char *prefix, struct ktr_header *kth,
register_t **ap, int *argsize)
{
struct ktr_entry *kte;
if (*argsize == 0)
errx(EXIT_FAILURE, "argument expected");
/*
* XXX: binary emulation mode.
*/
kte = getpendq(kth, KTR_NAMEI, NULL);
if (kte == NULL)
argprint(prefix, ap, argsize);
else {
xwprintf("%s", prefix);
nameiprint(&kte->kte_kth);
free(kte);
(*ap)++;
*argsize -= sizeof(register_t);
}
}
void
syscallnameprint(int code)
{
if (code >= cur_emul->nsysnames || code < 0)
xwprintf("[%d]", code);
else
xwprintf("%s", cur_emul->sysnames[code]);
}
void
argprint(const char *prefix, register_t **ap, int *argsize)
{
if (decimal)
xwprintf("%s%ld", prefix, (long)**ap);
else
xwprintf("%s%#lx", prefix, (long)**ap);
(*ap)++;
*argsize -= sizeof(register_t);
}
void
syscallprint(struct ktr_header *kth)
{
struct ktr_syscall *ktr = (struct ktr_syscall *)(kth + 1);
register_t *ap;
const char *s;
int argsize;
syscallnameprint(ktr->ktr_code);
/*
* Arguments processing.
*/
argsize = ktr->ktr_argsize;
if (argsize == 0) {
xwprintf("(");
goto noargument;
}
ap = (register_t *)(ktr + 1);
if (!fancy)
goto print_first;
switch (ktr->ktr_code) {
/*
* All these have a path as the first param.
* The order is same as syscalls.master.
*/
case SYS_open:
case SYS_link:
case SYS_unlink:
case SYS_chdir:
case SYS___mknod50:
case SYS_chmod:
case SYS_chown:
case SYS_unmount:
case SYS_access:
case SYS_chflags:
case SYS_acct:
case SYS_revoke:
case SYS_symlink:
case SYS_readlink:
case SYS_execve:
case SYS_chroot:
case SYS_rename:
case SYS_mkfifo:
case SYS_mkdir:
case SYS_rmdir:
case SYS___utimes50:
case SYS_compat_50_quotactl:
case SYS___quotactl:
case SYS___statvfs190:
case SYS_compat_30_getfh:
case SYS_pathconf:
case SYS_truncate:
case SYS_undelete:
case SYS___posix_rename:
case SYS_lchmod:
case SYS_lchown:
case SYS___lutimes50:
case SYS___stat50:
case SYS___lstat50:
case SYS___posix_chown:
case SYS___posix_lchown:
case SYS_lchflags:
case SYS___getfh30:
nameiargprint("(", kth, &ap, &argsize);
/*
* 2nd argument is also pathname.
*/
switch (ktr->ktr_code) {
case SYS_link:
case SYS_rename:
case SYS___posix_rename:
nameiargprint(", ", kth, &ap, &argsize);
break;
}
break;
case SYS_compat_16___sigaction14 :
if ((int)*ap < 0 || (int)*ap >= MAXSIGNALS)
xwprintf("(%d", (int)*ap);
else
xwprintf("(%s", signals[(int)*ap].name);
ap++;
argsize -= sizeof(register_t);
break;
case SYS_ioctl :
argprint("(", &ap, &argsize);
if ((s = ioctlname(*ap)) != NULL)
xwprintf(", %s", s);
else
ioctldecode(*ap);
ap++;
argsize -= sizeof(register_t);
break;
case SYS_ptrace :
if ((long)*ap >= 0 &&
*ap < (register_t)__arraycount(ptrace_ops))
xwprintf("(%s", ptrace_ops[*ap]);
#ifdef PT_MACHDEP_STRINGS
else if (*ap >= PT_FIRSTMACH &&
*ap - PT_FIRSTMACH < (register_t)
__arraycount(ptrace_machdep_ops))
xwprintf("(%s", ptrace_machdep_ops[*ap - PT_FIRSTMACH]);
#endif
else
xwprintf("(%ld", (long)*ap);
ap++;
argsize -= sizeof(register_t);
break;
default:
print_first:
argprint("(", &ap, &argsize);
break;
}
/* Print rest of argument. */
while (argsize > 0)
argprint(", ", &ap, &argsize);
noargument:
xwprintf(")");
}
void
ktrsyscall(struct ktr_entry *kte)
{
struct ktr_header *kth = &kte->kte_kth;
struct ktr_syscall *ktr = (struct ktr_syscall *)(kth + 1);
switch (ktr->ktr_code) {
case SYS_exit:
dumpheader(kth);
syscallprint(kth);
break;
default:
putpendq(kte);
return;
}
free(kte);
}
void
sysretprint(struct ktr_header *kth)
{
struct ktr_sysret *ktr = (struct ktr_sysret *)(kth + 1);
register_t ret = ktr->ktr_retval;
int error = ktr->ktr_error;
indent(50);
if (error == EJUSTRETURN)
xwprintf(" JUSTRETURN");
else if (error == ERESTART)
xwprintf(" RESTART");
else if (error) {
xwprintf(" Err#%d", error);
if (error < MAXERRNOS && error >= 0)
xwprintf(" %s", errnos[error].name);
} else
switch (ktr->ktr_code) {
case SYS_mremap:
case SYS_mmap:
xwprintf(" = %p", (void *)(intptr_t)ret);
break;
default:
xwprintf(" = %ld", (long)ret);
if (kth->ktr_len > (int)offsetof(struct ktr_sysret,
ktr_retval_1) && ktr->ktr_retval_1 != 0)
xwprintf(", %ld", (long)ktr->ktr_retval_1);
break;
}
}
void
ktrsysret(struct ktr_entry *kte)
{
struct ktr_header *kth = &kte->kte_kth;
struct ktr_sysret *ktr = (struct ktr_sysret *)(kth + 1);
struct ktr_entry *emul;
struct ktr_entry *genio;
struct ktr_entry *syscall_ent;
dumpheader(kth);
/* Print syscall name and arguments. */
syscall_ent = getpendq(kth, KTR_SYSCALL, NULL);
if (syscall_ent == NULL) {
/*
* Possibly a child of fork/vfork, or tracing of
* process started during system call.
*/
syscallnameprint(ktr->ktr_code);
} else {
syscallprint(&syscall_ent->kte_kth);
free(syscall_ent);
}
/* Print return value and an error if any. */
sysretprint(kth);
genio = getpendq(kth, KTR_GENIO, NULL);
if (genio != NULL) {
genioprint(&genio->kte_kth);
free(genio);
}
emul = getpendq(kth, KTR_EMUL, NULL);
if (emul != NULL) {
newline();
ktremul(emul);
}
flushpendq(kte);
free(kte);
}
void
nameiprint(struct ktr_header *kth)
{
xwprintf("\"%.*s\"", kth->ktr_len, (char *)(kth + 1));
}
#ifdef notused
void
ktrnamei(struct ktr_entry *kte)
{
struct ktr_header *kth = &kte->kte_kth;
dumpheader(kth);
xwprintf("namei(");
nameiprint(kth);
xwprintf(")");
free(kte);
}
#endif
void
ktremul(struct ktr_entry *kte)
{
struct ktr_header *kth = &kte->kte_kth;
char *emul = (char *)(kth + 1);
dumpheader(kth);
xwprintf("emul(%s)", emul);
setemul(emul, kth->ktr_pid, 1);
free(kte);
}
void
genioprint(struct ktr_header *kth)
{
struct ktr_genio *ktr = (struct ktr_genio *)(kth + 1);
static int screenwidth = 0;
int datalen = kth->ktr_len - sizeof(struct ktr_genio);
/*
* Need to be unsigned type so that positive value is passed
* to vis(), which will call isgraph().
*/
unsigned char *dp = (unsigned char *)(ktr + 1);
int w;
char visbuf[5];
if (screenwidth == 0) {
struct winsize ws;
if (fancy && ioctl(fileno(stderr), TIOCGWINSZ, &ws) != -1 &&
ws.ws_col > 8)
screenwidth = ws.ws_col;
else
screenwidth = 80;
}
if (maxdata && datalen > maxdata)
datalen = maxdata;
newline();
xwprintf(" \"");
for (; datalen > 0; datalen--, dp++) {
(void) vis(visbuf, *dp, VIS_NL|VIS_TAB|VIS_CSTYLE,
/* We put NUL at the end of buffer when reading */
*(dp + 1));
visbuf[4] = '\0';
w = strlen(visbuf);
if (width + w + 2 >= screenwidth)
break;
xwprintf("%s", visbuf);
if (width + 2 >= screenwidth)
break;
}
xwprintf("\"");
}
#ifdef notused
void
ktrgenio(struct ktr_entry *kte)
{
struct ktr_header *kth = &kte->kte_kth;
struct ktr_genio *ktr = (struct ktr_genio *)(kth + 1);
dumpheader(kth);
xwprintf("genio fd %d %s",
ktr->ktr_fd, ktr->ktr_rw ? "write" : "read");
genioprint(kth);
free(kte);
}
#endif
void
ktrpsig(struct ktr_entry *kte)
{
struct ktr_header *kth = &kte->kte_kth;
struct ktr_psig *psig = (struct ktr_psig *)(kth + 1);
dumpheader(kth);
xwprintf("SIG%s ", sys_signame[psig->signo]);
if (psig->action == SIG_DFL)
xwprintf("SIG_DFL");
else {
xwprintf("caught handler=0x%lx mask=0x%lx code=0x%x",
(u_long)psig->action, (unsigned long)psig->mask.__bits[0],
psig->code);
}
free(kte);
}
void
ktrcsw(struct ktr_entry *kte)
{
struct ktr_header *kth = &kte->kte_kth;
struct ktr_csw *cs = (struct ktr_csw *)(kth + 1);
dumpheader(kth);
xwprintf("%s %s", cs->out ? "stop" : "resume",
cs->user ? "user" : "kernel");
free(kte);
}