ptrace: Catch signals, start work on debugger

This commit is contained in:
K. Lange 2021-09-22 20:26:16 +09:00
parent 7bf635c492
commit 470b2bfabb
9 changed files with 441 additions and 184 deletions

227
apps/dbg.c Normal file
View File

@ -0,0 +1,227 @@
/**
* @brief Debugger.
*
* @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>
#include <toaru/rline.h>
struct regs {
uintptr_t r15, r14, r13, r12;
uintptr_t r11, r10, r9, r8;
uintptr_t rbp, rdi, rsi, rdx, rcx, rbx, rax;
uintptr_t int_no, err_code;
uintptr_t rip, cs, rflags, rsp, ss;
};
static void dump_regs(struct regs * r) {
fprintf(stdout,
" $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
);
}
#define M(e) [e] = #e
const char * signal_names[256] = {
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),
};
static char * last_command = NULL;
static void show_commandline(pid_t pid, int status, struct regs * regs) {
fprintf(stderr, "[Process %d, $rip=%#zx]\n",
pid, regs->rip);
while (1) {
char buf[4096] = {0};
rline_exit_string = "";
rline_exp_set_prompts("(dbg) ", "", 6, 0);
rline_exp_set_syntax("dbg");
rline_exp_set_tab_complete_func(NULL); /* TODO */
if (rline(buf, 4096) == 0) goto _exitDebugger;
char *nl = strstr(buf, "\n");
if (nl) *nl = '\0';
if (!strlen(buf)) {
if (last_command) {
strcpy(buf, last_command);
} else {
continue;
}
} else {
rline_history_insert(strdup(buf));
rline_scroll = 0;
if (last_command) free(last_command);
last_command = strdup(buf);
}
/* Tokenize just the first command */
char * arg = NULL;
char * sp = strstr(buf, " ");
if (sp) {
*sp = '\0';
arg = sp + 1;
}
if (!strcmp(buf, "show")) {
if (!arg) {
fprintf(stderr, "Things that can be shown:\n");
fprintf(stderr, " regs\n");
continue;
}
if (!strcmp(arg, "regs")) {
dump_regs(regs);
} else {
fprintf(stderr, "Don't know how to show '%s'\n", arg);
}
} else if (!strcmp(buf, "continue") || !strcmp(buf,"c")) {
int signum = WSTOPSIG(status);
if (signum == SIGINT) signum = 0;
ptrace(PTRACE_CONT, pid, NULL, (void*)(uintptr_t)signum);
return;
} else {
fprintf(stderr, "dbg: unrecognized command '%s'\n", buf);
continue;
}
}
_exitDebugger:
fprintf(stderr, "Exiting.\n");
exit(0);
}
static int usage(char * argv[]) {
#define T_I "\033[3m"
#define T_O "\033[0m"
fprintf(stderr, "usage: %s command...\n"
" -h " T_I "Show this help text." T_O "\n",
argv[0]);
return 1;
}
int main(int argc, char * argv[]) {
int opt;
while ((opt = getopt(argc, argv, "o:")) != -1) {
switch (opt) {
case 'h':
return (usage(argv), 0);
case '?':
return usage(argv);
}
}
if (optind == argc) {
return usage(argv);
}
/* TODO find argv[optind] */
/* TODO load symbols from it, and from its dependencies... with offsets... from ld.so... */
pid_t 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;
} else {
signal(SIGINT, SIG_IGN);
while (1) {
int status = 0;
pid_t res = waitpid(p, &status, WSTOPPED);
if (res < 0) {
if (errno == EINTR) continue;
fprintf(stderr, "%s: waitpid: %s\n", argv[0], strerror(errno));
} else {
if (WIFSTOPPED(status)) {
if (WSTOPSIG(status) == SIGTRAP) {
/* Don't care about TRAP right now */
ptrace(PTRACE_CONT, p, NULL, NULL);
} else {
printf("Program received signal %s.\n", signal_names[WSTOPSIG(status)]);
struct regs regs;
ptrace(PTRACE_GETREGS, res, NULL, &regs);
show_commandline(res, status, &regs);
}
} else if (WIFSIGNALED(status)) {
fprintf(stderr, "Process %d was killed by %s.\n", res, signal_names[WTERMSIG(status)]);
return 0;
} else if (WIFEXITED(status)) {
fprintf(stderr, "Process %d exited normally.\n", res);
return 0;
}
}
}
}
return 0;
}

View File

@ -422,19 +422,15 @@ void tab_complete_func(rline_context_t * c) {
command_adj += 1;
}
if (command_adj < argc && (!strcmp(argv[command_adj], "time"))) {
cursor_adj -= 1;
command_adj += 1;
}
/* sudo should shift commands */
if (command_adj < argc && (!strcmp(argv[command_adj], "sudo") || !strcmp(argv[command_adj], "gsudo"))) {
cursor_adj -= 1;
command_adj += 1;
}
/* So should strace */
if (command_adj < argc && (!strcmp(argv[command_adj], "strace"))) {
/* Various commands are generally prefixes */
if (command_adj < argc && (
!strcmp(argv[command_adj], "sudo") ||
!strcmp(argv[command_adj], "gsudo") ||
!strcmp(argv[command_adj], "time") ||
/* TODO: Both of these may take additional arguments... */
!strcmp(argv[command_adj], "strace") ||
!strcmp(argv[command_adj], "dbg")
)) {
cursor_adj -= 1;
command_adj += 1;
}

View File

@ -111,124 +111,163 @@ const char * syscall_names[] = {
[SYS_SHUTDOWN] = "shutdown",
};
#define M(e) [e] = #e
const char * errno_names[256] = {
#define ERRNO(e) [e] = #e
ERRNO(EPERM),
ERRNO(ENOENT),
ERRNO(ESRCH),
ERRNO(EINTR),
ERRNO(EIO),
ERRNO(ENXIO),
ERRNO(E2BIG),
ERRNO(ENOEXEC),
ERRNO(EBADF),
ERRNO(ECHILD),
ERRNO(EAGAIN),
ERRNO(ENOMEM),
ERRNO(EACCES),
ERRNO(EFAULT),
ERRNO(ENOTBLK),
ERRNO(EBUSY),
ERRNO(EEXIST),
ERRNO(EXDEV),
ERRNO(ENODEV),
ERRNO(ENOTDIR),
ERRNO(EISDIR),
ERRNO(EINVAL),
ERRNO(ENFILE),
ERRNO(EMFILE),
ERRNO(ENOTTY),
ERRNO(ETXTBSY),
ERRNO(EFBIG),
ERRNO(ENOSPC),
ERRNO(ESPIPE),
ERRNO(EROFS),
ERRNO(EMLINK),
ERRNO(EPIPE),
ERRNO(EDOM),
ERRNO(ERANGE),
ERRNO(ENOMSG),
ERRNO(EIDRM),
ERRNO(ECHRNG),
ERRNO(EL2NSYNC),
ERRNO(EL3HLT),
ERRNO(EL3RST),
ERRNO(ELNRNG),
ERRNO(EUNATCH),
ERRNO(ENOCSI),
ERRNO(EL2HLT),
ERRNO(EDEADLK),
ERRNO(ENOLCK),
ERRNO(EBADE),
ERRNO(EBADR),
ERRNO(EXFULL),
ERRNO(ENOANO),
ERRNO(EBADRQC),
ERRNO(EBADSLT),
ERRNO(EDEADLOCK),
ERRNO(EBFONT),
ERRNO(ENOSTR),
ERRNO(ENODATA),
ERRNO(ETIME),
ERRNO(ENOSR),
ERRNO(ENONET),
ERRNO(ENOPKG),
ERRNO(EREMOTE),
ERRNO(ENOLINK),
ERRNO(EADV),
ERRNO(ESRMNT),
ERRNO(ECOMM),
ERRNO(EPROTO),
ERRNO(EMULTIHOP),
ERRNO(ELBIN),
ERRNO(EDOTDOT),
ERRNO(EBADMSG),
ERRNO(EFTYPE),
ERRNO(ENOTUNIQ),
ERRNO(EBADFD),
ERRNO(EREMCHG),
ERRNO(ELIBACC),
ERRNO(ELIBBAD),
ERRNO(ELIBSCN),
ERRNO(ELIBMAX),
ERRNO(ELIBEXEC),
ERRNO(ENOSYS),
ERRNO(ENOTEMPTY),
ERRNO(ENAMETOOLONG),
ERRNO(ELOOP),
ERRNO(EOPNOTSUPP),
ERRNO(EPFNOSUPPORT),
ERRNO(ECONNRESET),
ERRNO(ENOBUFS),
ERRNO(EAFNOSUPPORT),
ERRNO(EPROTOTYPE),
ERRNO(ENOTSOCK),
ERRNO(ENOPROTOOPT),
ERRNO(ESHUTDOWN),
ERRNO(ECONNREFUSED),
ERRNO(EADDRINUSE),
ERRNO(ECONNABORTED),
ERRNO(ENETUNREACH),
ERRNO(ENETDOWN),
ERRNO(ETIMEDOUT),
ERRNO(EHOSTDOWN),
ERRNO(EHOSTUNREACH),
ERRNO(EINPROGRESS),
ERRNO(EALREADY),
ERRNO(EDESTADDRREQ),
ERRNO(EMSGSIZE),
ERRNO(EPROTONOSUPPORT),
ERRNO(ESOCKTNOSUPPORT),
ERRNO(EADDRNOTAVAIL),
ERRNO(EISCONN),
ERRNO(ENOTCONN),
ERRNO(ENOTSUP),
ERRNO(EOVERFLOW),
ERRNO(ECANCELED),
ERRNO(ENOTRECOVERABLE),
ERRNO(EOWNERDEAD),
ERRNO(ESTRPIPE),
#undef ERRNO
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),
};
const char * signal_names[256] = {
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),
};
#if 0
@ -741,29 +780,35 @@ int main(int argc, char * argv[]) {
if (res < 0) {
fprintf(stderr, "%s: waitpid: %s\n", argv[0], strerror(errno));
} else {
if (WSTOPSIG(status) == SIGTRAP) {
struct regs regs;
ptrace(PTRACE_GETREGS, p, NULL, &regs);
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;
/* 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));
}
ptrace(PTRACE_CONT, p, NULL, NULL);
} else if (WIFSIGNALED(status)) {
fprintf(logfile, "+++ killed by %s +++\n", signal_names[WTERMSIG(status)]);
return 0;
} else if (WIFEXITED(status)) {
fprintf(logfile, "pid %d has exited\n", res);
fprintf(logfile, "+++ exited with %d +++\n", WEXITSTATUS(status));
return 0;
}
}

4
apps/test-sigsegv.c Normal file
View File

@ -0,0 +1,4 @@
int main(int argc, char * argv[]) {
*(volatile int*)0x12345 = 42;
return 0;
}

View File

@ -2,5 +2,5 @@
long ptrace_attach(pid_t pid);
long ptrace_self(void);
long ptrace_signal(int reason);
long ptrace_continue(pid_t pid);
long ptrace_signal(int signal, int reason);
long ptrace_continue(pid_t pid, int signum);

View File

@ -16,9 +16,6 @@
#include <kernel/arch/x86_64/regs.h>
#include <kernel/arch/x86_64/irq.h>
#undef DEBUG_FAULTS
#define LOUD_SEGFAULTS
static struct idt_pointer idtp;
static idt_entry_t idt[256];
@ -205,30 +202,14 @@ struct regs * isr_handler(struct regs * r) {
map_more_stack(faulting_address & 0xFFFFffffFFFFf000);
break;
}
#ifdef DEBUG_FAULTS
arch_fatal();
#else
# ifdef LOUD_SEGFAULTS
printf("Page fault in pid=%d (%s; cpu=%d) at %#zx\n", (int)this_core->current_process->id, this_core->current_process->name, this_core->cpu_id, faulting_address);
dump_regs(r);
# endif
send_signal(this_core->current_process->id, SIGSEGV, 1);
#endif
break;
}
case 13: /* GPF */ {
#ifdef DEBUG_FAULTS
arch_fatal();
#else
if (!this_core->current_process || r->cs == 0x08) {
arch_fatal();
}
# ifdef LOUD_SEGFAULTS
printf("GPF in userspace on CPU %d\n", this_core->cpu_id);
dump_regs(r);
# endif
send_signal(this_core->current_process->id, SIGSEGV, 1);
#endif
break;
}
case 8: /* Double fault */ {
@ -250,14 +231,10 @@ struct regs * isr_handler(struct regs * r) {
}
default: {
if (r->int_no < 32) {
#ifdef DEBUG_FAULTS
arch_fatal();
#else
if (!this_core->current_process || r->cs == 0x08) {
arch_fatal();
}
send_signal(this_core->current_process->id, SIGILL, 1);
#endif
} else {
for (size_t i = 0; i < IRQ_CHAIN_DEPTH; i++) {
irq_handler_chain_t handler = irq_routines[i * IRQ_CHAIN_SIZE + (r->int_no - 32)];

View File

@ -35,9 +35,9 @@ long ptrace_self(void) {
/**
* @brief Trigger a ptrace event on the currently executing thread.
*/
long ptrace_signal(int reason) {
long ptrace_signal(int signal, int reason) {
__sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_SUSPENDED);
this_core->current_process->status = 0x7F | (SIGTRAP << 8) | (reason << 16);
this_core->current_process->status = 0x7F | (signal << 8) | (reason << 16);
process_t * parent = process_from_pid(this_core->current_process->tracer);
if (parent && !(parent->flags & PROC_FLAG_FINISHED)) {
@ -45,16 +45,18 @@ long ptrace_signal(int reason) {
}
switch_task(0);
return 0;
int signum = (this_core->current_process->status >> 8);
this_core->current_process->status = 0;
return signum;
}
long ptrace_continue(pid_t pid) {
long ptrace_continue(pid_t pid, int sig) {
process_t * tracee = process_from_pid(pid);
if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;
/* Unsuspend */
__sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED));
tracee->status = 0;
tracee->status = (sig << 8);
make_process_ready(tracee);
return 0;
@ -66,7 +68,7 @@ long ptrace_getregs(pid_t pid, void * data) {
if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;
/* Copy registers */
memcpy(data, tracee->syscall_registers, sizeof(struct regs));
memcpy(data, tracee->interrupt_registers ? tracee->interrupt_registers : tracee->syscall_registers, sizeof(struct regs));
return 0;
}
@ -100,7 +102,7 @@ long ptrace_handle(long request, pid_t pid, void * addr, void * data) {
case PTRACE_GETREGS:
return ptrace_getregs(pid,data);
case PTRACE_CONT:
return ptrace_continue(pid);
return ptrace_continue(pid,(uintptr_t)data);
case PTRACE_PEEKDATA:
return ptrace_peek(pid,addr,data);
default:

View File

@ -27,6 +27,7 @@
#include <kernel/process.h>
#include <kernel/signal.h>
#include <kernel/spinlock.h>
#include <kernel/ptrace.h>
static spin_lock_t sig_lock;
static spin_lock_t sig_lock_b;
@ -76,6 +77,11 @@ void handle_signal(process_t * proc, signal_t * sig) {
uintptr_t signum = sig->signum;
free(sig);
/* Are we being traced? */
if (this_core->current_process->flags & PROC_FLAG_TRACED) {
signum = ptrace_signal(signum, 0);
}
if (proc->flags & PROC_FLAG_FINISHED) {
return;
}

View File

@ -1139,7 +1139,7 @@ void syscall_handler(struct regs * r) {
this_core->current_process->syscall_registers = r;
if (this_core->current_process->flags & PROC_FLAG_TRACED) {
ptrace_signal(PTRACE_EVENT_SYSCALL_ENTER);
ptrace_signal(SIGTRAP, PTRACE_EVENT_SYSCALL_ENTER);
}
arch_syscall_return(r, func(
@ -1147,6 +1147,6 @@ void syscall_handler(struct regs * r) {
arch_syscall_arg3(r), arch_syscall_arg4(r)));
if (this_core->current_process->flags & PROC_FLAG_TRACED) {
ptrace_signal(PTRACE_EVENT_SYSCALL_EXIT);
ptrace_signal(SIGTRAP, PTRACE_EVENT_SYSCALL_EXIT);
}
}