cosmopolitan/tool/build/strace.c
Justine Tunney 072e1d2910 Make signal handling work well across platforms
- Fix sigsuspend() on XNU
- Fix strsignal() on non-Linux
- Add unit tests for strsignal()
- Add unit tests for setitimer()
- Add unit tests for sigsuspend()
- Rewrite setitimer() for New Technology
- Rewrite nanosleep() for New Technology
- Polyfill SIGALRM on the New Technology
- select(0,0,0,0) on NT now calls pause()
- Remove some NTDLL calls that aren't needed
- Polyfill SA_NOCLDWAIT on the New Technology
- Polyfill SA_RESETHAND on the New Technology
- Polyfill sigprocmask() on the New Technology
- Polyfill SIGCHLD+SIG_IGN on the New Technology
- Polyfill SA_RESTART masking on the New Technology
- Deliver console signals from main thread on New Technology
- Document SA_RESTART behavior w/ @sarestartable / @norestart
- System call trace in MODE=dbg now prints inherited FDs and signal mask
2022-03-25 07:28:57 -07:00

1056 lines
49 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 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 SIGSET 10
#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, "rt_sigaction", 4, INT, {SIG}},
{&__NR_sigprocmask, "rt_sigprocmask", 4, INT, {INT, SIGSET, SIGSET, LONG}},
{&__NR_sigpending, "rt_sigpending", 2, INT, {SIGSET, LONG}},
{&__NR_sigsuspend, "rt_sigsuspend", 2, INT, {SIGSET, LONG}},
{&__NR_rt_sigqueueinfo, "rt_sigqueueinfo", 6},
{&__NR_ioctl, "ioctl", 3, INT, {INT, ULONG, ULONG}},
{&__NR_pread, "pread64", 4, LONG, {INT, BUF, ULONG, ULONG}},
{&__NR_pwrite, "pwrite64", 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_pipe2, "pipe2", 2, 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_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_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, "getdents64", 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, "newfstatat", 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_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 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;
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 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, 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 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(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);
}