diff --git a/libc/calls/calls.h b/libc/calls/calls.h index 100577a17..8303da205 100644 --- a/libc/calls/calls.h +++ b/libc/calls/calls.h @@ -202,7 +202,7 @@ int wait3(int *, int, struct rusage *); int wait4(int, int *, int, struct rusage *); int waitpid(int, int *, int); intptr_t syscall(int, ...); -long ptrace(int, int, void *, void *); +long ptrace(int, ...); long telldir(DIR *); long times(struct tms *); size_t GetFileSize(const char *); diff --git a/libc/calls/directmap-nt.c b/libc/calls/directmap-nt.c index 9e649f029..4572ad0bb 100644 --- a/libc/calls/directmap-nt.c +++ b/libc/calls/directmap-nt.c @@ -21,7 +21,6 @@ #include "libc/nt/enum/pageflags.h" #include "libc/nt/memory.h" #include "libc/nt/runtime.h" -#include "libc/nt/struct/overlapped.h" #include "libc/runtime/directmap.internal.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" diff --git a/libc/calls/ptrace.c b/libc/calls/ptrace.c index 8866ca608..189e4127c 100644 --- a/libc/calls/ptrace.c +++ b/libc/calls/ptrace.c @@ -17,16 +17,73 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" +#include "libc/calls/strace.internal.h" +#include "libc/dce.h" +#include "libc/sysv/consts/ptrace.h" #include "libc/sysv/errfuns.h" +static const char *__ptrace_describe_request(int x) { + if (x == -1) return "-1"; + if (x == PTRACE_TRACEME) return "PTRACE_TRACEME"; + if (x == PTRACE_PEEKDATA) return "PTRACE_PEEKDATA"; + if (x == PTRACE_GETFPREGS) return "PTRACE_GETFPREGS"; + if (x == PTRACE_PEEKTEXT) return "PTRACE_PEEKTEXT"; + if (x == PTRACE_POKEDATA) return "PTRACE_POKEDATA"; + if (x == PTRACE_PEEKUSER) return "PTRACE_PEEKUSER"; + if (x == PTRACE_POKETEXT) return "PTRACE_POKETEXT"; + if (x == PTRACE_POKEUSER) return "PTRACE_POKEUSER"; + if (x == PTRACE_GETREGS) return "PTRACE_GETREGS"; + if (x == PTRACE_GETREGSET) return "PTRACE_GETREGSET"; + if (x == PTRACE_SETFPREGS) return "PTRACE_SETFPREGS"; + if (x == PTRACE_SETREGS) return "PTRACE_SETREGS"; + if (x == PTRACE_SETREGSET) return "PTRACE_SETREGSET"; + if (x == PTRACE_GETSIGINFO) return "PTRACE_GETSIGINFO"; + if (x == PTRACE_SETSIGINFO) return "PTRACE_SETSIGINFO"; + if (x == PTRACE_PEEKSIGINFO) return "PTRACE_PEEKSIGINFO"; + if (x == PTRACE_GETSIGMASK) return "PTRACE_GETSIGMASK"; + if (x == PTRACE_SETSIGMASK) return "PTRACE_SETSIGMASK"; + if (x == PTRACE_SETOPTIONS) return "PTRACE_SETOPTIONS"; + if (x == PTRACE_GETEVENTMSG) return "PTRACE_GETEVENTMSG"; + if (x == PTRACE_CONT) return "PTRACE_CONT"; + if (x == PTRACE_SINGLESTEP) return "PTRACE_SINGLESTEP"; + if (x == PTRACE_SYSCALL) return "PTRACE_SYSCALL"; + if (x == PTRACE_LISTEN) return "PTRACE_LISTEN"; + if (x == PTRACE_KILL) return "PTRACE_KILL"; + if (x == PTRACE_INTERRUPT) return "PTRACE_INTERRUPT"; + if (x == PTRACE_ATTACH) return "PTRACE_ATTACH"; + if (x == PTRACE_SEIZE) return "PTRACE_SEIZE"; + if (x == PTRACE_SECCOMP_GET_FILTER) return "PTRACE_SECCOMP_GET_FILTER"; + if (x == PTRACE_DETACH) return "PTRACE_DETACH"; + return "PTRACE_WUT"; +} + /** * Traces process. * * @param request can be PTRACE_xxx * @note de facto linux only atm */ -long ptrace(int request, int pid, void *addr, void *data) { - /* TODO(jart): FreeBSD addr and data args are different */ - if (request == -1) return einval(); /* see consts.sh */ - return sys_ptrace(request, pid, addr, data); +long ptrace(int request, ...) { + // TODO(jart): FreeBSD addr and data args are different + int pid; + va_list va; + bool ispeek; + long rc, peek; + void *addr, *data; + va_start(va, request); + pid = va_arg(va, int); + addr = va_arg(va, void *); + data = va_arg(va, void *); + va_end(va); + if (request == -1) { + rc = einval(); /* see consts.sh */ + } else { + ispeek = IsLinux() && request - 1u < 3; + if (ispeek) data = &peek; + rc = sys_ptrace(request, pid, addr, data); + if (rc != -1 && ispeek) rc = peek; + } + STRACE("ptrace(%s, %d, %p, %p) → %ld% m", __ptrace_describe_request(request), + pid, addr, data); + return rc; } diff --git a/libc/fmt/fmt.c b/libc/fmt/fmt.c index ca34275ab..42cb0aca5 100644 --- a/libc/fmt/fmt.c +++ b/libc/fmt/fmt.c @@ -41,6 +41,7 @@ } while (0) extern bool __nomultics; +extern bool __replmode; static const char kSpecialFloats[2][2][4] = {{"INF", "inf"}, {"NAN", "nan"}}; @@ -377,8 +378,15 @@ hidden int __fmt(void *fn, void *arg, const char *format, va_list va) { goto FormatString; case 'r': - flags |= FLAGS_REPR; - /* fallthrough */ + // undocumented %r specifier + // used for good carriage return + // helps integrate loggers with repls + if (!__replmode) { + break; + } else { + p = "\r\e[K"; + goto FormatString; + } case 'q': flags |= FLAGS_QUOTE; @@ -393,6 +401,8 @@ hidden int __fmt(void *fn, void *arg, const char *format, va_list va) { break; case 'n': + // nonstandard %n specifier + // used to print newlines that work in raw terminal modes if (__nomultics) PUT('\r'); PUT('\n'); break; diff --git a/libc/intrin/kprintf.greg.c b/libc/intrin/kprintf.greg.c index 8d3fded6d..e8583dff9 100644 --- a/libc/intrin/kprintf.greg.c +++ b/libc/intrin/kprintf.greg.c @@ -468,6 +468,7 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt, va_list va, if (cols) --cols; /* end quote */ } goto EmitChar; + case 'm': if (!(x = errno) && sign == ' ' && (!IsWindows() || !__imp_GetLastError())) { @@ -479,7 +480,10 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt, va_list va, } else { goto FormatDecimal; } + case 'n': + // nonstandard %n specifier + // used to print newlines that work in raw terminal modes if (__nomultics) { if (p < e) *p = '\r'; ++p; @@ -487,13 +491,18 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt, va_list va, if (p < e) *p = '\n'; ++p; break; + case 'r': + // undocumented %r specifier + // used for good carriage return + // helps integrate loggers with repls if (!__replmode) { break; } else { s = "\r\033[K"; goto FormatString; } + case 'S': c = 's'; type = 1; @@ -695,10 +704,12 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt, va_list va, privileged size_t ksnprintf(char *b, size_t n, const char *fmt, ...) { size_t m; va_list v; - struct Timestamps t = {0}; + struct Timestamps t; + t = kenter(); va_start(v, fmt); m = kformat(b, n, fmt, v, t); va_end(v); + kleave(t); return m; } @@ -713,8 +724,12 @@ privileged size_t ksnprintf(char *b, size_t n, const char *fmt, ...) { * @vforksafe */ privileged size_t kvsnprintf(char *b, size_t n, const char *fmt, va_list v) { - struct Timestamps t = {0}; - return kformat(b, n, fmt, v, t); + size_t m; + struct Timestamps t; + t = kenter(); + m = kformat(b, n, fmt, v, t); + kleave(t); + return m; } /** diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index 4b494828c..36b388753 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -98,7 +98,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, prog = basename(program_invocation_name); bufmode = f->bufmode; if (bufmode == _IOLBF) f->bufmode = _IOFBF; - if ((fprintf)(f, "%c%s%06ld:%s:%d:%.*s:%d] ", "FEWIVDNT"[level & 7], buf32, + if ((fprintf)(f, "%r%c%s%06ld:%s:%d:%.*s:%d] ", "FEWIVDNT"[level & 7], buf32, rem1000000int64(div1000int64(dots)), file, line, strchrnul(prog, '.') - prog, prog, getpid()) <= 0) { vflogf_onfail(f); diff --git a/libc/stdio/append.internal.h b/libc/stdio/append.internal.h index bb71336c1..c6560a99d 100644 --- a/libc/stdio/append.internal.h +++ b/libc/stdio/append.internal.h @@ -19,6 +19,8 @@ ssize_t appendw(char **, uint64_t); ssize_t appends(char **, const char *); ssize_t appendf(char **, const char *, ...); ssize_t vappendf(char **, const char *, va_list); +ssize_t kappendf(char **, const char *, ...); +ssize_t kvappendf(char **, const char *, va_list); #if defined(__GNUC__) && !defined(__STRICT_ANSI__) #define appendf(BUF, FMT, ...) appendf(BUF, PFLINK(FMT), ##__VA_ARGS__) diff --git a/libc/stdio/fopen.c b/libc/stdio/fopen.c index 3e78067fc..84dcde81e 100644 --- a/libc/stdio/fopen.c +++ b/libc/stdio/fopen.c @@ -56,21 +56,18 @@ static int openpathname(const char *pathname, int flags, bool *out_noclose) { * @note microsoft unilaterally deprecated this function lool */ FILE *fopen(const char *pathname, const char *mode) { - FILE *f; + FILE *f = 0; bool noclose; int fd, flags; - STRACE("fopen(%s)", pathname); flags = fopenflags(mode); pathname = fixpathname(pathname, flags); if ((fd = openpathname(pathname, flags, &noclose)) != -1) { if ((f = fdopen(fd, mode)) != NULL) { f->noclose = noclose; - return f; - } else { - if (!noclose) close(fd); - return NULL; + } else if (!noclose) { + close(fd); } - } else { - return NULL; } + STRACE("fopen(%#s, %#s) → %p% m", pathname, mode, f); + return f; } diff --git a/libc/stdio/kappendf.c b/libc/stdio/kappendf.c new file mode 100644 index 000000000..8977758ca --- /dev/null +++ b/libc/stdio/kappendf.c @@ -0,0 +1,40 @@ +/*-*- 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/stdio/append.internal.h" + +/** + * Appends formatted string to buffer w/ kprintf, e.g. + * + * char *b = 0; + * kappendf(&b, "hello %d\n", 123); + * free(b); + * + * @return bytes appended or -1 if `ENOMEM` + * @see appendz(b).i to get buffer length + * @note O(1) amortized buffer growth + * @see kprintf() + */ +ssize_t kappendf(char **b, const char *fmt, ...) { + int n; + va_list va; + va_start(va, fmt); + n = kvappendf(b, fmt, va); + va_end(va); + return n; +} diff --git a/libc/stdio/kvappendf.c b/libc/stdio/kvappendf.c new file mode 100644 index 000000000..e14ea82ce --- /dev/null +++ b/libc/stdio/kvappendf.c @@ -0,0 +1,68 @@ +/*-*- 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/assert.h" +#include "libc/intrin/kprintf.h" +#include "libc/macros.internal.h" +#include "libc/mem/mem.h" +#include "libc/stdio/append.internal.h" + +#define W sizeof(size_t) + +/** + * Appends formatted string to buffer. + * + * This is an alternative to vappendf() that uses the kprintf() + * formatting facility. This has some advantages in terms of + * performance, code size, and memory safety. There's also + * disadvantages, such as lack of floating-point directives. + * + * @see kprintf() + */ +ssize_t kvappendf(char **b, const char *f, va_list v) { + char *p; + int r, s; + size_t n; + va_list w; + struct appendz z; + z = appendz((p = *b)); + va_copy(w, v); + if ((r = kvsnprintf(p + z.i, z.n ? z.n - W - z.i : 0, f, v)) >= 0) { + n = ROUNDUP(z.i + r + 1, 8) + W; + if (n > z.n) { + if (!z.n) z.n = W * 2; + while (n > z.n) z.n += z.n >> 1; + z.n = ROUNDUP(z.n, W); + if ((p = realloc(p, z.n))) { + z.n = malloc_usable_size(p); + assert(!(z.n & (W - 1))); + s = kvsnprintf(p + z.i, z.n - W - z.i, f, w); + assert(s == r); + *b = p; + } else { + va_end(w); + return -1; + } + } + z.i += r; + if (!IsTiny() && W == 8) z.i |= (size_t)APPEND_COOKIE << 48; + *(size_t *)(p + z.n - W) = z.i; + } + va_end(w); + return r; +} diff --git a/libc/stdio/tmpfile.c b/libc/stdio/tmpfile.c index c95c65d80..ca3184590 100644 --- a/libc/stdio/tmpfile.c +++ b/libc/stdio/tmpfile.c @@ -18,7 +18,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/safemacros.internal.h" #include "libc/calls/calls.h" -#include "libc/calls/strace.internal.h" #include "libc/fmt/fmt.h" #include "libc/intrin/kprintf.h" #include "libc/macros.internal.h" diff --git a/tool/build/strace.c b/tool/build/strace.c new file mode 100644 index 000000000..7e4fcdab5 --- /dev/null +++ b/tool/build/strace.c @@ -0,0 +1,1048 @@ +/*-*- 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 2022 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/assert.h" +#include "libc/bits/bits.h" +#include "libc/calls/calls.h" +#include "libc/calls/sigbits.h" +#include "libc/calls/struct/iovec.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" +#include "libc/calls/struct/stat.h" +#include "libc/calls/struct/user_regs_struct.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/fmt/fmt.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/kprintf.h" +#include "libc/log/check.h" +#include "libc/log/log.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/append.internal.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/nr.h" +#include "libc/sysv/consts/ptrace.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/w.h" + +/** + * @fileoverview ptrace() tutorial + */ + +#define PROLOGUE "%r%8d %'18T " + +#undef __NR_execve +#define __WALL 0x40000000 + +#define PTR 0 +#define ULONG 0 +#define INT 1 +#define LONG 2 +#define STR 3 +#define BUF 4 +#define IOV 5 +#define STRLIST 6 +#define INTPTR 7 +#define STAT 8 +#define SIG 9 +#define OCTAL 0x80 + +static const struct Syscall { + long *number; + const char *name; + unsigned char arity; + unsigned char ret; + unsigned char arg[6]; +} kSyscalls[] = { + // clang-format off + {&__NR_exit, "exit", 1, INT, {INT}}, + {&__NR_exit_group, "exit_group", 1, INT, {INT}}, + {&__NR_read, "read", 3, LONG, {INT, BUF, ULONG}}, + {&__NR_write, "write", 3, LONG, {INT, BUF, ULONG}}, + {&__NR_open, "open", 3, INT, {STR, INT, OCTAL|INT}}, + {&__NR_close, "close", 1, INT, {INT}}, + {&__NR_stat, "stat", 2, INT, {STR, STAT}}, + {&__NR_fstat, "fstat", 2, INT, {INT, STAT}}, + {&__NR_lstat, "lstat", 2, INT, {INT, STAT}}, + {&__NR_poll, "poll", 3, INT, {PTR, INT, INT}}, + {&__NR_ppoll, "ppoll", 4, INT}, + {&__NR_lseek, "lseek", 3, LONG, {INT, LONG, INT}}, + {&__NR_mmap, "mmap", 6, ULONG, {PTR, ULONG, INT, INT, INT, ULONG}}, + {&__NR_msync, "msync", 3, INT, {PTR, ULONG, INT}}, + {&__NR_mprotect, "mprotect", 3, INT, {PTR, ULONG, INT}}, + {&__NR_munmap, "munmap", 2, INT, {PTR, ULONG}}, + {&__NR_sigaction, "sigaction", 4, INT, {SIG}}, + {&__NR_sigprocmask, "sigprocmask", 3, INT}, + {&__NR_ioctl, "ioctl", 3, INT, {INT, ULONG, ULONG}}, + {&__NR_pread, "pread", 4, LONG, {INT, BUF, ULONG, ULONG}}, + {&__NR_pwrite, "pwrite", 4, LONG, {INT, BUF, ULONG, ULONG}}, + {&__NR_readv, "readv", 3, LONG, {INT, IOV, INT}}, + {&__NR_writev, "writev", 3, LONG, {INT, IOV, INT}}, + {&__NR_access, "access", 2, INT, {STR, OCTAL|INT}}, + {&__NR_pipe, "pipe", 1, INT}, + {&__NR_select, "select", 5}, + {&__NR_pselect, "pselect", 6}, + {&__NR_pselect6, "pselect6", 6}, + {&__NR_sched_yield, "sched_yield", 0, INT}, + {&__NR_mremap, "mremap", 5}, + {&__NR_mincore, "mincore", 6}, + {&__NR_madvise, "madvise", 6}, + {&__NR_shmget, "shmget", 6}, + {&__NR_shmat, "shmat", 6}, + {&__NR_shmctl, "shmctl", 6}, + {&__NR_dup, "dup", 1, INT, {INT}}, + {&__NR_dup2, "dup2", 2, INT, {INT, INT}}, + {&__NR_pause, "pause", 0, INT}, + {&__NR_nanosleep, "nanosleep", 2}, + {&__NR_getitimer, "getitimer", 2}, + {&__NR_setitimer, "setitimer", 3}, + {&__NR_alarm, "alarm", 1}, + {&__NR_getpid, "getpid", 0, INT}, + {&__NR_sendfile, "sendfile", 6}, + {&__NR_socket, "socket", 3, INT, {INT, INT, INT}}, + {&__NR_connect, "connect", 3}, + {&__NR_accept, "accept", 3}, + {&__NR_sendto, "sendto", 6}, + {&__NR_recvfrom, "recvfrom", 6}, + {&__NR_sendmsg, "sendmsg", 6}, + {&__NR_recvmsg, "recvmsg", 6}, + {&__NR_shutdown, "shutdown", 6}, + {&__NR_bind, "bind", 6}, + {&__NR_listen, "listen", 6}, + {&__NR_getsockname, "getsockname", 6}, + {&__NR_getpeername, "getpeername", 6}, + {&__NR_socketpair, "socketpair", 6}, + {&__NR_setsockopt, "setsockopt", 6}, + {&__NR_getsockopt, "getsockopt", 6}, + {&__NR_fork, "fork", 0, INT}, + {&__NR_vfork, "vfork", 0, INT}, + {&__NR_posix_spawn, "posix_spawn", 6}, + {&__NR_execve, "execve", 3, INT, {STR, STRLIST, STRLIST}}, + {&__NR_wait4, "wait4", 4, INT, {INT, INTPTR, INT, PTR}}, + {&__NR_kill, "kill", 2, INT, {INT, SIG}}, + {&__NR_killpg, "killpg", 2, INT, {INT, SIG}}, + {&__NR_clone, "clone", 5, INT, {PTR, PTR, INTPTR, INTPTR, ULONG}}, + {&__NR_tkill, "tkill", 2, INT, {INT, SIG}}, + {&__NR_futex, "futex", 6}, + {&__NR_set_robust_list, "set_robust_list", 6}, + {&__NR_get_robust_list, "get_robust_list", 6}, + {&__NR_uname, "uname", 6}, + {&__NR_semget, "semget", 6}, + {&__NR_semop, "semop", 6}, + {&__NR_semctl, "semctl", 6}, + {&__NR_shmdt, "shmdt", 6}, + {&__NR_msgget, "msgget", 6}, + {&__NR_msgsnd, "msgsnd", 6}, + {&__NR_msgrcv, "msgrcv", 6}, + {&__NR_msgctl, "msgctl", 6}, + {&__NR_fcntl, "fcntl", 3, INT, {INT, INT, ULONG}}, + {&__NR_flock, "flock", 6}, + {&__NR_fsync, "fsync", 6}, + {&__NR_fdatasync, "fdatasync", 6}, + {&__NR_truncate, "truncate", 2, INT, {STR, ULONG}}, + {&__NR_ftruncate, "ftruncate", 6, INT, {INT, ULONG}}, + {&__NR_getcwd, "getcwd", 2, INT, {BUF, ULONG}}, + {&__NR_chdir, "chdir", 1, INT, {STR}}, + {&__NR_fchdir, "fchdir", 1, INT, {INT}}, + {&__NR_rename, "rename", 2, INT, {STR, STR}}, + {&__NR_mkdir, "mkdir", 2, INT, {STR, OCTAL|INT}}, + {&__NR_rmdir, "rmdir", 1, INT, {STR}}, + {&__NR_creat, "creat", 2, INT, {STR, OCTAL|INT}}, + {&__NR_link, "link", 2, INT, {STR, STR}}, + {&__NR_unlink, "unlink", 1, INT, {STR}}, + {&__NR_symlink, "symlink", 6}, + {&__NR_readlink, "readlink", 6}, + {&__NR_chmod, "chmod", 6}, + {&__NR_fchmod, "fchmod", 6}, + {&__NR_chown, "chown", 6}, + {&__NR_fchown, "fchown", 6}, + {&__NR_lchown, "lchown", 6}, + {&__NR_umask, "umask", 1, OCTAL|INT, {OCTAL|INT}}, + {&__NR_gettimeofday, "gettimeofday", 6}, + {&__NR_getrlimit, "getrlimit", 6}, + {&__NR_getrusage, "getrusage", 6}, + {&__NR_sysinfo, "sysinfo", 6}, + {&__NR_times, "times", 6}, + {&__NR_ptrace, "ptrace", 6}, + {&__NR_syslog, "syslog", 6}, + {&__NR_getuid, "getuid", 0, INT}, + {&__NR_getgid, "getgid", 0, INT}, + {&__NR_getppid, "getppid", 0, INT}, + {&__NR_getpgrp, "getpgrp", 0, INT}, + {&__NR_setsid, "setsid", 6}, + {&__NR_getsid, "getsid", 0}, + {&__NR_getpgid, "getpgid", 0}, + {&__NR_setpgid, "setpgid", 6}, + {&__NR_geteuid, "geteuid", 0, INT}, + {&__NR_getegid, "getegid", 0, INT}, + {&__NR_getgroups, "getgroups", 6}, + {&__NR_setgroups, "setgroups", 6}, + {&__NR_setreuid, "setreuid", 6}, + {&__NR_setregid, "setregid", 6}, + {&__NR_setuid, "setuid", 6}, + {&__NR_setgid, "setgid", 6}, + {&__NR_setresuid, "setresuid", 6}, + {&__NR_setresgid, "setresgid", 6}, + {&__NR_getresuid, "getresuid", 6}, + {&__NR_getresgid, "getresgid", 6}, + {&__NR_sigpending, "sigpending", 6}, + {&__NR_sigsuspend, "sigsuspend", 6}, + {&__NR_sigaltstack, "sigaltstack", 6}, + {&__NR_mknod, "mknod", 6}, + {&__NR_mknodat, "mknodat", 6}, + {&__NR_mkfifo, "mkfifo", 6}, + {&__NR_mkfifoat, "mkfifoat", 6}, + {&__NR_statfs, "statfs", 6}, + {&__NR_fstatfs, "fstatfs", 6}, + {&__NR_getpriority, "getpriority", 6}, + {&__NR_setpriority, "setpriority", 6}, + {&__NR_mlock, "mlock", 6}, + {&__NR_munlock, "munlock", 6}, + {&__NR_mlockall, "mlockall", 6}, + {&__NR_munlockall, "munlockall", 6}, + {&__NR_setrlimit, "setrlimit", 6}, + {&__NR_chroot, "chroot", 6}, + {&__NR_sync, "sync", 6}, + {&__NR_acct, "acct", 6}, + {&__NR_settimeofday, "settimeofday", 6}, + {&__NR_mount, "mount", 6}, + {&__NR_reboot, "reboot", 6}, + {&__NR_quotactl, "quotactl", 6}, + {&__NR_setfsuid, "setfsuid", 6}, + {&__NR_setfsgid, "setfsgid", 6}, + {&__NR_capget, "capget", 6}, + {&__NR_capset, "capset", 6}, + {&__NR_sigtimedwait, "sigtimedwait", 6}, + {&__NR_rt_sigqueueinfo, "rt_sigqueueinfo", 6}, + {&__NR_personality, "personality", 6}, + {&__NR_ustat, "ustat", 6}, + {&__NR_sysfs, "sysfs", 6}, + {&__NR_sched_setparam, "sched_setparam", 6}, + {&__NR_sched_getparam, "sched_getparam", 6}, + {&__NR_sched_setscheduler, "sched_setscheduler", 6}, + {&__NR_sched_getscheduler, "sched_getscheduler", 6}, + {&__NR_sched_get_priority_max, "sched_get_priority_max", 6}, + {&__NR_sched_get_priority_min, "sched_get_priority_min", 6}, + {&__NR_sched_rr_get_interval, "sched_rr_get_interval", 6}, + {&__NR_vhangup, "vhangup", 6}, + {&__NR_modify_ldt, "modify_ldt", 6}, + {&__NR_pivot_root, "pivot_root", 6}, + {&__NR__sysctl, "_sysctl", 6}, + {&__NR_prctl, "prctl", 6}, + {&__NR_arch_prctl, "arch_prctl", 2, INT, {INT, ULONG}}, + {&__NR_adjtimex, "adjtimex", 6}, + {&__NR_umount2, "umount2", 6}, + {&__NR_swapon, "swapon", 6}, + {&__NR_swapoff, "swapoff", 6}, + {&__NR_sethostname, "sethostname", 6}, + {&__NR_setdomainname, "setdomainname", 6}, + {&__NR_iopl, "iopl", 6}, + {&__NR_ioperm, "ioperm", 6}, + {&__NR_init_module, "init_module", 6}, + {&__NR_delete_module, "delete_module", 6}, + {&__NR_gettid, "gettid", 6}, + {&__NR_readahead, "readahead", 6}, + {&__NR_setxattr, "setxattr", 6}, + {&__NR_fsetxattr, "fsetxattr", 6}, + {&__NR_getxattr, "getxattr", 6}, + {&__NR_fgetxattr, "fgetxattr", 6}, + {&__NR_listxattr, "listxattr", 6}, + {&__NR_flistxattr, "flistxattr", 6}, + {&__NR_removexattr, "removexattr", 6}, + {&__NR_fremovexattr, "fremovexattr", 6}, + {&__NR_lsetxattr, "lsetxattr", 6}, + {&__NR_lgetxattr, "lgetxattr", 6}, + {&__NR_llistxattr, "llistxattr", 6}, + {&__NR_lremovexattr, "lremovexattr", 6}, + {&__NR_sched_setaffinity, "sched_setaffinity", 6}, + {&__NR_sched_getaffinity, "sched_getaffinity", 6}, + {&__NR_cpuset_getaffinity, "cpuset_getaffinity", 6}, + {&__NR_cpuset_setaffinity, "cpuset_setaffinity", 6}, + {&__NR_io_setup, "io_setup", 6}, + {&__NR_io_destroy, "io_destroy", 6}, + {&__NR_io_getevents, "io_getevents", 6}, + {&__NR_io_submit, "io_submit", 6}, + {&__NR_io_cancel, "io_cancel", 6}, + {&__NR_lookup_dcookie, "lookup_dcookie", 6}, + {&__NR_epoll_create, "epoll_create", 6}, + {&__NR_epoll_wait, "epoll_wait", 6}, + {&__NR_epoll_ctl, "epoll_ctl", 6}, + {&__NR_getdents, "getdents", 6}, + {&__NR_set_tid_address, "set_tid_address", 1}, + {&__NR_restart_syscall, "restart_syscall", 6}, + {&__NR_semtimedop, "semtimedop", 6}, + {&__NR_fadvise, "fadvise", 6}, + {&__NR_timer_create, "timer_create", 6}, + {&__NR_timer_settime, "timer_settime", 6}, + {&__NR_timer_gettime, "timer_gettime", 6}, + {&__NR_timer_getoverrun, "timer_getoverrun", 6}, + {&__NR_timer_delete, "timer_delete", 6}, + {&__NR_clock_settime, "clock_settime", 6}, + {&__NR_clock_gettime, "clock_gettime", 6}, + {&__NR_clock_getres, "clock_getres", 6}, + {&__NR_clock_nanosleep, "clock_nanosleep", 6}, + {&__NR_tgkill, "tgkill", 6}, + {&__NR_mbind, "mbind", 6}, + {&__NR_set_mempolicy, "set_mempolicy", 6}, + {&__NR_get_mempolicy, "get_mempolicy", 6}, + {&__NR_mq_open, "mq_open", 6}, + {&__NR_mq_unlink, "mq_unlink", 6}, + {&__NR_mq_timedsend, "mq_timedsend", 6}, + {&__NR_mq_timedreceive, "mq_timedreceive", 6}, + {&__NR_mq_notify, "mq_notify", 6}, + {&__NR_mq_getsetattr, "mq_getsetattr", 6}, + {&__NR_kexec_load, "kexec_load", 6}, + {&__NR_waitid, "waitid", 6}, + {&__NR_add_key, "add_key", 6}, + {&__NR_request_key, "request_key", 6}, + {&__NR_keyctl, "keyctl", 6}, + {&__NR_ioprio_set, "ioprio_set", 6}, + {&__NR_ioprio_get, "ioprio_get", 6}, + {&__NR_inotify_init, "inotify_init", 6}, + {&__NR_inotify_add_watch, "inotify_add_watch", 6}, + {&__NR_inotify_rm_watch, "inotify_rm_watch", 6}, + {&__NR_openat, "openat", 4, INT, {INT, STR, INT, OCTAL|INT}}, + {&__NR_mkdirat, "mkdirat", 3, INT, {INT, STR, OCTAL|INT}}, + {&__NR_fchownat, "fchownat", 6}, + {&__NR_utime, "utime", 6}, + {&__NR_utimes, "utimes", 6}, + {&__NR_futimesat, "futimesat", 6}, + {&__NR_futimes, "futimes", 6}, + {&__NR_futimens, "futimens", 6}, + {&__NR_fstatat, "fstatat", 4, INT, {INT, STR, STAT, INT}}, + {&__NR_unlinkat, "unlinkat", 3, INT, {INT, STR, INT}}, + {&__NR_renameat, "renameat", 4, INT, {INT, STR, INT, STR}}, + {&__NR_linkat, "linkat", 6}, + {&__NR_symlinkat, "symlinkat", 6}, + {&__NR_readlinkat, "readlinkat", 6}, + {&__NR_fchmodat, "fchmodat", 6}, + {&__NR_faccessat, "faccessat", 4, INT, {INT, STR, INT, INT}}, + {&__NR_unshare, "unshare", 6}, + {&__NR_splice, "splice", 6}, + {&__NR_tee, "tee", 6}, + {&__NR_sync_file_range, "sync_file_range", 4}, + {&__NR_vmsplice, "vmsplice", 6}, + {&__NR_migrate_pages, "migrate_pages", 6}, + {&__NR_move_pages, "move_pages", 6}, + {&__NR_preadv, "preadv", 4, LONG, {INT, IOV, ULONG, ULONG}}, + {&__NR_pwritev, "pwritev", 6, LONG, {INT, IOV, ULONG, ULONG}}, + {&__NR_utimensat, "utimensat", 6}, + {&__NR_fallocate, "fallocate", 6}, + {&__NR_posix_fallocate, "posix_fallocate", 6}, + {&__NR_accept4, "accept4", 4}, + {&__NR_dup3, "dup3", 3, INT}, + {&__NR_pipe2, "pipe2", 2}, + {&__NR_epoll_pwait, "epoll_pwait", 6}, + {&__NR_epoll_create1, "epoll_create1", 6}, + {&__NR_perf_event_open, "perf_event_open", 6}, + {&__NR_inotify_init1, "inotify_init1", 6}, + {&__NR_rt_tgsigqueueinfo, "rt_tgsigqueueinfo", 6}, + {&__NR_signalfd, "signalfd", 6}, + {&__NR_signalfd4, "signalfd4", 6}, + {&__NR_eventfd, "eventfd", 6}, + {&__NR_eventfd2, "eventfd2", 6}, + {&__NR_timerfd_create, "timerfd_create", 6}, + {&__NR_timerfd_settime, "timerfd_settime", 6}, + {&__NR_timerfd_gettime, "timerfd_gettime", 6}, + {&__NR_recvmmsg, "recvmmsg", 6}, + {&__NR_fanotify_init, "fanotify_init", 6}, + {&__NR_fanotify_mark, "fanotify_mark", 6}, + {&__NR_prlimit, "prlimit", 6}, + {&__NR_name_to_handle_at, "name_to_handle_at", 6}, + {&__NR_open_by_handle_at, "open_by_handle_at", 6}, + {&__NR_clock_adjtime, "clock_adjtime", 6}, + {&__NR_syncfs, "syncfs", 6}, + {&__NR_sendmmsg, "sendmmsg", 6}, + {&__NR_setns, "setns", 6}, + {&__NR_getcpu, "getcpu", 6}, + {&__NR_process_vm_readv, "process_vm_readv", 6}, + {&__NR_process_vm_writev, "process_vm_writev", 6}, + {&__NR_kcmp, "kcmp", 6}, + {&__NR_finit_module, "finit_module", 6}, + {&__NR_sched_setattr, "sched_setattr", 6}, + {&__NR_sched_getattr, "sched_getattr", 6}, + {&__NR_renameat2, "renameat2", 6}, + {&__NR_seccomp, "seccomp", 6}, + {&__NR_getrandom, "getrandom", 6}, + {&__NR_memfd_create, "memfd_create", 6}, + {&__NR_kexec_file_load, "kexec_file_load", 6}, + {&__NR_bpf, "bpf", 6}, + {&__NR_execveat, "execveat", 6}, + {&__NR_userfaultfd, "userfaultfd", 6}, + {&__NR_membarrier, "membarrier", 6}, + {&__NR_mlock2, "mlock2", 6}, + {&__NR_copy_file_range, "copy_file_range", 6}, + {&__NR_preadv2, "preadv2", 6}, + {&__NR_pwritev2, "pwritev2", 6}, + {&__NR_pkey_mprotect, "pkey_mprotect", 6}, + {&__NR_pkey_alloc, "pkey_alloc", 6}, + {&__NR_pkey_free, "pkey_free", 6}, + {&__NR_statx, "statx", 6}, + {&__NR_io_pgetevents, "io_pgetevents", 6}, + {&__NR_rseq, "rseq", 6}, + {&__NR_pidfd_send_signal, "pidfd_send_signal", 6}, + {&__NR_io_uring_setup, "io_uring_setup", 6}, + {&__NR_io_uring_enter, "io_uring_enter", 6}, + {&__NR_io_uring_register, "io_uring_register", 6}, + // clang-format on +}; + +static const struct Errno { + long *number; + const char *name; +} kErrnos[] = { + {&ENOSYS, "ENOSYS"}, // + {&EPERM, "EPERM"}, // + {&ENOENT, "ENOENT"}, // + {&ESRCH, "ESRCH"}, // + {&EINTR, "EINTR"}, // + {&EIO, "EIO"}, // + {&ENXIO, "ENXIO"}, // + {&E2BIG, "E2BIG"}, // + {&ENOEXEC, "ENOEXEC"}, // + {&EBADF, "EBADF"}, // + {&ECHILD, "ECHILD"}, // + {&EAGAIN, "EAGAIN"}, // + {&ENOMEM, "ENOMEM"}, // + {&EACCES, "EACCES"}, // + {&EFAULT, "EFAULT"}, // + {&ENOTBLK, "ENOTBLK"}, // + {&EBUSY, "EBUSY"}, // + {&EEXIST, "EEXIST"}, // + {&EXDEV, "EXDEV"}, // + {&ENODEV, "ENODEV"}, // + {&ENOTDIR, "ENOTDIR"}, // + {&EISDIR, "EISDIR"}, // + {&EINVAL, "EINVAL"}, // + {&ENFILE, "ENFILE"}, // + {&EMFILE, "EMFILE"}, // + {&ENOTTY, "ENOTTY"}, // + {&ETXTBSY, "ETXTBSY"}, // + {&EFBIG, "EFBIG"}, // + {&ENOSPC, "ENOSPC"}, // + {&EDQUOT, "EDQUOT"}, // + {&ESPIPE, "ESPIPE"}, // + {&EROFS, "EROFS"}, // + {&EMLINK, "EMLINK"}, // + {&EPIPE, "EPIPE"}, // + {&EDOM, "EDOM"}, // + {&ERANGE, "ERANGE"}, // + {&EDEADLK, "EDEADLK"}, // + {&ENAMETOOLONG, "ENAMETOOLONG"}, // + {&ENOLCK, "ENOLCK"}, // + {&ENOTEMPTY, "ENOTEMPTY"}, // + {&ELOOP, "ELOOP"}, // + {&ENOMSG, "ENOMSG"}, // + {&EIDRM, "EIDRM"}, // + {&ETIME, "ETIME"}, // + {&EPROTO, "EPROTO"}, // + {&EOVERFLOW, "EOVERFLOW"}, // + {&EILSEQ, "EILSEQ"}, // + {&EUSERS, "EUSERS"}, // + {&ENOTSOCK, "ENOTSOCK"}, // + {&EDESTADDRREQ, "EDESTADDRREQ"}, // + {&EMSGSIZE, "EMSGSIZE"}, // + {&EPROTOTYPE, "EPROTOTYPE"}, // + {&ENOPROTOOPT, "ENOPROTOOPT"}, // + {&EPROTONOSUPPORT, "EPROTONOSUPPORT"}, // + {&ESOCKTNOSUPPORT, "ESOCKTNOSUPPORT"}, // + {&ENOTSUP, "ENOTSUP"}, // + {&EOPNOTSUPP, "EOPNOTSUPP"}, // + {&EPFNOSUPPORT, "EPFNOSUPPORT"}, // + {&EAFNOSUPPORT, "EAFNOSUPPORT"}, // + {&EADDRINUSE, "EADDRINUSE"}, // + {&EADDRNOTAVAIL, "EADDRNOTAVAIL"}, // + {&ENETDOWN, "ENETDOWN"}, // + {&ENETUNREACH, "ENETUNREACH"}, // + {&ENETRESET, "ENETRESET"}, // + {&ECONNABORTED, "ECONNABORTED"}, // + {&ECONNRESET, "ECONNRESET"}, // + {&ENOBUFS, "ENOBUFS"}, // + {&EISCONN, "EISCONN"}, // + {&ENOTCONN, "ENOTCONN"}, // + {&ESHUTDOWN, "ESHUTDOWN"}, // + {&ETOOMANYREFS, "ETOOMANYREFS"}, // + {&ETIMEDOUT, "ETIMEDOUT"}, // + {&ECONNREFUSED, "ECONNREFUSED"}, // + {&EHOSTDOWN, "EHOSTDOWN"}, // + {&EHOSTUNREACH, "EHOSTUNREACH"}, // + {&EALREADY, "EALREADY"}, // + {&EINPROGRESS, "EINPROGRESS"}, // + {&ESTALE, "ESTALE"}, // + {&EREMOTE, "EREMOTE"}, // + {&EBADRPC, "EBADRPC"}, // + {&ERPCMISMATCH, "ERPCMISMATCH"}, // + {&EPROGUNAVAIL, "EPROGUNAVAIL"}, // + {&EPROGMISMATCH, "EPROGMISMATCH"}, // + {&EPROCUNAVAIL, "EPROCUNAVAIL"}, // + {&EFTYPE, "EFTYPE"}, // + {&EAUTH, "EAUTH"}, // + {&ENEEDAUTH, "ENEEDAUTH"}, // + {&EPROCLIM, "EPROCLIM"}, // + {&ENOATTR, "ENOATTR"}, // + {&EPWROFF, "EPWROFF"}, // + {&EDEVERR, "EDEVERR"}, // + {&EBADEXEC, "EBADEXEC"}, // + {&EBADARCH, "EBADARCH"}, // + {&ESHLIBVERS, "ESHLIBVERS"}, // + {&EBADMACHO, "EBADMACHO"}, // + {&ENOPOLICY, "ENOPOLICY"}, // + {&EBADMSG, "EBADMSG"}, // + {&ECANCELED, "ECANCELED"}, // + {&EOWNERDEAD, "EOWNERDEAD"}, // + {&ENOTRECOVERABLE, "ENOTRECOVERABLE"}, // + {&ENONET, "ENONET"}, // + {&ERESTART, "ERESTART"}, // + {&ENOSR, "ENOSR"}, // + {&ENOSTR, "ENOSTR"}, // + {&ENODATA, "ENODATA"}, // + {&EMULTIHOP, "EMULTIHOP"}, // + {&ENOLINK, "ENOLINK"}, // + {&ENOMEDIUM, "ENOMEDIUM"}, // + {&EMEDIUMTYPE, "EMEDIUMTYPE"}, // + {&EWOULDBLOCK, "EWOULDBLOCK"}, // +}; + +struct Pid { + struct Pid *next; + struct Pid *prev; + int pid; + bool insyscall; + struct Syscall *call; + struct user_regs_struct args; + struct user_regs_struct res; +}; + +char *ob; // output buffer +struct Pid *sp; // active subprocess + +static ssize_t WriteAll(int fd, const char *p, size_t n) { + ssize_t rc; + size_t i, got; + for (i = 0; i < n;) { + rc = write(fd, p + i, n - i); + if (rc != -1) { + got = rc; + i += got; + } else if (errno != EINTR) { + return -1; + } + } + return i; +} + +static void Flush(void) { + WriteAll(2, ob, appendz(ob).i); + appendr(&ob, 0); +} + +static const char *GetErrnoName(int x) { + int i; + static char buf[16]; + if (x > 0) { + for (i = 0; i < ARRAYLEN(kErrnos); ++i) { + if (x == *kErrnos[i].number) { + return kErrnos[i].name; + } + } + } + int64toarray_radix10(x, buf); + return buf; +} + +static struct Syscall *GetSyscall(int x) { + int i; + if (x > 0) { + for (i = 0; i < ARRAYLEN(kSyscalls); ++i) { + if (x == *kSyscalls[i].number) { + return kSyscalls + i; + } + } + } + return NULL; +} + +static char *PeekString(unsigned long x) { + union { + char buf[8]; + long word; + } u; + char *p; + unsigned offset; + unsigned long addr, i, n; + if (!x) return NULL; + addr = ROUNDDOWN(x, 8); + offset = x - addr; + u.word = ptrace(PTRACE_PEEKTEXT, sp->pid, addr); + n = strnlen(u.buf + offset, 8 - offset); + p = malloc(n); + memcpy(p, u.buf + offset, n); + if (n == 8 - offset) { + do { + addr += 8; + u.word = ptrace(PTRACE_PEEKDATA, sp->pid, addr); + i = strnlen(u.buf, 8); + p = realloc(p, n + i); + memcpy(p + n, u.buf, i); + n += i; + } while (i == 8); + } + p = realloc(p, n + 1); + p[n] = 0; + return p; +} + +static char *PrintString(char *s) { + kappendf(&ob, "%#s", s); + return s; +} + +static char *PeekData(unsigned long x, size_t size) { + union { + char buf[8]; + long word; + } u; + char *p; + unsigned offset; + unsigned long addr, i, n; + if (!x) return NULL; + addr = ROUNDDOWN(x, 8); + offset = x - addr; + u.word = ptrace(PTRACE_PEEKTEXT, sp->pid, addr); + p = malloc(size); + memcpy(p, u.buf + offset, 8 - offset); + for (i = 8 - offset; i < size; i += MIN(8, size - i)) { + addr += 8; + u.word = ptrace(PTRACE_PEEKDATA, sp->pid, addr); + memcpy(p + i, u.buf, MIN(8, size - i)); + } + return p; +} + +static char *PrintData(char *data, size_t size) { + kappendf(&ob, "%#.*hhs%s", MIN(40, size), data, size > 40 ? "..." : "", data); + return data; +} + +static struct iovec *PeekIov(unsigned long addr, int len) { + int i; + char *p; + long word; + struct iovec *iov; + if (!addr) return NULL; + p = malloc(len * 16); + for (i = 0; i < len * 2; ++i, addr += sizeof(word)) { + word = ptrace(PTRACE_PEEKTEXT, sp->pid, addr); + memcpy(p + i * sizeof(word), &word, sizeof(word)); + } + iov = (struct iovec *)p; + for (i = 0; i < len; ++i) { + iov[i].iov_base = PeekData((long)iov[i].iov_base, iov[i].iov_len); + } + return iov; +} + +static struct iovec *PrintIov(struct iovec *iov, int iovlen) { + int i; + if (iov) { + appendw(&ob, '{'); + for (i = 0; i < iovlen; ++i) { + if (i) appendw(&ob, READ16LE(", ")); + appendw(&ob, '{'); + PrintData(iov[i].iov_base, iov[i].iov_len); + kappendf(&ob, ", %#lx}", iov[i].iov_len); + } + appendw(&ob, '}'); + } else { + appendw(&ob, READ32LE("NULL")); + } + return iov; +} + +static void FreeIov(struct iovec *iov, int iovlen) { + int i; + if (iov) { + for (i = 0; i < iovlen; ++i) { + free(iov[i].iov_base); + } + free(iov); + } +} + +static char **PeekStringList(unsigned long addr) { + char *p; + long word; + size_t i, n; + char **list; + if (!addr) return NULL; + for (p = NULL, n = 0;; ++n) { + p = realloc(p, (n + 1) * sizeof(word)); + word = ptrace(PTRACE_PEEKTEXT, sp->pid, addr + n * sizeof(word)); + memcpy(p + n * sizeof(word), &word, sizeof(word)); + if (!word) break; + } + list = (char **)p; + for (i = 0; i < n; ++i) { + list[i] = PeekString((long)list[i]); + } + return list; +} + +static char **PrintStringList(char **list) { + int i; + if (list) { + appendw(&ob, '{'); + for (i = 0;; ++i) { + if (i) appendw(&ob, READ16LE(", ")); + PrintString(list[i]); + if (!list[i]) break; + } + appendw(&ob, '}'); + } else { + appendw(&ob, READ32LE("NULL")); + } + return list; +} + +static void FreeStringList(char **list) { + int i; + if (list) { + for (i = 0; list[i]; ++i) { + free(list[i]); + } + free(list); + } +} + +static struct stat *PeekStat(unsigned long addr) { + int i; + char *p; + long word; + if (!addr) return NULL; + p = malloc(sizeof(struct stat)); + for (i = 0; i < sizeof(struct stat); i += sizeof(word)) { + word = ptrace(PTRACE_PEEKTEXT, sp->pid, addr + i); + memcpy(p + i, &word, sizeof(word)); + } + return (struct stat *)p; +} + +static struct stat *PrintStat(struct stat *st) { + bool printed; + printed = false; + appendw(&ob, '{'); + if (st->st_size) { + kappendf(&ob, ".st_size = %#lx", st->st_size); + printed = true; + } + if (st->st_mode) { + if (printed) appendw(&ob, READ16LE(", ")); + kappendf(&ob, ".st_mode = %#o", st->st_mode); + printed = true; + } + appendw(&ob, '}'); + return st; +} + +static void PrintSyscallArg(int type, unsigned long x, unsigned long y) { + char *s; + switch (type & 31) { + case ULONG: + kappendf(&ob, "%#lx", x); + break; + case INT: + if (type & OCTAL) { + kappendf(&ob, "%#o", x); + } else { + kappendf(&ob, "%d", x); + } + break; + case LONG: + kappendf(&ob, "%ld", x); + break; + case STR: + free(PrintString(PeekString(x))); + break; + case BUF: + free(PrintData(PeekData(x, y), y)); + break; + case IOV: + FreeIov(PrintIov(PeekIov(x, y), y), y); + break; + case STAT: + free(PrintStat(PeekStat(x))); + break; + case SIG: + appends(&ob, strsignal(x)); + break; + case STRLIST: + FreeStringList(PrintStringList(PeekStringList(x))); + break; + case INTPTR: + if (x) { + s = PeekData(x, 4); + kappendf(&ob, "{%d}", READ32LE(s)); + free(s); + } + break; + default: + kappendf(&ob, "wut[%'ld]", x); + break; + } +} + +static void PrintSyscall(void) { + if ((sp->call = GetSyscall(sp->args.orig_rax))) { + kappendf(&ob, PROLOGUE " %s(", sp->pid, sp->call->name); + if (0 < sp->call->arity) { + PrintSyscallArg(sp->call->arg[0], sp->args.rdi, sp->args.rsi); + if (1 < sp->call->arity) { + appendw(&ob, READ16LE(", ")); + PrintSyscallArg(sp->call->arg[1], sp->args.rsi, sp->args.rdx); + if (2 < sp->call->arity) { + appendw(&ob, READ16LE(", ")); + PrintSyscallArg(sp->call->arg[2], sp->args.rdx, sp->args.r10); + if (3 < sp->call->arity) { + appendw(&ob, READ16LE(", ")); + PrintSyscallArg(sp->call->arg[3], sp->args.r10, sp->args.r8); + if (4 < sp->call->arity) { + appendw(&ob, READ16LE(", ")); + PrintSyscallArg(sp->call->arg[4], sp->args.r8, sp->args.r9); + if (5 < sp->call->arity) { + appendw(&ob, READ16LE(", ")); + PrintSyscallArg(sp->call->arg[5], sp->args.r9, 0); + } + } + } + } + } + } + appendw(&ob, ')'); + } +} + +static void PrintSyscallResult(void) { + if (sp->call) { + appends(&ob, " → "); + if (sp->res.rax > (unsigned long)-4096) { + kappendf(&ob, "-1 %s", GetErrnoName(-sp->res.rax)); + } else { + PrintSyscallArg(sp->call->ret, sp->res.rax, 0); + } + kappendf(&ob, "%n"); + Flush(); + } +} + +wontreturn void PropagateExit(int wstatus) { + exit(WEXITSTATUS(wstatus)); +} + +wontreturn void PropagateTermination(int wstatus) { + sigset_t mask; + // if signal that terminated program was sent to our whole + // process group, then that signal should be delivered and kill + // this process the moment it's unblocked here. we only unblock + // here because if the child process ignores the signal and does + // exit(0) then we want to propagate that intent too. + sigemptyset(&mask); + sigaddset(&mask, WTERMSIG(wstatus)); + sigprocmask(SIG_UNBLOCK, &mask, 0); + // otherwise we can propagate it by the exit code convention + // commonly used with shell scripts + exit(128 + WTERMSIG(wstatus)); +} + +wontreturn void StraceMain(int argc, char *argv[]) { + bool islast; + unsigned long msg; + struct Pid *child; + struct Pid pidlist; + sigset_t mask, truemask; + struct sigaction sigdfl; + int rc, root, wstatus, resume, signal, injectsignal; + + if (!IsLinux()) { + kprintf("error: ptrace() is only supported on linux right now%n"); + exit(1); + } + + if (IsModeDbg()) ShowCrashReports(); + + if (argc < 2) { + kprintf("Usage: %s PROGRAM [ARGS...]%n", argv[0]); + exit(1); + } + + pidlist.next = sp = calloc(1, sizeof(struct Pid)); + sp->prev = &pidlist; + + sigdfl.sa_flags = 0; + sigdfl.sa_handler = SIG_DFL; + sigemptyset(&sigdfl.sa_mask); + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGQUIT); + sigaddset(&mask, SIGTERM); + sigprocmask(SIG_BLOCK, &mask, &truemask); + + CHECK_NE(-1, (sp->pid = root = fork())); + if (!sp->pid) { + sigprocmask(SIG_SETMASK, &truemask, 0); + ptrace(PTRACE_TRACEME); + execvp(argv[1], argv + 1); + _Exit(127); + } + + // wait for ptrace(PTRACE_TRACEME) to be called + CHECK_EQ(sp->pid, waitpid(sp->pid, &wstatus, 0)); + + // configure linux process tracing + CHECK_NE(-1, ptrace(PTRACE_SETOPTIONS, sp->pid, 0, + PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | + PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | + PTRACE_O_TRACEEXIT)); + + // continue child process setting breakpoint at next system call + CHECK_NE(-1, ptrace(PTRACE_SYSCALL, sp->pid, 0, 0)); + + for (;;) { + // wait for something to happen + CHECK_NE(-1, (rc = waitpid(-1, &wstatus, WUNTRACED | __WALL))); + + // iterate through linked list to find signalled process + for (sp = pidlist.next;; sp = sp->next) { + CHECK_NE(NULL, sp); + if (sp->pid == rc) { + break; + } + } + + // handle actual exit + if (WIFEXITED(wstatus)) { + kprintf(PROLOGUE " exited with %d%n", sp->pid, WEXITSTATUS(wstatus)); + sp->prev->next = sp->next; + if (sp->next) sp->next->prev = sp->prev; + islast = sp->pid == root || !pidlist.next; + free(sp); + sp = 0; + // we exit when the last process being monitored exits + if (islast) { + PropagateExit(wstatus); + } else { + continue; + } + } + + // handle actual kill + if (WIFSIGNALED(wstatus)) { + kprintf(PROLOGUE " exited with signal %s%n", sp->pid, + strsignal(WTERMSIG(wstatus))); + sp->prev->next = sp->next; + if (sp->next) sp->next->prev = sp->prev; + islast = sp->pid == root || !pidlist.next; + free(sp); + sp = 0; + // we die when the last process being monitored dies + if (islast) { + PropagateTermination(wstatus); + } else { + continue; + } + } + + // handle trace events + assert(WIFSTOPPED(wstatus)); + injectsignal = 0; + resume = PTRACE_SYSCALL; + signal = (wstatus >> 8) & 0xffff; + if (signal == SIGTRAP) { + // trace system call + if (!sp->insyscall) { + CHECK_NE(-1, ptrace(PTRACE_GETREGS, sp->pid, 0, &sp->args)); + if (sp->args.orig_rax == __NR_execve) { + PrintSyscall(); + } + sp->insyscall = true; + } else if (sp->insyscall) { + CHECK_NE(-1, ptrace(PTRACE_GETREGS, sp->pid, 0, &sp->res)); + if (sp->args.orig_rax != __NR_execve) { + PrintSyscall(); + } + PrintSyscallResult(); + sp->insyscall = false; + } + Flush(); + + } else if (signal == (SIGTRAP | (PTRACE_EVENT_FORK << 8)) || + signal == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) || + signal == (SIGTRAP | (PTRACE_EVENT_CLONE << 8))) { + // trace multiple processes + // we can track multiple processes at the same time + CHECK_NE(-1, ptrace(PTRACE_GETEVENTMSG, sp->pid, 0, &msg)); + if (signal == (SIGTRAP | (PTRACE_EVENT_FORK << 8))) { + kappendf(&ob, PROLOGUE " fork() → 0%n", msg); + } else if (signal == (SIGTRAP | (PTRACE_EVENT_VFORK << 8))) { + kappendf(&ob, PROLOGUE " vfork() → 0%n", msg); + } else { + kappendf(&ob, PROLOGUE " clone() → 0%n", msg); + } + child = calloc(1, sizeof(struct Pid)); + child->next = pidlist.next; + child->prev = &pidlist; + child->pid = msg; + pidlist.next = child; + ptrace(PTRACE_SYSCALL, sp->pid, 0, 0); + ptrace(PTRACE_SYSCALL, child->pid, 0, 0); + Flush(); + continue; + + } else if (signal == (SIGTRAP | (PTRACE_EVENT_EXIT << 8))) { + // trace exit system call + // this gives us an opportunity to read the process memory + // we need to restart this process, for it to actually die + PrintSyscall(); + kappendf(&ob, "%n"); + Flush(); + resume = PTRACE_CONT; + injectsignal = WSTOPSIG(wstatus); + + } else if (signal == (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) { + // handle execve() process replacement + // do nothing + + } else { + // trace signal delivery + kappendf(&ob, PROLOGUE " %s (%#x)%n", sp->pid, strsignal(signal & 0x7f), + signal); + Flush(); + injectsignal = signal; + } + + // trace events always freeze the traced process + // this call will help it to start running again + ptrace(resume, sp->pid, 0, injectsignal); + } +} + +int main(int argc, char *argv[]) { + StraceMain(argc, argv); +} diff --git a/tool/net/net.mk b/tool/net/net.mk index 8c61cdfc0..9a8072629 100644 --- a/tool/net/net.mk +++ b/tool/net/net.mk @@ -22,6 +22,7 @@ TOOL_NET_COMS = \ o/$(MODE)/tool/net/redbean-static.com \ o/$(MODE)/tool/net/redbean-unsecure.com \ o/$(MODE)/tool/net/redbean-original.com \ + o/$(MODE)/tool/net/redbean-assimilate.com \ o/$(MODE)/tool/net/echoserver.com \ o/$(MODE)/tool/net/wb.com @@ -314,6 +315,33 @@ o/$(MODE)/tool/net/redbean-original.com.dbg: \ o/$(MODE)/tool/net/redbean-original.o: tool/net/redbean.c o/$(MODE)/tool/net/redbean.o @$(COMPILE) -AOBJECTIFY.c $(OBJECTIFY.c) -DSTATIC -DUNSECURE -DREDBEAN=\"redbean-original\" $(OUTPUT_OPTION) $< +# REDBEAN-ASSIMILATE.COM +# +# Same as REDBEAN.COM except without no-modify-self behavior. + +o/$(MODE)/tool/net/redbean-assimilate.com.dbg: \ + o/$(MODE)/tool/net/redbean.com.dbg + @cp -f $< $@ + +o/$(MODE)/tool/net/redbean-assimilate.com: \ + o/$(MODE)/tool/net/redbean-assimilate.com.dbg \ + o/$(MODE)/third_party/infozip/zip.com \ + o/$(MODE)/tool/build/symtab.com \ + tool/net/net.mk \ + tool/net/help.txt \ + tool/net/.init.lua \ + tool/net/favicon.ico \ + tool/net/redbean.png + @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ + @$(COMPILE) -AMKDIR -T$@ mkdir -p o/$(MODE)/tool/net/.redbean-assimilate + @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com -o o/$(MODE)/tool/net/.redbean-assimilate/.symtab $< + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/infozip/zip.com -9qj $@ \ + o/$(MODE)/tool/net/.redbean-assimilate/.symtab \ + tool/net/help.txt \ + tool/net/.init.lua \ + tool/net/favicon.ico \ + tool/net/redbean.png + .PHONY: o/$(MODE)/tool/net o/$(MODE)/tool/net: \ $(TOOL_NET_BINS) \ diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 054a669e0..4c4162b42 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -2674,7 +2674,7 @@ static void LaunchBrowser(const char *path) { sigprocmask(SIG_BLOCK, &chldmask, &savemask); CHECK_NE(-1, (pid = fork())); if (!pid) { - setpgid(getpid(), getpid()); + setpgid(getpid(), getpid()); // ctrl-c'ing redbean shouldn't kill browser sigaction(SIGINT, &saveint, 0); sigaction(SIGQUIT, &savequit, 0); sigprocmask(SIG_SETMASK, &savemask, 0); @@ -6605,7 +6605,7 @@ static void RestoreApe(void) { WARNF("(srvr) can't restore .ape"); free(p); } else { - WARNF("(srvr) /.ape not found"); + INFOF("(srvr) /.ape not found"); } } @@ -6907,6 +6907,10 @@ void RedBean(int argc, char *argv[]) { if (daemonize) { Daemonize(); } else { + // xxx: create process group to make it easier to propagate SIGTERM + // to children. the downside to doing this seems to be that + // ctrl-c isn't propagating as expected when running redbean + // underneath strace.com :| setpgid(getpid(), getpid()); if (logpath) { close(2);