[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[PATCH] POSIX and SVr4 compliant SIGSEGV handling



Hi,

I wrote some code that uses sigaction to catch a SIGSEGV and noticed
that the propper kernel support was missing on alpha and, as it turns
out, on other not so new archs as well. I believe the support was
added to i386 in the late 2.1.x kernels or with 2.2.x, all archs
created after that should have it (like S390) and most (all?) archs
present before that might miss it (like m68k).

The patch below is a near 1:1 copy of the relevant code lines from
arch/i386/mm/fault.c. The main difference is that it know fills in a
siginfo structure and calls force_sig_info() instead of
force_sig(). The other minor change is that it updates min_flt and
maj_flt for the task.

I also included a small test program that generates two segfaults (one
by raise and one real) and catches them. The address(0x10) that caused
the second fault should be shown. This patch fixes it for alpha.

May the Source be with you,
                        Goswin

#include <iostream>
#include <signal.h>
#include <cstring>

void sigSegvHandler(int, siginfo_t *sigInfo, void *) {
    static int n = 0;
    if (n++ > 1) exit(0); // prevent endless loop
    std::cout << "Withing segfault" << std::endl;
    if (sigInfo->si_code == SEGV_MAPERR
	|| sigInfo->si_code == SEGV_ACCERR) {
	std::cout << "MAPERR or ACCERR: Adress = 0x" << sigInfo->si_addr << std::endl;
    } else {
	std::cout << "Unknown reason(" << sigInfo->si_code << ")\n";
    }
    return;
}

int main() {
    struct sigaction sigSegvAction;
    
    bzero(&sigSegvAction, sizeof(sigSegvAction));
    sigSegvAction.sa_sigaction = &sigSegvHandler;
    sigSegvAction.sa_flags = SA_SIGINFO | SA_NOMASK;
    
    if (sigaction(SIGSEGV, &sigSegvAction, NULL) == -1) {
	perror("MOOSE: sigSegvAction");
	exit(1);
    }

    std::cout << "Segfaulting" << std::endl; 
    raise(SIGSEGV);
    volatile int a = *(int *)16L;
}
--- linux-2.4.9-ac5-orig/arch/alpha/mm/fault.c	Fri Aug 31 21:01:16 2001
+++ linux-2.4.9-ac5-mrvn/arch/alpha/mm/fault.c	Fri Aug 31 21:45:46 2001
@@ -111,10 +111,12 @@
 do_page_fault(unsigned long address, unsigned long mmcsr,
 	      long cause, struct pt_regs *regs)
 {
+	struct task_struct *tsk;
 	struct vm_area_struct * vma;
-	struct mm_struct *mm = current->mm;
+	struct mm_struct *mm;
 	unsigned int fixup;
 	int fault;
+	siginfo_t info;
 
 	/* As of EV6, a load into $31/$f31 is a prefetch, and never faults
 	   (or is suppressed by the PALcode).  Support that for older CPUs
@@ -130,11 +132,15 @@
 		}
 	}
 
+	tsk = current;
+	mm =  = current->mm;
+	
 	/* If we're in an interrupt context, or have no user context,
 	   we must not take the fault.  */
 	if (!mm || in_interrupt())
 		goto no_context;
 
+	info.si_code = SEGV_MAPERR;
 #ifdef CONFIG_ALPHA_LARGE_VMALLOC
 	if (address >= TASK_SIZE)
 		goto vmalloc_fault;
@@ -155,6 +161,7 @@
  * we can handle it..
  */
 good_area:
+	info.si_code = SEGV_ACCERR;
 	if (cause < 0) {
 		if (!(vma->vm_flags & VM_EXEC))
 			goto bad_area;
@@ -175,10 +182,18 @@
 	fault = handle_mm_fault(mm, vma, address, cause > 0);
 	up_read(&mm->mmap_sem);
 
-	if (fault < 0)
-		goto out_of_memory;
-	if (fault == 0)
+	switch (fault) {
+	case 1:
+		tsk->min_flt++;
+		break;
+	case 2:
+		tsk->maj_flt++;
+		break;
+	case 0:
 		goto do_sigbus;
+	default:
+		goto out_of_memory;
+	}
 
 	return;
 
@@ -190,7 +205,11 @@
 	up_read(&mm->mmap_sem);
 
 	if (user_mode(regs)) {
-		force_sig(SIGSEGV, current);
+		info.si_signo = SIGSEGV;
+		info.si_errno = 0;
+		/* info.si_code has been set above */
+		info.si_addr = (void *)address;
+		force_sig_info(SIGSEGV, &info, tsk);
 		return;
 	}
 
@@ -201,7 +220,7 @@
 		newpc = fixup_exception(dpf_reg, fixup, regs->pc);
 #if 0
 		printk("%s: Exception at [<%lx>] (%lx) handled successfully\n",
-		       current->comm, regs->pc, newpc);
+		       tsk->comm, regs->pc, newpc);
 #endif
 		regs->pc = newpc;
 		return;
@@ -222,7 +241,7 @@
  */
 out_of_memory:
 	printk(KERN_ALERT "VM: killing process %s(%d)\n",
-	       current->comm, current->pid);
+	       tsk->comm, tsk->pid);
 	if (!user_mode(regs))
 		goto no_context;
 	do_exit(SIGKILL);
@@ -232,7 +251,11 @@
 	 * Send a sigbus, regardless of whether we were in kernel
 	 * or user mode.
 	 */
-	force_sig(SIGBUS, current);
+	info.si_code = SIGBUS;
+	info.si_errno = 0;
+	info.si_code = BUS_ADRERR;
+	info.si_addr = (void *)address;
+	force_sig_info(SIGBUS, &info, tsk);
 	if (!user_mode(regs))
 		goto no_context;
 	return;
@@ -240,7 +263,11 @@
 #ifdef CONFIG_ALPHA_LARGE_VMALLOC
 vmalloc_fault:
 	if (user_mode(regs)) {
-		force_sig(SIGSEGV, current);
+		info.si_signo = SIGSEGV;
+		info.si_errno = 0;
+		/* info.si_code has been set above */
+		info.si_addr = (void *)address;
+		force_sig_info(SIGSEGV, &info, tsk);
 		return;
 	} else {
 		/* Synchronize this task's top level page-table
@@ -248,7 +275,7 @@
 		long offset = __pgd_offset(address);
 		pgd_t *pgd, *pgd_k;
 
-		pgd = current->active_mm->pgd + offset;
+		pgd = tsk->active_mm->pgd + offset;
 		pgd_k = swapper_pg_dir + offset;
 		if (pgd_present(*pgd_k)) {
 			pgd_val(*pgd) = pgd_val(*pgd_k);

Reply to: