mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 19:43:32 +00:00
39bf41f4eb
- 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
990 lines
28 KiB
C
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);
|
|
}
|
|
}
|