toaruos/apps/strace.c

1036 lines
24 KiB
C

/**
* @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 <errno.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/signal_defs.h>
#include <sys/sysfunc.h>
#include <sys/utsname.h>
#include <sys/time.h>
#include <syscall_nums.h>
static FILE * logfile;
struct regs {
/* Pushed by common stub */
uintptr_t r15, r14, r13, r12;
uintptr_t r11, r10, r9, r8;
uintptr_t rbp, rdi, rsi, rdx, rcx, rbx, rax;
/* Pushed by wrapper */
uintptr_t int_no, err_code;
/* Pushed by interrupt */
uintptr_t rip, cs, rflags, rsp, ss;
};
/* 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", r->rax);
if ((intptr_t)r->rax < 0) print_error(-r->rax);
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 (r->rax >= sizeof(syscall_mask)) return;
if (!syscall_mask[r->rax]) return;
fprintf(logfile, "%s(", syscall_names[r->rax]);
switch (r->rax) {
case SYS_OPEN:
string_arg(pid, r->rbx); COMMA;
open_flags(r->rcx);
break;
case SYS_READ:
fd_arg(pid, r->rbx); COMMA;
/* Plus two more when done */
break;
case SYS_WRITE:
fd_arg(pid, r->rbx); COMMA;
buffer_arg(pid, r->rcx, r->rdx); COMMA;
uint_arg(r->rdx);
break;
case SYS_CLOSE:
fd_arg(pid, r->rbx);
break;
case SYS_SBRK:
uint_arg(r->rbx);
break;
case SYS_SEEK:
fd_arg(pid, r->rbx); COMMA;
int_arg(r->rcx); COMMA;
switch (r->rdx) {
case 0: fprintf(logfile, "SEEK_SET"); break;
case 1: fprintf(logfile, "SEEK_CUR"); break;
case 2: fprintf(logfile, "SEEK_END"); break;
default: int_arg(r->rdx); break;
}
break;
case SYS_STATF:
string_arg(pid, r->rbx); COMMA;
pointer_arg(r->rcx);
break;
case SYS_LSTAT:
string_arg(pid, r->rbx); COMMA;
pointer_arg(r->rcx);
break;
case SYS_READDIR:
fd_arg(pid, r->rbx); COMMA;
int_arg(r->rcx); COMMA;
pointer_arg(r->rdx);
break;
case SYS_KILL:
int_arg(r->rbx); COMMA; /* pid_arg? */
int_arg(r->rcx); /* TODO signal name */
break;
case SYS_CHDIR:
string_arg(pid, r->rbx);
break;
case SYS_GETCWD:
/* output is first arg */
pointer_arg(r->rbx); COMMA; /* TODO syscall outputs */
uint_arg(r->rcx);
break;
case SYS_CLONE:
pointer_arg(r->rbx); COMMA;
pointer_arg(r->rcx); COMMA;
pointer_arg(r->rdx);
break;
case SYS_SETHOSTNAME:
string_arg(pid, r->rbx);
break;
case SYS_GETHOSTNAME:
/* plus one more when done */
break;
case SYS_MKDIR:
string_arg(pid, r->rbx); COMMA;
uint_arg(r->rcx);
break;
case SYS_SHUTDOWN:
int_arg(r->rbx); COMMA;
int_arg(r->rcx);
break;
case SYS_ACCESS:
string_arg(pid, r->rbx); COMMA;
int_arg(r->rcx);
break;
case SYS_PTRACE:
switch (r->rbx) {
C(PTRACE_ATTACH);
C(PTRACE_CONT);
C(PTRACE_DETACH);
C(PTRACE_TRACEME);
C(PTRACE_GETREGS);
C(PTRACE_PEEKDATA);
default: int_arg(r->rbx); break;
} COMMA;
int_arg(r->rcx); COMMA;
pointer_arg(r->rdx); COMMA;
pointer_arg(r->rsi);
break;
case SYS_EXECVE:
string_arg(pid, r->rbx); COMMA;
string_array_arg(pid, r->rcx); COMMA;
pointer_arg(r->rdx);
break;
case SYS_SHM_OBTAIN:
string_arg(pid, r->rbx); COMMA;
pointer_arg(r->rcx);
break;
case SYS_SHM_RELEASE:
string_arg(pid, r->rbx);
break;
case SYS_SIGNAL:
signal_arg(r->rbx); COMMA;
pointer_arg(r->rcx);
break;
case SYS_SYSFUNC:
switch (r->rbx) {
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(r->rbx); break;
} COMMA;
pointer_arg(r->rcx);
break;
case SYS_FSWAIT:
int_arg(r->rbx); COMMA;
fds_arg(pid, r->rbx, r->rcx);
break;
case SYS_FSWAIT2:
int_arg(r->rbx); COMMA;
fds_arg(pid, r->rbx, r->rcx); COMMA;
int_arg(r->rdx);
break;
case SYS_FSWAIT3:
int_arg(r->rbx); COMMA;
fds_arg(pid, r->rbx, r->rcx); COMMA;
int_arg(r->rdx); COMMA;
pointer_arg(r->rsi);
break;
case SYS_IOCTL:
fd_arg(pid, r->rbx); COMMA;
int_arg(r->rcx); COMMA;
pointer_arg(r->rdx);
break;
case SYS_WAITPID:
int_arg(r->rbx); COMMA;
pointer_arg(r->rcx); COMMA;
int_arg(r->rdx); /* TODO waitpid options */
break;
case SYS_EXT:
int_arg(r->rbx);
fprintf(logfile, ") = ?\n");
return;
case SYS_UNAME:
/* One output arg */
break;
case SYS_SLEEPABS:
uint_arg(r->rbx); COMMA;
uint_arg(r->rcx);
break;
case SYS_SLEEP:
uint_arg(r->rbx); COMMA;
uint_arg(r->rcx);
break;
case SYS_PIPE:
/* Arg is a pointer */
break;
case SYS_DUP2:
fd_arg(pid, r->rbx); COMMA;
fd_arg(pid, r->rcx);
break;
case SYS_MOUNT:
string_arg(pid, r->rbx); COMMA;
string_arg(pid, r->rcx); COMMA;
uint_arg(r->rdx); COMMA;
pointer_arg(r->rsi);
break;
case SYS_UMASK:
int_arg(r->rbx);
break;
case SYS_UNLINK:
string_arg(pid, r->rbx);
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, r->rcx, r->rax); COMMA;
uint_arg(r->rdx);
maybe_errno(r);
break;
case SYS_GETHOSTNAME:
string_arg(pid, r->rbx);
maybe_errno(r);
break;
case SYS_UNAME:
struct_utsname_arg(pid, r->rbx);
maybe_errno(r);
break;
case SYS_PIPE:
fds_arg(pid, 2, r->rbx);
maybe_errno(r);
break;
case SYS_GETTIMEOFDAY:
struct_timeval_arg(pid, r->rbx);
maybe_errno(r);
break;
/* sbrk() returns an address */
case SYS_SBRK:
fprintf(logfile, ") = %#zx\n", r->rax);
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, &regs);
/* Event type */
int event = (status >> 16) & 0xFF;
switch (event) {
case PTRACE_EVENT_SYSCALL_ENTER:
previous_syscall = regs.rax;
handle_syscall(p, &regs);
break;
case PTRACE_EVENT_SYSCALL_EXIT:
finish_syscall(p, previous_syscall, &regs);
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;
}