ART异常处理机制(2)
来源:互联网 发布:管家婆进销存软件 价格 编辑:程序博客网 时间:2024/06/07 03:02
在本篇介绍 StackOverflowError 在 ART种的实现。本篇基础:ART异常处理机制(1) - SIGSEGV信号的拦截和处理
在 ART异常处理机制1 中,我们已经知道了 ART会注册信号处理函数,优先尝试处理 SIGSEGV信号。ART中总共有 4 种 handler 享有优先处理 SIGSEGV信号的权利。而StackOverflowHandler排在第二位,由于SuspendHandler是 Disable的,所以实际上第一个处理 SIGSEGV的是 StackOverflowHandler。今天我们主要分析下它的实现以及 StackOverflowError的检测和抛出。
先看下StackOverflowHandler实现的注释:
// Stack overflow fault handler.//// This checks that the fault address is equal to the current stack pointer// minus the overflow region size (8K typically). The instruction sequence// that generates this signal is://// sub r12,sp,#8192// ldr.w r12,[r12,#0]//// The second instruction will fault if r12 is inside the protected region// on the stack.//// If we determine this is a stack overflow we need to move the stack pointer// to the overflow region below the protected region.
说的实际就是 stack overflow检测方法,意思是如果 sp-8k在 stack上的 protected region 空间内,那么在执行第二条指令 ldr.w r12, [r12, #0] 时,就会产生一个 SIGSEGV信号。这个8k 取决于 stack reserved page的大小,也有可能是 16k,说到这里,我们还是需要一个清晰的 java stack的布局,才能更明了的分析这个话题了。下图是一个 ART thread的布局:
图的说明:
- ART中的thread也是通过 pthread创建的,所以 pthread 开头的标注都是pthread内部的数据成员
- 以ART开头的标注都是 ART内部/ART thread内部的数据成员
- 在低地址的前两个page,都是 ---p权限,就是用来做 stack overflow检测的,分别是 pthread和ART创建的 guard page
- 接下来黄色部分描述的 8KB 是为了抛出 StackOverflow Exception 预留的栈空间,因为如果不预留的话,在overflow的时候,就没有栈空间用来执行抛出 StackOverflow 异常了
- 再下面的绿色部分,是线程真正能够使用的栈空间
- 最下面的部分,用来存储 pthread_internal_t 来描述当前 pthread,并有个 alignment保证下面的数据 16 byte对齐
7f8000d000-7f8000e000 ---p 00000000 00:00 0 [anon:thread stack guard page]7f8000e000-7f8000f000 ---p 00000000 00:00 07f8000f000-7f8010a000 rw-p 00000000 00:00 0 [stack:7496]
现在就比较明了了,在栈的末尾,先有 8KB的 protect page,都是 ---p权限,所以任何的尝试从 protected page 读取或者写入数据的指令,都会触发 segement fault,从而产生一个 SIGSEGV 信号。一般情况下,线程运行的过程中,SP都是在图中的浅绿色部分栈空间中,认为栈空间还充足。但只要 SP超出浅绿色部分栈空间,剩余的栈空间就不到 8+4+4=16KB了。此时认为当前线程发生了 stack overflow问题;那么此时 SP- 8192(reserved) 计算得出的地址,就会在 protected page范围内,此时再执行 ldr.w r12, [r12, #0] 就会产生一个SIGSEGV信号,且 fault_addr = SP-8192。而在没有发生 overflow的情况下,SP还在浅绿色部分栈空间,SP-8192的地址肯定是可以被访问的,就不会发生 segement fault。
这样,我们在StackOverflowHander的Action中,只需要检查当前这个 SIGSEGV错误中的 fault_addr是否是等于 SP - 8192,如果相等,这说明当前线程发生了 stack overflow错误,需要抛出StackOverflow异常。
需要指明的是,这样的检测代码都在一个java 函数对应的 generated code的最开始位置,也即在跳转到一个Java 函数后,需要先检查是否发生了 overflow,因为接下来的代码中很快就会减小 SP来使用栈空间。比如:
34: void sun.util.logging.PlatformLogger.warning(java.lang.String, java.lang.Object[]) (dex_method_idx=26617) DEX CODE: 0x0000: 5420 4b27 | iget-object v0, v2, Lsun/util/logging/PlatformLogger$LoggerProxy; sun.util.logging.PlatformLogger.loggerProxy // field@10059 0x0002: 6201 3c27 | sget-object v1, Lsun/util/logging/PlatformLogger$Level; sun.util.logging.PlatformLogger$Level.WARNING // field@10044 0x0004: 6e40 d267 1043 | invoke-virtual {v0, v1, v3, v4}, void sun.util.logging.PlatformLogger$LoggerProxy.doLog(sun.util.logging.PlatformLogger$Level, java.lang.String, java.lang.Object[]) // method@26578 CODE: (code_offset=0x0094ffb4 size_offset=0x0094ffb0 size=144)... 0x0094ffb4: d1400bf0 sub x16, sp, #0x2000 (8192) 0x0094ffb8: b940021f ldr wzr, [x16] StackMap [native_pc=0x94ffbc] (dex_pc=0x0, native_pc_offset=0x8, dex_register_map_offset=0xffffffff, inline_info_offset=0xffffffff, register_mask=0x0, stack_mask=0b0000000000000000) 0x0094ffbc: f8190fe0 str x0, [sp, #-112]! 0x0094ffc0: a90457f4 stp x20, x21, [sp, #64] 0x0094ffc4: a9055ff6 stp x22, x23, [sp, #80] 0x0094ffc8: a9067bf8 stp x24, lr, [sp, #96] ....
bool StackOverflowHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIBUTE_UNUSED, void* context) { struct ucontext* uc = reinterpret_cast<struct ucontext*>(context); struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext); uintptr_t sp = sc->arm_sp; uintptr_t fault_addr = sc->fault_address; uintptr_t overflow_addr = sp - GetStackOverflowReservedBytes(kArm); // Check that the fault address is the value expected for a stack overflow. if (fault_addr != overflow_addr) { VLOG(signals) << "Not a stack overflow"; return false; } VLOG(signals) << "Stack overflow found"; sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow); // The kernel will now return to the address in sc->arm_pc. return true;}
在这里发现 fault addr 匹配 SP-Reserved 后,就会跳转到 art_quick_throw_stack_overflow 去抛出异常: /* * Called by managed code to create and deliver a StackOverflowError. */NO_ARG_RUNTIME_EXCEPTION art_quick_throw_stack_overflow, artThrowStackOverflowFromCode
看下这个宏:.macro NO_ARG_RUNTIME_EXCEPTION c_name, cxx_name .extern \cxx_nameENTRY \c_name SETUP_SAVE_ALL_CALLEE_SAVES_FRAME r0 @ save all registers as basis for long jump context mov r0, r9 @ pass Thread::Current bl \cxx_name @ \cxx_name(Thread*)END \c_name.endm
展开后应该是: .extern artThrowStackOverflowFromCodeENTRY art_quick_throw_stack_overflow SETUP_SAVE_ALL_CALLEE_SAVES_FRAME r0 @ save all registers as basis for long jump context mov r0, r9 @ pass Thread::Current bl artThrowStackOverflowFromCode @ \cxx_name(Thread*)END art_quick_throw_stack_overflow
所以最终是跳转到 artThrowStackOverflowFromCode函数,进行异常的抛出:extern "C" NO_RETURN void artThrowStackOverflowFromCode(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { ScopedQuickEntrypointChecks sqec(self); ThrowStackOverflowError(self); self->QuickDeliverException();}
ThrowStackOverflowError()函数,会创建一个 StackOverflowError类对象,并设置好 error message,stack trace等信息,然后使用 self->SetException(e),把这个Throwable对象设置到当前线程的 tlsPtr_.exception 成员中,以便后续通过self->GetException()进行获取。void ThrowStackOverflowError(Thread* self) { self->SetStackEndForStackOverflow(); // Allow space on the stack for constructor to execute. JNIEnvExt* env = self->GetJniEnv(); std::string msg("stack size "); msg += PrettySize(self->GetStackSize()); std::string error_msg; // Allocate an uninitialized object. ScopedLocalRef<jobject> exc(env, env->AllocObject(WellKnownClasses::java_lang_StackOverflowError)); if (exc.get() != nullptr) { ScopedLocalRef<jstring> s(env, env->NewStringUTF(msg.c_str())); if (s.get() != nullptr) { env->SetObjectField(exc.get(), WellKnownClasses::java_lang_Throwable_detailMessage, s.get()); // cause. env->SetObjectField(exc.get(), WellKnownClasses::java_lang_Throwable_cause, exc.get()); // suppressedExceptions. ScopedLocalRef<jobject> emptylist(env, env->GetStaticObjectField( WellKnownClasses::java_util_Collections, WellKnownClasses::java_util_Collections_EMPTY_LIST)); CHECK(emptylist.get() != nullptr); env->SetObjectField(exc.get(), WellKnownClasses::java_lang_Throwable_suppressedExceptions, emptylist.get()); // stackState is set as result of fillInStackTrace. fillInStackTrace calls // nativeFillInStackTrace. ScopedLocalRef<jobject> stack_state_val(env, nullptr); { ScopedObjectAccessUnchecked soa(env); stack_state_val.reset(soa.Self()->CreateInternalStackTrace<false>(soa)); } if (stack_state_val.get() != nullptr) { env->SetObjectField(exc.get(), WellKnownClasses::java_lang_Throwable_stackState, stack_state_val.get()); // stackTrace. ScopedLocalRef<jobject> stack_trace_elem(env, env->GetStaticObjectField( WellKnownClasses::libcore_util_EmptyArray, WellKnownClasses::libcore_util_EmptyArray_STACK_TRACE_ELEMENT)); env->SetObjectField(exc.get(), WellKnownClasses::java_lang_Throwable_stackTrace, stack_trace_elem.get()); } else { error_msg = "Could not create stack trace."; } // Throw the exception. self->SetException(self->DecodeJObject(exc.get())->AsThrowable()); } else { // Could not allocate a string object. error_msg = "Couldn't throw new StackOverflowError because JNI NewStringUTF failed."; } } else { error_msg = "Could not allocate StackOverflowError object."; } if (!error_msg.empty()) { LOG(WARNING) << error_msg; CHECK(self->IsExceptionPending()); } bool explicit_overflow_check = Runtime::Current()->ExplicitStackOverflowChecks(); self->ResetDefaultStackEnd(); // Return to default stack size. // And restore protection if implicit checks are on. if (!explicit_overflow_check) { self->ProtectStack(); }}
需要说明的是 SetStackEndForStackOverflow()函数设置 tlsPtr_.stack_end = tlsPtr_.stack_begin;表示正在处理 stack overflow,并且把 ART设置的那 4KB的protected region设置为可读写(这么做的原因是增加4KB的可使用栈空间,以满足 stack overflow抛出过程的使用)。下面一部分代码就是 error msg,stacktrace填充的逻辑。业务逻辑完成后,再次把 tlsPtr_.stack_end还原到 Reserved page之前的位置,并把那 4KB region重新protect 起来。
而在ThrowStackOverflowError函数之后的 QuickDeliverException函数的功能就是尝试从当前 thread 的 stack 上找到当前 Exception对应的 catch block,交给其去处理。
分析过程中的几个疑问
1.一个线程发生 StackOverflow,触发 segement fault时,这个线程的状态是怎样的?
答:解释不太好,先说下暂时的理解,待后续研究CPU异常处理机制。访问不可读的内存时,应该会产生page fault(此时应该在内核态了),在后续page fault处理流程中,发现该page fault符合 SIGSEGV信号的条件,然后给发生异常的线程发送 SIGSEGV信号。所以,触发 segment fault时,处于内核态。
2.我们知道产生的 SIGSEGV 会由ART处理,那么是哪个线程处理的?
答:从第一点的答案可以知道,内核会把 SIGSEGV信号发送给发生异常的那个线程处理,处理时机应该是CPU异常处理机制执行完成,从内核态切换到用户态的时候,检查到有pending signal,然后调用信号处理函数去处理这个信号。相当于发生异常的线程会调用 SigChain::Hander函数来处理 SIGSEGV信号。
3.检测 StackOverflow的位置除了上面了解的一种,还有哪些位置会检测 StackOverflow?
上面提到的这种是generated code中,在函数的入口位置(此时这个函数还没有开辟当前frame的栈空间),进行检测stack overflow。
那么在 Interpreter 模式,或者从quick模式切换到 Interpreter 模式时,显然没有 generated code了,我们自己设想的话,这个检测应该在切换的过程中 (还没有开始给准备执行的java 函数分配栈空间)完成。检查了一下代码,发现有这么些地方会检查 stack overflow问题:
- reflection.cc:InvokeWithVarArgs() / InvokeWithJValues() / InvokeVirtualOrInterfaceWithJValues() / InvokeVirtualOrInterfaceWithVarArgs() / InvokeMethod(),在这几个函数的入口位置都会进行 stack overflow检测
- interpreter.cc:EnterInterpreterFromInvoke() / EnterInterpreterFromEntryPoint() / ArtInterpreterToInterpreterBridge() 在这几个函数入口 也会检查
- art_method.cc:void ArtMethod::Invoke() 这个函数入口也会进行检查
总的来讲,原理就是在执行将要跳转到的函数的栈空间开辟之前,完成 stack overflow异常的检测。
Java stack overflow 异常说到这里。
- ART异常处理机制(2)
- ART异常处理机制(1)
- ART异常处理机制(4)
- ART异常处理机制(3)
- ART世界探险(10) - 异常处理
- struts 2 异常处理机制
- java异常处理机制 2
- Java异常处理机制(2)
- PHP 5.0异常处理机制(2)
- 19-php的异常处理机制2
- java中的异常处理机制(2)
- C#异常处理机制
- 异常处理机制
- 异常处理机制
- C++异常处理机制
- 异常处理机制
- 异常处理机制【转】
- java异常处理机制
- android 多线程断点续传下载
- 购物车二级列表
- 深度学习---之pooling层的作用与缺陷
- Linux命令
- robotframework自动化系列:删除操作流程以及总结
- ART异常处理机制(2)
- hibernate框架的查询优化(二十三)
- Centos6.8 安装MySQL5.7
- strings.xml里字符串拼接、占位符和常用替换符号
- log4j
- ListView中嵌套(ListView)控件时item的点击事件不起作的问题解决方法
- 排序算法总结
- 【JDK1.8】JDK1.8集合源码阅读——TreeMap(一)
- 封装RxJava+Retrofit+OkHttp