From d2527bee92e735e11b12cf8b3d050eab0a9585e1 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 27 May 2024 22:00:59 -0300 Subject: [PATCH] context changes and scheduler userspace check fix --- kernel-src/arch/x86-64/context.asm | 3 +- kernel-src/include/x86-64/arch/context.h | 2 +- kernel-src/sys/interrupt.c | 8 ++-- kernel-src/sys/scheduler.c | 59 ++++++++++++++++++++---- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/kernel-src/arch/x86-64/context.asm b/kernel-src/arch/x86-64/context.asm index 7f1d4dc7..671126f3 100644 --- a/kernel-src/arch/x86-64/context.asm +++ b/kernel-src/arch/x86-64/context.asm @@ -41,6 +41,7 @@ arch_context_switch: ; since this uses the C system V abi, some registers can be clobbered (in this case, r10 and r11) ; rdi has the pointer to the function to call ; rsi has the pointer to the stack the function should have on call. if NULL the stack is not changed +; rdx has the pointer to the arguments to pass to the function ; if the main function returns, do a context switch as the registers could have been changed global arch_context_saveandcall arch_context_saveandcall: @@ -79,9 +80,9 @@ arch_context_saveandcall: push qword 0 ; GS sub rsp, 8 ; cr2 - mov r11, rdi ; save function to call mov rdi, rsp ; first argument is the context struct + mov rsi, rdx ; pass argument call r11 ; jump to the desired function ; do context switch to return diff --git a/kernel-src/include/x86-64/arch/context.h b/kernel-src/include/x86-64/arch/context.h index 774f12c0..4ffdb76a 100644 --- a/kernel-src/include/x86-64/arch/context.h +++ b/kernel-src/include/x86-64/arch/context.h @@ -77,7 +77,7 @@ typedef struct { } void arch_context_switch(context_t *context); -void arch_context_saveandcall(void (*fn)(context_t *context), void *stack); +void arch_context_saveandcall(void (*fn)(context_t *context, void *argument), void *stack, void *argument); #include diff --git a/kernel-src/sys/interrupt.c b/kernel-src/sys/interrupt.c index acdcb70a..b26e7716 100644 --- a/kernel-src/sys/interrupt.c +++ b/kernel-src/sys/interrupt.c @@ -7,7 +7,7 @@ void arch_interrupt_disable(); void arch_interrupt_enable(); -#define DOPENDING_SAVE() arch_context_saveandcall(dopending, NULL) +#define DOPENDING_SAVE() arch_context_saveandcall(dopending, NULL, NULL) static void removefromqueue(isr_t *isr) { if (isr->prev) @@ -41,7 +41,7 @@ static void runisr(isr_t *isr, context_t *ctx) { interrupt_loweripl(oldipl); } -static void dopending(context_t *ctx) { +static void dopending(context_t *ctx, void *) { bool entrystatus = _cpu()->intstatus; if (entrystatus) { arch_interrupt_disable(); @@ -85,7 +85,7 @@ static void dopending(context_t *ctx) { // if any new interrupts are pending, they will be taken care of here // XXX this is kinda hacky could be very very bad if there are a lot of interrupts - dopending(ctx); + dopending(ctx, NULL); } cleanup: @@ -106,7 +106,7 @@ void interrupt_isr(int vec, context_t *ctx) { if (_cpu()->ipl > isr->priority) { runisr(isr, ctx); - dopending(ctx); + dopending(ctx, NULL); } else { insertinqueue(isr); isr->pending = true; diff --git a/kernel-src/sys/scheduler.c b/kernel-src/sys/scheduler.c index 95339530..88583743 100644 --- a/kernel-src/sys/scheduler.c +++ b/kernel-src/sys/scheduler.c @@ -222,6 +222,9 @@ static __attribute__((noreturn)) void switchthread(thread_t *thread) { __assert((thread->flags & SCHED_THREAD_FLAGS_QUEUED) == 0); spinlock_release(&runqueuelock); + void *schedulerstack = _cpu()->schedulerstack; + __assert(!((void *)thread->context.rsp < schedulerstack && (void *)thread->context.rsp >= (schedulerstack - SCHEDULER_STACK_SIZE))); + ARCH_CONTEXT_SWITCHTHREAD(thread); __builtin_unreachable(); } @@ -336,7 +339,7 @@ void sched_inactiveproc(proc_t *proc) { sched_destroyproc(proc); } -static void threadexit_internal(context_t *) { +static void threadexit_internal(context_t *, void *) { thread_t *thread = _cpu()->thread; // we don't need to access the current thread anymore, as any state will be ignored and we are running on the scheduler stack // as well as not being preempted at all @@ -375,22 +378,60 @@ __attribute__((noreturn)) void sched_threadexit() { } interrupt_set(false); - arch_context_saveandcall(threadexit_internal, _cpu()->schedulerstack); + arch_context_saveandcall(threadexit_internal, _cpu()->schedulerstack, NULL); __builtin_unreachable(); } -// called right before going back to userspace in places like the syscall handler or arch_context_switch -__attribute__((no_caller_saved_registers)) void sched_userspacecheck(context_t *context, bool syscall, uint64_t syscallerrno, uint64_t syscallret) { +typedef struct { + context_t *context; + bool syscall; + uint64_t syscallerrno; + uint64_t syscallret; +} checkargs_t; + +// SHOULD NOT BE CALLED WITH THE SCHEDULER STACK +static void userspacecheck(void *_args) { + checkargs_t *args = _args; + __assert(_cpu() >= (cpu_t *)0xffffffff80000000); thread_t *thread = _cpu()->thread; - if (thread == NULL || ARCH_CONTEXT_ISUSER(context) == false) - return; if (thread->shouldexit) { interrupt_set(true); sched_threadexit(); } - while (signal_check(thread, context, syscall, syscallret, syscallerrno)) ; + while (signal_check(thread, args->context, args->syscall, args->syscallret, args->syscallerrno)) ; +} + +static void checktrampoline(context_t *context, void *_args) { + checkargs_t *args = _args; + userspacecheck(args); + _cpu()->intstatus = ARCH_CONTEXT_INTSTATUS(args->context); + arch_context_switch(args->context); +} + +// called right before going back to userspace in places like the syscall handler or arch_context_switch +__attribute__((no_caller_saved_registers)) void sched_userspacecheck(context_t *context, bool syscall, uint64_t syscallerrno, uint64_t syscallret) { + __assert(_cpu()); + if (_cpu()->thread == NULL || ARCH_CONTEXT_ISUSER(context) == false) + return; + + + checkargs_t args = { + .context = context, + .syscall = syscall, + .syscallerrno = syscallerrno, + .syscallret = syscallret + }; + + // return context is user mode, so its kernel stack is free + // if we are running on the scheduler stack, run on the kernel stack + // so we don't risk stack corruption (as we have to turn on interrupts should the thread need to exit) + if (&args < (checkargs_t *)_cpu()->schedulerstack && &args >= (checkargs_t *)((uintptr_t)_cpu()->schedulerstack - SCHEDULER_STACK_SIZE)) { + arch_context_saveandcall(checktrampoline, _cpu()->thread->kernelstacktop, &args); + } else { + userspacecheck(&args); + } } void sched_stopotherthreads() { @@ -428,7 +469,7 @@ void sched_terminateprogram(int status) { sched_threadexit(); } -static void yield(context_t *context) { +static void yield(context_t *context, void *) { thread_t *thread = _cpu()->thread; spinlock_acquire(&runqueuelock); @@ -480,7 +521,7 @@ static void yield(context_t *context) { int sched_yield() { bool sleeping = _cpu()->thread->flags & SCHED_THREAD_FLAGS_SLEEP; bool old = sleeping ? _cpu()->thread->sleepintstatus : interrupt_set(false); - arch_context_saveandcall(yield, _cpu()->schedulerstack); + arch_context_saveandcall(yield, _cpu()->schedulerstack, NULL); interrupt_set(old); return sleeping ? _cpu()->thread->wakeupreason : 0; }