Signal handling rewrite for Linux / i386

Andrew Haley aph@cambridge.redhat.com
Tue Jan 22 06:04:00 GMT 2002


Please ignore everything I wrote before coffee this morning...

Bryce McKinlay writes:
 > Andrew Haley wrote:
 > 
 > >+ // Size of a thread's stack
 > >+ #define STACK_SIZE (1024 * 1024)
 > >
 > 
 > Is it wise to fix the size of the stack like this?

No, it isn't.  You're right.

 > Don't we lose something by circumventing the kernel's
 > variable-size/floating stack feature?

I don't think so.  If we're going to detect stack overflow, and the
JLS says we must, we need to have some fixed limit.  Inevitably any
such limit will be rather arbitrary.

 > Certainly, it seems that ulimit -s won't be respected (which can be
 > useful if you want to run a lot of threads on a 32-bit machine and
 > avoid running out of virtual memory).

This is a revised version of the patch that takes RLIMIT_STACK into
account.  It also maps the whole initial stack rather than just the
guard page, because otherwise the gc may fault while scanning the
initial stack.

I'm not totally happy about this, though.  It is my belief that Java
threads need much smaller stack sizes than C threads, so using the
same stack size is probably wrong.  On the other hand, it proably
isn't important: it just means that we detect overflow rather late.

Andrew.

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

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

	* 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.
	
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/22 13:42:48
*************** 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/22 13:42:51
*************** 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/22 13:42:51
*************** 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,87 ----
  #include <signal.h>
  #include <sys/syscall.h>
+ #include <unistd.h>
+ #include <sys/mman.h>
+ #include <sys/time.h>
+ #include <sys/resource.h>
  
  #define HANDLE_SEGV 1
  #define HANDLE_FPE 1
+ #define TARGET_USES_ALTSTACK 1
  
+ // Size of a thread's stack
+ #define DEFAULT_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 ****
--- 94,108 ----
    _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)  
  
--- 168,193 ----
  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
--- 197,213 ----
      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, 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 */
--- 216,312 ----
   * 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.  */
! 
! 
! static rlim_t
! thread_stack_size ()
! {
!   struct rlimit limit;
! 
!   getrlimit(RLIMIT_STACK, &limit);
!   if (limit.rlim_cur == RLIM_INFINITY)
!     limit.rlim_cur = DEFAULT_STACK_SIZE;
!   
!   return limit.rlim_cur;
! }
! 
! 
! /* Create a stack for a thread.  */
! #define CREATE_STACK(STACK_ADDR, ATTR)					\
! do									\
!   {									\
!     const int stacksize = thread_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 total_size 							\
!     = thread_stack_size () + SIG_STACK_SIZE + GUARD_AREA_SIZE;		\
!   void *stack_limit							\
!     = (void *)(((unsigned long)__builtin_frame_address(0)		\
! 		- total_size)						\
! 	       & -getpagesize ());					\
!   stack_limit = mmap(stack_limit, total_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