1 /* strace.c - Trace system calls. 2 * 3 * Copyright 2020 The Android Open Source Project 4 * 5 * See https://man7.org/linux/man-pages/man2/syscall.2.html 6 7 USE_STRACE(NEWTOY(strace, "^p#s#v", TOYFLAG_USR|TOYFLAG_SBIN)) 8 9 config STRACE 10 bool "strace" 11 default n 12 help 13 usage: strace [-fv] [-p PID] [-s NUM] COMMAND [ARGS...] 14 15 Trace systems calls made by a process. 16 17 -s String length limit. 18 -v Dump all of large structs/arrays. 19 */ 20 21 #include <sys/ptrace.h> 22 #include <sys/syscall.h> 23 #include <sys/user.h> 24 25 #define FOR_strace 26 #include "toys.h" 27 28 GLOBALS( 29 long s, p; 30 31 char ioctl[32], *fmt; 32 long regs[256/sizeof(long)], syscall; 33 pid_t pid; 34 int arg; 35 ) 36 37 struct user_regs_struct regs; 38 39 40 // Syscall args from https://man7.org/linux/man-pages/man2/syscall.2.html 41 // REG_ORDER is args 0-6, SYSCALL, RESULT 42 #if defined(__ARM_EABI__) 43 static const char REG_ORDER[] = {0,1,2,3,4,5,7,0}; 44 #elif defined(__ARM_ARCH) && __ARM_ARCH == 8 45 static const char REG_ORDER[] = {0,1,2,3,4,5,8,0}; 46 #elif defined(__i386__) 47 // ebx,ecx,edx,esi,edi,ebp,orig_eax,eax 48 static const char REG_ORDER[] = {0,1,2,3,4,5,11,6}; 49 #elif defined(__m68k__) 50 // d1,d2,d3,d4,d5,a0,orig_d0,d0 51 static const char REG_ORDER[] = {0,1,2,3,4,7,16,14); 52 #elif defined(__PPC__) || defined(__PPC64__) 53 static const char REG_ORDER[] = {3,4,5,6,7,8,0,3}; 54 #elif defined(__s390__) // also covers s390x 55 // r2,r3,r4,r5,r6,r7,r1,r2 but mask+addr before r0 so +2 56 static const char REG_ORDER[] = {4,5,6,7,8,9,3,4}; 57 #elif defined(__sh__) 58 static const char REG_ORDER[] = {4,5,6,7,0,1,3,0}; 59 #elif defined(__x86_64__) 60 // rdi,rsi,rdx,r10,r8,r9,orig_rax,rax 61 static const char REG_ORDER[] = {14,13,12,7,9,8,15,10}; 62 #else 63 #error unsupported architecture 64 #endif 65 66 #define C(x) case x: return #x 67 68 #define FS_IOC_FSGETXATTR 0x801c581f 69 #define FS_IOC_FSSETXATTR 0x401c5820 70 #define FS_IOC_GETFLAGS 0x80086601 71 #define FS_IOC_SETFLAGS 0x40086602 72 #define FS_IOC_GETVERSION 0x80087601 73 #define FS_IOC_SETVERSION 0x40047602 74 struct fsxattr { 75 unsigned fsx_xflags; 76 unsigned fsx_extsize; 77 unsigned fsx_nextents; 78 unsigned fsx_projid; 79 unsigned fsx_cowextsize; 80 char fsx_pad[8]; 81 }; 82 83 static char *strioctl(int i) 84 { 85 switch (i) { 86 C(FS_IOC_FSGETXATTR); 87 C(FS_IOC_FSSETXATTR); 88 C(FS_IOC_GETFLAGS); 89 C(FS_IOC_GETVERSION); 90 C(FS_IOC_SETFLAGS); 91 C(FS_IOC_SETVERSION); 92 C(SIOCGIFADDR); 93 C(SIOCGIFBRDADDR); 94 C(SIOCGIFCONF); 95 C(SIOCGIFDSTADDR); 96 C(SIOCGIFFLAGS); 97 C(SIOCGIFHWADDR); 98 C(SIOCGIFMAP); 99 C(SIOCGIFMTU); 100 C(SIOCGIFNETMASK); 101 C(SIOCGIFTXQLEN); 102 C(TCGETS); 103 C(TCSETS); 104 C(TIOCGWINSZ); 105 C(TIOCSWINSZ); 106 } 107 sprintf(toybuf, "%#x", i); 108 return toybuf; 109 } 110 111 // TODO: move to lib, implement errno(1)? 112 static char *strerrno(int e) 113 { 114 switch (e) { 115 // uapi errno-base.h 116 C(EPERM); 117 C(ENOENT); 118 C(ESRCH); 119 C(EINTR); 120 C(EIO); 121 C(ENXIO); 122 C(E2BIG); 123 C(ENOEXEC); 124 C(EBADF); 125 C(ECHILD); 126 C(EAGAIN); 127 C(ENOMEM); 128 C(EACCES); 129 C(EFAULT); 130 C(ENOTBLK); 131 C(EBUSY); 132 C(EEXIST); 133 C(EXDEV); 134 C(ENODEV); 135 C(ENOTDIR); 136 C(EISDIR); 137 C(EINVAL); 138 C(ENFILE); 139 C(EMFILE); 140 C(ENOTTY); 141 C(ETXTBSY); 142 C(EFBIG); 143 C(ENOSPC); 144 C(ESPIPE); 145 C(EROFS); 146 C(EMLINK); 147 C(EPIPE); 148 C(EDOM); 149 C(ERANGE); 150 // uapi errno.h 151 C(EDEADLK); 152 C(ENAMETOOLONG); 153 C(ENOLCK); 154 C(ENOSYS); 155 C(ENOTEMPTY); 156 C(ELOOP); 157 C(ENOMSG); 158 // ...etc; fill in as we see them in practice? 159 } 160 sprintf(toybuf, "%d", e); 161 return toybuf; 162 } 163 164 #undef C 165 166 static void xptrace(int req, pid_t pid, void *addr, void *data) 167 { 168 if (ptrace(req, pid, addr, data)) perror_exit("ptrace pid %d", pid); 169 } 170 171 static void get_regs() 172 { 173 xptrace(PTRACE_GETREGS, TT.pid, 0, TT.regs); 174 } 175 176 static void ptrace_struct(long addr, void *dst, size_t bytes) 177 { 178 int offset = 0, i; 179 long v; 180 181 for (i=0; i<bytes; i+=sizeof(long)) { 182 errno = 0; 183 v = ptrace(PTRACE_PEEKDATA, TT.pid, addr + offset); 184 if (errno) perror_exit("PTRACE_PEEKDATA failed"); 185 memcpy(dst + offset, &v, sizeof(v)); 186 offset += sizeof(long); 187 } 188 } 189 190 // TODO: this all relies on having the libc structs match the kernel structs, 191 // which isn't always true for glibc... 192 static void print_struct(long addr) 193 { 194 if (!addr) { // All NULLs look the same... 195 fprintf(stderr, "NULL"); 196 while (*TT.fmt != '}') ++TT.fmt; 197 ++TT.fmt; 198 } else if (strstart(&TT.fmt, "ifreq}")) { 199 struct ifreq ir; 200 201 ptrace_struct(addr, &ir, sizeof(ir)); 202 // TODO: is this always an ioctl? use TT.regs[REG_ORDER[1]] to work out what to show. 203 fprintf(stderr, "{...}"); 204 } else if (strstart(&TT.fmt, "fsxattr}")) { 205 struct fsxattr fx; 206 207 ptrace_struct(addr, &fx, sizeof(fx)); 208 fprintf(stderr, "{fsx_xflags=%#x, fsx_extsize=%d, fsx_nextents=%d, " 209 "fsx_projid=%d, fsx_cowextsize=%d}", fx.fsx_xflags, fx.fsx_extsize, 210 fx.fsx_nextents, fx.fsx_projid, fx.fsx_cowextsize); 211 } else if (strstart(&TT.fmt, "long}")) { 212 long l; 213 214 ptrace_struct(addr, &l, sizeof(l)); 215 fprintf(stderr, "%ld", l); 216 } else if (strstart(&TT.fmt, "longx}")) { 217 long l; 218 219 ptrace_struct(addr, &l, sizeof(l)); 220 fprintf(stderr, "%#lx", l); 221 } else if (strstart(&TT.fmt, "rlimit}")) { 222 struct rlimit rl; 223 224 ptrace_struct(addr, &rl, sizeof(rl)); 225 fprintf(stderr, "{rlim_cur=%lld, rlim_max=%lld}", 226 (long long)rl.rlim_cur, (long long)rl.rlim_max); 227 } else if (strstart(&TT.fmt, "sigset}")) { 228 long long ss; 229 int i; 230 231 ptrace_struct(addr, &ss, sizeof(ss)); 232 fprintf(stderr, "["); 233 for (i=0; i<64;++i) { 234 // TODO: use signal names, fix spacing 235 if (ss & (1ULL<<i)) fprintf(stderr, "%d ", i); 236 } 237 fprintf(stderr, "]"); 238 } else if (strstart(&TT.fmt, "stat}")) { 239 struct stat sb; 240 241 ptrace_struct(addr, &sb, sizeof(sb)); 242 // TODO: decode IFMT bits in st_mode 243 if (FLAG(v)) { 244 // TODO: full atime/mtime/ctime dump. 245 fprintf(stderr, "{st_dev=makedev(%#x, %#x), st_ino=%ld, st_mode=%o, " 246 "st_nlink=%ld, st_uid=%d, st_gid=%d, st_blksize=%ld, st_blocks=%ld, " 247 "st_size=%lld, st_atime=%ld, st_mtime=%ld, st_ctime=%ld}", 248 dev_major(sb.st_dev), dev_minor(sb.st_dev), sb.st_ino, sb.st_mode, 249 sb.st_nlink, sb.st_uid, sb.st_gid, sb.st_blksize, sb.st_blocks, 250 (long long)sb.st_size, sb.st_atime, sb.st_mtime, sb.st_ctime); 251 } else { 252 fprintf(stderr, "{st_mode=%o, st_size=%lld, ...}", sb.st_mode, 253 (long long)sb.st_size); 254 } 255 } else if (strstart(&TT.fmt, "termios}")) { 256 struct termios to; 257 258 ptrace_struct(addr, &to, sizeof(to)); 259 fprintf(stderr, "{c_iflag=%#lx, c_oflag=%#lx, c_cflag=%#lx, c_lflag=%#lx}", 260 (long)to.c_iflag, (long)to.c_oflag, (long)to.c_cflag, (long)to.c_lflag); 261 } else if (strstart(&TT.fmt, "timespec}")) { 262 struct timespec ts; 263 264 ptrace_struct(addr, &ts, sizeof(ts)); 265 fprintf(stderr, "{tv_sec=%lld, tv_nsec=%lld}", 266 (long long)ts.tv_sec, (long long)ts.tv_nsec); 267 } else if (strstart(&TT.fmt, "winsize}")) { 268 struct winsize ws; 269 270 ptrace_struct(addr, &ws, sizeof(ws)); 271 fprintf(stderr, "{ws_row=%hu, ws_col=%hu, ws_xpixel=%hu, ws_ypixel=%hu}", 272 ws.ws_row, ws.ws_col, ws.ws_xpixel, ws.ws_ypixel); 273 } else abort(); 274 } 275 276 static void print_ptr(long addr) 277 { 278 if (!addr) fprintf(stderr, "NULL"); 279 else fprintf(stderr, "0x%lx", addr); 280 } 281 282 static void print_string(long addr) 283 { 284 long offset = 0, total = 0; 285 int done = 0, i; 286 287 fputc('"', stderr); 288 while (!done) { 289 errno = 0; 290 long v = ptrace(PTRACE_PEEKDATA, TT.pid, addr + offset); 291 if (errno) return; 292 memcpy(toybuf, &v, sizeof(v)); 293 for (i=0; i<sizeof(v); ++i) { 294 if (!toybuf[i]) { 295 // TODO: handle the case of dumping n bytes (e.g. read()/write()), not 296 // just NUL-terminated strings. 297 done = 1; 298 break; 299 } 300 if (isprint(toybuf[i])) fputc(toybuf[i], stderr); 301 else { 302 // TODO: reuse an existing escape function. 303 fputc('\\', stderr); 304 if (toybuf[i] == '\n') fputc('n', stderr); 305 else if (toybuf[i] == '\r') fputc('r', stderr); 306 else if (toybuf[i] == '\t') fputc('t', stderr); 307 else fprintf(stderr, "x%2.2x", toybuf[i]); 308 } 309 if (++total >= TT.s) { 310 done = 1; 311 break; 312 } 313 } 314 offset += sizeof(v); 315 } 316 fputc('"', stderr); 317 } 318 319 static void print_bitmask(int bitmask, long v, char *zero, ...) 320 { 321 va_list ap; 322 int first = 1; 323 324 if (!v && zero) { 325 fprintf(stderr, "%s", zero); 326 return; 327 } 328 329 va_start(ap, zero); 330 for (;;) { 331 int this = va_arg(ap, int); 332 char *name; 333 334 if (!this) break; 335 name = va_arg(ap, char*); 336 if (bitmask) { 337 if (v & this) { 338 fprintf(stderr, "%s%s", first?"":"|", name); 339 first = 0; 340 v &= ~this; 341 } 342 } else { 343 if (v == this) { 344 fprintf(stderr, "%s", name); 345 v = 0; 346 break; 347 } 348 } 349 } 350 va_end(ap); 351 if (v) fprintf(stderr, "%s%#lx", first?"":"|", v); 352 } 353 354 static void print_flags(long v) 355 { 356 #define C(n) n, #n 357 if (strstart(&TT.fmt, "access|")) { 358 print_bitmask(1, v, "F_OK", C(R_OK), C(W_OK), C(X_OK), 0); 359 } else if (strstart(&TT.fmt, "mmap|")) { 360 print_bitmask(1, v, 0, C(MAP_SHARED), C(MAP_PRIVATE), C(MAP_32BIT), 361 C(MAP_ANONYMOUS), C(MAP_FIXED), C(MAP_GROWSDOWN), C(MAP_HUGETLB), 362 C(MAP_DENYWRITE), 0); 363 } else if (strstart(&TT.fmt, "open|")) { 364 print_bitmask(1, v, "O_RDONLY", C(O_WRONLY), C(O_RDWR), C(O_CLOEXEC), 365 C(O_CREAT), C(O_DIRECTORY), C(O_EXCL), C(O_NOCTTY), C(O_NOFOLLOW), 366 C(O_TRUNC), C(O_ASYNC), C(O_APPEND), C(O_DSYNC), C(O_EXCL), 367 C(O_NOATIME), C(O_NONBLOCK), C(O_PATH), C(O_SYNC), 368 0x4000, "O_DIRECT", 0x8000, "O_LARGEFILE", 0x410000, "O_TMPFILE", 0); 369 } else if (strstart(&TT.fmt, "prot|")) { 370 print_bitmask(1,v,"PROT_NONE",C(PROT_READ),C(PROT_WRITE),C(PROT_EXEC),0); 371 } else abort(); 372 } 373 374 static void print_alternatives(long v) 375 { 376 if (strstart(&TT.fmt, "rlimit^")) { 377 print_bitmask(0, v, "RLIMIT_CPU", C(RLIMIT_FSIZE), C(RLIMIT_DATA), 378 C(RLIMIT_STACK), C(RLIMIT_CORE), C(RLIMIT_RSS), C(RLIMIT_NPROC), 379 C(RLIMIT_NOFILE), C(RLIMIT_MEMLOCK), C(RLIMIT_AS), C(RLIMIT_LOCKS), 380 C(RLIMIT_SIGPENDING), C(RLIMIT_MSGQUEUE), C(RLIMIT_NICE), 381 C(RLIMIT_RTPRIO), C(RLIMIT_RTTIME), 0); 382 } else if (strstart(&TT.fmt, "seek^")) { 383 print_bitmask(0, v, "SEEK_SET", C(SEEK_CUR), C(SEEK_END), C(SEEK_DATA), 384 C(SEEK_HOLE), 0); 385 } else if (strstart(&TT.fmt, "sig^")) { 386 print_bitmask(0, v, "SIG_BLOCK", C(SIG_UNBLOCK), C(SIG_SETMASK), 0); 387 } else abort(); 388 } 389 390 static void print_args() 391 { 392 int i; 393 394 // Loop through arguments and print according to format string 395 for (i = 0; *TT.fmt; i++, TT.arg++) { 396 long v = TT.regs[REG_ORDER[TT.arg]]; 397 char *s, ch; 398 399 if (i) fprintf(stderr, ", "); 400 switch (ch = *TT.fmt++) { 401 case 'd': fprintf(stderr, "%ld", v); break; // decimal 402 case 'f': if ((int) v == AT_FDCWD) fprintf(stderr, "AT_FDCWD"); 403 else fprintf(stderr, "%ld", v); 404 break; 405 case 'i': fprintf(stderr, "%s", strioctl(v)); break; // decimal 406 case 'm': fprintf(stderr, "%03o", (unsigned) v); break; // mode for open() 407 case 'o': fprintf(stderr, "%ld", v); break; // off_t 408 case 'p': print_ptr(v); break; 409 case 's': print_string(v); break; 410 case 'S': // The libc-reserved signals aren't known to num_to_sig(). 411 // TODO: use an strace-only routine for >= 32? 412 if (!(s = num_to_sig(v))) fprintf(stderr, "%ld", v); 413 else fprintf(stderr, "SIG%s", s); 414 break; 415 case 'z': fprintf(stderr, "%zd", v); break; // size_t 416 case 'x': fprintf(stderr, "%lx", v); break; // hex 417 418 case '{': print_struct(v); break; 419 case '|': print_flags(v); break; 420 case '^': print_alternatives(v); break; 421 422 case '/': return; // Separates "enter" and "exit" arguments. 423 424 default: fprintf(stderr, "?%c<0x%lx>", ch, v); break; 425 } 426 } 427 } 428 429 static void print_enter(void) 430 { 431 char *name; 432 433 get_regs(); 434 TT.syscall = TT.regs[REG_ORDER[6]]; 435 if (TT.syscall == __NR_ioctl) { 436 name = "ioctl"; 437 switch (TT.regs[REG_ORDER[1]]) { 438 case FS_IOC_FSGETXATTR: TT.fmt = "fi/{fsxattr}"; break; 439 case FS_IOC_FSSETXATTR: TT.fmt = "fi{fsxattr}"; break; 440 case FS_IOC_GETFLAGS: TT.fmt = "fi/{longx}"; break; 441 case FS_IOC_GETVERSION: TT.fmt = "fi/{long}"; break; 442 case FS_IOC_SETFLAGS: TT.fmt = "fi{long}"; break; 443 case FS_IOC_SETVERSION: TT.fmt = "fi{long}"; break; 444 //case SIOCGIFCONF: struct ifconf 445 case SIOCGIFADDR: 446 case SIOCGIFBRDADDR: 447 case SIOCGIFDSTADDR: 448 case SIOCGIFFLAGS: 449 case SIOCGIFHWADDR: 450 case SIOCGIFMAP: 451 case SIOCGIFMTU: 452 case SIOCGIFNETMASK: 453 case SIOCGIFTXQLEN: TT.fmt = "fi/{ifreq}"; break; 454 case SIOCSIFADDR: 455 case SIOCSIFBRDADDR: 456 case SIOCSIFDSTADDR: 457 case SIOCSIFFLAGS: 458 case SIOCSIFHWADDR: 459 case SIOCSIFMAP: 460 case SIOCSIFMTU: 461 case SIOCSIFNETMASK: 462 case SIOCSIFTXQLEN: TT.fmt = "fi{ifreq}"; break; 463 case TCGETS: TT.fmt = "fi/{termios}"; break; 464 case TCSETS: TT.fmt = "fi{termios}"; break; 465 case TIOCGWINSZ: TT.fmt = "fi/{winsize}"; break; 466 case TIOCSWINSZ: TT.fmt = "fi{winsize}"; break; 467 default: 468 TT.fmt = (TT.regs[REG_ORDER[0]]&1) ? "fip" : "fi/p"; 469 break; 470 } 471 } else switch (TT.syscall) { 472 #define SC(n,f) case __NR_ ## n: name = #n; TT.fmt = f; break 473 SC(access, "s|access|"); 474 SC(arch_prctl, "dp"); 475 SC(brk, "p"); 476 SC(close, "d"); 477 SC(connect, "fpd"); // TODO: sockaddr 478 SC(dup, "f"); 479 SC(dup2, "ff"); 480 SC(dup3, "ff|open|"); 481 SC(execve, "spp"); 482 SC(exit_group, "d"); 483 SC(fcntl, "fdp"); // TODO: probably needs special case 484 SC(fstat, "f/{stat}"); 485 SC(futex, "pdxppx"); 486 SC(getdents64, "dpz"); 487 SC(geteuid, ""); 488 SC(getuid, ""); 489 490 SC(getxattr, "sspz"); 491 SC(lgetxattr, "sspz"); 492 SC(fgetxattr, "fspz"); 493 494 SC(lseek, "fo^seek^"); 495 SC(lstat, "s/{stat}"); 496 SC(mmap, "pz|prot||mmap|fx"); 497 SC(mprotect, "pz|prot|"); 498 SC(mremap, "pzzdp"); // TODO: flags 499 SC(munmap, "pz"); 500 SC(nanosleep, "{timespec}/{timespec}"); 501 SC(newfstatat, "fs/{stat}d"); 502 SC(open, "sd|open|m"); 503 SC(openat, "fs|open|m"); 504 SC(poll, "pdd"); 505 SC(prlimit64, "d^rlimit^{rlimit}/{rlimit}"); 506 SC(read, "d/sz"); 507 SC(readlinkat, "s/sz"); 508 SC(rt_sigaction, "Sppz"); 509 SC(rt_sigprocmask, "^sig^{sigset}/{sigset}z"); 510 SC(set_robust_list, "pd"); 511 SC(set_tid_address, "p"); 512 SC(socket, "ddd"); // TODO: flags 513 SC(stat, "s/{stat}"); 514 SC(statfs, "sp"); 515 SC(sysinfo, "p"); 516 SC(umask, "m"); 517 SC(uname, "p"); 518 SC(write, "dsz"); 519 default: 520 sprintf(name = toybuf, "SYS_%ld", TT.syscall); 521 TT.fmt = "pppppp"; 522 break; 523 } 524 525 fprintf(stderr, "%s(", name); 526 TT.arg = 0; 527 print_args(); 528 } 529 530 static void print_exit(void) 531 { 532 long result; 533 534 get_regs(); 535 result = TT.regs[REG_ORDER[7]]; 536 if (*TT.fmt) print_args(); 537 fprintf(stderr, ") = "); 538 if (result >= -4095UL) 539 fprintf(stderr, "-1 %s (%s)", strerrno(-result), strerror(-result)); 540 else if (TT.syscall==__NR_mmap || TT.syscall==__NR_brk) print_ptr(result); 541 else fprintf(stderr, "%ld", result); 542 fputc('\n', stderr); 543 } 544 545 static int next(void) 546 { 547 int status; 548 549 for (;;) { 550 ptrace(PTRACE_SYSCALL, TT.pid, 0, 0); 551 waitpid(TT.pid, &status, 0); 552 // PTRACE_O_TRACESYSGOOD sets bit 7 to indicate a syscall. 553 if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) return 1; 554 if (WIFEXITED(status)) return 0; 555 fprintf(stderr, "[stopped %d (%x)]\n", status, WSTOPSIG(status)); 556 } 557 } 558 559 static void strace_detach(int s) 560 { 561 xptrace(PTRACE_DETACH, TT.pid, 0, 0); 562 exit(1); 563 } 564 565 void strace_main(void) 566 { 567 int status; 568 569 if (!FLAG(s)) TT.s = 32; 570 571 if (FLAG(p)) { 572 if (*toys.optargs) help_exit("No arguments with -p"); 573 TT.pid = TT.p; 574 signal(SIGINT, strace_detach); 575 // TODO: PTRACE_SEIZE instead? 576 xptrace(PTRACE_ATTACH, TT.pid, 0, 0); 577 } else { 578 if (!*toys.optargs) help_exit("Needs 1 argument"); 579 TT.pid = xfork(); 580 if (!TT.pid) { 581 errno = 0; 582 ptrace(PTRACE_TRACEME); 583 if (errno) perror_exit("PTRACE_TRACEME failed"); 584 raise(SIGSTOP); 585 toys.stacktop = 0; 586 xexec(toys.optargs); 587 } 588 } 589 590 do { 591 waitpid(TT.pid, &status, 0); 592 } while (!WIFSTOPPED(status)); 593 594 // TODO: PTRACE_O_TRACEEXIT 595 // TODO: PTRACE_O_TRACEFORK/PTRACE_O_TRACEVFORK/PTRACE_O_TRACECLONE for -f. 596 errno = 0; 597 ptrace(PTRACE_SETOPTIONS, TT.pid, 0, PTRACE_O_TRACESYSGOOD); 598 if (errno) perror_exit("PTRACE_SETOPTIONS PTRACE_O_TRACESYSGOOD failed"); 599 600 // TODO: real strace swallows the failed execve()s if it started the child 601 602 for (;;) { 603 if (!next()) break; 604 print_enter(); 605 if (!next()) break; 606 print_exit(); 607 } 608 609 // TODO: support -f and keep track of children. 610 waitpid(TT.pid, &status, 0); 611 if (WIFEXITED(status)) 612 fprintf(stderr, "+++ exited with %d +++\n", WEXITSTATUS(status)); 613 if (WIFSTOPPED(status)) 614 fprintf(stderr, "+++ stopped with %d +++\n", WSTOPSIG(status)); 615 } 616