This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

PR java/1373: recursion stress test causes segmentation fault


Time to resurrect this patch.

This should work on any port which has a single stack that frows
downwards.

Last time this came around I didn't check it in because pthread_self()
was inefficient with user-provided thread stacks.  With the new
pthread library that isn't really an issue.

This doesn't trap stack overflows in the interpreter, which faults and
then immediately re-throws the Error.  There's a nice way to fix that,
but it should be part of a different patch.

There is a remaining issue, and that is how much space the garbage
collector needs to run.  Hans pointed out that the gc might run while
we're on the altstack, and we need to allow space for that.  The
alternative is not to allocate any memory in the SEGV handler, but
that's rather impractical.

What happens if GC itself hits the stack limit?  There are two ways to
approach this: either unprotect the guard page during GC operations or
have the GC do a stack probe before anything else.  The latter is
neater IMO, but the subject for a later patch.

Andrew.


2004-10-20  Andrew Haley  <aph@redhat.com>

	* include/x86_64-signal.h 
	include overflowsupp.h.
	(INIT_SEGV): Set SA_ONSTACK.
	(MAKE_THROW_FRAME): New arg, ADDR, the faulting address.
	* include/overflowsupp.h: New file.
	* prims.cc (catch_segv): Rewrite.
	(SEGV_ACTION): New define.
	* posix-threads.cc (struct starter): stackaddr: new field.
	(really_start): Create signal stack.
	(_Jv_ThreadStart): Create signal stack.

Index: posix-threads.cc
===================================================================
RCS file: /cvs/gcc/gcc/libjava/posix-threads.cc,v
retrieving revision 1.35
diff -p -2 -c -r1.35 posix-threads.cc
*** posix-threads.cc	12 Aug 2004 16:20:08 -0000	1.35
--- posix-threads.cc	20 Oct 2004 14:33:49 -0000
*************** details.  */
*** 31,34 ****
--- 31,35 ----
  #include <gcj/cni.h>
  #include <jvm.h>
+ #include <java-signal.h>
  #include <java/lang/Thread.h>
  #include <java/lang/System.h>
*************** struct starter
*** 42,45 ****
--- 43,49 ----
    _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)
*** 390,393 ****
--- 394,403 ----
    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
*** 431,434 ****
--- 441,450 ----
    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.99
diff -p -2 -c -r1.99 prims.cc
*** prims.cc	26 Sep 2004 20:38:40 -0000	1.99
--- prims.cc	20 Oct 2004 14:33:49 -0000
*************** details.  */
*** 54,57 ****
--- 54,58 ----
  #include <java/lang/NullPointerException.h>
  #include <java/lang/OutOfMemoryError.h>
+ #include <java/lang/StackOverflowError.h>
  #include <java/lang/System.h>
  #include <java/lang/VMThrowable.h>
*************** unblock_signal (int signum __attribute__
*** 140,155 ****
  #endif
  }
! #endif
  
  #ifdef HANDLE_SEGV
  SIGNAL_HANDLER (catch_segv)
  {
!   java::lang::NullPointerException *nullp 
!     = new java::lang::NullPointerException;
    unblock_signal (SIGSEGV);
!   MAKE_THROW_FRAME (nullp);
!   throw nullp;
  }
! #endif
  
  #ifdef HANDLE_FPE
--- 141,172 ----
  #endif
  }
! #endif // defined (HANDLE_SEGV) || defined(HANDLE_FPE)
  
  #ifdef HANDLE_SEGV
+ 
+ /* This default action for catching SIGSEGV always throws a
+    NullPointerException, but if it's possible to detect a
+    StackOverflowError the target should override HANDLE_SEGV.
+ 
+    MAKE_THROW_FRAME must be called before creating the Throwable
+    instance.  */
+ #ifndef SEGV_ACTION
+ #define SEGV_ACTION(_throwable)				\
+ do							\
+ {							\
+   MAKE_THROW_FRAME (_throwable);			\
+   _throwable = new java::lang::NullPointerException;	\
+ }							\
+ while (0)
+ #endif // SEGV_ACTION
+ 
  SIGNAL_HANDLER (catch_segv)
  {
!   java::lang::Throwable *t;
!   SEGV_ACTION (t);
    unblock_signal (SIGSEGV);
!   throw t;
  }
! #endif // HANDLE_SEGV
  
  #ifdef HANDLE_FPE
*************** _Jv_RunMain (jclass klass, const char *n
*** 1012,1015 ****
--- 1029,1038 ----
        _Jv_CreateJavaVM (NULL);
  
+ #ifdef TARGET_USES_ALTSTACK
+       // If we're using an alternate stack to handle signals, set it up
+       // now.
+       INIT_MAIN_SIGNAL_STACK;
+ #endif
+ 
        // Get the Runtime here.  We want to initialize it before searching
        // for `main'; that way it will be set up if `main' is a JNI method.
Index: include/overflowsupp.h
===================================================================
RCS file: include/overflowsupp.h
diff -N include/overflowsupp.h
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- include/overflowsupp.h	20 Oct 2004 14:33:49 -0000
***************
*** 0 ****
--- 1,171 ----
+ // overflowsupp.h - Support routines for detection of and recovery
+ // from stack overflow.
+ 
+ /* Copyright (C) 2004  Free Software Foundation
+ 
+    This file is part of libgcj.
+ 
+ This software is copyrighted work licensed under the terms of the
+ Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+ details.  */
+ 
+ #include <unistd.h>
+ #include <sys/mman.h>
+ 
+ #define TARGET_USES_ALTSTACK 1
+ 
+ // Total size of a thread's stack.  This is a megabyte on a 32-bit
+ // system and two megabytes on a 64-bit system.
+ #ifndef STACK_SIZE
+ #define STACK_SIZE (1024 * 256 * sizeof (void*))
+ #endif
+ 
+ // Default size of a thread's altstack, on which the SEGV handler is
+ // run.  Needs to be big enough to allow the gc to run.
+ #ifndef ALT_STACK_SIZE
+ #define ALT_STACK_SIZE (32 * 1024 * sizeof (void*))
+ #endif
+ 
+ // Default size of the guard area at the limit of a stack.  
+ #ifndef GUARD_AREA_SIZE
+ // Allow 8k words of local variables per method invocation.  This is
+ // generous for Java, which doesn't have any arrays on the stack, but
+ // might not be enough for a stack-smashing attack.
+ #define GUARD_AREA_SIZE (8 * 1024 * sizeof (void*))
+ #endif
+ 
+ #define TOTAL_STACK_SIZE (STACK_SIZE + ALT_STACK_SIZE + GUARD_AREA_SIZE)
+                                                                             
+ /*
+                                                                             
+ 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
+ stack pointer of the handler is within GUARD_AREA_SIZE of the faulting
+ address we signal StackOverflowError rather than NullPointerException.
+ The unwinder unwinds past the guard area back to the main stack.
+ 
+ */
+ 
+ /* Stack overflow detection. 
+ 
+    We protect some pages 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 = TOTAL_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 = ALT_STACK_SIZE + GUARD_AREA_SIZE;			\
+   void *stack_limit							\
+     = (void *)(((unsigned long)__builtin_frame_address(0)		\
+ 		- TOTAL_STACK_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 = ALT_STACK_SIZE;					\
+ 									\
+     /* Protect our guard page and set our altstack.  */			\
+     if (mprotect (stack_base + ALT_STACK_SIZE,				\
+ 		  GUARD_AREA_SIZE, PROT_READ))				\
+       abort ();								\
+     if (sigaltstack (&st, NULL))					\
+       abort ();								\
+   }									\
+ while (0)
+ 
+ #define SEGV_ACTION(_exception)						\
+ do									\
+ {									\
+   void *addr;								\
+   MAKE_THROW_FRAME(_exception, addr);					\
+ 									\
+   /* If the current stack pointer is within				\
+      getpagesize()*4+GUARD_AREA_SIZE of the faulting address we've got	\
+      a stack overflow.  This assumes that the return address of the	\
+      signal handler is within the first four pages of the		\
+      altstack.  */							\
+   if ((unsigned)((char *)addr						\
+ 		 - (char *)__builtin_frame_address (0))			\
+       < getpagesize () * 4 + GUARD_AREA_SIZE)				\
+     _exception = new java::lang::StackOverflowError ();			\
+   else									\
+     _exception = new java::lang::NullPointerException ();		\
+ }									\
+ while (0)
+ 
Index: include/x86_64-signal.h
===================================================================
RCS file: /cvs/gcc/gcc/libjava/include/x86_64-signal.h,v
retrieving revision 1.6
diff -p -2 -c -r1.6 x86_64-signal.h
*** include/x86_64-signal.h	8 Feb 2004 17:35:51 -0000	1.6
--- include/x86_64-signal.h	20 Oct 2004 14:33:49 -0000
*************** details.  */
*** 18,26 ****
  #include <signal.h>
  #include <sys/syscall.h>
  
  #define HANDLE_SEGV 1
  
  #define SIGNAL_HANDLER(_name)	\
! static void _Jv_##_name (int, siginfo_t *, void *_p)
  
  extern "C" 
--- 18,27 ----
  #include <signal.h>
  #include <sys/syscall.h>
+ #include <overflowsupp.h>
  
  #define HANDLE_SEGV 1
  
  #define SIGNAL_HANDLER(_name)	\
! static void _Jv_##_name (int, siginfo_t *_si, void *_p)
  
  extern "C" 
*************** extern "C" 
*** 35,39 ****
  }
  
! #define MAKE_THROW_FRAME(_exception)					     \
  do									     \
  {									     \
--- 36,40 ----
  }
  
! #define MAKE_THROW_FRAME(_exception, ADDR)				     \
  do									     \
  {									     \
*************** do									     \
*** 44,47 ****
--- 45,49 ----
    volatile struct sigcontext *_sc = (struct sigcontext *) &_uc->uc_mcontext; \
    _sc->rip += 2;							     \
+   ADDR = _si->si_addr;							     \
  }									     \
  while (0)
*************** do								\
*** 68,72 ****
      act.k_sa_sigaction = _Jv_catch_segv;			\
      sigemptyset (&act.k_sa_mask);				\
!     act.k_sa_flags = SA_SIGINFO|0x4000000;			\
      act.k_sa_restorer = restore_rt;				\
      syscall (SYS_rt_sigaction, SIGSEGV, &act, NULL, _NSIG / 8);	\
--- 70,74 ----
      act.k_sa_sigaction = _Jv_catch_segv;			\
      sigemptyset (&act.k_sa_mask);				\
!     act.k_sa_flags = SA_SIGINFO|0x4000000|SA_ONSTACK;		\
      act.k_sa_restorer = restore_rt;				\
      syscall (SYS_rt_sigaction, SIGSEGV, &act, NULL, _NSIG / 8);	\
*************** do								\
*** 74,83 ****
  while (0)  
  
! /* We use syscall(SYS_rt_sigaction) in INIT_SEGV instead of
!  * sigaction() because on some systems the pthreads wrappers for
!  * signal handlers are not compiled with unwind information, so it's
!  * not possible to unwind through them.  This is a problem that will
!  * go away if all systems ever have pthreads libraries that are
!  * compiled with unwind info.  */
  
  #endif /* JAVA_SIGNAL_H */
--- 76,86 ----
  while (0)  
  
! /* You might wonder why we use syscall(SYS_rt_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 throw.  This trick will only work when we are
!  * called _directly_ by the kernel, because linuxthreads wraps signal
!  * handlers: the sigcontext passed is a copy of the sigcontext we want
!  * to modify.  */
  
  #endif /* JAVA_SIGNAL_H */


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]