kernel: improve comments in sys/ptrace.c

This commit is contained in:
K. Lange 2022-03-13 16:07:53 +09:00
parent 74e1d8c62c
commit b297ab3fed

View File

@ -5,10 +5,23 @@
* Provides single stepping, cross-process memory inspection, * Provides single stepping, cross-process memory inspection,
* regiser inspection, poking, and syscall trace events. * regiser inspection, poking, and syscall trace events.
* *
* @warning This ptrace implementation is incomplete.
*
* We are missing a lot of @c ptrace functionality found in other
* operating systems, and even some of the functionality we have is
* only partially implemented or may not work as it should.
*
* This implementation was intended primarily to support having a
* @c strace command in userspace, and also provides some limited
* support for a debugger.
*
* @see apps/dbg.c
* @see apps/strace.c
*
* @copyright * @copyright
* This file is part of ToaruOS and is released under the terms * This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md * of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2021 K. Lange * Copyright (C) 2021-2022 K. Lange
*/ */
#include <stdint.h> #include <stdint.h>
#include <errno.h> #include <errno.h>
@ -30,6 +43,18 @@
#error "no regs" #error "no regs"
#endif #endif
/**
* @brief Internally set the tracer of a tracee process.
*
* Sets up @p tracer to trace @p tracee and sets @p tracee as
* tracing the default events (syscalls and signals).
*
* A tracer can trace multiple tracees, but a tracee can only be
* traced by one tracer.
*
* @param tracer Process that is the doing the tracing
* @param tracee Process that is breing traced
*/
static void _ptrace_trace(process_t * tracer, process_t * tracee) { static void _ptrace_trace(process_t * tracer, process_t * tracee) {
spin_lock(tracer->wait_lock); spin_lock(tracer->wait_lock);
__sync_or_and_fetch(&tracee->flags, (PROC_FLAG_TRACE_SYSCALLS | PROC_FLAG_TRACE_SIGNALS)); __sync_or_and_fetch(&tracee->flags, (PROC_FLAG_TRACE_SYSCALLS | PROC_FLAG_TRACE_SIGNALS));
@ -45,6 +70,21 @@ static void _ptrace_trace(process_t * tracer, process_t * tracee) {
spin_unlock(tracer->wait_lock); spin_unlock(tracer->wait_lock);
} }
/**
* @brief Start tracing a process.
*
* @ref PTRACE_ATTACH
*
* Sets the current process to be the tracer for the target tracee.
* Both the tracer and tracee will resume normally, until the next
* ptrace event stops the tracee.
*
* TODO What happens if the process is already being traced?
*
* @param pid Tracee ID
* @returns 0 on success, -ESRCH if the tracee is invalid, -EPERM if the tracee
* is not owned by the same user as the tracer and the tracer is not root.
*/
long ptrace_attach(pid_t pid) { long ptrace_attach(pid_t pid) {
process_t * tracer = (process_t *)this_core->current_process; process_t * tracer = (process_t *)this_core->current_process;
process_t * tracee = process_from_pid(pid); process_t * tracee = process_from_pid(pid);
@ -56,6 +96,21 @@ long ptrace_attach(pid_t pid) {
return 0; return 0;
} }
/**
* @brief Set the current process to be traced by its parent.
*
* @ref PTRACE_TRACEME
*
* Generally, this is used through the @c ptrace system call by
* the debugger or @c strace implementation after forking a child
* process and before calling @c exec.
*
* The calling process will resume immediately.
*
* TODO What happens if we are already being traced?
*
* @returns 0 on success, -EINVAL if the parent was not found.
*/
long ptrace_self(void) { long ptrace_self(void) {
process_t * tracee = (process_t*)this_core->current_process; process_t * tracee = (process_t*)this_core->current_process;
process_t * tracer = process_get_parent(tracee); process_t * tracer = process_get_parent(tracee);
@ -68,6 +123,33 @@ long ptrace_self(void) {
/** /**
* @brief Trigger a ptrace event on the currently executing thread. * @brief Trigger a ptrace event on the currently executing thread.
*
* @ref PTRACE_EVENT_SINGLESTEP
* @ref PTRACE_EVENT_SYSCALL_ENTER
* @ref PTRACE_EVENT_SYSCALL_EXIT
*
* Called elsewhere in the kernel when a trace event happens that is
* not currently being ignored, such as upon entry into a syscall handler,
* or exit from a syscall handler, or before a signal would be delivered.
*
* Runs in the kernel context of the tracee, causes the tracee to be suspended
* and awakens the tracer to return from its @c ptrace call.
*
* When the kernel context for this process is resumed, the signal number
* will be checked from the tracee's status and returned to caller that
* initiated the ptrace event.
*
* When resuming from a signal event, the new signal number will replace the
* old signal number. In this case, if the new signal number is 0 it will
* be discarded and the tracee will continue as if it had ignored it.
*
* When resuming from other events, signals are generally sent directly
* and the process will act on the signal when it has an opportunity to
* return to userspace.
*
* @param signal Signal number if @p reason is 0.
* @param reason PTRACE_EVENT value describing the event; 0 for signal delivery.
* @returns Signal number from tracee status upon resumption.
*/ */
long ptrace_signal(int signal, int reason) { long ptrace_signal(int signal, int reason) {
this_core->current_process->status = 0x7F | (signal << 8) | (reason << 16); this_core->current_process->status = 0x7F | (signal << 8) | (reason << 16);
@ -86,6 +168,17 @@ long ptrace_signal(int signal, int reason) {
return signum; return signum;
} }
/**
* @brief Resume a traced process.
*
* Unsuspends the traced process, sending an appropriate signal if one
* was currently pending or if one was sent by the tracer through either
* of @ref ptrace_continue or @ref ptrace_detach.
*
* @param pid Tracee ID
* @param tracee Tracee process object
* @param sig Signal number to send, or 0 if none.
*/
static void signal_and_continue(pid_t pid, process_t * tracee, int sig) { static void signal_and_continue(pid_t pid, process_t * tracee, int sig) {
/* Unsuspend */ /* Unsuspend */
__sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED)); __sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED));
@ -101,6 +194,20 @@ static void signal_and_continue(pid_t pid, process_t * tracee, int sig) {
} }
} }
/**
* @brief Resume the tracee until the next event.
*
* @ref PTRACE_CONT
*
* Allows the tracee to resume execution, while optionally sending
* a signal. This signal may be the one that triggered the ptrace
* event from which the process is being resumed, or a new signal,
* or no signal at all.
*
* @param pid Tracee ID
* @param sig Signal to send to tracee on resume, or 0 for none.
* @returns 0 on success, -ESRCH if tracee is invalid.
*/
long ptrace_continue(pid_t pid, int sig) { long ptrace_continue(pid_t pid, int sig) {
process_t * tracee = process_from_pid(pid); process_t * tracee = process_from_pid(pid);
if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;
@ -110,6 +217,17 @@ long ptrace_continue(pid_t pid, int sig) {
return 0; return 0;
} }
/**
* @brief Stop tracing a tracee.
*
* @ref PTRACE_DETACH
*
* Marks the tracee as no longer being traced and resumes it.
*
* @param pid Tracee ID
* @param sig Signal to send to tracee on resume, or 0 for none.
* @returns 0 on success, -ESRCH if tracee is invalid.
*/
long ptrace_detach(pid_t pid, int sig) { long ptrace_detach(pid_t pid, int sig) {
process_t * tracee = process_from_pid(pid); process_t * tracee = process_from_pid(pid);
if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;
@ -122,6 +240,28 @@ long ptrace_detach(pid_t pid, int sig) {
return 0; return 0;
} }
/**
* @brief Obtain the register context of the tracee.
*
* @ref PTRACE_GETREGS
*
* Copies the interrupt register context of the tracee into a tracer-provided
* address. The size, meaning, and layout of the data copied is architecture-dependent.
*
* Currently this is either @c interrupt_registers or @c syscall_registers, depending
* on what is available. Since the tracee needs to be suspended this should represent
* the actual userspace register context when it resumes.
*
* On AArch64 we also add ELR, which isn't in the interrupt or syscall register contexts,
* but pushed somewhere else...
*
* TODO We should support reading FPU regs as well.
* TODO @c PTRACE_SETREGS so we can modify them.
*
* @param pid Tracee ID
* @param data Address in tracer to write data into.
* @returns 0 on success, -ESRCH if tracee is invalid.
*/
long ptrace_getregs(pid_t pid, void * data) { long ptrace_getregs(pid_t pid, void * data) {
if (!data || ptr_validate(data, "ptrace")) return -EFAULT; if (!data || ptr_validate(data, "ptrace")) return -EFAULT;
process_t * tracee = process_from_pid(pid); process_t * tracee = process_from_pid(pid);
@ -136,6 +276,21 @@ long ptrace_getregs(pid_t pid, void * data) {
return 0; return 0;
} }
/**
* @brief Read one byte from the tracee's memory.
*
* @ref PTRACE_PEEKDATA
*
* Reads one byte of data from the tracee process's memory space.
* Other implementations of @c PTRACE_PEEKDATA may write other sizes of data,
* but to make this as straightforward as possible, we only support single
* bytes. Maybe in the future we'll support other sizes...
*
* @param pid Tracee ID
* @param addr Virtual address in the tracee context to write to.
* @param data Address in the tracer to store the read byte into.
* @returns 0 on success, -EFAULT if the requested address is not mapped and readable in the tracee, -ESRCH if tracee is invalid.
*/
long ptrace_peek(pid_t pid, void * addr, void * data) { long ptrace_peek(pid_t pid, void * addr, void * data) {
if (!data || ptr_validate(data, "ptrace")) return -EFAULT; if (!data || ptr_validate(data, "ptrace")) return -EFAULT;
process_t * tracee = process_from_pid(pid); process_t * tracee = process_from_pid(pid);
@ -158,6 +313,24 @@ long ptrace_peek(pid_t pid, void * addr, void * data) {
return 0; return 0;
} }
/**
* @brief Place a byte of data into the tracee's memory.
*
* @ref PTRACE_POKEDATA
*
* Writes one byte of data into the tracee process's memory space.
* Other implementations of @c PTRACE_POKEDATA may write other sizes of data,
* but to make this as straightforward as possible, we only support single
* bytes. Maybe in the future we'll support other sizes...
*
* TODO This uses mmu_map_from_physical and doesn't do any cache maintenance?
* It will probably break when, eg., poking instructions on ARM...
*
* @param pid Tracee ID
* @param addr Virtual address in the tracee context to write to.
* @param data Address in the tracer context to read one byte from.
* @returns 0 on success, -ESRCH if tracee is invalid, -EFAULT if the tracee address is not mapped or not writable.
*/
long ptrace_poke(pid_t pid, void * addr, void * data) { long ptrace_poke(pid_t pid, void * addr, void * data) {
if (!data || ptr_validate(data, "ptrace")) return -EFAULT; if (!data || ptr_validate(data, "ptrace")) return -EFAULT;
process_t * tracee = process_from_pid(pid); process_t * tracee = process_from_pid(pid);
@ -180,6 +353,21 @@ long ptrace_poke(pid_t pid, void * addr, void * data) {
return 0; return 0;
} }
/**
* @brief Disable tracing of syscalls in the tracee.
*
* @ref PTRACE_SIGNALS_ONLY_PLZ
*
* Turns off tracing of syscalls in the tracee. Only signals will be
* traced. To turn syscall tracing back on, restart tracing by detaching
* and re-attaching to the tracee.
*
* TODO We need a better interface to configure tracing, so we can offer
* more complex options than just signals and syscalls...
*
* @param pid Tracee ID
* @returns 0 on success, -ESRCH if the tracee was not found or the current process is not its tracer.
*/
long ptrace_signals_only(pid_t pid) { long ptrace_signals_only(pid_t pid) {
process_t * tracee = process_from_pid(pid); process_t * tracee = process_from_pid(pid);
if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;
@ -187,6 +375,23 @@ long ptrace_signals_only(pid_t pid) {
return 0; return 0;
} }
/**
* @brief Enable single-stepping for a process.
*
* @ref PTRACE_SINGLESTEP
*
* Enables an architecture-specific mechanism for single step debugging
* in the requested process. When the process resumes, it will execute
* one instruction and then fault back to the kernel, and the tracer
* will be alerted.
*
* Single stepping will be disabled again when the process returns from
* the fault, and must be re-enabled by another call to @c ptrace_singlstep.
*
* @param pid ID of the process to enable single-step for
* @param sig Signal number to hand to the process when it resumes, or 0.
* @returns 0 on success, -ESRCH if the process could not be found or is not a tracee of the current process.
*/
long ptrace_singlestep(pid_t pid, int sig) { long ptrace_singlestep(pid_t pid, int sig) {
process_t * tracee = process_from_pid(pid); process_t * tracee = process_from_pid(pid);
if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;
@ -206,6 +411,21 @@ long ptrace_singlestep(pid_t pid, int sig) {
return 0; return 0;
} }
/**
* @brief Handle ptrace system call requests.
*
* Internal interface for dispatching @c ptrace system calls. Maps
* arguments from the system call to the various ptrace functions.
*
* @note This is the direct system call implementation. Data coming
* in here is directly from the arguments of the system call.
*
* @param request Request type
* @param pid Tracee ID
* @param addr Address to peek or poke
* @param data Place to put or read data, depending on the function
* @returns Generally, status codes. -EINVAL for an invalid request.
*/
long ptrace_handle(long request, pid_t pid, void * addr, void * data) { long ptrace_handle(long request, pid_t pid, void * addr, void * data) {
switch (request) { switch (request) {
case PTRACE_ATTACH: case PTRACE_ATTACH: