diff options
Diffstat (limited to 'arch/um/kernel/trap.c')
| -rw-r--r-- | arch/um/kernel/trap.c | 133 | 
1 files changed, 106 insertions, 27 deletions
diff --git a/arch/um/kernel/trap.c b/arch/um/kernel/trap.c index 637c6505dc0..5678c3571e7 100644 --- a/arch/um/kernel/trap.c +++ b/arch/um/kernel/trap.c @@ -6,15 +6,15 @@  #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" -#include "sysdep/sigcontext.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 @@ -30,6 +30,7 @@ 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; @@ -40,6 +41,9 @@ int handle_page_fault(unsigned long address, unsigned long ip,  	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) @@ -55,17 +59,24 @@ int handle_page_fault(unsigned long address, unsigned long ip,  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 {  		int fault; -		fault = handle_mm_fault(mm, vma, address, is_write ? FAULT_FLAG_WRITE : 0); +		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; @@ -75,10 +86,18 @@ good_area:  			}  			BUG();  		} -		if (fault & VM_FAULT_MAJOR) -			current->maj_flt++; -		else -			current->min_flt++; +		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); @@ -109,9 +128,33 @@ out_of_memory:  	 * (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"); +}  static void bad_segv(struct faultinfo fi, unsigned long ip)  { @@ -136,11 +179,12 @@ void fatal_sigsegv(void)  	os_dump_core();  } -void segv_handler(int sig, struct uml_pt_regs *regs) +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)) { +		show_segv_info(regs);  		bad_segv(*fi, UPT_IP(regs));  		return;  	} @@ -162,9 +206,12 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,  	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(); -		return 0; +		goto out;  	}  	else if (current->mm == NULL) {  		show_regs(container_of(regs, struct pt_regs, regs)); @@ -186,7 +233,7 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,  	catcher = current->thread.fault_catcher;  	if (!err) -		return 0; +		goto out;  	else if (catcher != NULL) {  		current->thread.fault_addr = (void *) address;  		UML_LONGJMP(catcher, 1); @@ -194,7 +241,7 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,  	else if (current->thread.fault_addr != NULL)  		panic("fault_addr set but no fault catcher");  	else if (!is_user && arch_fixup(ip, regs)) -		return 0; +		goto out;  	if (!is_user) {  		show_regs(container_of(regs, struct pt_regs, regs)); @@ -202,6 +249,8 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,  		      address, ip);  	} +	show_segv_info(regs); +  	if (err == -EACCES) {  		si.si_signo = SIGBUS;  		si.si_errno = 0; @@ -216,11 +265,19 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,  		current->thread.arch.faultinfo = fi;  		force_sig_info(SIGSEGV, &si, current);  	} + +out: +	if (regs) +		current->thread.segv_regs = NULL; +  	return 0;  } -void relay_signal(int sig, struct uml_pt_regs *regs) +void relay_signal(int sig, struct siginfo *si, struct uml_pt_regs *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 " @@ -230,18 +287,40 @@ void relay_signal(int sig, struct uml_pt_regs *regs)  	arch_examine_signal(sig, regs); -	current->thread.arch.faultinfo = *UPT_FAULTINFO(regs); -	force_sig(sig, current); +	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, struct uml_pt_regs *regs) +void bus_handler(int sig, struct siginfo *si, struct uml_pt_regs *regs)  {  	if (current->thread.fault_catcher != NULL)  		UML_LONGJMP(current->thread.fault_catcher, 1); -	else relay_signal(sig, regs); +	else +		relay_signal(sig, si, regs);  } -void winch(int sig, struct uml_pt_regs *regs) +void winch(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)  {  	do_IRQ(WINCH_IRQ, regs);  }  | 
