Add tool for parsing strace output

This commit is contained in:
Justine Tunney 2021-02-20 10:45:55 -08:00
parent b740cca642
commit 3e1fd1d962
5 changed files with 1003 additions and 6 deletions

View file

@ -508,7 +508,7 @@ apesh: .ascii "'\n#'\"\n" # sixth edition shebang
.shstub ape_elf_phnum,2 # 38: e_phnum .shstub ape_elf_phnum,2 # 38: e_phnum
.ascii "\\0\\0" # 3a: e_shentsize .ascii "\\0\\0" # 3a: e_shentsize
.shstub ape_elf_shnum,2 # 3c: e_shnum .shstub ape_elf_shnum,2 # 3c: e_shnum
.shstub ape_elf_shstrndx,2 # 3e: e_shstrndx .shstub ape_elf_shstrndx,2 # 3e: e_shstrndx
.ascii "' >&7\n" .ascii "' >&7\n"
.ascii "exec 7<&-\n" .ascii "exec 7<&-\n"
.ascii "fi\n" .ascii "fi\n"

View file

@ -289,8 +289,8 @@ COMPILE.F.flags = $(cc.flags) $(cpp.flags) $(copt.flags) $(f.flags)
COMPILE.i.flags = $(cc.flags) $(copt.flags) $(c.flags) COMPILE.i.flags = $(cc.flags) $(copt.flags) $(c.flags)
COMPILE.ii.flags = $(cc.flags) $(copt.flags) $(cxx.flags) COMPILE.ii.flags = $(cc.flags) $(copt.flags) $(cxx.flags)
LINK.flags = $(DEFAULT_LDFLAGS) $(CONFIG_LDFLAGS) $(LDFLAGS) LINK.flags = $(DEFAULT_LDFLAGS) $(CONFIG_LDFLAGS) $(LDFLAGS)
OBJECTIFY.c.flags = $(OBJECTIFY.S.flags) $(c.flags) OBJECTIFY.c.flags = $(OBJECTIFY.S.flags) $(copt.flags) $(c.flags)
OBJECTIFY.cxx.flags = $(OBJECTIFY.S.flags) $(cxx.flags) OBJECTIFY.cxx.flags = $(OBJECTIFY.S.flags) $(copt.flags) $(cxx.flags)
OBJECTIFY.s.flags = $(ASONLYFLAGS) $(s.flags) OBJECTIFY.s.flags = $(ASONLYFLAGS) $(s.flags)
OBJECTIFY.S.flags = $(copt.flags) $(cc.flags) $(o.flags) $(cpp.flags) $(S.flags) OBJECTIFY.S.flags = $(copt.flags) $(cc.flags) $(o.flags) $(cpp.flags) $(S.flags)
OBJECTIFY.f.flags = $(copt.flags) $(cc.flags) $(o.flags) $(copt.flags) $(S.flags) $(f.flags) OBJECTIFY.f.flags = $(copt.flags) $(cc.flags) $(o.flags) $(copt.flags) $(S.flags) $(f.flags)

View file

@ -27,8 +27,8 @@
* @note no xnu/rhel5 support if dirfdAT_FDCWDflags0 * @note no xnu/rhel5 support if dirfdAT_FDCWDflags0
* @asyncsignalsafe * @asyncsignalsafe
*/ */
int utimensat(int dirfd, const char *path, int utimensat(int dirfd, const char *path, const struct timespec ts[2],
const struct timespec ts[hasatleast 2], int flags) { int flags) {
if (!IsWindows()) { if (!IsWindows()) {
return sys_utimensat(dirfd, path, ts, flags); return sys_utimensat(dirfd, path, ts, flags);
} else { } else {

View file

@ -28,7 +28,14 @@
* *
* This is a higher level version of the commandv() function. Programs * This is a higher level version of the commandv() function. Programs
* that spawn subprocesses can use this function to determine the path * that spawn subprocesses can use this function to determine the path
* at startup. * at startup. Here's an example how you could use it:
*
* if ((strace = commandvenv("STRACE", "strace"))) {
* strace = strdup(strace);
* } else {
* fprintf(stderr, "error: please install strace\n");
* exit(1);
* }
* *
* @param var is environment variable which may be used to override * @param var is environment variable which may be used to override
* PATH search, and it can force a NULL result if it's empty * PATH search, and it can force a NULL result if it's empty

990
tool/build/pstrace.c Normal file
View file

@ -0,0 +1,990 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2021 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/bits/bits.h"
#include "libc/calls/calls.h"
#include "libc/calls/sigbits.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/nr.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/x/x.h"
#include "third_party/dlmalloc/dlmalloc.internal.h"
#include "third_party/getopt/getopt.h"
/**
* @fileoverview Pythonic System Call Trace
*
* This program invokes `strace` as a subprocess and turns its output
* into Python data structures. It is useful because strace output is
* this weird plaintext format that's so famously difficult to parse.
*
* For example, you can run this command:
*
* pstrace -o trace.pylog echo hello world
*
* After which you may parse the output with Python:
*
* for line in open('trace.pylog'):
* pid,time,elap,kind,x = eval(line)
* if kind == 1:
* name,ret,args = x
* print "%s%r -> %d" % (name, args, ret)
*
* This program traces the subset of system calls governing processes
* and files. To do that we must track file descriptor lifetimes too.
* We also track system calls that are problematic for build configs,
* such as sockets, since compiling code shouldn't need the Internet.
*
* @note this tool is linux only
* @note freebsd: truss PROC ARGS
* @note appleos: sudo dtruss PROC ARGS
* @note openbsd: ktrace PROC ARGS && kdump -f ktrace.out
* @note windows: https://github.com/rogerorr/NtTrace
*/
#define DEBUG "%ld: %s", lineno, line
#define READ128BE(S) ((uint128_t)READ64BE(S) << 64 | READ64BE((S) + 8))
#define APPEND(L) \
do { \
if (++L.n > L.c) { \
L.c = MAX(11, L.c); \
L.c += L.c >> 1; \
L.p = realloc(L.p, L.c * sizeof(*L.p)); \
} \
memset(L.p + L.n - 1, 0, sizeof(*L.p)); \
} while (0)
struct Trace {
struct Slices {
long n, c;
struct Slice {
int n, c;
char *p;
} * p;
} slices;
struct HashTable {
long i, n;
struct HashEntry {
long h;
long i;
} * p;
} sliceindex;
struct Strlists {
long n, c;
struct Strlist {
long n, c;
long *p; // slices.p[p[i]]
} * p;
} strlists;
struct Events {
long n, c;
struct Event {
enum EventKind {
EK_NONE,
EK_CALL,
EK_EXIT, // ret is kernel code
EK_SIGNAL, // ret is signal code
EK_KILLED, // ret is signal code
} kind;
unsigned char arity;
unsigned char syscall;
bool is_interrupted;
int us;
int elap;
int pid;
long sec;
long ret;
long lineno;
struct Arg {
enum ArgKind {
AK_LONG, // x
AK_STR, // slices.p[x]
AK_STRLIST, // strlists.p[x]
AK_INTPAIR, // (x&0xffffffff, x>>32)
} kind;
long name;
long x;
} arg[6];
} * p;
} events;
};
static const struct Syscall {
char name[16];
} kSyscalls[] = {
{"accept"}, //
{"accept4"}, //
{"access"}, //
{"bind"}, //
{"chdir"}, //
{"chmod"}, //
{"chown"}, //
{"chroot"}, //
{"clone"}, //
{"close"}, //
{"connect"}, //
{"creat"}, //
{"dup"}, //
{"dup2"}, //
{"dup3"}, //
{"epoll_create"}, //
{"epoll_create1"}, //
{"eventfd"}, //
{"eventfd2"}, //
{"execve"}, //
{"execveat"}, //
{"faccessat"}, //
{"fchmodat"}, //
{"fchownat"}, //
{"fdatasync"}, //
{"fcntl"}, //
{"flock"}, //
{"fork"}, //
{"fsync"}, //
{"lchown"}, //
{"link"}, //
{"linkat"}, //
{"listen"}, //
{"memfd_create"}, //
{"mkdir"}, //
{"mkdirat"}, //
{"mknod"}, //
{"mknodat"}, //
{"open"}, //
{"openat"}, //
{"pipe"}, //
{"pipe2"}, //
{"readlink"}, //
{"readlinkat"}, //
{"rename"}, //
{"renameat"}, //
{"renameat2"}, //
{"rmdir"}, //
{"signalfd"}, //
{"signalfd4"}, //
{"socket"}, //
{"socketpair"}, //
{"statfs"}, //
{"symlink"}, //
{"symlinkat"}, //
{"sync"}, //
{"syncfs"}, //
{"timerfd_create"}, //
{"truncate"}, //
{"unlink"}, //
{"unlinkat"}, //
{"utimensat"}, //
{"vfork"}, //
};
static const struct Signal {
char name[8];
unsigned char number;
} kSignals[] = {
{"SIGABRT", 6}, //
{"SIGALRM", 14}, //
{"SIGBUS", 7}, //
{"SIGCHLD", 17}, //
{"SIGCONT", 18}, //
{"SIGFPE", 8}, //
{"SIGHUP", 1}, //
{"SIGILL", 4}, //
{"SIGINT", 2}, //
{"SIGIO", 29}, //
{"SIGIOT", 6}, //
{"SIGKILL", 9}, //
{"SIGPIPE", 13}, //
{"SIGPOLL", 29}, //
{"SIGPROF", 27}, //
{"SIGPWR", 30}, //
{"SIGQUIT", 3}, //
{"SIGSEGV", 11}, //
{"SIGSTOP", 19}, //
{"SIGSYS", 31}, //
{"SIGTERM", 15}, //
{"SIGTRAP", 5}, //
{"SIGTSTP", 20}, //
{"SIGTTIN", 21}, //
{"SIGTTOU", 22}, //
{"SIGURG", 23}, //
{"SIGUSR1", 10}, //
{"SIGUSR2", 12}, //
{"SIGWINCH", 28}, //
{"SIGXCPU", 24}, //
{"SIGXFSZ", 25}, //
};
static const struct Errno {
char name[16];
unsigned char number;
} kErrnos[] = {
{"E2BIG", 7}, //
{"EACCES", 13}, //
{"EADDRINUSE", 98}, //
{"EADDRNOTAVAIL", 99}, //
{"EADV", 68}, //
{"EAFNOSUPPORT", 97}, //
{"EAGAIN", 11}, //
{"EALREADY", 114}, //
{"EBADE", 52}, //
{"EBADF", 9}, //
{"EBADFD", 77}, //
{"EBADMSG", 74}, //
{"EBADR", 53}, //
{"EBADRQC", 56}, //
{"EBADSLT", 57}, //
{"EBFONT", 59}, //
{"EBUSY", 16}, //
{"ECANCELED", 125}, //
{"ECHILD", 10}, //
{"ECHRNG", 44}, //
{"ECOMM", 70}, //
{"ECONNABORTED", 103}, //
{"ECONNREFUSED", 111}, //
{"ECONNRESET", 104}, //
{"EDEADLK", 35}, //
{"EDESTADDRREQ", 89}, //
{"EDOM", 33}, //
{"EDOTDOT", 73}, //
{"EDQUOT", 122}, //
{"EEXIST", 17}, //
{"EFAULT", 14}, //
{"EFBIG", 27}, //
{"EHOSTDOWN", 112}, //
{"EHOSTUNREACH", 113}, //
{"EHWPOISON", 133}, //
{"EIDRM", 43}, //
{"EILSEQ", 84}, //
{"EINPROGRESS", 115}, //
{"EINTR", 4}, //
{"EINVAL", 22}, //
{"EIO", 5}, //
{"EISCONN", 106}, //
{"EISDIR", 21}, //
{"EISNAM", 120}, //
{"EKEYEXPIRED", 127}, //
{"EKEYREJECTED", 129}, //
{"EKEYREVOKED", 128}, //
{"EL2HLT", 51}, //
{"EL2NSYNC", 45}, //
{"EL3HLT", 46}, //
{"EL3RST", 47}, //
{"ELIBACC", 79}, //
{"ELIBBAD", 80}, //
{"ELIBEXEC", 83}, //
{"ELIBMAX", 82}, //
{"ELIBSCN", 81}, //
{"ELNRNG", 48}, //
{"ELOOP", 40}, //
{"EMEDIUMTYPE", 124}, //
{"EMFILE", 24}, //
{"EMLINK", 31}, //
{"EMSGSIZE", 90}, //
{"EMULTIHOP", 72}, //
{"ENAMETOOLONG", 36}, //
{"ENAVAIL", 119}, //
{"ENETDOWN", 100}, //
{"ENETRESET", 102}, //
{"ENETUNREACH", 101}, //
{"ENFILE", 23}, //
{"ENOANO", 55}, //
{"ENOBUFS", 105}, //
{"ENOCSI", 50}, //
{"ENODATA", 61}, //
{"ENODEV", 19}, //
{"ENOENT", 2}, //
{"ENOEXEC", 8}, //
{"ENOKEY", 126}, //
{"ENOLCK", 37}, //
{"ENOLINK", 67}, //
{"ENOMEDIUM", 123}, //
{"ENOMEM", 12}, //
{"ENOMSG", 42}, //
{"ENONET", 64}, //
{"ENOPKG", 65}, //
{"ENOPROTOOPT", 92}, //
{"ENOSPC", 28}, //
{"ENOSR", 63}, //
{"ENOSTR", 60}, //
{"ENOSYS", 38}, //
{"ENOTBLK", 15}, //
{"ENOTCONN", 107}, //
{"ENOTDIR", 20}, //
{"ENOTEMPTY", 39}, //
{"ENOTNAM", 118}, //
{"ENOTRECOVERABLE", 131}, //
{"ENOTSOCK", 88}, //
{"ENOTSUP", 95}, //
{"ENOTTY", 25}, //
{"ENOTUNIQ", 76}, //
{"ENXIO", 6}, //
{"EOPNOTSUPP", 95}, //
{"EOVERFLOW", 75}, //
{"EOWNERDEAD", 130}, //
{"EPERM", 1}, //
{"EPFNOSUPPORT", 96}, //
{"EPIPE", 32}, //
{"EPROTO", 71}, //
{"EPROTONOSUPPORT", 93}, //
{"EPROTOTYPE", 91}, //
{"ERANGE", 34}, //
{"EREMCHG", 78}, //
{"EREMOTE", 66}, //
{"EREMOTEIO", 121}, //
{"ERESTART", 85}, //
{"ERFKILL", 132}, //
{"EROFS", 30}, //
{"ESHUTDOWN", 108}, //
{"ESOCKTNOSUPPORT", 94}, //
{"ESPIPE", 29}, //
{"ESRCH", 3}, //
{"ESRMNT", 69}, //
{"ESTALE", 116}, //
{"ESTRPIPE", 86}, //
{"ETIME", 62}, //
{"ETIMEDOUT", 110}, //
{"ETOOMANYREFS", 109}, //
{"ETXTBSY", 26}, //
{"EUCLEAN", 117}, //
{"EUNATCH", 49}, //
{"EUSERS", 87}, //
{"EWOULDBLOCK", 11}, //
{"EXDEV", 18}, //
{"EXFULL", 54}, //
};
static char **strace_args;
static size_t strace_args_len;
static volatile bool interrupted;
static long Hash(const void *p, size_t n) {
unsigned h, i;
for (h = i = 0; i < n; i++) {
h += ((unsigned char *)p)[i];
h *= 0x9e3779b1;
}
return MAX(1, h);
}
static uint64_t MakeKey64(const char *p, size_t n) {
char k[8] = {0};
memcpy(k, p, n);
return READ64BE(k);
}
static uint128_t MakeKey128(const char *p, size_t n) {
char k[16] = {0};
memcpy(k, p, n);
return READ128BE(k);
}
static int GetSyscall(const char *name, size_t namelen) {
int m, l, r;
uint128_t x, y;
char *endofname;
if (namelen && namelen <= 16) {
x = MakeKey128(name, namelen);
l = 0;
r = ARRAYLEN(kSyscalls) - 1;
while (l <= r) {
m = (l + r) >> 1;
y = READ128BE(kSyscalls[m].name);
if (x < y) {
r = m - 1;
} else if (x > y) {
l = m + 1;
} else {
return m;
}
}
}
return -1;
}
static int GetErrno(const char *name, size_t namelen) {
int m, l, r;
uint128_t x, y;
char *endofname;
if (namelen && namelen <= 16) {
x = MakeKey128(name, namelen);
l = 0;
r = ARRAYLEN(kErrnos) - 1;
while (l <= r) {
m = (l + r) >> 1;
y = READ128BE(kErrnos[m].name);
if (x < y) {
r = m - 1;
} else if (x > y) {
l = m + 1;
} else {
return kErrnos[m].number;
}
}
}
return -1;
}
static int GetSignal(const char *name, size_t namelen) {
int m, l, r;
uint64_t x, y;
char *endofname;
if (namelen && namelen <= 8) {
x = MakeKey64(name, namelen);
l = 0;
r = ARRAYLEN(kSignals) - 1;
while (l <= r) {
m = (l + r) >> 1;
y = READ64BE(kSignals[m].name);
if (x < y) {
r = m - 1;
} else if (x > y) {
l = m + 1;
} else {
return kSignals[m].number;
}
}
}
return -1;
}
static struct Trace *NewTrace(void) {
return calloc(1, sizeof(struct Trace));
}
static void FreeTrace(struct Trace *t) {
long i;
if (t) {
for (i = 0; i < t->slices.n; ++i) {
free(t->slices.p[i].p);
}
free(t->slices.p);
free(t->sliceindex.p);
for (i = 0; i < t->strlists.n; ++i) {
free(t->strlists.p[i].p);
}
free(t->strlists.p);
free(t->events.p);
free(t);
}
}
static void AppendStrlists(struct Trace *t) {
APPEND(t->strlists);
}
static void AppendStrlist(struct Strlist *l) {
APPEND((*l));
}
static void AppendEvent(struct Trace *t) {
APPEND(t->events);
}
static void AppendSlices(struct Trace *t) {
APPEND(t->slices);
}
static void AppendSlice(struct Slice *s, int c) {
APPEND((*s));
s->p[s->n - 1] = c;
}
static long Intern(struct Trace *t, char *data, long size) {
struct HashEntry *p;
long i, j, k, n, m, h, n2;
h = Hash(data, size);
n = t->sliceindex.n;
i = 0;
if (n) {
k = 0;
do {
i = (h + k + ((k + 1) >> 1)) & (n - 1);
if (t->sliceindex.p[i].h == h &&
t->slices.p[t->sliceindex.p[i].i].n == size &&
!memcmp(t->slices.p[t->sliceindex.p[i].i].p, data, size)) {
free(data);
return t->sliceindex.p[i].i;
}
++k;
} while (t->sliceindex.p[i].h);
}
if (++t->sliceindex.i >= (n >> 1)) {
m = n ? n << 1 : 16;
p = calloc(m, sizeof(struct HashEntry));
for (j = 0; j < n; ++j) {
if (t->sliceindex.p[j].h) {
k = 0;
do {
i = (t->sliceindex.p[j].h + k + ((k + 1) >> 1)) & (m - 1);
++k;
} while (p[i].h);
p[i].h = t->sliceindex.p[j].h;
p[i].i = t->sliceindex.p[j].i;
}
}
k = 0;
do {
i = (h + k + ((k + 1) >> 1)) & (m - 1);
++k;
} while (p[i].h);
free(t->sliceindex.p);
t->sliceindex.p = p;
t->sliceindex.n = m;
}
AppendSlices(t);
t->slices.p[t->slices.n - 1].p = data;
t->slices.p[t->slices.n - 1].n = size;
t->sliceindex.p[i].i = t->slices.n - 1;
t->sliceindex.p[i].h = h;
return t->slices.n - 1;
}
static long ReadCharLiteral(struct Slice *buf, long c, char *p, long *i) {
if (c != '\\') return c;
switch ((c = p[(*i)++])) {
case 'a':
return '\a';
case 'b':
return '\b';
case 't':
return '\t';
case 'n':
return '\n';
case 'v':
return '\v';
case 'f':
return '\f';
case 'r':
return '\r';
case 'e':
return 033;
case 'x':
if (isxdigit(p[*i])) {
c = hextoint(p[(*i)++]);
if (isxdigit(p[*i])) {
c = c * 16 + hextoint(p[(*i)++]);
}
}
return c;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
c -= '0';
if ('0' <= p[*i] && p[*i] <= '7') {
c = c * 8 + (p[(*i)++] - '0');
if ('0' <= p[*i] && p[*i] <= '7') {
c = c * 8 + (p[(*i)++] - '0');
}
}
return c;
default:
return c;
}
}
static long GetDuration(long sec1, long us1, long sec2, long us2) {
long elap;
if ((elap = (sec2 - sec1) * 1000000)) {
return elap + 1000000 - us1 + us2;
} else {
return elap + us2 - us1;
}
}
static void Parse(struct Trace *t, const char *line, long lineno) {
char *p, *q;
struct Slice b;
long c, i, j, k, arg, pid, event, sec, us;
p = line;
pid = strtol(p, &p, 10);
while (*p == ' ') ++p;
sec = strtol(p, &p, 10);
CHECK_EQ('.', *p++, DEBUG);
us = strtol(p, &p, 10);
CHECK_EQ(' ', *p++, DEBUG);
if (startswith(p, "<... ")) {
CHECK_NOTNULL((p = strchr(p, '>')));
++p;
for (event = t->events.n; event--;) {
if (t->events.p[event].pid == pid) {
CHECK(t->events.p[event].is_interrupted, DEBUG);
t->events.p[event].is_interrupted = false;
t->events.p[event].elap =
GetDuration(t->events.p[event].sec, t->events.p[event].us, sec, us);
break;
}
}
CHECK_GE(event, 0);
} else {
AppendEvent(t);
event = t->events.n - 1;
t->events.p[event].pid = pid;
t->events.p[event].sec = sec;
t->events.p[event].us = us;
t->events.p[event].lineno = lineno;
if (startswith(p, "+++ exited with ")) {
p += strlen("+++ exited with ");
t->events.p[event].kind = EK_EXIT;
t->events.p[event].ret = atoi(p);
return;
} else if (startswith(p, "+++ killed by ")) {
p += strlen("+++ killed by ");
CHECK((q = strchr(p, ' ')), DEBUG);
t->events.p[event].kind = EK_KILLED;
t->events.p[event].ret = GetSignal(p, q - p);
return;
} else if (startswith(p, "--- ")) {
p += 4;
CHECK(isalpha(*p), DEBUG);
CHECK((q = strchr(p, ' ')), DEBUG);
t->events.p[event].kind = EK_SIGNAL;
t->events.p[event].ret = GetSignal(p, q - p);
return;
} else if (isalpha(*p) && (q = strchr(p, '('))) {
t->events.p[event].kind = EK_CALL;
CHECK_NE(-1, (t->events.p[event].syscall = GetSyscall(p, q - p)), DEBUG);
p = q + 1;
}
}
for (;;) {
if (*p == ',') ++p;
while (*p == ' ') ++p;
CHECK(*p, DEBUG);
if (startswith(p, "<unfinished ...>")) {
t->events.p[event].is_interrupted = true;
break;
} else if (*p == ')') {
++p;
while (isspace(*p)) ++p;
CHECK_EQ('=', *p++, DEBUG);
while (isspace(*p)) ++p;
CHECK(isdigit(*p) || *p == '-', DEBUG);
t->events.p[event].ret = strtol(p, &p, 0);
if (t->events.p[event].ret == -1) {
while (isspace(*p)) ++p;
CHECK((q = strchr(p, ' ')), DEBUG);
if ((t->events.p[event].ret = GetErrno(p, q - p)) != -1) {
t->events.p[event].ret = -t->events.p[event].ret;
}
}
break;
}
CHECK_LT((arg = t->events.p[event].arity++), 6);
if (isalpha(*p) && !startswith(p, "NULL")) {
memset(&b, 0, sizeof(b));
for (; isalpha(*p) || *p == '_'; ++p) {
AppendSlice(&b, *p);
}
t->events.p[event].arg[arg].name = Intern(t, b.p, b.n);
CHECK_EQ('=', *p++, DEBUG);
} else {
t->events.p[event].arg[arg].name = -1;
}
if (startswith(p, "NULL")) {
p += 4;
t->events.p[event].arg[arg].kind = AK_LONG;
t->events.p[event].arg[arg].x = 0;
} else if (*p == '-' || isdigit(*p)) {
t->events.p[event].arg[arg].kind = AK_LONG;
for (;;) {
t->events.p[event].arg[arg].x |= strtol(p, &p, 0);
if (*p == '|') {
++p;
} else {
break;
}
}
} else if (*p == '{') {
CHECK_NOTNULL((p = strchr(p, '}')), DEBUG);
++p;
} else if (*p == '"') {
memset(&b, 0, sizeof(b));
for (j = 0; (c = p[++j]);) {
if (c == '"') {
p += j + 1;
break;
}
c = ReadCharLiteral(&b, c, p, &j);
AppendSlice(&b, c);
}
t->events.p[event].arg[arg].kind = AK_STR;
t->events.p[event].arg[arg].x = Intern(t, b.p, b.n);
} else if (*p == '[') {
++p;
if (isdigit(*p)) {
t->events.p[event].arg[arg].kind = AK_INTPAIR;
t->events.p[event].arg[arg].x = strtol(p, &p, 0) & 0xffffffff;
CHECK_EQ(',', *p++, DEBUG);
CHECK_EQ(' ', *p++, DEBUG);
t->events.p[event].arg[arg].x |= strtol(p, &p, 0) << 32;
CHECK_EQ(']', *p++, DEBUG);
} else {
AppendStrlists(t);
for (j = 0;; ++j) {
if (*p == ']') {
++p;
break;
}
if (*p == ',') ++p;
if (*p == ' ') ++p;
CHECK_EQ('"', *p, DEBUG);
memset(&b, 0, sizeof(b));
for (k = 0; (c = p[++k]);) {
if (c == '"') {
p += k + 1;
break;
}
c = ReadCharLiteral(&b, c, p, &k);
AppendSlice(&b, c);
}
AppendStrlist(&t->strlists.p[t->strlists.n - 1]);
t->strlists.p[t->strlists.n - 1]
.p[t->strlists.p[t->strlists.n - 1].n - 1] = Intern(t, b.p, b.n);
}
t->events.p[event].arg[arg].kind = AK_STRLIST;
t->events.p[event].arg[arg].x = t->strlists.n - 1;
}
} else {
CHECK(false, DEBUG);
}
}
}
static void PrintArg(FILE *f, struct Trace *t, long ev, long arg) {
long i, x;
x = t->events.p[ev].arg[arg].name;
if (x != -1) {
fprintf(f, "b%`'.*s:", t->slices.p[x].n, t->slices.p[x].p);
}
x = t->events.p[ev].arg[arg].x;
switch (t->events.p[ev].arg[arg].kind) {
case AK_LONG:
fprintf(f, "%ld", x);
break;
case AK_STR:
fprintf(f, "b%`'.*s", t->slices.p[x].n, t->slices.p[x].p);
break;
case AK_INTPAIR:
fprintf(f, "(%d,%d)", x >> 32, x);
break;
case AK_STRLIST:
fprintf(f, "(");
for (i = 0; i < t->strlists.p[x].n; ++i) {
fprintf(f, "b%`'.*s,", t->slices.p[t->strlists.p[x].p[i]].n,
t->slices.p[t->strlists.p[x].p[i]].p);
}
fprintf(f, ")");
break;
default:
abort();
}
}
static void PrintEvent(FILE *f, struct Trace *t, long ev) {
long arg;
fprintf(f, "(%d,%ld,%d,%d,", t->events.p[ev].pid,
t->events.p[ev].sec * 1000000 + t->events.p[ev].us,
t->events.p[ev].elap, t->events.p[ev].kind);
switch (t->events.p[ev].kind) {
case EK_EXIT:
case EK_SIGNAL:
case EK_KILLED:
fprintf(f, "%d", t->events.p[ev].ret);
break;
case EK_CALL:
CHECK_LT(t->events.p[ev].syscall, ARRAYLEN(kSyscalls));
fprintf(f, "(b%`'s,%ld,", kSyscalls[t->events.p[ev].syscall].name,
t->events.p[ev].ret);
fprintf(f, "%c",
t->events.p[ev].arity && t->events.p[ev].arg[0].name != -1 ? '{'
: '(');
for (arg = 0; arg < t->events.p[ev].arity; ++arg) {
PrintArg(f, t, ev, arg);
fprintf(f, ",");
}
fprintf(f, "%c)",
t->events.p[ev].arity && t->events.p[ev].arg[0].name != -1 ? '}'
: ')');
break;
default:
break;
}
fprintf(f, ")");
}
static void AppendArg(char *arg) {
strace_args = realloc(strace_args, ++strace_args_len * sizeof(*strace_args));
strace_args[strace_args_len - 1] = arg;
}
static wontreturn void PrintUsage(FILE *f, int rc) {
fprintf(f, "Usage: %s [-o OUT] PROG [ARGS...]\n", program_invocation_name);
exit(rc);
}
int main(int argc, char *argv[]) {
int i;
int ws;
int opt;
int pid;
long ev;
FILE *fin;
FILE *fout;
char *line;
long lineno;
char *strace;
int pipefds[2];
struct Trace *t;
sigset_t block, mask;
struct sigaction ignore, saveint, savequit;
/*
* parse prefix arguments
*/
fout = stderr;
while ((opt = getopt(argc, argv, "?ho:")) != -1) {
switch (opt) {
case 'o':
fout = fopen(optarg, "w");
break;
case 'h':
case '?':
PrintUsage(stdout, EXIT_SUCCESS);
default:
PrintUsage(stderr, EX_USAGE);
}
}
if (optind == argc) {
PrintUsage(stderr, EX_USAGE);
}
/*
* resolve full paths of dependencies
*/
if ((strace = commandvenv("STRACE", "strace"))) {
strace = strdup(strace);
} else {
fprintf(stderr, "error: please install strace\n");
exit(1);
}
/*
* create strace argument list
*/
AppendArg("strace");
AppendArg("-q"); // don't log attach/detach noise
AppendArg("-v"); // don't abbreviate arrays
AppendArg("-f"); // follow subprocesses
AppendArg("-ttt"); // print unixseconds.micros
AppendArg("-X"); // print numbers instead of symbols
AppendArg("raw"); // e.g. 2 vs. O_RDWR
AppendArg("-s"); // don't abbreviate data
AppendArg("805306368"); // strace won't let us go higher
AppendArg("-e"); // system calls that matter
AppendArg(
"open,close,access,pipe,dup,dup2,socket,connect,accept,bind,listen,"
"socketpair,fork,vfork,execve,clone,flock,fsync,fdatasync,truncate,chdir,"
"rename,mkdir,rmdir,creat,link,unlink,symlink,readlink,chmod,chown,fcntl,"
"lchown,mknod,mknodat,statfs,chroot,sync,epoll_create,openat,mkdirat,"
"fchownat,unlinkat,renameat,linkat,symlinkat,readlinkat,fchmodat,fchdir,"
"faccessat,utimensat,accept4,dup3,pipe2,epoll_create1,signalfd,signalfd4,"
"eventfd,eventfd2,timerfd_create,syncfs,renameat2,memfd_create,execveat");
CHECK_NE(-1, pipe(pipefds));
AppendArg("-o");
AppendArg(xasprintf("/dev/fd/%d", pipefds[1]));
for (i = optind; i < argc; ++i) {
AppendArg(argv[i]);
}
AppendArg(NULL);
/*
* spawn strace
*/
ignore.sa_flags = 0;
ignore.sa_handler = SIG_IGN;
sigemptyset(&ignore.sa_mask);
sigaction(SIGINT, &ignore, &saveint);
sigaction(SIGQUIT, &ignore, &savequit);
sigfillset(&block);
sigprocmask(SIG_BLOCK, &block, &mask);
CHECK_NE(-1, (pid = vfork()));
if (!pid) {
close(pipefds[0]);
sigaction(SIGINT, &saveint, NULL);
sigaction(SIGQUIT, &savequit, NULL);
sigprocmask(SIG_SETMASK, &mask, NULL);
execv(strace, strace_args);
_exit(127);
}
close(pipefds[1]);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_SETMASK, &mask, NULL);
/*
* read output of strace until eof
*/
fin = fdopen(pipefds[0], "r");
t = NewTrace();
for (ev = 0, lineno = 1; !interrupted && (line = xgetline(fin)); ++lineno) {
chomp(line);
Parse(t, line, lineno);
free(line);
for (; ev < t->events.n && !t->events.p[ev].is_interrupted; ++ev) {
PrintEvent(fout, t, ev);
fprintf(fout, "\n");
}
}
FreeTrace(t);
CHECK_NE(-1, fclose(fout));
/*
* wait for strace to exit
*/
while (waitpid(pid, &ws, 0) == -1) {
CHECK_EQ(EINTR, errno);
}
CHECK_NE(-1, fclose(fin));
/*
* propagate exit
*/
if (WIFEXITED(ws)) {
return WEXITSTATUS(ws);
} else {
return 128 + WTERMSIG(ws);
}
}