/** * @brief Process system call tracer. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static FILE * logfile; #ifdef __x86_64__ # include # define syscall_result(r) ((r)->rax) # define syscall_num(r) ((r)->rax) # define syscall_arg1(r) ((r)->rbx) # define syscall_arg2(r) ((r)->rcx) # define syscall_arg3(r) ((r)->rdx) # define syscall_arg4(r) ((r)->rsi) # define syscall_arg5(r) ((r)->rdi) #elif defined(__aarch64__) # include # define syscall_result(r) ((r)->x0) # define syscall_num(r) ((r)->x0) # define syscall_arg1(r) ((r)->x1) # define syscall_arg2(r) ((r)->x2) # define syscall_arg3(r) ((r)->x3) # define syscall_arg4(r) ((r)->x4) # define syscall_arg5(r) ((r)->x5) #endif /* System call names */ const char * syscall_names[] = { [SYS_EXT] = "exit", [SYS_GETEUID] = "geteuid", [SYS_OPEN] = "open", [SYS_READ] = "read", [SYS_WRITE] = "write", [SYS_CLOSE] = "close", [SYS_GETTIMEOFDAY] = "gettimeofday", [SYS_GETPID] = "getpid", [SYS_SBRK] = "sbrk", [SYS_UNAME] = "uname", [SYS_SEEK] = "seek", [SYS_STAT] = "stat", [SYS_GETUID] = "getuid", [SYS_SETUID] = "setuid", [SYS_READDIR] = "readdir", [SYS_CHDIR] = "chdir", [SYS_GETCWD] = "getcwd", [SYS_SETHOSTNAME] = "sethostname", [SYS_GETHOSTNAME] = "gethostname", [SYS_MKDIR] = "mkdir", [SYS_GETTID] = "gettid", [SYS_SYSFUNC] = "sysfunc", [SYS_IOCTL] = "ioctl", [SYS_ACCESS] = "access", [SYS_STATF] = "statf", [SYS_CHMOD] = "chmod", [SYS_UMASK] = "umask", [SYS_UNLINK] = "unlink", [SYS_MOUNT] = "mount", [SYS_SYMLINK] = "symlink", [SYS_READLINK] = "readlink", [SYS_LSTAT] = "lstat", [SYS_CHOWN] = "chown", [SYS_SETSID] = "setsid", [SYS_SETPGID] = "setpgid", [SYS_GETPGID] = "getpgid", [SYS_DUP2] = "dup2", [SYS_EXECVE] = "execve", [SYS_FORK] = "fork", [SYS_WAITPID] = "waitpid", [SYS_YIELD] = "yield", [SYS_SLEEPABS] = "sleepabs", [SYS_SLEEP] = "sleep", [SYS_PIPE] = "pipe", [SYS_FSWAIT] = "fswait", [SYS_FSWAIT2] = "fswait_timeout", [SYS_FSWAIT3] = "fswait_multi", [SYS_CLONE] = "clone", [SYS_OPENPTY] = "openpty", [SYS_SHM_OBTAIN] = "shm_obtain", [SYS_SHM_RELEASE] = "shm_release", [SYS_SIGNAL] = "signal", [SYS_KILL] = "kill", [SYS_REBOOT] = "reboot", [SYS_GETGID] = "getgid", [SYS_GETEGID] = "getegid", [SYS_SETGID] = "setgid", [SYS_GETGROUPS] = "getgroups", [SYS_SETGROUPS] = "setgroups", [SYS_TIMES] = "times", [SYS_PTRACE] = "ptrace", [SYS_SOCKET] = "socket", [SYS_SETSOCKOPT] = "setsockopt", [SYS_BIND] = "bind", [SYS_ACCEPT] = "accept", [SYS_LISTEN] = "listen", [SYS_CONNECT] = "connect", [SYS_GETSOCKOPT] = "getsockopt", [SYS_RECV] = "recv", [SYS_SEND] = "send", [SYS_SHUTDOWN] = "shutdown", }; char syscall_mask[] = { [SYS_EXT] = 1, [SYS_GETEUID] = 1, [SYS_OPEN] = 1, [SYS_READ] = 1, [SYS_WRITE] = 1, [SYS_CLOSE] = 1, [SYS_GETTIMEOFDAY] = 1, [SYS_GETPID] = 1, [SYS_SBRK] = 1, [SYS_UNAME] = 1, [SYS_SEEK] = 1, [SYS_STAT] = 1, [SYS_GETUID] = 1, [SYS_SETUID] = 1, [SYS_READDIR] = 1, [SYS_CHDIR] = 1, [SYS_GETCWD] = 1, [SYS_SETHOSTNAME] = 1, [SYS_GETHOSTNAME] = 1, [SYS_MKDIR] = 1, [SYS_GETTID] = 1, [SYS_SYSFUNC] = 1, [SYS_IOCTL] = 1, [SYS_ACCESS] = 1, [SYS_STATF] = 1, [SYS_CHMOD] = 1, [SYS_UMASK] = 1, [SYS_UNLINK] = 1, [SYS_MOUNT] = 1, [SYS_SYMLINK] = 1, [SYS_READLINK] = 1, [SYS_LSTAT] = 1, [SYS_CHOWN] = 1, [SYS_SETSID] = 1, [SYS_SETPGID] = 1, [SYS_GETPGID] = 1, [SYS_DUP2] = 1, [SYS_EXECVE] = 1, [SYS_FORK] = 1, [SYS_WAITPID] = 1, [SYS_YIELD] = 1, [SYS_SLEEPABS] = 1, [SYS_SLEEP] = 1, [SYS_PIPE] = 1, [SYS_FSWAIT] = 1, [SYS_FSWAIT2] = 1, [SYS_FSWAIT3] = 1, [SYS_CLONE] = 1, [SYS_OPENPTY] = 1, [SYS_SHM_OBTAIN] = 1, [SYS_SHM_RELEASE] = 1, [SYS_SIGNAL] = 1, [SYS_KILL] = 1, [SYS_REBOOT] = 1, [SYS_GETGID] = 1, [SYS_GETEGID] = 1, [SYS_SETGID] = 1, [SYS_GETGROUPS] = 1, [SYS_SETGROUPS] = 1, [SYS_TIMES] = 1, [SYS_PTRACE] = 1, [SYS_SOCKET] = 1, [SYS_SETSOCKOPT] = 1, [SYS_BIND] = 1, [SYS_ACCEPT] = 1, [SYS_LISTEN] = 1, [SYS_CONNECT] = 1, [SYS_GETSOCKOPT] = 1, [SYS_RECV] = 1, [SYS_SEND] = 1, [SYS_SHUTDOWN] = 1, }; #define M(e) [e] = #e const char * errno_names[256] = { M(EPERM), M(ENOENT), M(ESRCH), M(EINTR), M(EIO), M(ENXIO), M(E2BIG), M(ENOEXEC), M(EBADF), M(ECHILD), M(EAGAIN), M(ENOMEM), M(EACCES), M(EFAULT), M(ENOTBLK), M(EBUSY), M(EEXIST), M(EXDEV), M(ENODEV), M(ENOTDIR), M(EISDIR), M(EINVAL), M(ENFILE), M(EMFILE), M(ENOTTY), M(ETXTBSY), M(EFBIG), M(ENOSPC), M(ESPIPE), M(EROFS), M(EMLINK), M(EPIPE), M(EDOM), M(ERANGE), M(ENOMSG), M(EIDRM), M(ECHRNG), M(EL2NSYNC), M(EL3HLT), M(EL3RST), M(ELNRNG), M(EUNATCH), M(ENOCSI), M(EL2HLT), M(EDEADLK), M(ENOLCK), M(EBADE), M(EBADR), M(EXFULL), M(ENOANO), M(EBADRQC), M(EBADSLT), M(EDEADLOCK), M(EBFONT), M(ENOSTR), M(ENODATA), M(ETIME), M(ENOSR), M(ENONET), M(ENOPKG), M(EREMOTE), M(ENOLINK), M(EADV), M(ESRMNT), M(ECOMM), M(EPROTO), M(EMULTIHOP), M(ELBIN), M(EDOTDOT), M(EBADMSG), M(EFTYPE), M(ENOTUNIQ), M(EBADFD), M(EREMCHG), M(ELIBACC), M(ELIBBAD), M(ELIBSCN), M(ELIBMAX), M(ELIBEXEC), M(ENOSYS), M(ENOTEMPTY), M(ENAMETOOLONG), M(ELOOP), M(EOPNOTSUPP), M(EPFNOSUPPORT), M(ECONNRESET), M(ENOBUFS), M(EAFNOSUPPORT), M(EPROTOTYPE), M(ENOTSOCK), M(ENOPROTOOPT), M(ESHUTDOWN), M(ECONNREFUSED), M(EADDRINUSE), M(ECONNABORTED), M(ENETUNREACH), M(ENETDOWN), M(ETIMEDOUT), M(EHOSTDOWN), M(EHOSTUNREACH), M(EINPROGRESS), M(EALREADY), M(EDESTADDRREQ), M(EMSGSIZE), M(EPROTONOSUPPORT), M(ESOCKTNOSUPPORT), M(EADDRNOTAVAIL), M(EISCONN), M(ENOTCONN), M(ENOTSUP), M(EOVERFLOW), M(ECANCELED), M(ENOTRECOVERABLE), M(EOWNERDEAD), M(ESTRPIPE), 0 }; const char * signal_names[NSIG] = { M(SIGHUP), M(SIGINT), M(SIGQUIT), M(SIGILL), M(SIGTRAP), M(SIGABRT), M(SIGEMT), M(SIGFPE), M(SIGKILL), M(SIGBUS), M(SIGSEGV), M(SIGSYS), M(SIGPIPE), M(SIGALRM), M(SIGTERM), M(SIGUSR1), M(SIGUSR2), M(SIGCHLD), M(SIGPWR), M(SIGWINCH), M(SIGURG), M(SIGPOLL), M(SIGSTOP), M(SIGTSTP), M(SIGCONT), M(SIGTTIN), M(SIGTTOUT), M(SIGVTALRM), M(SIGPROF), M(SIGXCPU), M(SIGXFSZ), M(SIGWAITING), M(SIGDIAF), M(SIGHATE), M(SIGWINEVENT), M(SIGCAT), M(SIGTTOU), }; #if 0 static void dump_regs(struct regs * r) { fprintf(logfile, " $rip=0x%016lx\n" " $rsi=0x%016lx,$rdi=0x%016lx,$rbp=0x%016lx,$rsp=0x%016lx\n" " $rax=0x%016lx,$rbx=0x%016lx,$rcx=0x%016lx,$rdx=0x%016lx\n" " $r8= 0x%016lx,$r9= 0x%016lx,$r10=0x%016lx,$r11=0x%016lx\n" " $r12=0x%016lx,$r13=0x%016lx,$r14=0x%016lx,$r15=0x%016lx\n" " cs=0x%016lx ss=0x%016lx rflags=0x%016lx int=0x%02lx err=0x%02lx\n", r->rip, r->rsi, r->rdi, r->rbp, r->rsp, r->rax, r->rbx, r->rcx, r->rdx, r->r8, r->r9, r->r10, r->r11, r->r12, r->r13, r->r14, r->r15, r->cs, r->ss, r->rflags, r->int_no, r->err_code ); } #endif static void open_flags(int flags) { if (!flags) { fprintf(logfile, "O_RDONLY"); return; } /* That's all that's valid right now */ flags &= 0xFFFF; #define H(flg) do { if (flags & flg) { fprintf(logfile, #flg); flags &= (~flg); if (flags) fprintf(logfile, "|"); } } while (0) H(O_WRONLY); H(O_RDWR); H(O_APPEND); H(O_CREAT); H(O_TRUNC); H(O_EXCL); H(O_NOFOLLOW); H(O_PATH); H(O_NONBLOCK); H(O_DIRECTORY); if (flags) { fprintf(logfile, "(%#x)", flags); } } static void string_arg(pid_t pid, uintptr_t ptr) { if (ptr == 0) { fprintf(logfile, "NULL"); return; } fprintf(logfile, "\""); size_t size = 0; uint8_t buf = 0; do { long result = ptrace(PTRACE_PEEKDATA, pid, (void*)ptr, &buf); if (result != 0) break; if (!buf) { fprintf(logfile, "\""); return; } if (buf == '\\') fprintf(logfile, "\\\\"); else if (buf == '"') fprintf(logfile, "\\\""); else if (buf >= ' ' && buf < '~') fprintf(logfile, "%c", buf); else if (buf == '\r') fprintf(logfile, "\\r"); else if (buf == '\n') fprintf(logfile, "\\n"); else fprintf(logfile, "\\x%02x", buf); ptr++; size++; if (size > 30) break; } while (buf); fprintf(logfile, "\"..."); } #define C(arg) case arg: fprintf(logfile, #arg); break #define COMMA fprintf(logfile, ", "); static void pointer_arg(uintptr_t ptr) { if (ptr == 0) fprintf(logfile, "NULL"); else fprintf(logfile, "%#zx", ptr); } static void uint_arg(size_t val) { fprintf(logfile, "%zu", val); } static void int_arg(size_t val) { fprintf(logfile, "%zd", val); } static void fd_arg(pid_t pid, int val) { /* TODO: Look up file in user data? */ fprintf(logfile, "%d", val); } static int data_read_bytes(pid_t pid, uintptr_t addr, char * buf, size_t size) { for (unsigned int i = 0; i < size; ++i) { if (ptrace(PTRACE_PEEKDATA, pid, (void*)addr++, &buf[i])) { return 1; } } return 0; } static int data_read_int(pid_t pid, uintptr_t addr) { int x; data_read_bytes(pid, addr, (char*)&x, sizeof(int)); return x; } static uintptr_t data_read_ptr(pid_t pid, uintptr_t addr) { uintptr_t x; data_read_bytes(pid, addr, (char*)&x, sizeof(uintptr_t)); return x; } static void fds_arg(pid_t pid, size_t ecount, uintptr_t array) { fprintf(logfile, "["); for (size_t count = 0; count < 10 && count < ecount; ++count) { int x = data_read_int(pid, array); fprintf(logfile, "%d", x); if (count + 1 < ecount) fprintf(logfile, ","); array += sizeof(int); } fprintf(logfile, "]"); } static void string_array_arg(pid_t pid, uintptr_t array) { fprintf(logfile, "["); uintptr_t val = data_read_ptr(pid, array); for (size_t count = 0; count < 10; ++count) { string_arg(pid, val); array += sizeof(uintptr_t); val = data_read_ptr(pid, array); if (val) { COMMA; } else break; } fprintf(logfile, "]"); } static void buffer_arg(pid_t pid, uintptr_t buffer, ssize_t count) { if (count < 0) { fprintf(logfile, "..."); } else if (buffer == 0) { fprintf(logfile, "NULL"); } else { ssize_t x = 0; uint8_t buf = 0; fprintf(logfile, "\""); while (x < count && x < 30) { long result = ptrace(PTRACE_PEEKDATA, pid, (void*)buffer, &buf); if (result != 0) break; if (buf == '\\') fprintf(logfile, "\\\\"); else if (buf == '"') fprintf(logfile, "\\\""); else if (buf >= ' ' && buf < '~') fprintf(logfile, "%c", buf); else if (buf == '\r') fprintf(logfile, "\\r"); else if (buf == '\n') fprintf(logfile, "\\n"); else fprintf(logfile, "\\x%02x", buf); buffer++; x++; } fprintf(logfile, "\""); if (x < count) fprintf(logfile, "..."); } } static void print_error(int err) { if (err > 255) return; fprintf(logfile, " %s (%s)", errno_names[err], strerror(err)); } static void maybe_errno(struct regs * r) { fprintf(logfile, ") = %ld", syscall_result(r)); if ((intptr_t)syscall_result(r) < 0) print_error(syscall_result(r)); fprintf(logfile, "\n"); } static void struct_utsname_arg(pid_t pid, uintptr_t ptr) { if (!ptr) { fprintf(logfile, "NULL"); return; } fprintf(logfile, "{"); fprintf(logfile, "sysname="); string_arg(pid, ptr + offsetof(struct utsname, sysname)); COMMA; fprintf(logfile, "nodename="); string_arg(pid, ptr + offsetof(struct utsname, nodename)); COMMA; fprintf(logfile, "...}"); } static void struct_timeval_arg(pid_t pid, uintptr_t ptr) { if (!ptr) { fprintf(logfile, "NULL"); return; } fprintf(logfile, "{"); fprintf(logfile, "tv_sec="); int_arg(data_read_ptr(pid, ptr + offsetof(struct timeval, tv_sec))); COMMA; fprintf(logfile, "tv_usec="); int_arg(data_read_ptr(pid, ptr + offsetof(struct timeval, tv_usec))); fprintf(logfile, "}"); } static void signal_arg(int signum) { if (signum >= 0 && signum < NSIG && signal_names[signum]) { fprintf(logfile, "%s", signal_names[signum]); } else { fprintf(logfile, "%d", signum); } } static void handle_syscall(pid_t pid, struct regs * r) { if (syscall_num(r) >= sizeof(syscall_mask)) return; if (!syscall_mask[syscall_num(r)]) return; fprintf(logfile, "%s(", syscall_names[syscall_num(r)]); switch (syscall_num(r)) { case SYS_OPEN: string_arg(pid, syscall_arg1(r)); COMMA; open_flags(syscall_arg2(r)); break; case SYS_READ: fd_arg(pid, syscall_arg1(r)); COMMA; /* Plus two more when done */ break; case SYS_WRITE: fd_arg(pid, syscall_arg1(r)); COMMA; buffer_arg(pid, syscall_arg2(r), syscall_arg3(r)); COMMA; uint_arg(syscall_arg3(r)); break; case SYS_CLOSE: fd_arg(pid, syscall_arg1(r)); break; case SYS_SBRK: uint_arg(syscall_arg1(r)); break; case SYS_SEEK: fd_arg(pid, syscall_arg1(r)); COMMA; int_arg(syscall_arg2(r)); COMMA; switch (syscall_arg3(r)) { case 0: fprintf(logfile, "SEEK_SET"); break; case 1: fprintf(logfile, "SEEK_CUR"); break; case 2: fprintf(logfile, "SEEK_END"); break; default: int_arg(syscall_arg3(r)); break; } break; case SYS_STATF: string_arg(pid, syscall_arg1(r)); COMMA; pointer_arg(syscall_arg2(r)); break; case SYS_LSTAT: string_arg(pid, syscall_arg1(r)); COMMA; pointer_arg(syscall_arg2(r)); break; case SYS_READDIR: fd_arg(pid, syscall_arg1(r)); COMMA; int_arg(syscall_arg2(r)); COMMA; pointer_arg(syscall_arg3(r)); break; case SYS_KILL: int_arg(syscall_arg1(r)); COMMA; /* pid_arg? */ int_arg(syscall_arg2(r)); /* TODO signal name */ break; case SYS_CHDIR: string_arg(pid, syscall_arg1(r)); break; case SYS_GETCWD: /* output is first arg */ pointer_arg(syscall_arg1(r)); COMMA; /* TODO syscall outputs */ uint_arg(syscall_arg2(r)); break; case SYS_CLONE: pointer_arg(syscall_arg1(r)); COMMA; pointer_arg(syscall_arg2(r)); COMMA; pointer_arg(syscall_arg3(r)); break; case SYS_SETHOSTNAME: string_arg(pid, syscall_arg1(r)); break; case SYS_GETHOSTNAME: /* plus one more when done */ break; case SYS_MKDIR: string_arg(pid, syscall_arg1(r)); COMMA; uint_arg(syscall_arg2(r)); break; case SYS_SHUTDOWN: int_arg(syscall_arg1(r)); COMMA; int_arg(syscall_arg2(r)); break; case SYS_ACCESS: string_arg(pid, syscall_arg1(r)); COMMA; int_arg(syscall_arg2(r)); break; case SYS_PTRACE: switch (syscall_arg1(r)) { C(PTRACE_ATTACH); C(PTRACE_CONT); C(PTRACE_DETACH); C(PTRACE_TRACEME); C(PTRACE_GETREGS); C(PTRACE_PEEKDATA); default: int_arg(syscall_arg1(r)); break; } COMMA; int_arg(syscall_arg2(r)); COMMA; pointer_arg(syscall_arg3(r)); COMMA; pointer_arg(syscall_arg4(r)); break; case SYS_EXECVE: string_arg(pid, syscall_arg1(r)); COMMA; string_array_arg(pid, syscall_arg2(r)); COMMA; pointer_arg(syscall_arg3(r)); break; case SYS_SHM_OBTAIN: string_arg(pid, syscall_arg1(r)); COMMA; pointer_arg(syscall_arg2(r)); break; case SYS_SHM_RELEASE: string_arg(pid, syscall_arg1(r)); break; case SYS_SIGNAL: signal_arg(syscall_arg1(r)); COMMA; pointer_arg(syscall_arg2(r)); break; case SYS_SYSFUNC: switch (syscall_arg1(r)) { C(TOARU_SYS_FUNC_SYNC); C(TOARU_SYS_FUNC_LOGHERE); C(TOARU_SYS_FUNC_KDEBUG); C(TOARU_SYS_FUNC_INSMOD); C(TOARU_SYS_FUNC_SETHEAP); C(TOARU_SYS_FUNC_MMAP); C(TOARU_SYS_FUNC_THREADNAME); C(TOARU_SYS_FUNC_SETGSBASE); C(TOARU_SYS_FUNC_NPROC); default: int_arg(syscall_arg1(r)); break; } COMMA; pointer_arg(syscall_arg2(r)); break; case SYS_FSWAIT: int_arg(syscall_arg1(r)); COMMA; fds_arg(pid, syscall_arg1(r), syscall_arg2(r)); break; case SYS_FSWAIT2: int_arg(syscall_arg1(r)); COMMA; fds_arg(pid, syscall_arg1(r), syscall_arg2(r)); COMMA; int_arg(syscall_arg3(r)); break; case SYS_FSWAIT3: int_arg(syscall_arg1(r)); COMMA; fds_arg(pid, syscall_arg1(r), syscall_arg2(r)); COMMA; int_arg(syscall_arg3(r)); COMMA; pointer_arg(syscall_arg4(r)); break; case SYS_IOCTL: fd_arg(pid, syscall_arg1(r)); COMMA; int_arg(syscall_arg2(r)); COMMA; pointer_arg(syscall_arg3(r)); break; case SYS_WAITPID: int_arg(syscall_arg1(r)); COMMA; pointer_arg(syscall_arg2(r)); COMMA; int_arg(syscall_arg3(r)); /* TODO waitpid options */ break; case SYS_EXT: int_arg(syscall_arg1(r)); fprintf(logfile, ") = ?\n"); return; case SYS_UNAME: /* One output arg */ break; case SYS_SLEEPABS: uint_arg(syscall_arg1(r)); COMMA; uint_arg(syscall_arg2(r)); break; case SYS_SLEEP: uint_arg(syscall_arg1(r)); COMMA; uint_arg(syscall_arg2(r)); break; case SYS_PIPE: /* Arg is a pointer */ break; case SYS_DUP2: fd_arg(pid, syscall_arg1(r)); COMMA; fd_arg(pid, syscall_arg2(r)); break; case SYS_MOUNT: string_arg(pid, syscall_arg1(r)); COMMA; string_arg(pid, syscall_arg2(r)); COMMA; uint_arg(syscall_arg3(r)); COMMA; pointer_arg(syscall_arg4(r)); break; case SYS_UMASK: int_arg(syscall_arg1(r)); break; case SYS_UNLINK: string_arg(pid, syscall_arg1(r)); break; case SYS_GETTIMEOFDAY: /* two output args */ break; /* These have no arguments: */ case SYS_YIELD: case SYS_FORK: case SYS_GETEUID: case SYS_GETPID: case SYS_GETUID: case SYS_REBOOT: case SYS_GETTID: case SYS_SETSID: case SYS_GETGID: case SYS_GETEGID: break; default: fprintf(logfile, "..."); break; } fflush(stdout); } static void finish_syscall(pid_t pid, int syscall, struct regs * r) { if (syscall >= (int)sizeof(syscall_mask)) return; if (syscall >= 0 && !syscall_mask[syscall]) return; switch (syscall) { case -1: break; /* This is ptrace(PTRACE_TRACEME)... probably... */ /* read() returns data in second value */ case SYS_READ: buffer_arg(pid, syscall_arg2(r), syscall_result(r)); COMMA; uint_arg(syscall_arg3(r)); maybe_errno(r); break; case SYS_GETHOSTNAME: string_arg(pid, syscall_arg1(r)); maybe_errno(r); break; case SYS_UNAME: struct_utsname_arg(pid, syscall_arg1(r)); maybe_errno(r); break; case SYS_PIPE: fds_arg(pid, 2, syscall_arg1(r)); maybe_errno(r); break; case SYS_GETTIMEOFDAY: struct_timeval_arg(pid, syscall_arg1(r)); maybe_errno(r); break; /* sbrk() returns an address */ case SYS_SBRK: fprintf(logfile, ") = %#zx\n", syscall_result(r)); break; /* Most things return -errno, or positive valid result */ default: maybe_errno(r); break; } } static int usage(char * argv[]) { #define T_I "\033[3m" #define T_O "\033[0m" fprintf(stderr, "usage: %s [-o logfile] [-e trace=...] [-p PID] [command...]\n" " -o logfile " T_I "Write tracing output to a file." T_O "\n" " -h " T_I "Show this help text." T_O "\n" " -e trace=... " T_I "Set tracing options." T_O "\n" " -p PID " T_I "Trace an existing process." T_O "\n", argv[0]); return 1; } int main(int argc, char * argv[]) { logfile = stdout; pid_t p = 0; int opt; while ((opt = getopt(argc, argv, "ho:e:p:")) != -1) { switch (opt) { case 'p': p = atoi(optarg); break; case 'o': logfile = fopen(optarg, "w"); if (!logfile) { fprintf(stderr, "%s: %s: %s\n", argv[0], optarg, strerror(errno)); return 1; } break; case 'e': if (strstr(optarg,"trace=") == optarg) { /* First, disable everything. */ memset(syscall_mask, 0, sizeof(syscall_mask)); /* Now look at each comma-separated option */ char * option = optarg + 6; char * comma = strstr(option, ","); do { if (comma) *comma = '\0'; if (*option == '%') { /* Check for special options */ if (!strcmp(option+1,"net") || !strcmp(option+1,"network")) { int syscalls[] = { SYS_SOCKET, SYS_SETSOCKOPT, SYS_BIND, SYS_ACCEPT, SYS_LISTEN, SYS_CONNECT, SYS_GETSOCKOPT, SYS_RECV, SYS_SEND, SYS_SHUTDOWN, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"file")) { int syscalls[] = { SYS_OPEN, SYS_STATF, SYS_LSTAT, SYS_ACCESS, SYS_EXECVE, SYS_GETCWD, SYS_CHDIR, SYS_MKDIR, SYS_SYMLINK, SYS_UNLINK, SYS_CHMOD, SYS_CHOWN, SYS_MOUNT, SYS_READLINK, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"desc")) { int syscalls[] = { SYS_OPEN, SYS_READ, SYS_WRITE, SYS_CLOSE, SYS_STAT, SYS_FSWAIT, SYS_FSWAIT2, SYS_FSWAIT3, SYS_SEEK, SYS_IOCTL, SYS_PIPE, SYS_MKPIPE, SYS_DUP2, SYS_READDIR, SYS_OPENPTY, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"memory")) { int syscalls[] = { SYS_SBRK, SYS_SHM_OBTAIN, SYS_SHM_RELEASE, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"ipc")) { int syscalls[] = { SYS_SHM_OBTAIN, SYS_SHM_RELEASE, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"signal")) { int syscalls[] = { SYS_SIGNAL, SYS_KILL, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"process")) { int syscalls[] = { SYS_EXT, SYS_EXECVE, SYS_FORK, SYS_CLONE, SYS_WAITPID, SYS_KILL, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"creds")) { int syscalls[] = { SYS_GETUID, SYS_GETGID, SYS_GETGROUPS, SYS_GETEGID, SYS_GETEUID, SYS_SETUID, SYS_SETGID, SYS_SETGROUPS, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else { fprintf(stderr, "%s: Unrecognized syscall group: %s\n", argv[0], option + 1); return 1; } } else { /* Check the list */ int set_something = 0; for (size_t i = 0; i < sizeof(syscall_names) / sizeof(*syscall_names); ++i) { if (syscall_names[i] && !strcmp(option,syscall_names[i])) { syscall_mask[i] = 1; set_something = 1; break; } } if (!set_something) { fprintf(stderr, "%s: Unrecognized syscall name: %s\n", argv[0], option); return 1; } } if (comma) { option = comma + 1; } } while (comma); } else { char * eq = strstr(optarg, "="); if (eq) *eq = '\0'; fprintf(stderr, "%s: Unrecognized -e option: %s\n", argv[0], optarg); return 1; } break; case 'h': return (usage(argv), 0); case '?': return usage(argv); } } if (!p && optind == argc) { return usage(argv); } if (!p) { p = fork(); if (!p) { if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) { fprintf(stderr, "%s: ptrace: %s\n", argv[0], strerror(errno)); return 1; } execvp(argv[optind], &argv[optind]); return 1; } signal(SIGINT, SIG_IGN); } else { if (ptrace(PTRACE_ATTACH, p, NULL, NULL) < 0) { fprintf(stderr, "%s: ptrace: %s\n", argv[0], strerror(errno)); return 1; } } int previous_syscall = -1; while (1) { int status = 0; pid_t res = waitpid(p, &status, WSTOPPED); if (res < 0) { fprintf(stderr, "%s: waitpid: %s\n", argv[0], strerror(errno)); } else { if (WIFSTOPPED(status)) { if (WSTOPSIG(status) == SIGTRAP) { struct regs regs; ptrace(PTRACE_GETREGS, p, NULL, ®s); /* Event type */ int event = (status >> 16) & 0xFF; switch (event) { case PTRACE_EVENT_SYSCALL_ENTER: previous_syscall = syscall_num(®s); handle_syscall(p, ®s); break; case PTRACE_EVENT_SYSCALL_EXIT: finish_syscall(p, previous_syscall, ®s); break; default: fprintf(logfile, "Unknown event.\n"); break; } ptrace(PTRACE_CONT, p, NULL, NULL); } else { fprintf(logfile, "--- %s ---\n", signal_names[WSTOPSIG(status)]); ptrace(PTRACE_CONT, p, NULL, (void*)(uintptr_t)WSTOPSIG(status)); } } else if (WIFSIGNALED(status)) { fprintf(logfile, "+++ killed by %s +++\n", signal_names[WTERMSIG(status)]); return 0; } else if (WIFEXITED(status)) { fprintf(logfile, "+++ exited with %d +++\n", WEXITSTATUS(status)); return 0; } } } return 0; }