This is most notably lacking -p and -f, and only covers a small subset
of system calls, but it's "useful" in the sense that you can strace
something like toybox date, say, and see everything.

There's a fundamental assumption here that we don't need to worry about
multiple personalities. (This is what makes "real" strace's build so
complex, and why Android has _never_ actually shipped a fully correct
strace build.) Given that the desktop's already been 64-bit only
for a while, microcontrollers are likely to stay 32-bit only for a
while, and the odd one out -- mobile -- is moving to be 64-bit only
(https://www.arm.com/blogs/blueprint/64-bit), so by the time this is
ready to graduate from pending, I'm assuming no-one will care!
---
 lib/portability.h     |   2 +
 toys/pending/strace.c | 618 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 620 insertions(+)
 create mode 100644 toys/pending/strace.c
From 9e58a04ad2eed7576c0760e5eb4af2724b63aa65 Mon Sep 17 00:00:00 2001
From: Elliott Hughes <[email protected]>
Date: Fri, 17 Sep 2021 14:27:44 -0700
Subject: [PATCH] strace: initial commit.

This is most notably lacking -p and -f, and only covers a small subset
of system calls, but it's "useful" in the sense that you can strace
something like toybox date, say, and see everything.

There's a fundamental assumption here that we don't need to worry about
multiple personalities. (This is what makes "real" strace's build so
complex, and why Android has _never_ actually shipped a fully correct
strace build.) Given that the desktop's already been 64-bit only
for a while, microcontrollers are likely to stay 32-bit only for a
while, and the odd one out -- mobile -- is moving to be 64-bit only
(https://www.arm.com/blogs/blueprint/64-bit), so by the time this is
ready to graduate from pending, I'm assuming no-one will care!
---
 lib/portability.h     |   2 +
 toys/pending/strace.c | 618 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 620 insertions(+)
 create mode 100644 toys/pending/strace.c

diff --git a/lib/portability.h b/lib/portability.h
index bb792f10..22e82ad6 100644
--- a/lib/portability.h
+++ b/lib/portability.h
@@ -371,3 +371,5 @@ int dev_makedev(int major, int minor);
 char *fs_type_name(struct statfs *statfs);
 
 int get_block_device_size(int fd, unsigned long long *size);
+
+#include <sys/user.h>
diff --git a/toys/pending/strace.c b/toys/pending/strace.c
new file mode 100644
index 00000000..cd87dd87
--- /dev/null
+++ b/toys/pending/strace.c
@@ -0,0 +1,618 @@
+/* strace.c - Trace system calls.
+ *
+ * Copyright 2020 The Android Open Source Project
+
+USE_STRACE(NEWTOY(strace, "^p#s#v", TOYFLAG_USR|TOYFLAG_SBIN))
+
+config STRACE
+  bool "strace"
+  default n
+  help
+    usage: strace [-fv] [-p PID] [-s NUM] COMMAND [ARGS...]
+
+    Trace systems calls made by a process.
+
+    -s	String length limit.
+    -v	Dump all of large structs/arrays.
+*/
+
+#include <sys/ptrace.h>
+#include <sys/syscall.h>
+
+#define FOR_strace
+#include "toys.h"
+
+GLOBALS(
+  long s;
+  long p;
+
+  struct user_regs_struct regs;
+  pid_t pid;
+  char *fmt;
+  char ioctl[32];
+  int arg;
+)
+
+#if defined(__x86_64__)
+#define REG_SYSCALL TT.regs.orig_rax
+#define REG_ARG0 TT.regs.rdi
+#define REG_ARG1 TT.regs.rsi
+#define REG_ARG2 TT.regs.rdx
+#define REG_ARG3 TT.regs.r10
+#define REG_ARG4 TT.regs.r8
+#define REG_ARG5 TT.regs.r9
+#define REG_RESULT TT.regs.rax
+#elif defined(__i386__)
+#define REG_SYSCALL TT.regs.orig_eax
+#define REG_ARG0 TT.regs.ebx
+#define REG_ARG1 TT.regs.ecx
+#define REG_ARG2 TT.regs.edx
+#define REG_ARG3 TT.regs.esi
+#define REG_ARG4 TT.regs.edi
+#define REG_ARG5 TT.regs.ebp
+#define REG_RESULT TT.regs.eax
+#else
+// TODO: arm/arm64
+#error unsupported architecture
+#endif
+
+#define C(x) case x: return #x
+
+#define FS_IOC_FSGETXATTR 0x801c581f
+#define FS_IOC_FSSETXATTR 0x401c5820
+#define FS_IOC_GETFLAGS 0x80086601
+#define FS_IOC_SETFLAGS 0x40086602
+#define FS_IOC_GETVERSION 0x80087601
+#define FS_IOC_SETVERSION 0x40047602
+struct fsxattr {
+  unsigned fsx_xflags;
+  unsigned fsx_extsize;
+  unsigned fsx_nextents;
+  unsigned fsx_projid;
+  unsigned fsx_cowextsize;
+  char fsx_pad[8];
+};
+
+static char *strioctl(int i)
+{
+  switch (i) {
+    C(FS_IOC_FSGETXATTR);
+    C(FS_IOC_FSSETXATTR);
+    C(FS_IOC_GETFLAGS);
+    C(FS_IOC_GETVERSION);
+    C(FS_IOC_SETFLAGS);
+    C(FS_IOC_SETVERSION);
+    C(SIOCGIFADDR);
+    C(SIOCGIFBRDADDR);
+    C(SIOCGIFCONF);
+    C(SIOCGIFDSTADDR);
+    C(SIOCGIFFLAGS);
+    C(SIOCGIFHWADDR);
+    C(SIOCGIFMAP);
+    C(SIOCGIFMTU);
+    C(SIOCGIFNETMASK);
+    C(SIOCGIFTXQLEN);
+    C(TCGETS);
+    C(TCSETS);
+    C(TIOCGWINSZ);
+    C(TIOCSWINSZ);
+  }
+  sprintf(toybuf, "%#x", i);
+  return toybuf;
+}
+
+// TODO: move to lib, implement errno(1)?
+static char *strerrno(int e)
+{
+  switch (e) {
+    // uapi errno-base.h
+    C(EPERM);
+    C(ENOENT);
+    C(ESRCH);
+    C(EINTR);
+    C(EIO);
+    C(ENXIO);
+    C(E2BIG);
+    C(ENOEXEC);
+    C(EBADF);
+    C(ECHILD);
+    C(EAGAIN);
+    C(ENOMEM);
+    C(EACCES);
+    C(EFAULT);
+    C(ENOTBLK);
+    C(EBUSY);
+    C(EEXIST);
+    C(EXDEV);
+    C(ENODEV);
+    C(ENOTDIR);
+    C(EISDIR);
+    C(EINVAL);
+    C(ENFILE);
+    C(EMFILE);
+    C(ENOTTY);
+    C(ETXTBSY);
+    C(EFBIG);
+    C(ENOSPC);
+    C(ESPIPE);
+    C(EROFS);
+    C(EMLINK);
+    C(EPIPE);
+    C(EDOM);
+    C(ERANGE);
+    // uapi errno.h
+    C(EDEADLK);
+    C(ENAMETOOLONG);
+    C(ENOLCK);
+    C(ENOSYS);
+    C(ENOTEMPTY);
+    C(ELOOP);
+    C(ENOMSG);
+    // ...etc; fill in as we see them in practice?
+  }
+  sprintf(toybuf, "%d", e);
+  return toybuf;
+}
+
+#undef C
+
+static void xptrace(int req, pid_t pid, void *addr, void *data)
+{
+  if (ptrace(req, pid, addr, data)) perror_exit("ptrace pid %d", pid);
+}
+
+static void get_regs()
+{
+  xptrace(PTRACE_GETREGS, TT.pid, 0, &TT.regs);
+}
+
+static long get_arg()
+{
+  switch (TT.arg) {
+    case 0: return REG_ARG0;
+    case 1: return REG_ARG1;
+    case 2: return REG_ARG2;
+    case 3: return REG_ARG3;
+    case 4: return REG_ARG4;
+    case 5: return REG_ARG5;
+    default: error_exit("no arg %d", TT.arg);
+  }
+}
+
+static void ptrace_struct(long addr, void* dst, size_t bytes)
+{
+  int offset = 0, i;
+
+  for (i=0; i<bytes; i+=sizeof(long)) {
+    errno = 0;
+    long v = ptrace(PTRACE_PEEKDATA, TT.pid, addr + offset);
+    if (errno) perror_exit("PTRACE_PEEKDATA failed");
+    memcpy(dst + offset, &v, sizeof(v));
+    offset += sizeof(long);
+  }
+}
+
+// TODO: this all relies on having the libc structs match the kernel structs,
+// which isn't always true for glibc...
+static void print_struct(long addr)
+{
+  if (!addr) { // All NULLs look the same...
+    fprintf(stderr, "NULL");
+    while (*TT.fmt != '}') ++TT.fmt;
+    ++TT.fmt;
+  } else if (strstart(&TT.fmt, "ifreq}")) {
+    struct ifreq ir;
+
+    ptrace_struct(addr, &ir, sizeof(ir));
+    // TODO: is this always an ioctl? use REG_ARG1 to work out what to show.
+    fprintf(stderr, "{...}");
+  } else if (strstart(&TT.fmt, "fsxattr}")) {
+    struct fsxattr fx;
+
+    ptrace_struct(addr, &fx, sizeof(fx));
+    fprintf(stderr, "{fsx_xflags=%#x, fsx_extsize=%d, fsx_nextents=%d, "
+        "fsx_projid=%d, fsx_cowextsize=%d}", fx.fsx_xflags, fx.fsx_extsize,
+        fx.fsx_nextents, fx.fsx_projid, fx.fsx_cowextsize);
+  } else if (strstart(&TT.fmt, "long}")) {
+    long l;
+
+    ptrace_struct(addr, &l, sizeof(l));
+    fprintf(stderr, "%ld", l);
+  } else if (strstart(&TT.fmt, "longx}")) {
+    long l;
+
+    ptrace_struct(addr, &l, sizeof(l));
+    fprintf(stderr, "%#lx", l);
+  } else if (strstart(&TT.fmt, "rlimit}")) {
+    struct rlimit rl;
+
+    ptrace_struct(addr, &rl, sizeof(rl));
+    fprintf(stderr, "{rlim_cur=%lld, rlim_max=%lld}",
+        (long long)rl.rlim_cur, (long long)rl.rlim_max);
+  } else if (strstart(&TT.fmt, "sigset}")) {
+    long long ss;
+    int i;
+
+    ptrace_struct(addr, &ss, sizeof(ss));
+    fprintf(stderr, "[");
+    for (i=0; i<64;++i) {
+      // TODO: use signal names, fix spacing
+      if (ss & (1ULL<<i)) fprintf(stderr, "%d ", i);
+    }
+    fprintf(stderr, "]");
+  } else if (strstart(&TT.fmt, "stat}")) {
+    struct stat sb;
+
+    ptrace_struct(addr, &sb, sizeof(sb));
+    // TODO: decode IFMT bits in st_mode
+    if (FLAG(v)) {
+      // TODO: full atime/mtime/ctime dump.
+      fprintf(stderr, "{st_dev=makedev(%#x, %#x), st_ino=%ld, st_mode=%o, "
+          "st_nlink=%ld, st_uid=%d, st_gid=%d, st_blksize=%ld, st_blocks=%ld, "
+          "st_size=%lld, st_atime=%ld, st_mtime=%ld, st_ctime=%ld}",
+          dev_major(sb.st_dev), dev_minor(sb.st_dev), sb.st_ino, sb.st_mode,
+          sb.st_nlink, sb.st_uid, sb.st_gid, sb.st_blksize, sb.st_blocks,
+          (long long)sb.st_size, sb.st_atime, sb.st_mtime, sb.st_ctime);
+    } else {
+      fprintf(stderr, "{st_mode=%o, st_size=%lld, ...}", sb.st_mode,
+        (long long)sb.st_size);
+    }
+  } else if (strstart(&TT.fmt, "termios}")) {
+    struct termios to;
+
+    ptrace_struct(addr, &to, sizeof(to));
+    fprintf(stderr, "{c_iflag=%#lx, c_oflag=%#lx, c_cflag=%#lx, c_lflag=%#lx}",
+        (long)to.c_iflag, (long)to.c_oflag, (long)to.c_cflag, (long)to.c_lflag);
+  } else if (strstart(&TT.fmt, "timespec}")) {
+    struct timespec ts;
+
+    ptrace_struct(addr, &ts, sizeof(ts));
+    fprintf(stderr, "{tv_sec=%lld, tv_nsec=%lld}",
+        (long long)ts.tv_sec, (long long)ts.tv_nsec);
+  } else if (strstart(&TT.fmt, "winsize}")) {
+    struct winsize ws;
+
+    ptrace_struct(addr, &ws, sizeof(ws));
+    fprintf(stderr, "{ws_row=%hu, ws_col=%hu, ws_xpixel=%hu, ws_ypixel=%hu}",
+        ws.ws_row, ws.ws_col, ws.ws_xpixel, ws.ws_ypixel);
+  } else abort();
+}
+
+static void print_ptr(long addr)
+{
+  if (!addr) fprintf(stderr, "NULL");
+  else fprintf(stderr, "0x%lx", addr);
+}
+
+static void print_string(long addr)
+{
+  long offset = 0, total = 0;
+  int done = 0, i;
+
+  fputc('"', stderr);
+  while (!done) {
+    errno = 0;
+    long v = ptrace(PTRACE_PEEKDATA, TT.pid, addr + offset);
+    if (errno) return;
+    memcpy(toybuf, &v, sizeof(v));
+    for (i=0; i<sizeof(v); ++i) {
+      if (!toybuf[i]) {
+        // TODO: handle the case of dumping n bytes (e.g. read()/write()), not
+        // just NUL-terminated strings.
+        done = 1;
+        break;
+      }
+      if (isprint(toybuf[i])) fputc(toybuf[i], stderr);
+      else {
+        // TODO: reuse an existing escape function.
+        fputc('\\', stderr);
+        if (toybuf[i] == '\n') fputc('n', stderr);
+        else if (toybuf[i] == '\r') fputc('r', stderr);
+        else if (toybuf[i] == '\t') fputc('t', stderr);
+        else fprintf(stderr, "x%2.2x", toybuf[i]);
+      }
+      if (++total >= TT.s) {
+        done = 1;
+        break;
+      }
+    }
+    offset += sizeof(v);
+  }
+  fputc('"', stderr);
+}
+
+static void print_bitmask(int bitmask, long v, char *zero, ...)
+{
+  va_list ap;
+  int first = 1;
+
+  if (!v && zero) {
+    fprintf(stderr, "%s", zero);
+    return;
+  }
+
+  va_start(ap, zero);
+  for (;;) {
+    int this = va_arg(ap, int);
+    char *name;
+
+    if (!this) break;
+    name = va_arg(ap, char*);
+    if (bitmask) {
+      if (v & this) {
+        fprintf(stderr, "%s%s", first?"":"|", name);
+        first = 0;
+        v &= ~this;
+      }
+    } else {
+      if (v == this) {
+        fprintf(stderr, "%s", name);
+        v = 0;
+        break;
+      }
+    }
+  }
+  va_end(ap);
+  if (v) fprintf(stderr, "%s%#lx", first?"":"|", v);
+}
+
+static void print_flags(long v)
+{
+#define C(n) n, #n
+  if (strstart(&TT.fmt, "access|")) {
+    print_bitmask(1, v, "F_OK", C(R_OK), C(W_OK), C(X_OK), 0);
+  } else if (strstart(&TT.fmt, "mmap|")) {
+    print_bitmask(1, v, 0, C(MAP_SHARED), C(MAP_PRIVATE), C(MAP_32BIT),
+        C(MAP_ANONYMOUS), C(MAP_FIXED), C(MAP_GROWSDOWN), C(MAP_HUGETLB),
+        C(MAP_DENYWRITE), 0);
+  } else if (strstart(&TT.fmt, "open|")) {
+    print_bitmask(1, v, "O_RDONLY", C(O_WRONLY), C(O_RDWR), C(O_CLOEXEC),
+        C(O_CREAT), C(O_DIRECTORY), C(O_EXCL), C(O_NOCTTY), C(O_NOFOLLOW),
+        C(O_TRUNC), C(O_ASYNC), C(O_APPEND), C(O_DSYNC), C(O_EXCL),
+        C(O_NOATIME), C(O_NONBLOCK), C(O_PATH), C(O_SYNC),
+        0x4000, "O_DIRECT", 0x8000, "O_LARGEFILE", 0x410000, "O_TMPFILE", 0);
+  } else if (strstart(&TT.fmt, "prot|")) {
+    print_bitmask(1,v,"PROT_NONE",C(PROT_READ),C(PROT_WRITE),C(PROT_EXEC),0);
+  } else abort();
+}
+
+static void print_alternatives(long v)
+{
+  if (strstart(&TT.fmt, "rlimit^")) {
+    print_bitmask(0, v, "RLIMIT_CPU", C(RLIMIT_FSIZE), C(RLIMIT_DATA),
+        C(RLIMIT_STACK), C(RLIMIT_CORE), C(RLIMIT_RSS), C(RLIMIT_NPROC),
+        C(RLIMIT_NOFILE), C(RLIMIT_MEMLOCK), C(RLIMIT_AS), C(RLIMIT_LOCKS),
+        C(RLIMIT_SIGPENDING), C(RLIMIT_MSGQUEUE), C(RLIMIT_NICE),
+        C(RLIMIT_RTPRIO), C(RLIMIT_RTTIME), 0);
+  } else if (strstart(&TT.fmt, "seek^")) {
+    print_bitmask(0, v, "SEEK_SET", C(SEEK_CUR), C(SEEK_END), C(SEEK_DATA),
+        C(SEEK_HOLE), 0);
+  } else if (strstart(&TT.fmt, "sig^")) {
+    print_bitmask(0, v, "SIG_BLOCK", C(SIG_UNBLOCK), C(SIG_SETMASK), 0);
+  } else abort();
+}
+
+static void print_args()
+{
+  int i;
+
+  for (i=0; *TT.fmt; ++TT.arg, ++i) {
+    long v = get_arg();
+    char *s, ch;
+
+    if (i) fprintf(stderr, ", ");
+    switch (ch = *TT.fmt++) {
+      case 'd': fprintf(stderr, "%ld", v); break; // decimal
+      case 'f': if ((int) v == AT_FDCWD) fprintf(stderr, "AT_FDCWD");
+                else fprintf(stderr, "%ld", v);
+                break;
+      case 'i': fprintf(stderr, "%s", strioctl(v)); break; // decimal
+      case 'm': fprintf(stderr, "%03o", (unsigned) v); break; // mode for open()
+      case 'o': fprintf(stderr, "%ld", v); break; // off_t
+      case 'p': print_ptr(v); break;
+      case 's': print_string(v); break;
+      case 'S': // The libc-reserved signals aren't known to num_to_sig().
+                // TODO: use an strace-only routine for >= 32?
+                if (!(s = num_to_sig(v))) fprintf(stderr, "%ld", v);
+                else fprintf(stderr, "SIG%s", s);
+                break;
+      case 'z': fprintf(stderr, "%zd", v); break; // size_t
+      case 'x': fprintf(stderr, "%lx", v); break; // hex
+
+      case '{': print_struct(v); break;
+      case '|': print_flags(v); break;
+      case '^': print_alternatives(v); break;
+
+      case '/': return; // Separates "enter" and "exit" arguments.
+
+      default: fprintf(stderr, "?%c<0x%lx>", ch, v); break;
+    }
+  }
+}
+
+static void print_enter(void)
+{
+  char *name;
+
+  get_regs();
+  if (REG_SYSCALL == __NR_ioctl) {
+    name = "ioctl";
+    switch (REG_ARG1) {
+      case FS_IOC_FSGETXATTR: TT.fmt = "fi/{fsxattr}"; break;
+      case FS_IOC_FSSETXATTR: TT.fmt = "fi{fsxattr}"; break;
+      case FS_IOC_GETFLAGS: TT.fmt = "fi/{longx}"; break;
+      case FS_IOC_GETVERSION: TT.fmt = "fi/{long}"; break;
+      case FS_IOC_SETFLAGS: TT.fmt = "fi{long}"; break;
+      case FS_IOC_SETVERSION: TT.fmt = "fi{long}"; break;
+      //case SIOCGIFCONF: struct ifconf
+      case SIOCGIFADDR:
+      case SIOCGIFBRDADDR:
+      case SIOCGIFDSTADDR:
+      case SIOCGIFFLAGS:
+      case SIOCGIFHWADDR:
+      case SIOCGIFMAP:
+      case SIOCGIFMTU:
+      case SIOCGIFNETMASK:
+      case SIOCGIFTXQLEN: TT.fmt = "fi/{ifreq}"; break;
+      case SIOCSIFADDR:
+      case SIOCSIFBRDADDR:
+      case SIOCSIFDSTADDR:
+      case SIOCSIFFLAGS:
+      case SIOCSIFHWADDR:
+      case SIOCSIFMAP:
+      case SIOCSIFMTU:
+      case SIOCSIFNETMASK:
+      case SIOCSIFTXQLEN: TT.fmt = "fi{ifreq}"; break;
+      case TCGETS: TT.fmt = "fi/{termios}"; break;
+      case TCSETS: TT.fmt = "fi{termios}"; break;
+      case TIOCGWINSZ: TT.fmt = "fi/{winsize}"; break;
+      case TIOCSWINSZ: TT.fmt = "fi{winsize}"; break;
+      default:
+        TT.fmt = (REG_ARG0 & 1) ? "fip" : "fi/p";
+        break;
+    }
+  } else switch (REG_SYSCALL) {
+#define SC(n,f) case __NR_ ## n: name = #n; TT.fmt = f; break
+    SC(access, "s|access|");
+    SC(arch_prctl, "dp");
+    SC(brk, "p");
+    SC(close, "d");
+    SC(connect, "fpd"); // TODO: sockaddr
+    SC(dup, "f");
+    SC(dup2, "ff");
+    SC(dup3, "ff|open|");
+    SC(execve, "spp");
+    SC(exit_group, "d");
+    SC(fcntl, "fdp"); // TODO: probably needs special case
+    SC(fstat, "f/{stat}");
+    SC(futex, "pdxppx");
+    SC(getdents64, "dpz");
+    SC(geteuid, "");
+    SC(getuid, "");
+
+    SC(getxattr, "sspz");
+    SC(lgetxattr, "sspz");
+    SC(fgetxattr, "fspz");
+
+    SC(lseek, "fo^seek^");
+    SC(lstat, "s/{stat}");
+    SC(mmap, "pz|prot||mmap|fx");
+    SC(mprotect, "pz|prot|");
+    SC(mremap, "pzzdp"); // TODO: flags
+    SC(munmap, "pz");
+    SC(nanosleep, "{timespec}/{timespec}");
+    SC(newfstatat, "fs/{stat}d");
+    SC(open, "sd|open|m");
+    SC(openat, "fs|open|m");
+    SC(poll, "pdd");
+    SC(prlimit64, "d^rlimit^{rlimit}/{rlimit}");
+    SC(read, "d/sz");
+    SC(readlinkat, "s/sz");
+    SC(rt_sigaction, "Sppz");
+    SC(rt_sigprocmask, "^sig^{sigset}/{sigset}z");
+    SC(set_robust_list, "pd");
+    SC(set_tid_address, "p");
+    SC(socket, "ddd"); // TODO: flags
+    SC(stat, "s/{stat}");
+    SC(statfs, "sp");
+    SC(sysinfo, "p");
+    SC(umask, "m");
+    SC(uname, "p");
+    SC(write, "dsz");
+    default:
+      sprintf(name = toybuf, "SYS_%d", (int)REG_SYSCALL);
+      TT.fmt = "pppppp";
+      break;
+  }
+
+  fprintf(stderr, "%s(", name);
+  TT.arg = 0;
+  print_args();
+}
+
+static void print_exit(void)
+{
+  get_regs();
+  if (*TT.fmt) print_args();
+  fprintf(stderr, ") = ");
+  if (REG_RESULT >= -4095UL) {
+    fprintf(stderr, "-1 %s (%s)", strerrno(-REG_RESULT), strerror(-REG_RESULT));
+  } else if (REG_SYSCALL == __NR_mmap || REG_SYSCALL == __NR_brk) {
+    print_ptr(REG_RESULT);
+  } else {
+    fprintf(stderr, "%ld", (long)REG_RESULT);
+  }
+  fputc('\n', stderr);
+}
+
+static int next(void)
+{
+  int status;
+
+  for (;;) {
+    ptrace(PTRACE_SYSCALL, TT.pid, 0, 0);
+    waitpid(TT.pid, &status, 0);
+    // PTRACE_O_TRACESYSGOOD sets bit 7 to indicate a syscall.
+    if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) return 1;
+    if (WIFEXITED(status)) return 0;
+    fprintf(stderr, "[stopped %d (%x)]\n", status, WSTOPSIG(status));
+  }
+}
+
+static void strace_detach(int s)
+{
+  xptrace(PTRACE_DETACH, TT.pid, 0, 0);
+  exit(1);
+}
+
+void strace_main(void)
+{
+  int status;
+
+  if (!FLAG(s)) TT.s = 32;
+
+  if (FLAG(p)) {
+    if (*toys.optargs) help_exit("No arguments with -p");
+    TT.pid = TT.p;
+    signal(SIGINT, strace_detach);
+    // TODO: PTRACE_SEIZE instead?
+    xptrace(PTRACE_ATTACH, TT.pid, 0, 0);
+  } else {
+    if (!*toys.optargs) help_exit("Needs 1 argument");
+    TT.pid = xfork();
+    if (!TT.pid) {
+      errno = 0;
+      ptrace(PTRACE_TRACEME);
+      if (errno) perror_exit("PTRACE_TRACEME failed");
+      raise(SIGSTOP);
+      toys.stacktop = 0;
+      xexec(toys.optargs);
+    }
+  }
+
+  do {
+    waitpid(TT.pid, &status, 0);
+  } while (!WIFSTOPPED(status));
+
+  // TODO: PTRACE_O_TRACEEXIT
+  // TODO: PTRACE_O_TRACEFORK/PTRACE_O_TRACEVFORK/PTRACE_O_TRACECLONE for -f.
+  errno = 0;
+  ptrace(PTRACE_SETOPTIONS, TT.pid, 0, PTRACE_O_TRACESYSGOOD);
+  if (errno) perror_exit("PTRACE_SETOPTIONS PTRACE_O_TRACESYSGOOD failed");
+
+  // TODO: real strace swallows the failed execve()s if it started the child
+
+  for (;;) {
+    if (!next()) break;
+    print_enter();
+    if (!next()) break;
+    print_exit();
+  }
+
+  // TODO: support -f and keep track of children.
+  waitpid(TT.pid, &status, 0);
+  if (WIFEXITED(status))
+    fprintf(stderr, "+++ exited with %d +++\n", WEXITSTATUS(status));
+  if (WIFSTOPPED(status))
+    fprintf(stderr, "+++ stopped with %d +++\n", WSTOPSIG(status));
+}
-- 
2.33.0.464.g1972c5931b-goog

_______________________________________________
Toybox mailing list
[email protected]
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to