cosmopolitan/tool/build/pstrace.c
Justine Tunney 39bf41f4eb Make numerous improvements
- Python static hello world now 1.8mb
- Python static fully loaded now 10mb
- Python HTTPS client now uses MbedTLS
- Python REPL now completes import stmts
- Increase stack size for Python for now
- Begin synthesizing posixpath and ntpath
- Restore Python \N{UNICODE NAME} support
- Restore Python NFKD symbol normalization
- Add optimized code path for Intel SHA-NI
- Get more Python unit tests passing faster
- Get Python help() pagination working on NT
- Python hashlib now supports MbedTLS PBKDF2
- Make memcpy/memmove/memcmp/bcmp/etc. faster
- Add Mersenne Twister and Vigna to LIBC_RAND
- Provide privileged __printf() for error code
- Fix zipos opendir() so that it reports ENOTDIR
- Add basic chmod() implementation for Windows NT
- Add Cosmo's best functions to Python cosmo module
- Pin function trace indent depth to that of caller
- Show memory diagram on invalid access in MODE=dbg
- Differentiate stack overflow on crash in MODE=dbg
- Add stb_truetype and tools for analyzing font files
- Upgrade to UNICODE 13 and reduce its binary footprint
- COMPILE.COM now logs resource usage of build commands
- Start implementing basic poll() support on bare metal
- Set getauxval(AT_EXECFN) to GetModuleFileName() on NT
- Add descriptions to strerror() in non-TINY build modes
- Add COUNTBRANCH() macro to help with micro-optimizations
- Make error / backtrace / asan / memory code more unbreakable
- Add fast perfect C implementation of μ-Law and a-Law audio codecs
- Make strtol() functions consistent with other libc implementations
- Improve Linenoise implementation (see also github.com/jart/bestline)
- COMPILE.COM now suppresses stdout/stderr of successful build commands
2021-09-28 01:52:34 -07:00

990 lines
28 KiB
C

/*-*- 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.internal.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)); \
} \
bzero(L.p + L.n - 1, 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")) {
bzero(&b, 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 == '"') {
bzero(&b, 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);
bzero(&b, 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);
}
}