From 45819c035d0b92275de68e559f066cbe50996926 Mon Sep 17 00:00:00 2001 From: Jake Mannens Date: Wed, 25 Jul 2018 01:17:39 +1000 Subject: Fixed a bug in switch_to() in which the value of EBX was not popped prior to return. This meant that switching to the same task did not abort properly as the incorrect return address was popped off the stack. Fixed a bug where the task register was not initialized before the scheduler. This meant that on the first task switch, the CPU would dump it's current state to a random address (0 most likely), potentially corrupting important data. This has been corrected by introducing a 'garbage TSS' (and associated descriptor in the GDT) which is selected before the scheduler is initialized as a safe place for the data to be written. Modified the scheduler so that it now waits indefinitely until a task becomes ready to run. This fixes the possible bug where the scheduler won't re-schedule the currently running task if it is the only task on the system. Add signal handling capabilities to the kernel. The bulk of this is present in the subroutine check_signals() defined in traps.s. This function is called on every timer tick and system call prior to userspace return. The subroutine operates by pushing fake state information onto the kernel's stack, then using it to return to userspace. Prior to this, the subroutine pushes the return address 0xFFFFE000 onto the user's stack. This address corresponds to the unmapped page located between the top of the user's stack (lower) and the kernel's stack page (upper). When the user's signal handler tries to return, it will cause a page fault that will be used as a notification mechanism to inform the kernel that the signal handler is done. The kernel will then switch to the originally pushed state information and use it to return the task to the original execution state. Due to it's nature, check_signals() must only be called prior to exiting the kernel since it may not return. Added the header file 'signal.h' which defines (most) of the POSIX signals as well as the prototype for the signal() function. Added the 'signal' element to the task structure. This field is a bitmap of all currently pending signals. Added the 'sig_handlers' element to the task structure. This is an array of all user-defined signal handlers. Currently, a value of 0 indicates the default handler should be used whilst any other value is considered to be the address of a userspace signal handler. The ability to ignore a signal is not yet present but will be added sometime soon. Added the sys_signal system call to register a signal. Added the stub function sighandler_default() to sched.c which handles all signals not caught by the user. --- include/kernel/sched.h | 9 +++ include/kernel/sys.h | 3 +- include/signal.h | 35 ++++++++++++ include/unistd.h | 1 + kernel/asm.s | 1 + kernel/boot.s | 32 ++++++++++- kernel/memory.c | 1 + kernel/sched.c | 19 ++++--- kernel/sys.c | 21 +++++++ kernel/timer.s | 3 + kernel/traps.s | 146 ++++++++++++++++++++++++++++++++++++++++++++----- lib/signal.c | 11 ++++ usrbin/main.c | 10 +++- 13 files changed, 267 insertions(+), 25 deletions(-) create mode 100644 include/signal.h create mode 100644 lib/signal.c diff --git a/include/kernel/sched.h b/include/kernel/sched.h index 42b5b15..ac508b9 100644 --- a/include/kernel/sched.h +++ b/include/kernel/sched.h @@ -1,6 +1,7 @@ #ifndef _SCHED_H #define _SCHED_H +#include #include #include @@ -39,9 +40,17 @@ struct tss_struct { uint32_t io_map; } __attribute__((packed)); +/* + * WARNING: do not modify this structure + * unless the offsets defined in traps.s + * are updated + */ + struct task_struct { pid_t pid; int state; + uint32_t signal; + void *sig_handlers[NRSIG]; struct tss_struct tss; } __attribute__((packed)); diff --git a/include/kernel/sys.h b/include/kernel/sys.h index 248a93b..b2c1b3b 100644 --- a/include/kernel/sys.h +++ b/include/kernel/sys.h @@ -4,6 +4,7 @@ extern int sys_puts(void); extern int sys_time(void); extern int sys_getpid(void); extern int sys_getpdir(void); +extern int sys_signal(void); extern int sys_dummy(void); syscall_t call_table[256] = { @@ -11,7 +12,7 @@ syscall_t call_table[256] = { [1] = &sys_time, [2] = &sys_getpid, [3] = &sys_getpdir, - [4] = &sys_dummy, + [4] = &sys_signal, [5] = &sys_dummy, [6] = &sys_dummy, [7] = &sys_dummy, diff --git a/include/signal.h b/include/signal.h new file mode 100644 index 0000000..fee6c2b --- /dev/null +++ b/include/signal.h @@ -0,0 +1,35 @@ +#ifndef _SIGNAL_H +#define _SIGNAL_H + +#define NRSIG 32 + +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGPROF 27 +#define SIGSYS 31 + +void *signal(int, void (*)(int)); + +#endif diff --git a/include/unistd.h b/include/unistd.h index 6925541..3b144f6 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -9,6 +9,7 @@ typedef uint16_t pid_t; #define __SYS_time 1 #define __SYS_getpid 2 #define __SYS_getpdir 3 +#define __SYS_signal 4 #define _syscall0(type, name) \ type name(void) { \ diff --git a/kernel/asm.s b/kernel/asm.s index aced9b4..8b5c231 100644 --- a/kernel/asm.s +++ b/kernel/asm.s @@ -15,6 +15,7 @@ switch_to: mov ebx, [ctask] cmp ebx, [ebp+12] jne .skip + pop ebx pop ebp ret .skip: diff --git a/kernel/boot.s b/kernel/boot.s index 96f0e43..21b248c 100644 --- a/kernel/boot.s +++ b/kernel/boot.s @@ -11,6 +11,8 @@ extern syscall_init extern timer_init extern traps_init +%define NRTASKS 64 + ; Multiboot header section .mboothdr mboot_hdr: @@ -80,16 +82,22 @@ gdt: db 0xCF db 0x00 ; empty TSS slots for our C scheduler - times 64 dq 0 + times NRTASKS dq 0 + ; garbage TSS descriptor + dq 0 .end: gdtp: dw (gdt.end-gdt-1) dd gdt +garbage_tss: + times 104 db 0 + flush_gdt: push ebp mov ebp, esp + push ebx ; now, load the GDT lgdt [gdtp] mov ax, 0x10 @@ -100,6 +108,28 @@ flush_gdt: mov ss, ax jmp 0x08:.end .end: + ; finally, we load the task register with + ; a pointer to our dummy TSS. this is + ; necessary since the CPU *must* save the + ; previous task state *somewhere* before + ; switching tasks. to avoid overwriting + ; any sensitive information, we reserve + ; 104 bytes for a *garbage* TSS. + mov eax, 5 + add eax, NRTASKS + shl eax, 3 + mov ebx, eax + add ebx, gdt + mov edx, garbage_tss + mov dword [ebx], 103 ; limit - 1 + mov [ebx+2], dx + shr edx, 16 + mov [ebx+4], dl + mov [ebx+7], dh + mov byte [ebx+5], 0x89 + mov byte [ebx+6], 0x00 + ltr ax + pop ebx pop ebp ret diff --git a/kernel/memory.c b/kernel/memory.c index 465b44f..0c73970 100644 --- a/kernel/memory.c +++ b/kernel/memory.c @@ -85,6 +85,7 @@ void *map_page(void *page) { /* * invalidate any cached entries for * the mapped page window. + * TODO: skip this on i386 */ __asm__ ("invlpg %0" :: "m" (mapped_page)); diff --git a/kernel/sched.c b/kernel/sched.c index efbb80a..529db61 100644 --- a/kernel/sched.c +++ b/kernel/sched.c @@ -1,6 +1,9 @@ #include +#include +#include #include #include +#include #include #include @@ -144,14 +147,12 @@ struct task_struct *create_proc(void *bin, size_t len) { /* load in the user binary */ memcpy(USRSTART, bin, len); - /* - * here we initialize the task struct. - * currently, we only setup state information - * necessary to begin execution. - */ + /* initialize the task struct */ memset(task, 0, sizeof(struct task_struct)); task->pid = nextpid; task->state = TSTATE_RUNNING; + + /* initialize the task's state */ task->tss.cr3 = (uint32_t) pdir; task->tss.cs = 0x1B; task->tss.ds = 0x23; @@ -183,7 +184,6 @@ void sched_init(void) { nextpid = 1; ctask = NULL; - create_proc(&_usrbin_start, (size_t) &_usrbin_size); create_proc(&_usrbin_start, (size_t) &_usrbin_size); create_proc(&_usrbin_start, (size_t) &_usrbin_size); @@ -198,11 +198,12 @@ void reschedule(void) { return; n = ctaskn; - for(i = 0; i < NRTASKS; i++) { + while(1) { n = (n + 1) % NRTASKS; if(tasks[n].pid && tasks[n].state == TSTATE_RUNNING) break; } + switch_to(n, &tasks[n]); } @@ -217,3 +218,7 @@ void wake_up(struct task_struct **task) { (*task)->state = TSTATE_RUNNING; *task = NULL; } + +void sighandler_default(uint32_t sig) { + printk("[kernel] Handling signal 0x%02x for PID 0x%04x\n", sig, ctask->pid); +} diff --git a/kernel/sys.c b/kernel/sys.c index 8c33fdf..86fae41 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -1,13 +1,20 @@ #include #include #include +#include #include +#include #include #include extern uint32_t ticks; int sys_puts(char *s) { + uint32_t sigs = 5; + printk("[kernel] Setting signals %01x for PID 0x%04x\n", sigs, ctask->pid); + ctask->signal |= sigs; + + printk("[%04x] ", ctask->pid); return printk(s); } @@ -27,6 +34,20 @@ void *sys_getpdir(void) { return (void*) pdir; } +void *sys_signal(int sig, void (*func)(int)) { + if(sig < 0 || sig >= NRSIG) + return NULL; + + /* SIGKILL and SIGSTOP cannot be caught */ + if(sig == SIGKILL || sig == SIGSTOP) + return NULL; + + printk("Task %x registered new signal handler 0x%08x for signal %01x\n", ctask->pid, (uint32_t) func, sig); + ctask->sig_handlers[sig] = func; + + return func; +} + int sys_dummy(void) { return -ENOSYS; } diff --git a/kernel/timer.s b/kernel/timer.s index 760bcd7..c3af34b 100644 --- a/kernel/timer.s +++ b/kernel/timer.s @@ -1,5 +1,6 @@ global ticks global timer_init +extern check_signals extern register_isr extern sched_tick @@ -24,6 +25,8 @@ tick_handler: ; call the scheduler call sched_tick + ; handle any pending signals before returning to normal execution + call check_signals ; restore the data segment selectors pop ax diff --git a/kernel/traps.s b/kernel/traps.s index e084ac8..73e7f64 100644 --- a/kernel/traps.s +++ b/kernel/traps.s @@ -3,13 +3,24 @@ ; as well as the system call interface. ; +global check_signals global syscall_init global traps_init extern call_table +extern ctask extern idt extern panic extern printk extern register_isr +extern sighandler_default + +; here we define offsets into task_struct. +; if task_struct is modified, this must be +; too. + +%define ts_signal 6 +%define ts_sig_handlers 10 +%define ts_tss_esp0 142 %macro SAVE 0 pusha @@ -64,19 +75,6 @@ extern register_isr iret %endmacro -%macro RESTORE_SYS 0 - ; preserve our modified EAX (return value for syscalls) - mov [esp+30], eax - ; restore the data segment selectors - pop ax - mov ds, ax - mov es, ax - mov fs, ax - mov gs, ax - popa - iret -%endmacro - traps: dd exc_div dd exc_debug @@ -204,6 +202,34 @@ exc_gprot: exc_pagef: SAVE_ERR + ; here we check if the page fault was + ; caused by a process trying to return + ; from a signal handler (this should cause + ; a page fault for accessing page + ; 0xFFFFE000 during the handler's + ; execution). + + ; check if the offending address is 0xFFFFE000 + mov eax, cr2 + cmp eax, 0xFFFFE000 + jne .skip + ; check if we're actually handling a signal + mov eax, [ctask] + mov eax, [eax+ts_tss_esp0] + cmp eax, 0xFFFFFFFF + je .skip + ; final checks that a user-initiated read caused the fault + mov eax, ebx + and eax, 6 + cmp eax, 4 + jne .skip + jmp sigret +.skip: + + ; if the above conditions are false, a + ; signal handler was not trying to return, + ; so we handle the page fault normally + push ebx mov ebx, cr2 push ebx @@ -243,6 +269,9 @@ exc_reserved: syscall_handler: SAVE + ; push the arguments onto the stack, then + ; calculate the offset for the appropriate + ; handler function and call it push edx push ecx push ebx @@ -251,9 +280,98 @@ syscall_handler: add eax, call_table call [eax] add esp, 12 + ; preserve the syscall's return value + mov [esp+30], eax + mov eax, esp + ; check pending signals and handle them (if any) + push eax + call check_signals + add esp, 4 + + RESTORE - RESTORE_SYS +; void check_signals(void *saved_state) +check_signals: + push ebp + mov ebp, esp + push ebx + push esi + ; abort if a signal is already being handled + mov eax, [ctask] + mov ebx, [eax+ts_tss_esp0] + cmp ebx, 0xFFFFFFFF + jne .end + ; check if there are any pending signals + ; if so, we'll resume at the signal + ; handler if a custom handler has been + ; defined, otherwise, we run the default + ; handler. + mov edx, [eax+ts_signal] + cmp edx, 0 + je .end + ; find which signal has been triggered (then clear it's flag) + bsf ecx, edx + mov ebx, 1 + shl ebx, cl + xor ebx, 0xFFFFFFFF + and dword [eax+ts_signal], ebx + mov ebx, ecx + ; calculate offset to the handler function + shl ecx, 2 + add ecx, eax + add ecx, ts_sig_handlers + cmp dword [ecx], 0 + jne .custom_handler + ; call the default handler + push ebx + call sighandler_default + add esp, 4 + jmp .end +.custom_handler: + ; save our stack pointer + mov esi, [ebp+8] + mov [eax+ts_tss_esp0], esi + ; here, we push the return address onto + ; the user's stack. we give a bogus return + ; address of 0xFFFFE000 (the second-last + ; page) to deliberately trigger a page + ; fault that will alert us when the signal + ; handler tried to return. + mov ebx, [ebp+8] + mov esi, ebx + add esi, 46 + mov esi, [esi] + sub esi, 4 + mov dword [esi], 0xFFFFE000 + ; push a new state onto our stack + push dword [ebx+50] ; SS + push dword esi ; ESP + push dword [ebx+42] ; EFLAGS + push dword [ebx+38] ; CS + push dword [ecx] ; EIP + push dword 0 ; EAX + push dword 0 ; ECX + push dword 0 ; EDX + push dword 0 ; EBX + push dword 0 ; ESP + push dword [ebx+10] ; EBP + push dword 0 ; ESI + push dword 0 ; EDI + push word [ebx] ; DS + RESTORE +.end: + pop esi + pop ebx + pop ebp + ret +sigret: + call check_signals + ; restore our original stack frame and reset the TSS's ESP0 + mov esp, [eax+ts_tss_esp0] + mov dword [eax+ts_tss_esp0], 0xFFFFFFFF + ; finally, perform the normal syscall return + RESTORE syscall_init: push ebp diff --git a/lib/signal.c b/lib/signal.c new file mode 100644 index 0000000..343e2cc --- /dev/null +++ b/lib/signal.c @@ -0,0 +1,11 @@ +#include + +void *signal(int sig, void (*func)(int)) { + void *__res; + __asm__ volatile ( + "int $0x80" + : "=a" (__res) + : "a" (__SYS_signal), "b" (sig), "c" (func) + :); + return __res; +} diff --git a/usrbin/main.c b/usrbin/main.c index 83dcd90..2e409fd 100644 --- a/usrbin/main.c +++ b/usrbin/main.c @@ -1,15 +1,21 @@ +#include #include #include #include #include +void sig_handler(int s) { + printf("Handled the signal!\n"); +} + void main(void) { int x = 0; + /* signal(0, &sig_handler); */ printf("We did it ma!\n"); while(1) { - sleep(1); - printf("0x%04x:0x%08x: 0x%08x\n", getpid(), (uint32_t) getpdir(), x++); + /* sleep(1); */ + printf("0x%04x:0x%08x: 0x%08x, 0x%08x\n", getpid(), (uint32_t) getpdir(), time(), x++); } } -- cgit v1.3