/*-*- 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/calls/calls.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/siginfo.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/bits.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/nomultics.internal.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.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 SIGSET  10
#define PIPE    11
#define OCTAL   0x80

static const long __NR_brk = 12;
static const long __NR_sigreturn = 15;

static const struct Syscall {
  long *number;
  const char *name;
  char arity;
  char eager;
  unsigned char ret;
  unsigned char arg[6];
} kSyscalls[] = {
    // clang-format off
    {&__NR_exit,                           "exit",                           1, 1, INT, {INT}},
    {&__NR_exit_group,                     "exit_group",                     1, 1, INT, {INT}},
    {&__NR_read,                           "read",                           3, 1, LONG, {INT, BUF, ULONG}},
    {&__NR_write,                          "write",                          3, 3, LONG, {INT, BUF, ULONG}},
    {&__NR_open,                           "open",                           3, 3, INT, {STR, INT, OCTAL|INT}},
    {&__NR_close,                          "close",                          1, 1, INT, {INT}},
    {&__NR_brk,                            "brk",                            1, 1, ULONG, {ULONG}},
    {&__NR_stat,                           "stat",                           2, 1, INT, {STR, STAT}},
    {&__NR_fstat,                          "fstat",                          2, 1, INT, {INT, STAT}},
    {&__NR_lstat,                          "lstat",                          2, 1, INT, {INT, STAT}},
    {&__NR_poll,                           "poll",                           3, 3, INT, {PTR, INT, INT}},
    {&__NR_ppoll,                          "ppoll",                          4, 4, INT},
    {&__NR_lseek,                          "lseek",                          3, 3, LONG, {INT, LONG, INT}},
    {&__NR_mmap,                           "mmap",                           6, 6, ULONG, {PTR, ULONG, INT, INT, INT, ULONG}},
    {&__NR_msync,                          "msync",                          3, 3, INT, {PTR, ULONG, INT}},
    {&__NR_mprotect,                       "mprotect",                       3, 3, INT, {PTR, ULONG, INT}},
    {&__NR_munmap,                         "munmap",                         2, 2, INT, {PTR, ULONG}},
    {&__NR_sigreturn,                      "rt_sigreturn",                   6, 6, LONG},
    {&__NR_sigaction,                      "rt_sigaction",                   4, 4, INT, {SIG}},
    {&__NR_sigprocmask,                    "rt_sigprocmask",                 4, 4, INT, {INT, SIGSET, SIGSET, LONG}},
    {&__NR_sigpending,                     "rt_sigpending",                  2, 2, INT, {SIGSET, LONG}},
    {&__NR_sigsuspend,                     "rt_sigsuspend",                  2, 2, INT, {SIGSET, LONG}},
    {&__NR_rt_sigqueueinfo,                "rt_sigqueueinfo",                6, 6},
    {&__NR_ioctl,                          "ioctl",                          3, 3, INT, {INT, ULONG, ULONG}},
    {&__NR_pread,                          "pread64",                        4, 1, LONG, {INT, BUF, ULONG, ULONG}},
    {&__NR_pwrite,                         "pwrite64",                       4, 4, LONG, {INT, BUF, ULONG, ULONG}},
    {&__NR_readv,                          "readv",                          3, 1, LONG, {INT, IOV, INT}},
    {&__NR_writev,                         "writev",                         3, 3, LONG, {INT, IOV, INT}},
    {&__NR_access,                         "access",                         2, 2, INT, {STR, OCTAL|INT}},
    {&__NR_pipe,                           "pipe",                           1, 0, INT, {PIPE}},
    {&__NR_pipe2,                          "pipe2",                          2, 0, INT, {PIPE, INT}},
    {&__NR_select,                         "select",                         5, 5},
    {&__NR_pselect,                        "pselect",                        6, 6},
    {&__NR_pselect6,                       "pselect6",                       6, 6},
    {&__NR_sched_yield,                    "sched_yield",                    0, 0, INT},
    {&__NR_mremap,                         "mremap",                         5, 5},
    {&__NR_mincore,                        "mincore",                        6, 6},
    {&__NR_madvise,                        "madvise",                        6, 6},
    {&__NR_shmget,                         "shmget",                         6, 6},
    {&__NR_shmat,                          "shmat",                          6, 6},
    {&__NR_shmctl,                         "shmctl",                         6, 6},
    {&__NR_dup,                            "dup",                            1, 1, INT, {INT}},
    {&__NR_dup2,                           "dup2",                           2, 2, INT, {INT, INT}},
    {&__NR_pause,                          "pause",                          0, 0, INT},
    {&__NR_nanosleep,                      "nanosleep",                      2, 1},
    {&__NR_getitimer,                      "getitimer",                      2, 2},
    {&__NR_setitimer,                      "setitimer",                      3, 3},
    {&__NR_alarm,                          "alarm",                          1, 1},
    {&__NR_getpid,                         "getpid",                         0, 0, INT},
    {&__NR_sendfile,                       "sendfile",                       6, 6},
    {&__NR_socket,                         "socket",                         3, 3, INT, {INT, INT, INT}},
    {&__NR_connect,                        "connect",                        3, 3},
    {&__NR_accept,                         "accept",                         3, 3},
    {&__NR_sendto,                         "sendto",                         6, 6},
    {&__NR_recvfrom,                       "recvfrom",                       6, 6},
    {&__NR_sendmsg,                        "sendmsg",                        6, 6},
    {&__NR_recvmsg,                        "recvmsg",                        6, 6},
    {&__NR_shutdown,                       "shutdown",                       6, 6},
    {&__NR_bind,                           "bind",                           6, 6},
    {&__NR_listen,                         "listen",                         6, 6},
    {&__NR_getsockname,                    "getsockname",                    6, 6},
    {&__NR_getpeername,                    "getpeername",                    6, 6},
    {&__NR_socketpair,                     "socketpair",                     6, 6},
    {&__NR_setsockopt,                     "setsockopt",                     6, 6},
    {&__NR_getsockopt,                     "getsockopt",                     6, 6},
    {&__NR_fork,                           "fork",                           0, 0, INT},
    {&__NR_vfork,                          "vfork",                          0, 0, INT},
    {&__NR_posix_spawn,                    "posix_spawn",                    6, 6},
    {&__NR_execve,                         "execve",                         3, 3, INT, {STR, STRLIST, STRLIST}},
    {&__NR_wait4,                          "wait4",                          4, 4, INT, {INT, INTPTR, INT, PTR}},
    {&__NR_kill,                           "kill",                           2, 2, INT, {INT, SIG}},
    {&__NR_killpg,                         "killpg",                         2, 2, INT, {INT, SIG}},
    {&__NR_clone,                          "clone",                          5, 5, INT, {PTR, PTR, INTPTR, INTPTR, ULONG}},
    {&__NR_tkill,                          "tkill",                          2, 2, INT, {INT, SIG}},
    {&__NR_futex,                          "futex",                          6, 6},
    {&__NR_set_robust_list,                "set_robust_list",                6, 6},
    {&__NR_get_robust_list,                "get_robust_list",                6, 6},
    {&__NR_uname,                          "uname",                          6, 6},
    {&__NR_semget,                         "semget",                         6, 6},
    {&__NR_semop,                          "semop",                          6, 6},
    {&__NR_semctl,                         "semctl",                         6, 6},
    {&__NR_shmdt,                          "shmdt",                          6, 6},
    {&__NR_msgget,                         "msgget",                         6, 6},
    {&__NR_msgsnd,                         "msgsnd",                         6, 6},
    {&__NR_msgrcv,                         "msgrcv",                         6, 6},
    {&__NR_msgctl,                         "msgctl",                         6, 6},
    {&__NR_fcntl,                          "fcntl",                          3, 3, INT, {INT, INT, ULONG}},
    {&__NR_flock,                          "flock",                          6, 6},
    {&__NR_fsync,                          "fsync",                          6, 6},
    {&__NR_fdatasync,                      "fdatasync",                      6, 6},
    {&__NR_truncate,                       "truncate",                       2, 2, INT, {STR, ULONG}},
    {&__NR_ftruncate,                      "ftruncate",                      6, 6, INT, {INT, ULONG}},
    {&__NR_getcwd,                         "getcwd",                         2, 2, INT, {BUF, ULONG}},
    {&__NR_chdir,                          "chdir",                          1, 1, INT, {STR}},
    {&__NR_fchdir,                         "fchdir",                         1, 1, INT, {INT}},
    {&__NR_rename,                         "rename",                         2, 2, INT, {STR, STR}},
    {&__NR_mkdir,                          "mkdir",                          2, 2, INT, {STR, OCTAL|INT}},
    {&__NR_rmdir,                          "rmdir",                          1, 1, INT, {STR}},
    {&__NR_creat,                          "creat",                          2, 2, INT, {STR, OCTAL|INT}},
    {&__NR_link,                           "link",                           2, 2, INT, {STR, STR}},
    {&__NR_unlink,                         "unlink",                         1, 1, INT, {STR}},
    {&__NR_symlink,                        "symlink",                        6, 6},
    {&__NR_readlink,                       "readlink",                       3, 1, INT, {STR, BUF, ULONG}},
    {&__NR_chmod,                          "chmod",                          6, 6},
    {&__NR_fchmod,                         "fchmod",                         6, 6},
    {&__NR_chown,                          "chown",                          6, 6},
    {&__NR_fchown,                         "fchown",                         6, 6},
    {&__NR_lchown,                         "lchown",                         6, 6},
    {&__NR_umask,                          "umask",                          1, 1, OCTAL|INT, {OCTAL|INT}},
    {&__NR_gettimeofday,                   "gettimeofday",                   6, 6},
    {&__NR_getrlimit,                      "getrlimit",                      6, 6},
    {&__NR_getrusage,                      "getrusage",                      6, 6},
    {&__NR_sysinfo,                        "sysinfo",                        6, 6},
    {&__NR_times,                          "times",                          6, 6},
    {&__NR_ptrace,                         "ptrace",                         6, 6},
    {&__NR_syslog,                         "syslog",                         6, 6},
    {&__NR_getuid,                         "getuid",                         0, 0, INT},
    {&__NR_getgid,                         "getgid",                         0, 0, INT},
    {&__NR_getppid,                        "getppid",                        0, 0, INT},
    {&__NR_getpgrp,                        "getpgrp",                        0, 0, INT},
    {&__NR_setsid,                         "setsid",                         1, 1, INT, {INT}},
    {&__NR_getsid,                         "getsid",                         0, 0, INT},
    {&__NR_getpgid,                        "getpgid",                        0, 0, INT},
    {&__NR_setpgid,                        "setpgid",                        2, 2, INT, {INT, INT}},
    {&__NR_geteuid,                        "geteuid",                        0, 0, INT},
    {&__NR_getegid,                        "getegid",                        0, 0, INT},
    {&__NR_getgroups,                      "getgroups",                      6, 6},
    {&__NR_setgroups,                      "setgroups",                      6, 6},
    {&__NR_setreuid,                       "setreuid",                       6, 6},
    {&__NR_setregid,                       "setregid",                       6, 6},
    {&__NR_setuid,                         "setuid",                         1, 1, INT, {INT}},
    {&__NR_setgid,                         "setgid",                         1, 1, INT, {INT}},
    {&__NR_setresuid,                      "setresuid",                      6, 6},
    {&__NR_setresgid,                      "setresgid",                      6, 6},
    {&__NR_getresuid,                      "getresuid",                      6, 6},
    {&__NR_getresgid,                      "getresgid",                      6, 6},
    {&__NR_sigaltstack,                    "sigaltstack",                    6, 6},
    {&__NR_mknod,                          "mknod",                          6, 6},
    {&__NR_mknodat,                        "mknodat",                        6, 6},
    {&__NR_mkfifo,                         "mkfifo",                         6, 6},
    {&__NR_mkfifoat,                       "mkfifoat",                       6, 6},
    {&__NR_statfs,                         "statfs",                         6, 6},
    {&__NR_fstatfs,                        "fstatfs",                        6, 6},
    {&__NR_getpriority,                    "getpriority",                    6, 6},
    {&__NR_setpriority,                    "setpriority",                    6, 6},
    {&__NR_mlock,                          "mlock",                          6, 6},
    {&__NR_munlock,                        "munlock",                        6, 6},
    {&__NR_mlockall,                       "mlockall",                       6, 6},
    {&__NR_munlockall,                     "munlockall",                     6, 6},
    {&__NR_setrlimit,                      "setrlimit",                      6, 6},
    {&__NR_chroot,                         "chroot",                         6, 6},
    {&__NR_sync,                           "sync",                           6, 6},
    {&__NR_acct,                           "acct",                           6, 6},
    {&__NR_settimeofday,                   "settimeofday",                   6, 6},
    {&__NR_mount,                          "mount",                          6, 6},
    {&__NR_reboot,                         "reboot",                         6, 6},
    {&__NR_quotactl,                       "quotactl",                       6, 6},
    {&__NR_setfsuid,                       "setfsuid",                       6, 6},
    {&__NR_setfsgid,                       "setfsgid",                       6, 6},
    {&__NR_capget,                         "capget",                         6, 6},
    {&__NR_capset,                         "capset",                         6, 6},
    {&__NR_sigtimedwait,                   "sigtimedwait",                   6, 6},
    {&__NR_personality,                    "personality",                    6, 6},
    {&__NR_ustat,                          "ustat",                          6, 6},
    {&__NR_sysfs,                          "sysfs",                          6, 6},
    {&__NR_sched_setparam,                 "sched_setparam",                 6, 6},
    {&__NR_sched_getparam,                 "sched_getparam",                 6, 6},
    {&__NR_sched_setscheduler,             "sched_setscheduler",             6, 6},
    {&__NR_sched_getscheduler,             "sched_getscheduler",             6, 6},
    {&__NR_sched_get_priority_max,         "sched_get_priority_max",         6, 6},
    {&__NR_sched_get_priority_min,         "sched_get_priority_min",         6, 6},
    {&__NR_sched_rr_get_interval,          "sched_rr_get_interval",          6, 6},
    {&__NR_vhangup,                        "vhangup",                        6, 6},
    {&__NR_modify_ldt,                     "modify_ldt",                     6, 6},
    {&__NR_pivot_root,                     "pivot_root",                     6, 6},
    {&__NR__sysctl,                        "_sysctl",                        6, 6},
    {&__NR_prctl,                          "prctl",                          6, 6},
    {&__NR_arch_prctl,                     "arch_prctl",                     2, 2, INT, {INT, ULONG}},
    {&__NR_adjtimex,                       "adjtimex",                       6, 6},
    {&__NR_umount2,                        "umount2",                        6, 6},
    {&__NR_swapon,                         "swapon",                         6, 6},
    {&__NR_swapoff,                        "swapoff",                        6, 6},
    {&__NR_sethostname,                    "sethostname",                    6, 6},
    {&__NR_setdomainname,                  "setdomainname",                  6, 6},
    {&__NR_iopl,                           "iopl",                           6, 6},
    {&__NR_ioperm,                         "ioperm",                         6, 6},
    {&__NR_init_module,                    "init_module",                    6, 6},
    {&__NR_delete_module,                  "delete_module",                  6, 6},
    {&__NR_gettid,                         "gettid",                         6, 6},
    {&__NR_readahead,                      "readahead",                      6, 6},
    {&__NR_setxattr,                       "setxattr",                       6, 6},
    {&__NR_fsetxattr,                      "fsetxattr",                      6, 6},
    {&__NR_getxattr,                       "getxattr",                       6, 6},
    {&__NR_fgetxattr,                      "fgetxattr",                      6, 6},
    {&__NR_listxattr,                      "listxattr",                      6, 6},
    {&__NR_flistxattr,                     "flistxattr",                     6, 6},
    {&__NR_removexattr,                    "removexattr",                    6, 6},
    {&__NR_fremovexattr,                   "fremovexattr",                   6, 6},
    {&__NR_lsetxattr,                      "lsetxattr",                      6, 6},
    {&__NR_lgetxattr,                      "lgetxattr",                      6, 6},
    {&__NR_llistxattr,                     "llistxattr",                     6, 6},
    {&__NR_lremovexattr,                   "lremovexattr",                   6, 6},
    {&__NR_sched_setaffinity,              "sched_setaffinity",              6, 6},
    {&__NR_sched_getaffinity,              "sched_getaffinity",              6, 6},
    {&__NR_cpuset_getaffinity,             "cpuset_getaffinity",             6, 6},
    {&__NR_cpuset_setaffinity,             "cpuset_setaffinity",             6, 6},
    {&__NR_io_setup,                       "io_setup",                       6, 6},
    {&__NR_io_destroy,                     "io_destroy",                     6, 6},
    {&__NR_io_getevents,                   "io_getevents",                   6, 6},
    {&__NR_io_submit,                      "io_submit",                      6, 6},
    {&__NR_io_cancel,                      "io_cancel",                      6, 6},
    {&__NR_lookup_dcookie,                 "lookup_dcookie",                 6, 6},
    {&__NR_epoll_create,                   "epoll_create",                   6, 6},
    {&__NR_epoll_wait,                     "epoll_wait",                     6, 6},
    {&__NR_epoll_ctl,                      "epoll_ctl",                      6, 6},
    {&__NR_getdents,                       "getdents64",                     6, 6},
    {&__NR_set_tid_address,                "set_tid_address",                1, 1},
    {&__NR_restart_syscall,                "restart_syscall",                6, 6},
    {&__NR_semtimedop,                     "semtimedop",                     6, 6},
    {&__NR_fadvise,                        "fadvise",                        6, 6},
    {&__NR_timer_create,                   "timer_create",                   6, 6},
    {&__NR_timer_settime,                  "timer_settime",                  6, 6},
    {&__NR_timer_gettime,                  "timer_gettime",                  6, 6},
    {&__NR_timer_getoverrun,               "timer_getoverrun",               6, 6},
    {&__NR_timer_delete,                   "timer_delete",                   6, 6},
    {&__NR_clock_settime,                  "clock_settime",                  6, 6},
    {&__NR_clock_gettime,                  "clock_gettime",                  6, 6},
    {&__NR_clock_getres,                   "clock_getres",                   6, 6},
    {&__NR_clock_nanosleep,                "clock_nanosleep",                6, 6},
    {&__NR_tgkill,                         "tgkill",                         6, 6},
    {&__NR_mbind,                          "mbind",                          6, 6},
    {&__NR_set_mempolicy,                  "set_mempolicy",                  6, 6},
    {&__NR_get_mempolicy,                  "get_mempolicy",                  6, 6},
    {&__NR_mq_open,                        "mq_open",                        6, 6},
    {&__NR_mq_unlink,                      "mq_unlink",                      6, 6},
    {&__NR_mq_timedsend,                   "mq_timedsend",                   6, 6},
    {&__NR_mq_timedreceive,                "mq_timedreceive",                6, 6},
    {&__NR_mq_notify,                      "mq_notify",                      6, 6},
    {&__NR_mq_getsetattr,                  "mq_getsetattr",                  6, 6},
    {&__NR_kexec_load,                     "kexec_load",                     6, 6},
    {&__NR_waitid,                         "waitid",                         6, 6},
    {&__NR_add_key,                        "add_key",                        6, 6},
    {&__NR_request_key,                    "request_key",                    6, 6},
    {&__NR_keyctl,                         "keyctl",                         6, 6},
    {&__NR_ioprio_set,                     "ioprio_set",                     6, 6},
    {&__NR_ioprio_get,                     "ioprio_get",                     6, 6},
    {&__NR_inotify_init,                   "inotify_init",                   6, 6},
    {&__NR_inotify_add_watch,              "inotify_add_watch",              6, 6},
    {&__NR_inotify_rm_watch,               "inotify_rm_watch",               6, 6},
    {&__NR_openat,                         "openat",                         4, 4, INT, {INT, STR, INT, OCTAL|INT}},
    {&__NR_mkdirat,                        "mkdirat",                        3, 3, INT, {INT, STR, OCTAL|INT}},
    {&__NR_fchownat,                       "fchownat",                       6, 6},
    {&__NR_utime,                          "utime",                          6, 6},
    {&__NR_utimes,                         "utimes",                         6, 6},
    {&__NR_futimesat,                      "futimesat",                      6, 6},
    {&__NR_futimes,                        "futimes",                        6, 6},
    {&__NR_futimens,                       "futimens",                       6, 6},
    {&__NR_fstatat,                        "newfstatat",                     4, 2, INT, {INT, STR, STAT, INT}},
    {&__NR_unlinkat,                       "unlinkat",                       3, 3, INT, {INT, STR, INT}},
    {&__NR_renameat,                       "renameat",                       4, 4, INT, {INT, STR, INT, STR}},
    {&__NR_linkat,                         "linkat",                         6, 6},
    {&__NR_symlinkat,                      "symlinkat",                      6, 6},
    {&__NR_readlinkat,                     "readlinkat",                     6, 6},
    {&__NR_fchmodat,                       "fchmodat",                       6, 6},
    {&__NR_faccessat,                      "faccessat",                      4, 4, INT, {INT, STR, INT, INT}},
    {&__NR_unshare,                        "unshare",                        6, 6},
    {&__NR_splice,                         "splice",                         6, 6},
    {&__NR_tee,                            "tee",                            6, 6},
    {&__NR_sync_file_range,                "sync_file_range",                4, 4},
    {&__NR_vmsplice,                       "vmsplice",                       6, 6},
    {&__NR_migrate_pages,                  "migrate_pages",                  6, 6},
    {&__NR_move_pages,                     "move_pages",                     6, 6},
    {&__NR_preadv,                         "preadv",                         4, 1, LONG, {INT, IOV, ULONG, ULONG}},
    {&__NR_pwritev,                        "pwritev",                        6, 6, LONG, {INT, IOV, ULONG, ULONG}},
    {&__NR_utimensat,                      "utimensat",                      6, 6},
    {&__NR_fallocate,                      "fallocate",                      6, 6},
    {&__NR_posix_fallocate,                "posix_fallocate",                6, 6},
    {&__NR_accept4,                        "accept4",                        4, 4},
    {&__NR_dup3,                           "dup3",                           3, 3, INT},
    {&__NR_epoll_pwait,                    "epoll_pwait",                    6, 6},
    {&__NR_epoll_create1,                  "epoll_create1",                  6, 6},
    {&__NR_perf_event_open,                "perf_event_open",                6, 6},
    {&__NR_inotify_init1,                  "inotify_init1",                  6, 6},
    {&__NR_rt_tgsigqueueinfo,              "rt_tgsigqueueinfo",              6, 6},
    {&__NR_signalfd,                       "signalfd",                       6, 6},
    {&__NR_signalfd4,                      "signalfd4",                      6, 6},
    {&__NR_eventfd,                        "eventfd",                        6, 6},
    {&__NR_eventfd2,                       "eventfd2",                       6, 6},
    {&__NR_timerfd_create,                 "timerfd_create",                 6, 6},
    {&__NR_timerfd_settime,                "timerfd_settime",                6, 6},
    {&__NR_timerfd_gettime,                "timerfd_gettime",                6, 6},
    {&__NR_recvmmsg,                       "recvmmsg",                       6, 6},
    {&__NR_fanotify_init,                  "fanotify_init",                  6, 6},
    {&__NR_fanotify_mark,                  "fanotify_mark",                  6, 6},
    {&__NR_prlimit,                        "prlimit",                        6, 6},
    {&__NR_name_to_handle_at,              "name_to_handle_at",              6, 6},
    {&__NR_open_by_handle_at,              "open_by_handle_at",              6, 6},
    {&__NR_clock_adjtime,                  "clock_adjtime",                  6, 6},
    {&__NR_syncfs,                         "syncfs",                         6, 6},
    {&__NR_sendmmsg,                       "sendmmsg",                       6, 6},
    {&__NR_setns,                          "setns",                          6, 6},
    {&__NR_getcpu,                         "getcpu",                         6, 6},
    {&__NR_process_vm_readv,               "process_vm_readv",               6, 6},
    {&__NR_process_vm_writev,              "process_vm_writev",              6, 6},
    {&__NR_kcmp,                           "kcmp",                           6, 6},
    {&__NR_finit_module,                   "finit_module",                   6, 6},
    {&__NR_sched_setattr,                  "sched_setattr",                  6, 6},
    {&__NR_sched_getattr,                  "sched_getattr",                  6, 6},
    {&__NR_renameat2,                      "renameat2",                      6, 6},
    {&__NR_seccomp,                        "seccomp",                        6, 6},
    {&__NR_getrandom,                      "getrandom",                      6, 6},
    {&__NR_memfd_create,                   "memfd_create",                   6, 6},
    {&__NR_kexec_file_load,                "kexec_file_load",                6, 6},
    {&__NR_bpf,                            "bpf",                            6, 6},
    {&__NR_execveat,                       "execveat",                       6, 6},
    {&__NR_userfaultfd,                    "userfaultfd",                    6, 6},
    {&__NR_membarrier,                     "membarrier",                     6, 6},
    {&__NR_mlock2,                         "mlock2",                         6, 6},
    {&__NR_copy_file_range,                "copy_file_range",                6, 6},
    {&__NR_preadv2,                        "preadv2",                        6, 6},
    {&__NR_pwritev2,                       "pwritev2",                       6, 6},
    {&__NR_pkey_mprotect,                  "pkey_mprotect",                  6, 6},
    {&__NR_pkey_alloc,                     "pkey_alloc",                     6, 6},
    {&__NR_pkey_free,                      "pkey_free",                      6, 6},
    {&__NR_statx,                          "statx",                          6, 6},
    {&__NR_io_pgetevents,                  "io_pgetevents",                  6, 6},
    {&__NR_rseq,                           "rseq",                           6, 6},
    {&__NR_pidfd_send_signal,              "pidfd_send_signal",              6, 6},
    {&__NR_io_uring_setup,                 "io_uring_setup",                 6, 6},
    {&__NR_io_uring_enter,                 "io_uring_enter",                 6, 6},
    {&__NR_io_uring_register,              "io_uring_register",              6, 6},
    // clang-format on
};

static const struct Errno {
  errno_t *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 {
  int pid;
  uint32_t hash;
  bool insyscall;
  struct Syscall *call;
  struct Syscall fakecall;
  struct user_regs_struct args;
  char fakename[12];
};

struct PidList {
  uint32_t i, n;
  struct Pid *p;
};

char *ob;        // output buffer
struct Pid *sp;  // active subprocess

static uint32_t Hash(uint64_t pid) {
  uint32_t hash;
  hash = (pid * 6364136223846793005 + 1442695040888963407) >> 32;
  if (!hash) hash = 1;
  return hash;
}

static struct Pid *GetPid(struct PidList *list, int pid) {
  uint32_t i, hash, step;
  DCHECK_NE(0, pid);
  DCHECK_LE(list->i, list->n >> 1);
  if (list->n) {
    i = 0;
    step = 0;
    hash = Hash(pid);
    do {
      i = (hash + step * (step + 1) / 2) & (list->n - 1);
      if (list->p[i].pid == pid) {
        return list->p + i;
      }
      step++;
    } while (list->p[i].hash);
  }
  return 0;
}

static void ReservePid(struct PidList *list, int count) {
  size_t i, j, step;
  struct PidList old;
  DCHECK_LE(list->i, list->n >> 1);
  while (list->i + count >= (list->n >> 1)) {
    memcpy(&old, list, sizeof(*list));
    list->n = list->n ? list->n << 1 : 16;
    list->p = calloc(list->n, sizeof(*list->p));
    for (i = 0; i < old.n; ++i) {
      if (!old.p[i].hash) continue;
      step = 0;
      do {
        j = (old.p[i].hash + step * (step + 1) / 2) & (list->n - 1);
        step++;
      } while (list->p[j].hash);
      list->p[j] = old.p[i];
    }
    free(old.p);
  }
}

static struct Pid *AddPid(struct PidList *list, int pid) {
  struct Pid *item;
  uint32_t i, hash, step;
  DCHECK_NE(0, pid);
  DCHECK_LE(list->i, list->n >> 1);
  if (!(item = GetPid(list, pid))) {
    ReservePid(list, 1);
    hash = Hash(pid);
    ++list->i;
    i = 0;
    step = 0;
    do {
      i = (hash + step * (step + 1) / 2) & (list->n - 1);
      step++;
    } while (list->p[i].hash);
    list->p[i].hash = hash;
    list->p[i].pid = pid;
    item = list->p + i;
  }
  return item;
}

static void RemovePid(struct PidList *list, int pid) {
  uint32_t i, hash, step;
  DCHECK_NE(0, pid);
  DCHECK_LE(list->i, list->n >> 1);
  if (list->n) {
    i = 0;
    step = 0;
    hash = Hash(pid);
    do {
      i = (hash + step * (step + 1) / 2) & (list->n - 1);
      if (list->p[i].pid == pid) {
        bzero(list->p + i, sizeof(*list->p));
        list->i--;
        assert(list->i < 99999);
        return;
      }
      step++;
    } while (list->p[i].hash);
  }
}

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) {
  const char *s;
  static char buf[16];
  if ((s = _strerrno(x))) return s;
  FormatInt64(buf, x);
  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;
  errno = 0;
  u.word = ptrace(PTRACE_PEEKTEXT, sp->pid, addr);
  if (errno) return 0;
  n = strnlen(u.buf + offset, 8 - offset);
  p = calloc(1, n);
  memcpy(p, u.buf + offset, n);
  if (n == 8 - offset) {
    do {
      addr += 8;
      errno = 0;
      u.word = ptrace(PTRACE_PEEKDATA, sp->pid, addr);
      if (errno) break;
      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 void *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;
  errno = 0;
  u.word = ptrace(PTRACE_PEEKTEXT, sp->pid, addr);
  if (errno) return 0;
  p = calloc(1, size);
  memcpy(p, u.buf + offset, MIN(size, 8 - offset));
  for (i = 8 - offset; i < size; i += MIN(8, size - i)) {
    addr += 8;
    errno = 0;
    u.word = ptrace(PTRACE_PEEKDATA, sp->pid, addr);
    if (errno) break;
    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 = calloc(1, len * 16);
  for (i = 0; i < len * 2; ++i, addr += sizeof(word)) {
    errno = 0;
    word = ptrace(PTRACE_PEEKTEXT, sp->pid, addr);
    if (errno) break;
    memcpy(p + i * sizeof(word), &word, sizeof(word));
  }
  iov = (struct iovec *)p;
  for (i = 0; i < len; ++i) {
    errno = 0;
    iov[i].iov_base = PeekData((long)iov[i].iov_base, iov[i].iov_len);
    if (errno) break;
  }
  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 void PrintPipe(unsigned long addr) {
  unsigned long word;
  word = ptrace(PTRACE_PEEKDATA, sp->pid, addr);
  kappendf(&ob, "[{%d, %d}]", (int)word, (int)(word >> 32));
}

static struct stat *PeekStat(unsigned long addr) {
  int i;
  char *p;
  long word;
  if (!addr) return NULL;
  p = calloc(1, sizeof(struct stat));
  for (i = 0; i < sizeof(struct stat); i += sizeof(word)) {
    errno = 0;
    word = ptrace(PTRACE_PEEKTEXT, sp->pid, addr + i);
    if (errno) break;
    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 PrintSigset(unsigned long p) {
  kappendf(&ob, "{%#lx}", ptrace(PTRACE_PEEKTEXT, sp->pid, p));
}

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, MIN(32, y)), MIN(32, y)));
      break;
    case IOV:
      FreeIov(PrintIov(PeekIov(x, y), y), y);
      break;
    case STAT:
      free(PrintStat(PeekStat(x)));
      break;
    case PIPE:
      PrintPipe(x);
      break;
    case SIG:
      appends(&ob, strsignal(x));
      break;
    case SIGSET:
      PrintSigset(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(bool issecond) {
  int a, b, eager, arity;
  bool gotsome, isresuming, isunfinished;
  gotsome = false;
  if (issecond) {
    if (!sp->call) {
      return;
    }
  } else {
    if (!(sp->call = GetSyscall(sp->args.orig_rax))) {
      ksnprintf(sp->fakename, sizeof(sp->fakename), "%d", sp->args.orig_rax);
      bzero(&sp->fakecall, sizeof(sp->fakecall));
      sp->call = &sp->fakecall;
      sp->call->name = sp->fakename;
      sp->call->arity = 6;
      sp->call->eager = 6;
    }
    if (sp->args.orig_rax == __NR_execve) {
      if (sp->args.rax == -ENOSYS) {
        sp->args.rax = 0;
      }
    } else if (sp->args.rax == -ENOSYS) {
      return;
    }
  }
  isresuming = false;
  isunfinished = false;
  if (!isunfinished) {
    a = 0;
    b = sp->call->arity;
  } else if (!isresuming) {
    a = 0;
    b = sp->call->eager;
  } else {
    a = sp->call->eager;
    b = sp->call->arity;
  }
  kappendf(&ob, PROLOGUE " %s%s(", sp->pid, isresuming ? "<resuming> " : "",
           sp->call->name);
  if (a <= 0 && 0 < b) {
    if (gotsome) appendw(&ob, READ16LE(", "));
    PrintSyscallArg(sp->call->arg[0], sp->args.rdi, sp->args.rsi);
    gotsome = true;
  }
  if (a <= 1 && 1 < b) {
    if (gotsome) appendw(&ob, READ16LE(", "));
    PrintSyscallArg(sp->call->arg[1], sp->args.rsi, sp->args.rdx);
    gotsome = true;
  }
  if (a <= 2 && 2 < b) {
    if (gotsome) appendw(&ob, READ16LE(", "));
    PrintSyscallArg(sp->call->arg[2], sp->args.rdx, sp->args.r10);
    gotsome = true;
  }
  if (a <= 3 && 3 < b) {
    if (gotsome) appendw(&ob, READ16LE(", "));
    PrintSyscallArg(sp->call->arg[3], sp->args.r10, sp->args.r8);
    gotsome = true;
  }
  if (a <= 4 && 4 < b) {
    if (gotsome) appendw(&ob, READ16LE(", "));
    PrintSyscallArg(sp->call->arg[4], sp->args.r8, sp->args.r9);
    gotsome = true;
  }
  if (a <= 5 && 5 < b) {
    if (gotsome) appendw(&ob, READ16LE(", "));
    PrintSyscallArg(sp->call->arg[5], sp->args.r9, 0);
    gotsome = true;
  }
  if (isunfinished) {
    appends(&ob, ") → <unfinished>");
  } else {
    appends(&ob, ") → ");
    if (sp->args.rax > (unsigned long)-4096) {
      kappendf(&ob, "-1 %s", GetErrnoName(-sp->args.rax));
    } else {
      PrintSyscallArg(sp->call->ret, sp->args.rax, 0);
    }
  }
  kappendf(&ob, "%n");
  Flush();
  sp->call = 0;
}

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[]) {
  unsigned long msg;
  struct siginfo si;
  struct Pid *s, *child;
  struct PidList pidlist;
  sigset_t mask, origmask;
  int i, sig, evpid, root, wstatus, signal;
  struct sigaction sigign, saveint, savequit;

  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.i = 0;
  pidlist.n = 0;
  pidlist.p = 0;

  sigign.sa_flags = 0;
  sigign.sa_handler = SIG_IGN;
  sigemptyset(&sigign.sa_mask);

  sigaction(SIGINT, &sigign, &saveint);
  sigaction(SIGQUIT, &sigign, &savequit);

  sigemptyset(&mask);
  /* sigaddset(&mask, SIGCHLD); */
  sigprocmask(SIG_BLOCK, &mask, &origmask);

  CHECK_NE(-1, (root = fork()));
  if (!root) {
    sigaction(SIGINT, &saveint, 0);
    sigaction(SIGQUIT, &savequit, 0);
    sigprocmask(SIG_SETMASK, &origmask, 0);
    ptrace(PTRACE_TRACEME);
    execvp(argv[1], argv + 1);
    _Exit(127);
  }
  sp = AddPid(&pidlist, root);

  // 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 | PTRACE_O_TRACESYSGOOD)));

  // continue child process setting breakpoint at next system call
  CHECK_NE(-1, ptrace(PTRACE_SYSCALL, sp->pid, 0, 0));

  for (;;) {
    CHECK_NE(-1, (evpid = waitpid(-1, &wstatus, __WALL)));

    // prevent rehash in second addpid call
    ReservePid(&pidlist, 2);

    // we can get wait notifications before fork/vfork/etc. events
    sp = AddPid(&pidlist, evpid);

    // handle actual exit
    if (WIFEXITED(wstatus)) {
      kprintf(PROLOGUE " exited with %d%n", sp->pid, WEXITSTATUS(wstatus));
      RemovePid(&pidlist, sp->pid);
      sp = 0;
      // we exit when the last process being monitored exits
      if (!pidlist.i) {
        PropagateExit(wstatus);
      } else {
        continue;
      }
    }

    // handle actual kill
    if (WIFSIGNALED(wstatus)) {
      kprintf(PROLOGUE " exited with signal %G%n", sp->pid, WTERMSIG(wstatus));
      RemovePid(&pidlist, sp->pid);
      sp = 0;
      // we die when the last process being monitored dies
      if (!pidlist.i) {
        PropagateTermination(wstatus);
      } else {
        continue;
      }
    }

    // handle core dump
    if (WCOREDUMP(wstatus)) {
      kprintf(PROLOGUE " exited with core%n", sp->pid);
      RemovePid(&pidlist, sp->pid);
      sp = 0;
      // we die when the last process being monitored dies
      if (!pidlist.i) {
        PropagateTermination(wstatus);
      } else {
        continue;
      }
    }

    // handle trace events
    sig = 0;
    signal = (wstatus >> 8) & 0xffff;
    assert(WIFSTOPPED(wstatus));
    if (signal == SIGTRAP | PTRACE_EVENT_STOP) {
      CHECK_NE(-1, ptrace(PTRACE_GETSIGINFO, sp->pid, 0, &si));
      if (si.si_code == SIGTRAP || si.si_code == SIGTRAP | 0x80) {
        CHECK_NE(-1, ptrace(PTRACE_GETREGS, sp->pid, 0, &sp->args));
        PrintSyscall(sp->insyscall);
        sp->insyscall = !sp->insyscall;
        ptrace(PTRACE_SYSCALL, sp->pid, 0, 0);
      } else {
        sig = signal & 127;
        kappendf(&ob, PROLOGUE " got signal %G%n", sp->pid, sig);
        Flush();
        ptrace(PTRACE_SYSCALL, sp->pid, 0, sig);
      }
    } else if (signal == (SIGTRAP | (PTRACE_EVENT_EXIT << 8))) {
      CHECK_NE(-1, ptrace(PTRACE_GETEVENTMSG, sp->pid, 0, &msg));
      sig = WSTOPSIG(wstatus);
      ptrace(PTRACE_SYSCALL, sp->pid, 0, 0);
    } else if (signal == (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
      CHECK_NE(-1, ptrace(PTRACE_GETEVENTMSG, sp->pid, 0, &msg));
      ptrace(PTRACE_SYSCALL, sp->pid, 0, 0);
    } else if (signal == (SIGTRAP | (PTRACE_EVENT_FORK << 8)) ||
               signal == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) ||
               signal == (SIGTRAP | (PTRACE_EVENT_CLONE << 8))) {
      CHECK_NE(-1, ptrace(PTRACE_GETEVENTMSG, evpid, 0, &msg));
      child = AddPid(&pidlist, msg);
      child->pid = msg;
      if (signal == (SIGTRAP | (PTRACE_EVENT_FORK << 8))) {
        kappendf(&ob, PROLOGUE " fork() → %d%n", sp->pid, child->pid);
      } else if (signal == (SIGTRAP | (PTRACE_EVENT_VFORK << 8))) {
        kappendf(&ob, PROLOGUE " vfork() → %d%n", sp->pid, child->pid);
      } else {
        kappendf(&ob, PROLOGUE " clone() → %d%n", sp->pid, child->pid);
      }
      Flush();
      ptrace(PTRACE_SYSCALL, child->pid, 0, 0);
      ptrace(PTRACE_SYSCALL, sp->pid, 0, 0);
    } else {
      kappendf(&ob, PROLOGUE " gottish signal %G%n", sp->pid, sig);
      Flush();
      ptrace(PTRACE_SYSCALL, sp->pid, 0, signal & 127);
    }
  }
}

int main(int argc, char *argv[]) {
  StraceMain(argc, argv);
}