From nobody Mon Sep 17 00:00:00 2001 From: Pavel Afremov Date: Wed, 19 Jul 2006 17:33:59 +0400 Subject: [PATCH] This patch adds support of Stack Overflow Error (SOE) for JIT mode into VM. It's implementation for both Windows and Linux ia32 platforms based on protected page on the stack. There are two main schemes of SOE processing here: 1) If current frame is unwindable (usual java code) signal handler or vectored exception handler throw usual java exception. 2) If current frame is nonunwindable (JNI native code for example) VM sets exceptions for the current thread and continues its execution from interrupted. place. A code which works in nonunwindable mode should periodically check that no exception is raised. This implementation discover some problems in current VM implementation (including JIT): 1) Some parts of VM which use long recursion calls in nonunwindable mode (JIT compiler, verifier) don’t check that SOE is happened. I implemented check that there are 256 Kbytes of free stack, before start compilation. But I’m afraid it can be not enough sometimes. 2) If SOE throws during the first two command of compiled method, function "unwind" of the JIT can’t unwind frame correctly sometimes. --- vm/vmcore/include/environment.h | 1 vm/vmcore/include/exceptions.h | 8 + vm/vmcore/include/vm_threads.h | 6 + vm/vmcore/src/class_support/Environment.cpp | 1 vm/vmcore/src/exception/exceptions.cpp | 13 + vm/vmcore/src/init/vm_init.cpp | 4 vm/vmcore/src/jit/compile.cpp | 2 vm/vmcore/src/thread/thread_generic.cpp | 7 + vm/vmcore/src/util/linux/signals_ia32.cpp | 195 +++++++++++++++++++- .../src/util/win/ia32/nt_exception_filter.cpp | 180 +++++++++++++++++++++ 10 files changed, 404 insertions(+), 13 deletions(-) mode change 100644 => 100755 vm/vmcore/include/exceptions.h mode change 100644 => 100755 vm/vmcore/src/jit/compile.cpp mode change 100644 => 100755 vm/vmcore/src/util/win/ia32/nt_exception_filter.cpp 5eb121301b3747ca3715cde7ae18ad56b3aefeab diff --git a/vm/vmcore/include/environment.h b/vm/vmcore/include/environment.h index 03ba4d6..5ae7b0d 100644 --- a/vm/vmcore/include/environment.h +++ b/vm/vmcore/include/environment.h @@ -123,6 +123,7 @@ struct Global_Env { Class* java_lang_Error_Class; Class* java_lang_ExceptionInInitializerError_Class; Class* java_lang_NullPointerException_Class; + Class* java_lang_StackOverflowError_Class; Class* java_lang_ClassNotFoundException_Class; Class* java_lang_NoClassDefFoundError_Class; diff --git a/vm/vmcore/include/exceptions.h b/vm/vmcore/include/exceptions.h old mode 100644 new mode 100755 index 490f3b3..ec0a95d --- a/vm/vmcore/include/exceptions.h +++ b/vm/vmcore/include/exceptions.h @@ -206,6 +206,9 @@ void exn_athrow(ManagedObject* exn_obj, // Mutates the regs value, which should be used to "resume" the managed code. void exn_athrow_regs(Registers* regs, Class_Handle exn_class); +// exception catch callback to restore stack after Stack Overflow Error +void exception_catch_callback(); + //**** Runtime exception support // rth_throw takes an exception and throws it @@ -263,4 +266,9 @@ ManagedObject* get_current_thread_except VMEXPORT // temporary solution for interpreter unplug void __stdcall set_current_thread_exception(ManagedObject* obj); +void set_guard_stack(); +void init_stack_info(); +VMEXPORT size_t get_available_stack_size(); +VMEXPORT bool check_available_stack_size(size_t required_size); + #endif // _EXCEPTIONS_H_ diff --git a/vm/vmcore/include/vm_threads.h b/vm/vmcore/include/vm_threads.h index e7b87dd..bdbeffe 100644 --- a/vm/vmcore/include/vm_threads.h +++ b/vm/vmcore/include/vm_threads.h @@ -101,6 +101,12 @@ public: // TODO: Needs to be replaced with jobject! volatile ManagedObject* p_exception_object; + // flag which indicate that guard page on the stak should be restored + bool restore_guard_page; + + // thread stack address + void* stack_addr; + // Should JVMTI code be notified about exception in p_exception_object bool ti_exception_callback_pending; diff --git a/vm/vmcore/src/class_support/Environment.cpp b/vm/vmcore/src/class_support/Environment.cpp index 7980dbf..79000a2 100644 --- a/vm/vmcore/src/class_support/Environment.cpp +++ b/vm/vmcore/src/class_support/Environment.cpp @@ -116,6 +116,7 @@ #endif // !_IPF_ java_lang_Error_Class = NULL; java_lang_ExceptionInInitializerError_Class = NULL; java_lang_NullPointerException_Class = NULL; + java_lang_StackOverflowError_Class = NULL; java_lang_ArrayIndexOutOfBoundsException_Class = NULL; java_lang_ArrayStoreException_Class = NULL; java_lang_ArithmeticException_Class = NULL; diff --git a/vm/vmcore/src/exception/exceptions.cpp b/vm/vmcore/src/exception/exceptions.cpp index 800367c..f37a0af 100644 --- a/vm/vmcore/src/exception/exceptions.cpp +++ b/vm/vmcore/src/exception/exceptions.cpp @@ -510,6 +510,10 @@ void clear_current_thread_exception() // gc safe operation. assert(!tmn_is_suspend_enabled()); p_TLS_vmthread->p_exception_object = NULL; + + if (p_TLS_vmthread->restore_guard_page) { + set_guard_stack(); + } } //clear_current_thread_exception void rethrow_current_thread_exception() @@ -1079,6 +1083,15 @@ #ifndef _IPF_ #endif } //exn_athrow_regs +////////////////////////////////////////////////////////////////////////// +// Exception Catch support + +// exception catch callback to restore stack after Stack Overflow Error +void exception_catch_callback() { + if (p_TLS_vmthread->restore_guard_page) { + set_guard_stack(); + } +} ////////////////////////////////////////////////////////////////////////// // Runtime Exception Support diff --git a/vm/vmcore/src/init/vm_init.cpp b/vm/vmcore/src/init/vm_init.cpp index 4801123..c21befd 100644 --- a/vm/vmcore/src/init/vm_init.cpp +++ b/vm/vmcore/src/init/vm_init.cpp @@ -282,6 +282,8 @@ #endif // POINTER64 preload_class(env, "java/lang/ClassNotFoundException"); env->java_lang_NullPointerException_Class = preload_class(env, env->JavaLangNullPointerException_String); + env->java_lang_StackOverflowError_Class = + preload_class(env, "java/lang/StackOverflowError"); env->java_lang_ArrayIndexOutOfBoundsException_Class = preload_class(env, env->JavaLangArrayIndexOutOfBoundsException_String); env->java_lang_ArrayStoreException_Class = @@ -295,6 +297,8 @@ #endif // POINTER64 env->java_lang_OutOfMemoryError = oh_allocate_global_handle(); tmn_suspend_disable(); + // precompile StackOverflowError + class_alloc_new_object_and_run_default_constructor(env->java_lang_StackOverflowError_Class); env->java_lang_OutOfMemoryError->object = class_alloc_new_object(env->java_lang_OutOfMemoryError_Class); tmn_suspend_enable(); diff --git a/vm/vmcore/src/jit/compile.cpp b/vm/vmcore/src/jit/compile.cpp old mode 100644 new mode 100755 index e19465a..3b62810 --- a/vm/vmcore/src/jit/compile.cpp +++ b/vm/vmcore/src/jit/compile.cpp @@ -742,6 +742,8 @@ static JIT_Result compile_do_compilation return JIT_SUCCESS; } else if (method->get_state()==Method::ST_NotCompiled && exn_raised()) { return JIT_FAILURE; + } else if(!check_available_stack_size(256*1024)) { + return JIT_FAILURE; } JIT_Result res = JIT_FAILURE; diff --git a/vm/vmcore/src/thread/thread_generic.cpp b/vm/vmcore/src/thread/thread_generic.cpp index 1e938a1..80ba734 100644 --- a/vm/vmcore/src/thread/thread_generic.cpp +++ b/vm/vmcore/src/thread/thread_generic.cpp @@ -364,6 +364,13 @@ static void process_uncaught_exception(V //JNI implementation void __cdecl call_the_run_method( void * p_args ) { +#ifdef _IA32_ + init_stack_info(); +#ifdef PLATFORM_POSIX + set_guard_stack(); +#endif // PLATFORM_POSIX +#endif // _IA32_ + //when a new thread created, gc is disabled, because VM thread structure //was set 0 initially, then gc_enabled_status kept 0 till now VM_thread *p_vm_thread=(VM_thread *)(((void **)p_args)[0]); diff --git a/vm/vmcore/src/util/linux/signals_ia32.cpp b/vm/vmcore/src/util/linux/signals_ia32.cpp index 2734157..a9099a7 100644 --- a/vm/vmcore/src/util/linux/signals_ia32.cpp +++ b/vm/vmcore/src/util/linux/signals_ia32.cpp @@ -26,6 +26,7 @@ #define LOG_DOMAIN "port.old" #include "cxxlog.h" #include "platform.h" +#include #include #include #include @@ -102,7 +103,17 @@ static void linux_regs_to_sigcontext(uco uc->uc_mcontext.gregs[REG_ESP] = regs->esp; } -static bool linux_throw_from_sigcontext(ucontext_t *uc, Class* exc_clss) +static void throw_from_sigcontext(ucontext_t *uc, Class* exc_clss) +{ + Registers regs; + linux_sigcontext_to_regs(®s, uc); + + exn_athrow_regs(®s, exc_clss); + + linux_regs_to_sigcontext(uc, ®s); +} + +static bool java_throw_from_sigcontext(ucontext_t *uc, Class* exc_clss) { ASSERT_NO_INTERPRETER; unsigned *eip = (unsigned *) uc->uc_mcontext.gregs[REG_EIP]; @@ -111,12 +122,7 @@ static bool linux_throw_from_sigcontext( return false; } - Registers regs; - linux_sigcontext_to_regs(®s, uc); - - exn_athrow_regs(®s, exc_clss); - - linux_regs_to_sigcontext(uc, ®s); + throw_from_sigcontext(uc, exc_clss); return true; } @@ -185,6 +191,142 @@ void print_native_stack (unsigned *ebp) addr2line(buf); } +/* + * Information about stack + */ +inline void* find_stack_addr() { + int err; + void* stack_addr; + pthread_attr_t pthread_attr; + + pthread_t thread = pthread_self(); + err = pthread_getattr_np(thread, &pthread_attr); + err = pthread_attr_getstackaddr(&pthread_attr, &stack_addr); + pthread_attr_destroy(&pthread_attr); + return stack_addr; +} + +inline size_t find_stack_size() { + int err; + size_t stack_size; + pthread_attr_t pthread_attr; + + pthread_attr_init(&pthread_attr); + err = pthread_attr_getstacksize(&pthread_attr, &stack_size); + pthread_attr_destroy(&pthread_attr); + return stack_size; +} + +inline size_t find_guard_stack_size() { + return 64*1024; +} + +inline size_t find_guard_page_size() { + int err; + size_t guard_size; + pthread_attr_t pthread_attr; + + pthread_attr_init(&pthread_attr); + err = pthread_attr_getguardsize(&pthread_attr, &guard_size); + pthread_attr_destroy(&pthread_attr); + return guard_size; +} + +static size_t common_stack_size; +static size_t common_guard_stack_size; +static size_t common_guard_page_size; + +inline void* get_stack_addr() { + return p_TLS_vmthread->stack_addr; +} + +inline size_t get_stack_size() { + return common_stack_size; +} + +inline size_t get_guard_stack_size() { + return common_guard_stack_size; +} + +inline size_t get_guard_page_size() { + return common_guard_page_size; +} + + +void init_stack_info() { + p_TLS_vmthread->stack_addr = find_stack_addr(); + common_stack_size = find_stack_size(); + common_guard_stack_size = find_guard_stack_size(); + common_guard_page_size =find_guard_page_size(); +} + +void set_guard_stack() { + int err; + char* stack_addr = (char*) get_stack_addr(); + size_t stack_size = get_stack_size(); + size_t guard_stack_size = get_guard_stack_size(); + size_t guard_page_size = get_guard_page_size(); + + err = mprotect(stack_addr - stack_size + guard_page_size + guard_stack_size, + guard_page_size, PROT_NONE); + + stack_t sigalt; + sigalt.ss_sp = stack_addr - stack_size + guard_page_size; + sigalt.ss_flags = SS_ONSTACK; + sigalt.ss_size = guard_stack_size; + + err = sigaltstack (&sigalt, NULL); +} + +size_t get_available_stack_size() { + char* stack_adrr = (char*) get_stack_addr(); + size_t used_stack_size = stack_adrr - ((char*)&stack_adrr); + size_t available_stack_size = + get_stack_size() - used_stack_size + - get_guard_page_size() - get_guard_stack_size(); + return available_stack_size; +} + +bool check_available_stack_size(size_t required_size) { + if (get_available_stack_size() < required_size) { + exn_raise_by_name("java/lang/StackOverflowError"); + return false; + } else { + return true; + } +} + +void remove_guard_stack() { + int err; + char* stack_addr = (char*) get_stack_addr(); + size_t stack_size = get_stack_size(); + size_t guard_stack_size = get_guard_stack_size(); + size_t guard_page_size = get_guard_page_size(); + + err = mprotect(stack_addr - stack_size + guard_page_size + guard_stack_size, + guard_page_size, PROT_READ | PROT_WRITE); + + stack_t sigalt; + sigalt.ss_sp = stack_addr - stack_size + guard_page_size; + sigalt.ss_flags = SS_DISABLE; + sigalt.ss_size = guard_stack_size; + + err = sigaltstack (&sigalt, NULL); +} + +bool check_stack_overflow(siginfo_t *info, ucontext_t *uc) { + char* stack_addr = (char*) get_stack_addr(); + size_t stack_size = get_stack_size(); + size_t guard_stack_size = get_guard_stack_size(); + size_t guard_page_size = get_guard_page_size(); + + char* guard_page_begin = stack_addr - stack_size + guard_page_size + guard_stack_size; + char* guard_page_end = guard_page_begin + guard_page_size; + char* fault_addr = (char*)(info->si_addr); + //char* esp_value = (char*)(uc->uc_mcontext.gregs[REG_ESP]); + + return((guard_page_begin <= fault_addr) && (fault_addr <= guard_page_end)); +} /* * We find the true signal stack frame set-up by kernel,which is located @@ -193,17 +335,48 @@ void print_native_stack (unsigned *ebp) * returned, application can continue its execution in Java exception handler. */ +void stack_overflow_handler(int signum, siginfo_t* UNREF info, void* context) +{ + ucontext_t *uc = (ucontext_t *)context; + Global_Env *env = VM_Global_State::loader_env; + + if (java_throw_from_sigcontext( + uc, env->java_lang_StackOverflowError_Class)) { + return; + } else { + if (is_unwindable()) { + if (tmn_is_suspend_enabled()) { + tmn_suspend_disable(); + } + throw_from_sigcontext( + uc, env->java_lang_StackOverflowError_Class); + } else { + remove_guard_stack(); + exn_raise_by_name("java/lang/StackOverflowError"); + p_TLS_vmthread->restore_guard_page = true; + } + } +} + void null_java_reference_handler(int signum, siginfo_t* UNREF info, void* context) { ucontext_t *uc = (ucontext_t *)context; Global_Env *env = VM_Global_State::loader_env; + if (check_stack_overflow(info, uc)) { + stack_overflow_handler(signum, info, context); + return; + } + if (env->shutting_down != 0) { fprintf(stderr, "null_java_reference_handler(): called in shutdown stage\n"); } else if (!interpreter_enabled()) { - if (linux_throw_from_sigcontext( + if (java_throw_from_sigcontext( uc, env->java_lang_NullPointerException_Class)) { return; + } else { + fprintf(stderr, "SIGSEGV in VM code.\n"); + return; } } @@ -220,7 +393,7 @@ void null_java_divide_by_zero_handler(in if (env->shutting_down != 0) { fprintf(stderr, "null_java_divide_by_zero_handler(): called in shutdown stage\n"); } else if (!interpreter_enabled()) { - if (linux_throw_from_sigcontext( + if (java_throw_from_sigcontext( uc, env->java_lang_ArithmeticException_Class)) { return; } @@ -305,7 +478,7 @@ #define SC_SEARCH_WIDTH 3 ebp = (uint32 *)ebp[0]; } - if (sc_nest < 0) { + if (sc_nest < 0) { printf("cannot locate sigcontext.\n"); printf("Please add or remove any irrelevant statement(e.g. add a null printf) in VM source code, then rebuild it. If problem remains, please submit a bug report. Thank you very much\n"); exit(1); @@ -376,7 +549,7 @@ void initialize_signals() sigaction(SIGUSR2, &sa, NULL); sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; sa.sa_sigaction = &null_java_reference_handler; sigaction(SIGSEGV, &sa, NULL); diff --git a/vm/vmcore/src/util/win/ia32/nt_exception_filter.cpp b/vm/vmcore/src/util/win/ia32/nt_exception_filter.cpp old mode 100644 new mode 100755 index 148d3a0..fdf7c95 --- a/vm/vmcore/src/util/win/ia32/nt_exception_filter.cpp +++ b/vm/vmcore/src/util/win/ia32/nt_exception_filter.cpp @@ -21,6 +21,7 @@ #include "cxxlog.h" #include "method_lookup.h" +#include "m2n.h" #include "open/thread.h" #include "Environment.h" #include "exceptions.h" @@ -148,6 +149,141 @@ static void print_callstack(LPEXCEPTION_ fflush(stderr); } +/* + * Information about stack + */ +inline void* find_stack_addr() { + void* stack_addr; + size_t reg_size; + MEMORY_BASIC_INFORMATION memory_information; + + VirtualQuery(&memory_information, &memory_information, sizeof(memory_information)); + reg_size = memory_information.RegionSize; + stack_addr =((char*) memory_information.BaseAddress) + reg_size; + + return stack_addr; +} + +inline size_t find_stack_size() { + void* stack_addr; + size_t stack_size; + size_t reg_size; + MEMORY_BASIC_INFORMATION memory_information; + + VirtualQuery(&memory_information, &memory_information, sizeof(memory_information)); + reg_size = memory_information.RegionSize; + stack_addr = ((char*) memory_information.BaseAddress) + reg_size; + stack_size = ((char*) stack_addr) - ((char*) memory_information.AllocationBase); + + return stack_size; +} + +inline size_t find_guard_page_size() { + size_t guard_size; + SYSTEM_INFO system_info; + + GetSystemInfo(&system_info); + guard_size = system_info.dwPageSize; + + return guard_size; +} + +inline size_t find_guard_stack_size() { + // guaerded stack size on windows can be equals one page size only :( + return find_guard_page_size(); +} + +static size_t common_stack_size; +static size_t common_guard_stack_size; +static size_t common_guard_page_size; + +inline void* get_stack_addr() { + return p_TLS_vmthread->stack_addr; +} + +inline size_t get_stack_size() { + return common_stack_size; +} + +inline size_t get_guard_stack_size() { + return common_guard_stack_size; +} + +inline size_t get_guard_page_size() { + return common_guard_page_size; +} + + +void init_stack_info() { + p_TLS_vmthread->stack_addr = find_stack_addr(); + common_stack_size = find_stack_size(); + common_guard_stack_size = find_guard_stack_size(); + common_guard_page_size =find_guard_page_size(); +} + +void set_guard_stack() { + void* stack_addr = get_stack_addr(); + size_t stack_size = get_stack_size(); + size_t page_size = get_guard_page_size(); + + if (!VirtualFree((char*)stack_addr - stack_size + page_size, + page_size, MEM_DECOMMIT)) { + // should be successful always + assert(0); + } + + DWORD oldProtect; + + if (!VirtualProtect((char*)stack_addr - stack_size + page_size + page_size, + page_size, PAGE_GUARD | PAGE_READWRITE, &oldProtect)) { + // should be successful always + assert(0); + } + + p_TLS_vmthread->restore_guard_page = false; +} + +size_t get_available_stack_size() { + char* stack_adrr = (char*) get_stack_addr(); + size_t used_stack_size = ((size_t)stack_adrr) - ((size_t)(&stack_adrr)); + size_t available_stack_size = + get_stack_size() - used_stack_size + - get_guard_page_size() - get_guard_stack_size(); + return available_stack_size; +} + +bool check_available_stack_size(size_t required_size) { + if (get_available_stack_size() < required_size) { + exn_raise_by_name("java/lang/StackOverflowError"); + return false; + } else { + return true; + } +} + +// exception catch callback to restore stack after Stack Overflow Error +static void __cdecl exception_catch_callback_wrapper(){ + exception_catch_callback(); +} + +static void __declspec(naked) __stdcall naked_exception_catch_callback() { + __asm { + push ebp + mov ebp, esp + push eax + push ebx + push ecx + push edx + call exception_catch_callback_wrapper + pop edx + pop ecx + pop ebx + pop eax + leave + ret + } +} + LONG NTAPI vectored_exception_handler(LPEXCEPTION_POINTERS nt_exception) { DWORD code = nt_exception->ExceptionRecord->ExceptionCode; @@ -167,9 +303,22 @@ LONG NTAPI vectored_exception_handler(LP // method else crash handler or default handler is executed, this means that // it was thrown by VM C/C++ code. if ((code == STATUS_ACCESS_VIOLATION - || code == STATUS_INTEGER_DIVIDE_BY_ZERO) - && vm_identify_eip((void *)context->Eip) == VM_TYPE_JAVA) + || code == STATUS_INTEGER_DIVIDE_BY_ZERO + || code == STATUS_STACK_OVERFLOW) + && vm_identify_eip((void *)context->Eip) == VM_TYPE_JAVA) { run_default_handler = false; + } else if (code == STATUS_STACK_OVERFLOW) { + if (is_unwindable()) { + if (tmn_is_suspend_enabled()) { + tmn_suspend_disable(); + } + run_default_handler = false; + } else { + exn_raise_by_name("java/lang/StackOverflowError"); + p_TLS_vmthread->restore_guard_page = true; + return EXCEPTION_CONTINUE_EXECUTION; + } + } } else { if (VM_Global_State::loader_env->shutting_down > 1) { @@ -233,6 +382,17 @@ LONG NTAPI vectored_exception_handler(LP switch(nt_exception->ExceptionRecord->ExceptionCode) { + case STATUS_STACK_OVERFLOW: + // stack overflow exception -- see ...\vc\include\winnt.h + { + TRACE2("signals", "StackOverflowError detected at " + << (void *) context->Eip << " on the stack at " + << (void *) context->Esp); + // Lazy exception object creation + exc_clss = env->java_lang_StackOverflowError_Class; + p_TLS_vmthread->restore_guard_page = true; + } + break; case STATUS_ACCESS_VIOLATION: // null pointer exception -- see ...\vc\include\winnt.h { @@ -256,10 +416,26 @@ LONG NTAPI vectored_exception_handler(LP } Registers regs; + nt_to_vm_context(context, ®s); + uint32 exception_esp = regs.esp; + DebugUtilsTI* ti = VM_Global_State::loader_env->TI; + exn_athrow_regs(®s, exc_clss); + if (exception_esp < regs.esp) { + if (p_TLS_vmthread->restore_guard_page) { + regs.esp = regs.esp - 4; + *((uint32*) regs.esp) = regs.eip; + regs.eip = ((uint32)naked_exception_catch_callback); + } + } else { + // should be unreachable code + //jvmti_exception_catch_callback(®s); + assert(0); + } + vm_to_nt_context(®s, context); return EXCEPTION_CONTINUE_EXECUTION; -- 1.3.3