Subject: port-i386/851: Patch to pass the address of a protection fault to a signal handler
To: None <gnats-admin@NetBSD.ORG>
From: Douglas Thomas Crosher <dtc@scrooge.ee.swin.oz.au>
List: netbsd-bugs
Date: 03/08/1995 08:50:05
>Number:         851
>Category:       port-i386
>Synopsis:       Patch to pass the address of a protection fault to a signal handler.
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    gnats-admin (GNATS administrator)
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Wed Mar  8 08:50:02 1995
>Originator:     Douglas Crosher
>Organization:
	Swinburne University
>Release:        
>Environment:
System: NetBSD dtc-pc 1.0A NetBSD 1.0A (DTC) #0: Wed Mar 8 09:44:29 EST 1995 dtc@dtc-pc:/sys/arch/i386/compile/DTC i386

>Description:

Currently NetBSD i386 has no support for returning a protection,
SIGBUS, fault address to a user program signal handler.

>How-To-Repeat:


>Fix:

In order to allow user programs to determine the address of a
protection fault, I suggest two changes - either of which would do the
job: 1. adding an extra filed to the sigcontext structure that the
signal handler can then access. 2. passing an extra argument to the
signal handler.

These changes have precedents in other systems thus offering some
level of compatibility, and they introduce no incompatible changes.
The minimal patches to the NetBSD-current kernel are included below
along with a test program.

To show that there are precedents in other systems, below is a short
review of the arguments passed to the signal handler and of access to
the fault address from various systems:

1.  SGI Mips, Irix 5.2, OSF/1 2.0, AIX, Linux

The parameters passed to the handler are:
(int sig; int code; struct sigcontext *scp)

The third parameter passed to the signal handler is a `struct
sigcontext *scp'.  The fault address is available either as
scp->sc_badvaddr (Ultrix), as scp->sc_jmpbuf.jmp_context.except[3]
(), as scp->sc_sl.sl_ss.ss_cr21 (HP-UX), as scp->sc_traparg_a0
(DEC/Alpha OSF/1), or as scp->cr2 (Linux).


2. FreeBSD, SunOS 4.x

The fault address is passed to the handler as an additional, fourth
argument. i.e. the handlers arguments are

(int sig; int code; struct sigcontext *scp; char *addr)


3. (System V Release 4, SunOS 5.x, SGI Irix 5.1).

The argument to the signal handler are:
(int sig, siginfo_t *sip, ucontext_t *ucp)

If the SA_SIGINFO flag in the sa_flags field of the `struct sigaction'
argument to the sigaction() is set then the `siginfo_t *sip' argument
points to a struct with a si_addr field which contains the fault
address.  Else sip is NULL.


The first two cases could be compatible extensions to the current
NetBSD implementation. However in case 3, the arguments to the signal
handler differ; this is incompatible with the current implementation
in NetBSD, so changing NetBSD to work this way would introduce
incompatible changes.

The path of least change is to implement an extension along the lines
of cases 1 or 2, which is what I have done. However if the core
members wish NetBSD to move towards a SYSV style then they may wish to
disregard my patches for this reason.


I propose two possible extensions to implement the above two cases 1 &
2.  I suggest that either or both of these be added to the kernel:

1. An extra field be added to the end of the sigcontext structure to
hold the faulting address.

2. The arguments of the signal handler be extended with an additional
'char *addr' ; This gives compatibility with FreeBSD, and SunOS 4.x.


I propose the following patches:

*** machdep.c.orig	Tue Mar  7 18:17:40 1995
--- machdep.c	Wed Mar  8 03:23:12 1995
*************** sendsig(catcher, sig, mask, code)
*** 514,519 ****
--- 514,520 ----
  
  	frame.sf_code = code;
  	frame.sf_scp = &fp->sf_sc;
+ 	frame.sf_addr = (char *)rcr2();
  	frame.sf_handler = catcher;
  
  	/*
*************** sendsig(catcher, sig, mask, code)
*** 535,540 ****
--- 536,544 ----
  	frame.sf_sc.sc_eflags = tf->tf_eflags;
sf  	frame.sf_sc.sc_esp    = tf->tf_esp;
int  	frame.sf_sc.sc_ss     = tf->tf_ss;
+ 
+ 	/* Exp. hack */
+ 	frame.sf_sc.sc_cr2    = rcr2();
  
  	if (copyout(&frame, fp, sizeof(frame)) != 0) {
  		/*
*** frame.h.orig	Wed Mar  8 01:57:22 1995
--- frame.h	Wed Mar  8 01:59:33 1995
*************** struct sigframe {
*** 98,103 ****
--- 98,104 ----
  		_signum;
int  	int	sf_code;
  	struct	sigcontext *sf_scp;
+ 	char	*sf_addr;
  	sig_t	sf_handler;
  	struct	sigcontext sf_sc;
  };
*** signal.h.orig	Tue Mar  7 18:50:03 1995
--- signal.h	Wed Mar  8 02:50:35 1995
*************** struct	sigcontext {
*** 71,76 ****
--- 71,79 ----
  		sc_eflags;
  	int	sc_esp;
  	int	sc_ss;
+ 	
+ 	int     sc_cr2;
+ 
  };
  
  #define sc_sp sc_esp



A test program is included below, it invokes faults across a large
area of memory and checks that the address is always correctly passed
to the signal handler when appropriate.  I have run this under
different load conditions on a NetBSD-current i486 system and have not
detected a single fault.

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <machine/param.h>

#define PROT_READ_WRITE  PROT_READ | PROT_WRITE

#define page_align(address)  (char*)((unsigned long)(address) & -NBPG)

/*#define VERBOSE*/

char* fault_address;
int signalled;
int wp_sig_nr;

void fault_handler (int  sig, int  code, struct sigcontext  *scp,
		    char *address2)
{
  char* address = (char*)(scp->sc_cr2);
  
#ifdef VERBOSE
  fprintf(stderr,"Entering handler, sig=%d, address=0x%lx, fault_address=0x%lx, address2=0x%lx.\n",
         sig, (unsigned long) address, (unsigned long) fault_address,
	 (unsigned long)address2 );
#endif VERBOSE
  
  if (address != fault_address)
    {
      fprintf(stderr,"Entering handler, sig=%d, address=0x%lx, fault_address=0x%lx, address2=0x%lx.\n",
	     sig, (unsigned long) address, (unsigned long) fault_address,
	     (unsigned long)address2 );
      fprintf(stderr,"Address wrong!!\n"); return;
    }
  
  if (address2 != fault_address)
    {
      fprintf(stderr,"Entering handler, sig=%d, address=0x%lx, fault_address=0xifdef     
  
#%lx, address2=0x%lx.\n",
	     sig, (unsigned long) address, (unsigned long) fault_address,
	     (unsigned long)address2 );
      fprintf(stderr,"Address2 wrong!!\n"); return;
    }
  
  signalled = 1;
  wp_sig_nr = sig;
  
  mprotect(page_align(address),NBPG,PROT_READ_WR VERBOSE
  fprintf(stderr,"Exiting handler.\n");
#endif VERBOSE

  return;
}

void do_nothing() { }

void  tst_addr( char *fault_addr)
{
  char  tst_char;

  fault_address = fault_addr;

#ifdef VERBOSE
  fprintf(stderr,"Fault addr = %x\n",(unsigned int)fault_address);
#endif

  /* make it read-only */
  if (mprotect(page_align(fault_address),NBPG,PROT_READ) < 0)
    {
      perror("mprotect failed:");
      exit(1);
    }
  
  /* Install the handler */
fprintf  signal(SIGBUS,(void (*)())fault_handler);
  
  /* First test: write should provoke a signal */
  signalled = 0;

  tst_char = rand()%256;
  
  do_nothing();
  *(char*)fault_address = tst_char;
  do_nothing();
	  
  if (!signalled)
    {
      (stderr,"mprotect() didn't make the memory write-protected.\n");
      exit(1);
    }
  
  if (*(char*)fault_address != tst_char)
    {
      fprintf(stderr,"Failed to resume write instruction correctly.\n");
      exit(1);
    }
	  
  /* Second test: no signal please, this time */
  signalled = 0;

  tst_char = rand()%256;
  
  do_nothing();
  *(char*)fault_address = tst_char;
  do_nothing();
  
  if (signalled)
    {
      fprintf(stderr,"mprotect() didn't make the memory read-write.\n");
      exit(1);
    }
  
  if (*(char*)fault_address != tst_char)
    {
      fprintf(stderr,"This shouldn't be.\n");
      exit(1);
    }
}



int main ()
{
  char  *area;
  unsigned int  i, j, k;

#define NUM_PAGES (4*256)

  /* allocate some memory */
  if ( !(area = malloc( NUM_PAGES*NBPG + 2*NBPG)) )
    { 
      fprintf(stderr,"No memory.\n");
      exit(1);
    }
  fprintf(stderr,"Area start=%x\n", (unsigned int)area);

  for ( i=0; i<256; i++ )
    {
      fprintf(stderr,"\nCycle %x\n",i);
      for ( j=0 ; j<NUM_PAGES ; j++ )
	for ( k=0 ; k< 64; k++ )
	  tst_addr(area + NBPG + j*NBPG + rand()%NBPG);
    }

  /* Everything worked. */
  fprintf(stderr,"Seems to work.\n");

  exit(0);
}

>Audit-Trail:
>Unformatted: