diff options
Diffstat (limited to 'arch/um/kernel/trap.c')
| -rw-r--r-- | arch/um/kernel/trap.c | 339 |
1 files changed, 209 insertions, 130 deletions
diff --git a/arch/um/kernel/trap.c b/arch/um/kernel/trap.c index ac70fa5a2e2..5678c3571e7 100644 --- a/arch/um/kernel/trap.c +++ b/arch/um/kernel/trap.c @@ -1,40 +1,25 @@ /* - * Copyright (C) 2000, 2001 Jeff Dike (jdike@karaya.com) + * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) * Licensed under the GPL */ -#include "linux/kernel.h" -#include "asm/errno.h" -#include "linux/sched.h" -#include "linux/mm.h" -#include "linux/spinlock.h" -#include "linux/config.h" -#include "linux/init.h" -#include "linux/ptrace.h" -#include "asm/semaphore.h" -#include "asm/pgtable.h" -#include "asm/pgalloc.h" -#include "asm/tlbflush.h" -#include "asm/a.out.h" -#include "asm/current.h" -#include "asm/irq.h" -#include "sysdep/sigcontext.h" -#include "user_util.h" -#include "kern_util.h" -#include "kern.h" -#include "chan_kern.h" -#include "mconsole_kern.h" -#include "mem.h" -#include "mem_kern.h" -#include "sysdep/sigcontext.h" -#include "sysdep/ptrace.h" -#include "os.h" -#ifdef CONFIG_MODE_SKAS -#include "skas.h" -#endif -#include "os.h" +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/hardirq.h> +#include <linux/module.h> +#include <asm/current.h> +#include <asm/pgtable.h> +#include <asm/tlbflush.h> +#include <arch.h> +#include <as-layout.h> +#include <kern_util.h> +#include <os.h> +#include <skas.h> -/* Note this is constrained to return 0, -EFAULT, -EACCESS, -ENOMEM by segv(). */ +/* + * Note this is constrained to return 0, -EFAULT, -EACCESS, -ENOMEM by + * segv(). + */ int handle_page_fault(unsigned long address, unsigned long ip, int is_write, int is_user, int *code_out) { @@ -45,61 +30,83 @@ int handle_page_fault(unsigned long address, unsigned long ip, pmd_t *pmd; pte_t *pte; int err = -EFAULT; + unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE; *code_out = SEGV_MAPERR; - /* If the fault was during atomic operation, don't take the fault, just - * fail. */ + /* + * If the fault was during atomic operation, don't take the fault, just + * fail. + */ if (in_atomic()) goto out_nosemaphore; + if (is_user) + flags |= FAULT_FLAG_USER; +retry: down_read(&mm->mmap_sem); vma = find_vma(mm, address); - if(!vma) + if (!vma) goto out; - else if(vma->vm_start <= address) + else if (vma->vm_start <= address) goto good_area; - else if(!(vma->vm_flags & VM_GROWSDOWN)) + else if (!(vma->vm_flags & VM_GROWSDOWN)) goto out; - else if(is_user && !ARCH_IS_STACKGROW(address)) + else if (is_user && !ARCH_IS_STACKGROW(address)) goto out; - else if(expand_stack(vma, address)) + else if (expand_stack(vma, address)) goto out; good_area: *code_out = SEGV_ACCERR; - if(is_write && !(vma->vm_flags & VM_WRITE)) - goto out; - - /* Don't require VM_READ|VM_EXEC for write faults! */ - if(!is_write && !(vma->vm_flags & (VM_READ | VM_EXEC))) - goto out; + if (is_write) { + if (!(vma->vm_flags & VM_WRITE)) + goto out; + flags |= FAULT_FLAG_WRITE; + } else { + /* Don't require VM_READ|VM_EXEC for write faults! */ + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) + goto out; + } do { -survive: - switch (handle_mm_fault(mm, vma, address, is_write)){ - case VM_FAULT_MINOR: - current->min_flt++; - break; - case VM_FAULT_MAJOR: - current->maj_flt++; - break; - case VM_FAULT_SIGBUS: - err = -EACCES; - goto out; - case VM_FAULT_OOM: - err = -ENOMEM; - goto out_of_memory; - default: + int fault; + + fault = handle_mm_fault(mm, vma, address, flags); + + if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current)) + goto out_nosemaphore; + + if (unlikely(fault & VM_FAULT_ERROR)) { + if (fault & VM_FAULT_OOM) { + goto out_of_memory; + } else if (fault & VM_FAULT_SIGBUS) { + err = -EACCES; + goto out; + } BUG(); } + if (flags & FAULT_FLAG_ALLOW_RETRY) { + if (fault & VM_FAULT_MAJOR) + current->maj_flt++; + else + current->min_flt++; + if (fault & VM_FAULT_RETRY) { + flags &= ~FAULT_FLAG_ALLOW_RETRY; + flags |= FAULT_FLAG_TRIED; + + goto retry; + } + } + pgd = pgd_offset(mm, address); pud = pud_offset(pgd, address); pmd = pmd_offset(pud, address); pte = pte_offset_kernel(pmd, address); - } while(!pte_present(*pte)); + } while (!pte_present(*pte)); err = 0; - /* The below warning was added in place of + /* + * The below warning was added in place of * pte_mkyoung(); if (is_write) pte_mkdirty(); * If it's triggered, we'd see normally a hang here (a clean pte is * marked read-only to emulate the dirty bit). @@ -113,135 +120,207 @@ survive: out: up_read(&mm->mmap_sem); out_nosemaphore: - return(err); + return err; -/* - * We ran out of memory, or some other thing happened to us that made - * us unable to handle the page fault gracefully. - */ out_of_memory: - if (current->pid == 1) { - up_read(&mm->mmap_sem); - yield(); - down_read(&mm->mmap_sem); - goto survive; - } - goto out; + /* + * We ran out of memory, call the OOM killer, and return the userspace + * (which will retry the fault, or kill us if we got oom-killed). + */ + up_read(&mm->mmap_sem); + if (!is_user) + goto out_nosemaphore; + pagefault_out_of_memory(); + return 0; +} +EXPORT_SYMBOL(handle_page_fault); + +static void show_segv_info(struct uml_pt_regs *regs) +{ + struct task_struct *tsk = current; + struct faultinfo *fi = UPT_FAULTINFO(regs); + + if (!unhandled_signal(tsk, SIGSEGV)) + return; + + if (!printk_ratelimit()) + return; + + printk("%s%s[%d]: segfault at %lx ip %p sp %p error %x", + task_pid_nr(tsk) > 1 ? KERN_INFO : KERN_EMERG, + tsk->comm, task_pid_nr(tsk), FAULT_ADDRESS(*fi), + (void *)UPT_IP(regs), (void *)UPT_SP(regs), + fi->error_code); + + print_vma_addr(KERN_CONT " in ", UPT_IP(regs)); + printk(KERN_CONT "\n"); } -void segv_handler(int sig, union uml_pt_regs *regs) +static void bad_segv(struct faultinfo fi, unsigned long ip) +{ + struct siginfo si; + + si.si_signo = SIGSEGV; + si.si_code = SEGV_ACCERR; + si.si_addr = (void __user *) FAULT_ADDRESS(fi); + current->thread.arch.faultinfo = fi; + force_sig_info(SIGSEGV, &si, current); +} + +void fatal_sigsegv(void) +{ + force_sigsegv(SIGSEGV, current); + do_signal(); + /* + * This is to tell gcc that we're not returning - do_signal + * can, in general, return, but in this case, it's not, since + * we just got a fatal SIGSEGV queued. + */ + os_dump_core(); +} + +void segv_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs) { struct faultinfo * fi = UPT_FAULTINFO(regs); - if(UPT_IS_USER(regs) && !SEGV_IS_FIXABLE(fi)){ + if (UPT_IS_USER(regs) && !SEGV_IS_FIXABLE(fi)) { + show_segv_info(regs); bad_segv(*fi, UPT_IP(regs)); return; } segv(*fi, UPT_IP(regs), UPT_IS_USER(regs), regs); } -struct kern_handlers handlinfo_kern = { - .relay_signal = relay_signal, - .winch = winch, - .bus_handler = relay_signal, - .page_fault = segv_handler, - .sigio_handler = sigio_handler, - .timer_handler = timer_handler -}; /* * We give a *copy* of the faultinfo in the regs to segv. * This must be done, since nesting SEGVs could overwrite * the info in the regs. A pointer to the info then would * give us bad data! */ -unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user, void *sc) +unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user, + struct uml_pt_regs *regs) { struct siginfo si; - void *catcher; + jmp_buf *catcher; int err; - int is_write = FAULT_WRITE(fi); - unsigned long address = FAULT_ADDRESS(fi); - - if(!is_user && (address >= start_vm) && (address < end_vm)){ - flush_tlb_kernel_vm(); - return(0); - } - else if(current->mm == NULL) + int is_write = FAULT_WRITE(fi); + unsigned long address = FAULT_ADDRESS(fi); + + if (!is_user && regs) + current->thread.segv_regs = container_of(regs, struct pt_regs, regs); + + if (!is_user && (address >= start_vm) && (address < end_vm)) { + flush_tlb_kernel_vm(); + goto out; + } + else if (current->mm == NULL) { + show_regs(container_of(regs, struct pt_regs, regs)); panic("Segfault with no mm"); + } if (SEGV_IS_FIXABLE(&fi) || SEGV_MAYBE_FIXABLE(&fi)) - err = handle_page_fault(address, ip, is_write, is_user, &si.si_code); + err = handle_page_fault(address, ip, is_write, is_user, + &si.si_code); else { err = -EFAULT; - /* A thread accessed NULL, we get a fault, but CR2 is invalid. - * This code is used in __do_copy_from_user() of TT mode. */ + /* + * A thread accessed NULL, we get a fault, but CR2 is invalid. + * This code is used in __do_copy_from_user() of TT mode. + * XXX tt mode is gone, so maybe this isn't needed any more + */ address = 0; } catcher = current->thread.fault_catcher; - if(!err) - return(0); - else if(catcher != NULL){ + if (!err) + goto out; + else if (catcher != NULL) { current->thread.fault_addr = (void *) address; - do_longjmp(catcher, 1); + UML_LONGJMP(catcher, 1); } - else if(current->thread.fault_addr != NULL) + else if (current->thread.fault_addr != NULL) panic("fault_addr set but no fault catcher"); - else if(!is_user && arch_fixup(ip, sc)) - return(0); + else if (!is_user && arch_fixup(ip, regs)) + goto out; - if(!is_user) + if (!is_user) { + show_regs(container_of(regs, struct pt_regs, regs)); panic("Kernel mode fault at addr 0x%lx, ip 0x%lx", address, ip); + } + + show_segv_info(regs); if (err == -EACCES) { si.si_signo = SIGBUS; si.si_errno = 0; si.si_code = BUS_ADRERR; si.si_addr = (void __user *)address; - current->thread.arch.faultinfo = fi; + current->thread.arch.faultinfo = fi; force_sig_info(SIGBUS, &si, current); - } else if (err == -ENOMEM) { - printk("VM: killing process %s\n", current->comm); - do_exit(SIGKILL); } else { BUG_ON(err != -EFAULT); si.si_signo = SIGSEGV; si.si_addr = (void __user *) address; - current->thread.arch.faultinfo = fi; + current->thread.arch.faultinfo = fi; force_sig_info(SIGSEGV, &si, current); } - return(0); -} -void bad_segv(struct faultinfo fi, unsigned long ip) -{ - struct siginfo si; +out: + if (regs) + current->thread.segv_regs = NULL; - si.si_signo = SIGSEGV; - si.si_code = SEGV_ACCERR; - si.si_addr = (void __user *) FAULT_ADDRESS(fi); - current->thread.arch.faultinfo = fi; - force_sig_info(SIGSEGV, &si, current); + return 0; } -void relay_signal(int sig, union uml_pt_regs *regs) +void relay_signal(int sig, struct siginfo *si, struct uml_pt_regs *regs) { - if(arch_handle_signal(sig, regs)) return; - if(!UPT_IS_USER(regs)) + struct faultinfo *fi; + struct siginfo clean_si; + + if (!UPT_IS_USER(regs)) { + if (sig == SIGBUS) + printk(KERN_ERR "Bus error - the host /dev/shm or /tmp " + "mount likely just ran out of space\n"); panic("Kernel mode signal %d", sig); - current->thread.arch.faultinfo = *UPT_FAULTINFO(regs); - force_sig(sig, current); + } + + arch_examine_signal(sig, regs); + + memset(&clean_si, 0, sizeof(clean_si)); + clean_si.si_signo = si->si_signo; + clean_si.si_errno = si->si_errno; + clean_si.si_code = si->si_code; + switch (sig) { + case SIGILL: + case SIGFPE: + case SIGSEGV: + case SIGBUS: + case SIGTRAP: + fi = UPT_FAULTINFO(regs); + clean_si.si_addr = (void __user *) FAULT_ADDRESS(*fi); + current->thread.arch.faultinfo = *fi; +#ifdef __ARCH_SI_TRAPNO + clean_si.si_trapno = si->si_trapno; +#endif + break; + default: + printk(KERN_ERR "Attempted to relay unknown signal %d (si_code = %d)\n", + sig, si->si_code); + } + + force_sig_info(sig, &clean_si, current); } -void bus_handler(int sig, union uml_pt_regs *regs) +void bus_handler(int sig, struct siginfo *si, struct uml_pt_regs *regs) { - if(current->thread.fault_catcher != NULL) - do_longjmp(current->thread.fault_catcher, 1); - else relay_signal(sig, regs); + if (current->thread.fault_catcher != NULL) + UML_LONGJMP(current->thread.fault_catcher, 1); + else + relay_signal(sig, si, regs); } -void winch(int sig, union uml_pt_regs *regs) +void winch(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs) { do_IRQ(WINCH_IRQ, regs); } |
