Signal handling rewrite for Linux / i386

Andrew Haley aph@cambridge.redhat.com
Mon Jan 21 11:03:00 GMT 2002


Following conversations with kernel and glibc maintainers, I've got
what I think is a workable way to handle StackOverflowError.  It works
by allocating a small region at the end of a thread's stack for a
signal stack, so that stack overflow can be trapped and signalled.
This requires a read-only region between the main stack and the signal
stack.

This technique should work for many other processors, but will need to
be changed for processors with more than one stack such as the IA-64,
because we then have two stack limits to manage.

The interpreter needs to be modified for this to work with interpreted
code, but I think it's a small modification.

Also, a small compiler modification will be needed to ensure that we
never generate code with larger stack frames than the size of the
guard area.

Finally, I've fixed a couple of bugs that I found along the way.

Hans, do you expect that allocating stack areas is this way will cause
any problems for the gc?

Andrew.

2002-01-21  Andrew Haley  <aph@cambridge.redhat.com>

	* include/i386-signal.h (STACK_SIZE, SIG_STACK_SIZE,
	GUARD_AREA_SIZE, TARGET_USES_ALTSTACK): New.
	(HANDLE_SEGMENT_VIOLATION): New.
	(kernel_sigaction): New.
	(CREATE_STACK): New.
	(INIT_MAIN_SIGNAL_STACK): New.
	(INIT_SIGNAL_STACK): New.
	
	* prims.cc (SIGNAL_HANDLER): Call HANDLE_SEGMENT_VIOLATION if
	TARGET_USES_ALTSTACK.
	(_Jv_RunMain): Call INIT_MAIN_SIGNAL_STACK if
	TARGET_USES_ALTSTACK.

	* posix-threads.cc (struct starter): Add new member, stackaddr.
	(really_start): Call INIT_SIGNAL_STACK if TARGET_USES_ALTSTACK.
	(_Jv_ThreadStart): Call CREATE_STACK if TARGET_USES_ALTSTACK.

Index: posix-threads.cc
===================================================================
RCS file: /cvs/gcc/gcc/libjava/posix-threads.cc,v
retrieving revision 1.30
diff -c -2 -p -r1.30 posix-threads.cc
*** posix-threads.cc	2001/10/31 00:48:15	1.30
--- posix-threads.cc	2002/01/21 18:33:27
*************** details.  */
*** 28,31 ****
--- 28,32 ----
  #include <gcj/cni.h>
  #include <jvm.h>
+ #include <java-signal.h>
  #include <java/lang/Thread.h>
  #include <java/lang/System.h>
*************** struct starter
*** 38,41 ****
--- 39,45 ----
    _Jv_ThreadStartFunc *method;
    _Jv_Thread_t *data;
+ #ifdef TARGET_USES_ALTSTACK
+   char *stackaddr; /* We only use this if we're using a custom stack.  */
+ #endif
  };
  
*************** really_start (void *x)
*** 371,374 ****
--- 375,384 ----
    struct starter *info = (struct starter *) x;
  
+ #ifdef TARGET_USES_ALTSTACK
+   // If we're using an alternate stack to handle signals, initialize
+   // it now.
+   INIT_SIGNAL_STACK (info->stackaddr);
+ #endif
+ 
    _Jv_ThreadRegister (info->data);
  
*************** _Jv_ThreadStart (java::lang::Thread *thr
*** 408,411 ****
--- 418,427 ----
    info->method = meth;
    info->data = data;
+   
+ #ifdef TARGET_USES_ALTSTACK
+   // If we're using an alternate stack to handle signals, create
+   // our stack now.
+   CREATE_STACK (info->stackaddr, &attr);
+ #endif
  
    if (! thread->isDaemon())
Index: prims.cc
===================================================================
RCS file: /cvs/gcc/gcc/libjava/prims.cc,v
retrieving revision 1.66
diff -c -2 -p -r1.66 prims.cc
*** prims.cc	2001/12/16 22:28:35	1.66
--- prims.cc	2002/01/21 18:33:28
*************** details.  */
*** 61,64 ****
--- 61,65 ----
  #include <java/lang/NullPointerException.h>
  #include <java/lang/OutOfMemoryError.h>
+ #include <java/lang/StackOverflowError.h>
  #include <java/lang/System.h>
  #include <java/lang/reflect/Modifier.h>
*************** static java::lang::NullPointerException 
*** 126,131 ****
--- 127,138 ----
  SIGNAL_HANDLER (catch_segv)
  {
+ #ifdef TARGET_USES_ALTSTACK
+   java::lang::Throwable *thr;
+   HANDLE_SEGMENT_VIOLATION (thr);
+   _Jv_ThrowSignal (thr);
+ #else
    MAKE_THROW_FRAME (nullp);
    _Jv_ThrowSignal (nullp);
+ #endif
  }
  #endif
*************** _Jv_RunMain (jclass klass, const char *n
*** 1019,1022 ****
--- 1026,1035 ----
  #else
    _Jv_ThisExecutable (argv[0]);
+ #endif
+ 
+ #ifdef TARGET_USES_ALTSTACK
+   // If we're using an alternate stack to handle signals, set it up
+   // now.
+   INIT_MAIN_SIGNAL_STACK;
  #endif
  
Index: include/i386-signal.h
===================================================================
RCS file: /cvs/gcc/gcc/libjava/include/i386-signal.h,v
retrieving revision 1.13
diff -c -2 -p -r1.13 i386-signal.h
*** i386-signal.h	2001/07/17 23:59:07	1.13
--- i386-signal.h	2002/01/21 18:33:28
*************** Libgcj License.  Please consult the file
*** 9,14 ****
  details.  */
  
! /* This technique should work for all i386 based Unices which conform
!  * to iBCS2.  This includes all versions of Linux more recent than 1.3 
   */
  
--- 9,13 ----
  details.  */
  
! /* This is Linux specific, but should easily port to other flavours of UN*X.
   */
  
*************** details.  */
*** 19,30 ****
  #include <signal.h>
  #include <sys/syscall.h>
  
  #define HANDLE_SEGV 1
  #define HANDLE_FPE 1
  
  #define SIGNAL_HANDLER(_name)	\
  static void _name (int _dummy)
  
! #define MAKE_THROW_FRAME(_exception)					\
  do									\
  {									\
--- 18,85 ----
  #include <signal.h>
  #include <sys/syscall.h>
+ #include <unistd.h>
+ #include <sys/mman.h>
  
  #define HANDLE_SEGV 1
  #define HANDLE_FPE 1
+ #define TARGET_USES_ALTSTACK 1
  
+ // Size of a thread's stack
+ #define STACK_SIZE (1024 * 1024)
+ // Size of a thread's signal stack
+ #define SIG_STACK_SIZE ((size_t)(4 * getpagesize ()))
+ // Size of the guard area at the limit of a stack
+ #define GUARD_AREA_SIZE (getpagesize ())
+ 
+                                                                             
+ /*
+                                                                             
+ A stack looks like this: 
+ 
+ high memory        +-------------------------+      
+                    |                         |      
+                    |                         |      
+                    |          stack          |      
+                    |                         |      
+                    +-------------------------+  <- sp
+                    |            |            |      
+                    |            |            |      
+                    |            V            |      
+                    |                         |      
+                    |                         |      
+                    |                         |      
+                    |                         |      
+                    |                         |      
+                    |                         |      
+                    |                         |      
+                    +-------------------------+ 
+                    |                         |      
+                    |       guard area        |      
+                    |    (mapped READ_ONLY)   |      
+                    |                         |      
+                    +-------------------------+  
+                    |                         |  <- initial signal stack    
+                    |                         |
+                    |                         |
+                    |                         |
+                    |       alt stack         |
+                    |                         |
+                    |                         |
+                    |                         |
+                    |                         |
+ low memory         +-------------------------+
+ 
+ When a SEGV occurs, the signal is handled on the alt stack.  If the
+ signal stack pointer is within SIG_STACK_SIZE of the saved stack
+ pointer we signal StackOverflowError rather than NullPointerException.
+ The unwinder unwinds past the guard area back to the main stack.
+ 
+ */
+ 
+ 
  #define SIGNAL_HANDLER(_name)	\
  static void _name (int _dummy)
  
! #define HANDLE_SEGMENT_VIOLATION(_exception)				\
  do									\
  {									\
*************** do									\
*** 37,40 ****
--- 92,106 ----
    _regs->eip += 2;							\
  									\
+   /* If the current stack pointer is within				\
+      SIG_STACK_SIZE + GUARD_AREA_SIZE of the stack pointer at the time	\
+      of the exception we've got a stack overflow.  This is		\
+      conservative: it might cause a spurious overflow to be detected,	\
+      but only when the stack is almost about to overflow anyway.  */	\
+   if ((unsigned)((char *)_regs->esp					\
+ 		 - (char *)__builtin_frame_address (0))			\
+       < SIG_STACK_SIZE + GUARD_AREA_SIZE)				\
+     _exception = new java::lang::StackOverflowError ();			\
+   else									\
+     _exception = new java::lang::NullPointerException ();		\
  }									\
  while (0)
*************** do									\
*** 100,113 ****
  while (0)
  
! #define INIT_SEGV						\
! do								\
!   {								\
!     nullp = new java::lang::NullPointerException ();    	\
!     struct sigaction act;					\
!     act.sa_handler = catch_segv;				\
!     sigemptyset (&act.sa_mask);					\
!     act.sa_flags = 0;						\
!     syscall (SYS_sigaction, SIGSEGV, &act, NULL);		\
!   }								\
  while (0)  
  
--- 166,191 ----
  while (0)
  
! /* New Linux kernels use the rt_sigaction syscall (rather than just
!    sigaction.)  We use it here because we're calling the kernel
!    directly rather than via glibc.  The siagction structure that
!    rt_sigaction uses is a different shape from the one in userland and
!    not visible to us in a header file so we define it here.  */
! struct kernel_sigaction 
! {
!   __sighandler_t k_sa_handler;
!   unsigned long sa_flags;
!   void (*sa_restorer) (void);
!   sigset_t sa_mask;
! };
! 
! #define INIT_SEGV							\
! do									\
!   {									\
!     struct kernel_sigaction kact;					\
!     kact.k_sa_handler = catch_segv;					\
!     sigemptyset (&kact.sa_mask);					\
!     kact.sa_flags = SA_ONSTACK;						\
!     syscall (SYS_rt_sigaction, SIGSEGV, &kact, NULL, _NSIG/8);		\
!   }									\
  while (0)  
  
*************** do								\
*** 117,131 ****
      arithexception = new java::lang::ArithmeticException 	\
        (JvNewStringLatin1 ("/ by zero"));			\
!     struct sigaction act;					\
!     act.sa_handler = catch_fpe;					\
!     sigemptyset (&act.sa_mask);					\
!     act.sa_flags = 0;						\
!     syscall (SYS_sigaction, SIGFPE, &act, NULL);		\
    }								\
  while (0)  
  
! /* You might wonder why we use syscall(SYS_sigaction) in INIT_FPE
!  * instead of the standard sigaction().  This is necessary because of
!  * the shenanigans above where we increment the PC saved in the
   * context and then return.  This trick will only work when we are
   * called _directly_ by the kernel, because linuxthreads wraps signal
--- 195,211 ----
      arithexception = new java::lang::ArithmeticException 	\
        (JvNewStringLatin1 ("/ by zero"));			\
!     struct kernel_sigaction kact;				\
!     kact.k_sa_handler = catch_fpe;				\
!     sigemptyset (&kact.sa_mask);				\
!     kact.sa_flags = 0;						\
!     syscall (SYS_rt_sigaction, SIGFPE, &kact, NULL, _NSIG/8);	\
    }								\
  while (0)  
  
! /* You might wonder why we use syscall(SYS_rt_sigaction) in INIT_FPE
!  * and INIT_SEGV instead of the standard sigaction().  
! 
!  * There are two reasons for this: Firstly, of the shenanigans in
!  * HANDLE_DIVIDE_OVERFLOW where we increment the PC saved in the
   * context and then return.  This trick will only work when we are
   * called _directly_ by the kernel, because linuxthreads wraps signal
*************** while (0)  
*** 134,140 ****
   * handler to a linuxthreads wrapper, we will lose the PC adjustment
   * we made and return to the faulting instruction again.  Using
!  * syscall(SYS_sigaction) causes our handler to be called directly by
!  * the kernel, bypassing any wrappers.  This is a kludge, and a future
!  * version of this handler will do something better.  */
  
  #endif /* JAVA_SIGNAL_H */
--- 214,295 ----
   * handler to a linuxthreads wrapper, we will lose the PC adjustment
   * we made and return to the faulting instruction again.  Using
!  * syscall(SYS_rt_sigaction) causes our handler to be called directly
!  * by the kernel, bypassing any wrappers.
! 
!  * Also, there is at the present time no unwind info in the
!  * linuxthreads library's signal handlers and so we can't unwind
!  * through them anyway.  
! 
!  * Finally, the code that glibc used to return from a signal handler
!  * is subject to change.  */
! 
! /* Stack overflow detection. 
! 
!    We protect a page near the end of each thread's stack and set up
!    our signal stack to be on the other side of that page.  When the
!    stack overflows we'll catch the signal on the other side of the
!    protected page and return.  */
! 
! 
! /* Create a stack for a thread.  */
! #define CREATE_STACK(STACK_ADDR, ATTR)					\
! do									\
!   {									\
!     const int stacksize = STACK_SIZE;					\
!     void *stack_addr = 0;						\
! 									\
!     stack_addr = mmap(NULL, stacksize,					\
! 		    PROT_READ | PROT_WRITE | PROT_EXEC,			\
! 		    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);		\
!     if (stack_addr == MAP_FAILED)					\
!       {									\
! 	const char* msg = "Cannot create additional threads";		\
! 	throw new java::lang::OutOfMemoryError (JvNewStringUTF (msg));	\
!       }									\
!     pthread_attr_setstack (ATTR, stack_addr, stacksize);		\
!     STACK_ADDR = (char *)stack_addr;					\
!   }									\
! while (0)
! 
! /* Set the guard page and the altstack for the initial thread.  */
! #define INIT_MAIN_SIGNAL_STACK					\
! do								\
! {								\
!   size_t extra_size = SIG_STACK_SIZE + GUARD_AREA_SIZE;		\
!   void *stack_limit						\
!     = (void *)(((unsigned long)__builtin_frame_address(0)	\
! 	       - (STACK_SIZE + extra_size)) & -getpagesize ());	\
!   stack_limit = mmap(stack_limit, extra_size,			\
! 		     PROT_READ | PROT_WRITE | PROT_EXEC,	\
! 		     MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,	\
! 		     -1, 0);					\
!   if (stack_limit == MAP_FAILED)				\
!     {								\
!       perror ("Failed to map initial stack");			\
!       abort ();							\
!     }								\
!   INIT_SIGNAL_STACK (stack_limit);				\
! }								\
! while (0)
! 
! /* Protect the current thread's guard page and set its altstack.  */
! #define INIT_SIGNAL_STACK(STACK_BASE)			\
! do							\
!   {							\
!     stack_t st;						\
! 							\
!     char *stack_base = (char *)(STACK_BASE);		\
!     st.ss_sp = stack_base;				\
!     st.ss_flags = 0;					\
!     st.ss_size = SIG_STACK_SIZE;			\
! 							\
!     /* Protect our guard page and set our altstack.  */	\
!     if (mprotect (stack_base + SIG_STACK_SIZE,		\
! 		  GUARD_AREA_SIZE, PROT_READ))		\
!       abort ();						\
!     if (sigaltstack (&st, NULL))			\
!       abort ();						\
!   }							\
! while (0)
  
  #endif /* JAVA_SIGNAL_H */



More information about the Java mailing list