diff options
-rw-r--r-- | libdiskfs/=exc.c | 147 |
1 files changed, 93 insertions, 54 deletions
diff --git a/libdiskfs/=exc.c b/libdiskfs/=exc.c index 76cb1bcc..d272a08d 100644 --- a/libdiskfs/=exc.c +++ b/libdiskfs/=exc.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1994 Free Software Foundation + Copyright (C) 1994, 1995 Free Software Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -17,46 +17,93 @@ #include "priv.h" -#define EXC_TABLE_SIZE 1024 /* Ack!XXX -- should be dynamically sized! */ -/* This is the table of addresses for which we should report faults - back to the faulting thread. */ -static struct +/* List of preempters that are around. */ +struct preempt_record { + struct preempt_record *next; + struct hurd_signal_preempt preempter1, preempter2; struct pager *p; - vm_offset_t pager_offset; - void *offset; - long length; -} memory_fault_table[EXC_TABLE_SIZE]; + vm_address_t off; + void *addr; + long len; +}; + +static struct preempt_record *preempt_list; +static spin_lock_t preempt_list_lock = SPIN_LOCK_INITIALIZER; + +/* This special signal handler is run anytime we have taken a fault on + a page that we map (registered through + diskfs_register_memory_fault_area). What we do is just longjmp to the + context saved on the stack by diskfs_catch_exception. */ +static void +special_segv_handler (int code, int subcode, int error) +{ + struct preempt_record *rec; + struct thread_stuff *stack_record; + + /* SUBCODE is the address and ERROR is the error returned by the + pager through the kernel. But because there is an annoying + kernel bug (or rather unimplemented feature) the errors are not + actually passed back. So we have to scan the list of preempt + records and find the one that got us here, and then query the + pager to find out what error it gave the kernel. */ + spin_lock (&preempt_list_lock); + for (rec = preempt_list; rec; rec = rec->next) + if ((void *)subcode >= rec->addr + && (void *)subcode < rec->addr + rec->len) + break; + assert (rec); + spin_unlock (&preempt_list_lock); + error = pager_get_error (rec->p, rec->off + (void *)subcode - rec->addr); + + /* Now look up the stack record left by diskfs_catch_exception, consuming it + on the way */ + stack_record = (struct thread_stuff *) cthread_data (cthread_self ()); + assert (stack_record); + cthread_set_data (cthread_self (), (any_t)stack_record->link); + + /* And return to it... */ + longjmp (&stack_record->buf, error); -static spin_lock_t memory_fault_lock; + abort (); +} + +/* Return a signal handler for a thread which has faulted inside a + region registered as expecting such faults. This routine runs + inside the library's signal thread, and accordingly must be + careful. */ +static sighandler_t +segv_preempter (thread_t thread, int signo, + long int sigcode, int sigerror) +{ + /* Just assume that everything is cool (how could it not be?) + and return our special handler above. */ + return special_segv_handler; +} /* Mark the memory at ADDR continuing for LEN bytes as mapped from pager P - at offset OFF. Call when vm_map-ing part of the disk. - CAVEAT: addr must not be zero. */ + at offset OFF. Call when vm_map-ing part of the disk. */ void diskfs_register_memory_fault_area (struct pager *p, vm_address_t off, void *addr, long len) { - int i; - - assert (addr); - - spin_lock (&memory_fault_lock); - - for (i = 0; i < EXC_TABLE_SIZE; i++) - if (!memory_fault_table[i].offset) - { - memory_fault_table[i].p = p; - memory_fault_table[i].pager_offset = off; - memory_fault_table[i].offset = addr; - memory_fault_table[i].length = len; - spin_unlock (&memory_fault_lock); - return; - } - - assert (0); + struct preempt_record *rec = malloc (sizeof (struct preempt_record)); + + hurd_preempt_signals (&rec->preempter1, SIGSEGV, addr, addr + len, + segv_preempter); + hurd_preempt_signals (&rec->preempter1, SIGBUS, addr, addr + len, + segv_preempter); + rec->p = p; + rec->off = off; + rec->addr = addr; + rec->len = len; + + spin_lock (&preempt_list_lock); + rec->next = preempt_list; + preempt_list = rec; + spin_unlock (&preempt_list_lock); } /* Mark the memory at ADDR continuing for LEN bytes as no longer @@ -65,19 +112,25 @@ void diskfs_unregister_memory_fault_area (void *addr, long len) { - int i; - - assert (addr); - - spin_lock (&memory_fault_lock); - for (i = 0; i < EXC_TABLE_SIZE; i++) - if (memory_fault_table[i].offset == addr - && memory_fault_table[i].length == len) + struct preempt_record *rec, **prevp; + + spin_lock (&preempt_list_lock); + for (rec = preempt_list, prevp = &preempt_list; + rec; + rec = rec->next, prevp = &rec->next) + if (rec->addr == addr && rec->len == len) { - memory_fault_table[i].offset = 0; - spin_unlock (&memory_fault_lock); + /* This is it, make it go away. */ + *prevp = rec->next; + spin_unlock (&preempt_list_lock); + hurd_unpreempt_signals (&rec->preempter1, SIGSEGV); + hurd_unpreempt_signals (&rec->preempter2, SIGBUS); + free (rec); return; } + spin_unlock (&preempt_list_lock); + + /* Not found */ assert (0); } @@ -85,18 +138,4 @@ diskfs_unregister_memory_fault_area (void *addr, void init_exceptions () { - int i; - - for (i = 0; i < EXC_TABLE_SIZE; i++) - memory_fault_table[i].offset = 0; - spin_lock_init (&memory_fault_lock); - -#if notdebugging - mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &excport); - mach_port_insert_right (mach_task_self (), excport, excport, - MACH_MSG_TYPE_MAKE_SEND); - task_get_special_port (mach_task_self (), TASK_EXCEPTION_PORT, &oldexcport); - task_set_special_port (mach_task_self (), TASK_EXCEPTION_PORT, excport); - mach_port_deallocate (mach_task_self (), excport); -#endif } |