赞
踩
熟悉Android开发的同学都知道,如果我们应用程序中发生了java层的崩溃,我们可以通过下面方式捕获,
- Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
- @Override
- public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
- Log.i("jin", "uncaughtException " + e.getMessage());
- // do some something
- }
- });
如果崩溃在了主线程,这么操作就不好用了,因为在发生java崩溃的时候,主线程会退出Looper的loop循环,所以想要主线程异常也能捕获,并且正常运行的话,我们需要这么操作。
1234567- Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
- @Override
- public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
- Log.i("jin", "uncaughtException " + e.getMessage());
- Looper.loop();
- }
- });
那么为什么我们可以通过这种方式拦截到java层崩溃呢?我们首先简单模拟一个崩溃,
- Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
- @Override
- public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
- Log.i("jin", "uncaughtException " + e.getMessage(), new Exception());
- Looper.loop();
- }
- });
-
- int i = 9 / 0;
-
- uncaughtException Unable to create application com.jin.DemoApplication: java.lang.ArithmeticException: divide by zero
- java.lang.Exception
- at com.jin$1.uncaughtException(DemoApplication.java:80)
- at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1073)
- at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1068)
- at java.lang.Thread.dispatchUncaughtException(Thread.java:2203)
-
通过堆栈,我们可以知道,出现的异常是ArithmeticException,原因是除数为0,并且调用的逻辑是从
Thread中的dispatchUncaughtException分发下来的,一直到我们自己复写的uncaughtException方法里面。
OK,我们再来模拟一下,
- Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
- @Override
- public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
- Log.i("jin", "uncaughtException " + e.getMessage(), new Exception());
- Looper.loop();
- }
- });
-
- try {
- int i = 9 / 0;
- } catch (Exception e) {
- Log.i("jin", "exception : " + e.getMessage(), new Exception());
- }
-
- exception : divide by zero
- java.lang.Exception
- at com.jin.DemoApplication.onCreate(DemoApplication.java:88)
- at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1193)
- at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6848)
- at android.app.ActivityThread.access$1400(ActivityThread.java:246)
- at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1955)
- at android.os.Handler.dispatchMessage(Handler.java:106)
- at android.os.Looper.loop(Looper.java:236)
- at android.app.ActivityThread.main(ActivityThread.java:7876)
- at java.lang.reflect.Method.invoke(Native Method)
- at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
- at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
-
这段堆栈,大家就更熟悉了,无非是我们的Exception,被try-catch住了,那你知道虚拟机层trcy-catch是怎么处理的吗?需要补充的是,对于一些Exception来说,trcy-catch住不是一个好的手段,我们需要找到问题原因并且修复它,而不是针对可能出现的问题,catch住就完了。
本文以Android9.0虚拟机为例,针对上面异常代码的处理流程,对虚拟机层探究一下。
2.1.1
对上面代码进行下面命令,得到上面代码的字节码
adb shell dexdump -d sdcard/app-debug.apk
- int i = 9 / 0;
- Log.i("jin", "exception : " ,new Exception())
-
- 0023: const/16 v0, #int 9 // #9
- 0025: div-int/lit8 v0, v0, #int 0 // #00
- 0027: new-instance v1, Ljava/lang/Exception; // type@0db2
- 0029: invoke-direct {v1}, Ljava/lang/Exception;.<init>:()V // method@fca9
- 002c: const-string v2, "jin" // string@6a64
- 002e: const-string v3, "exception" // string@5549
- 0030: invoke-static {v2, v3, v1}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I // method@0139
- 0033: return-void
我们可以看到int i = 9 / 0; 这段代码其实执行的就是div-int字节码。
在虚拟机中,针对div-int字节码的处理指令如下所示:
art/runtime/interpreter/interpreter_switch_impl.cc
- case Instruction::DIV_INT: {
- PREAMBLE();
- bool success = DoIntDivide(shadow_frame, inst->VRegA_23x(inst_data),
- shadow_frame.GetVReg(inst->VRegB_23x()),
- shadow_frame.GetVReg(inst->VRegC_23x()));
- POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
-
我们继续跟一下DoIntDivide方法,有4个参数,第一个参数ShadowFrame是栈帧,第二个参数是结果,第三个参数是被除数,第4个参数是除数,我们看逻辑中会对除数判断,如果divisor == 0,那么会执行ThrowArithmeticExceptionDivideByZero()方法。
art/runtime/interpreter/interpreter_common.h
- // Handles div-int, div-int/2addr, div-int/li16 and div-int/lit8 instructions.
- // Returns true on success, otherwise throws a java.lang.ArithmeticException and return false.
- static inline bool DoIntDivide(ShadowFrame& shadow_frame, size_t result_reg,
- int32_t dividend, int32_t divisor)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- constexpr int32_t kMinInt = std::numeric_limits<int32_t>::min();
- if (UNLIKELY(divisor == 0)) {
- ThrowArithmeticExceptionDivideByZero();
- return false;
- }
- if (UNLIKELY(dividend == kMinInt && divisor == -1)) {
- shadow_frame.SetVReg(result_reg, kMinInt);
- } else {
- shadow_frame.SetVReg(result_reg, dividend / divisor);
- }
- return true;
- }
我们接着分析下ThrowArithmeticExceptionDivideByZero()方法,看到代码以后,是不是有一种似曾相识的感觉?
哈哈,是的,我们在前面第一部分中java层打印出的堆栈异常,就是这里抛出的!!
art/runtime/common_throws.cc
- void ThrowArithmeticExceptionDivideByZero() {
- ThrowException("Ljava/lang/ArithmeticException;", nullptr, "divide by zero");
- }
我们看下ThrowException的实现,里面一顿处理,最后调用了下self->ThrowNewException(),通过虚拟机中线程Thread类,将异常抛出。
art/runtime/common_throws.cc
- static void ThrowException(const char* exception_descriptor,
- ObjPtr<mirror::Class> referrer,
- const char* fmt,
- va_list* args = nullptr)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- std::ostringstream msg;
- if (args != nullptr) {
- std::string vmsg;
- StringAppendV(&vmsg, fmt, *args);
- msg << vmsg;
- } else {
- msg << fmt;
- }
- AddReferrerLocation(msg, referrer);
- Thread* self = Thread::Current();
- self->ThrowNewException(exception_descriptor, msg.str().c_str());
- }
2.1.2
我们继续看下Thread类,看它是怎么抛出异常的,重点做了两个事情,一个是构造异常对象,另一个是SetException,
art/runtime/thread.cc
- void Thread::ThrowNewException(const char* exception_class_descriptor,
- const char* msg) {
- // Callers should either clear or call ThrowNewWrappedException.
- AssertNoPendingExceptionForNewException(msg);
- ThrowNewWrappedException(exception_class_descriptor, msg);
- }
-
- void Thread::ThrowNewWrappedException(const char* exception_class_descriptor,
- const char* msg) {
- DCHECK_EQ(this, Thread::Current());
- ScopedObjectAccessUnchecked soa(this);
- StackHandleScope<3> hs(soa.Self());
- Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetCurrentClassLoader(soa.Self())));
- ScopedLocalRef<jobject> cause(GetJniEnv(), soa.AddLocalReference<jobject>(GetException()));
- ClearException();
- Runtime* runtime = Runtime::Current();
- auto* cl = runtime->GetClassLinker();
- Handle<mirror::Class> exception_class(
- hs.NewHandle(cl->FindClass(this, exception_class_descriptor, class_loader)));
- if (UNLIKELY(exception_class == nullptr)) {
- CHECK(IsExceptionPending());
- LOG(ERROR) << "No exception class " << PrettyDescriptor(exception_class_descriptor);
- return;
- }
-
- if (UNLIKELY(!runtime->GetClassLinker()->EnsureInitialized(soa.Self(), exception_class, true,
- true))) {
- DCHECK(IsExceptionPending());
- return;
- }
- DCHECK(!runtime->IsStarted() || exception_class->IsThrowableClass());
- Handle<mirror::Throwable> exception(
- hs.NewHandle(ObjPtr<mirror::Throwable>::DownCast(exception_class->AllocObject(this))));
-
- // If we couldn't allocate the exception, throw the pre-allocated out of memory exception.
- if (exception == nullptr) {
- SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError());
- return;
- }
- // Choose an appropriate constructor and set up the arguments.
- const char* signature;
- ScopedLocalRef<jstring> msg_string(GetJniEnv(), nullptr);
- if (msg != nullptr) {
- // Ensure we remember this and the method over the String allocation.
- msg_string.reset(
- soa.AddLocalReference<jstring>(mirror::String::AllocFromModifiedUtf8(this, msg)));
- if (UNLIKELY(msg_string.get() == nullptr)) {
- CHECK(IsExceptionPending()); // OOME.
- return;
- }
- if (cause.get() == nullptr) {
- signature = "(Ljava/lang/String;)V";
- } else {
- signature = "(Ljava/lang/String;Ljava/lang/Throwable;)V";
- }
- } else {
- if (cause.get() == nullptr) {
- signature = "()V";
- } else {
- signature = "(Ljava/lang/Throwable;)V";
- }
- }
- ArtMethod* exception_init_method =
- exception_class->FindConstructor(signature, cl->GetImagePointerSize());
- CHECK(exception_init_method != nullptr) << "No <init>" << signature << " in "
- << PrettyDescriptor(exception_class_descriptor);
- if (UNLIKELY(!runtime->IsStarted())) {
- // Something is trying to throw an exception without a started runtime, which is the common
- // case in the compiler. We won't be able to invoke the constructor of the exception, so set
- // the exception fields directly.
- if (msg != nullptr) {
- exception->SetDetailMessage(DecodeJObject(msg_string.get())->AsString());
- }
- if (cause.get() != nullptr) {
- exception->SetCause(DecodeJObject(cause.get())->AsThrowable());
- }
- .....省略部分代码
- if (LIKELY(!IsExceptionPending())) {
- SetException(exception.Get());
- }
- }
- }
其中构造异常对象的时候,我们会初始化Exception,然后调用父类的super方法,父类Throwable然后调用fillInStackTrace来填充堆栈,
- // Exception.java
- public class Exception extends Throwable {
- static final long serialVersionUID = -3387516993124229948L;
-
- /**
- * Constructs a new exception with {@code null} as its detail message.
- * The cause is not initialized, and may subsequently be initialized by a
- * call to {@link #initCause}.
- */
- public Exception() {
- super();
- }
- }
-
- // Throwable.java
- public Throwable() {
- fillInStackTrace();
- }
-
- public synchronized Throwable fillInStackTrace() {
- if (stackTrace != null ||
- backtrace != null /* Out of protocol state */ ) {
- // Android-changed: Use Android-specific nativeFillInStackTrace
- // fillInStackTrace(0);
- backtrace = nativeFillInStackTrace();
- // Android-changed: Use libcore.util.EmptyArray for the empty stack trace
- // stackTrace = UNASSIGNED_STACK;
- stackTrace = libcore.util.EmptyArray.STACK_TRACE_ELEMENT;
- }
- return this;
- }
-
- // Android-changed: Use Android-specific nativeFillInStackTrace
- // private native Throwable fillInStackTrace(int dummy);
- @FastNative
- private static native Object nativeFillInStackTrace();
-
最终调用thread中的CreateInternalStackTrace去获取调用堆栈。
art/runtime/thread.cc
- template<bool kTransactionActive>
- jobject Thread::CreateInternalStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const {
- // Compute depth of stack, save frames if possible to avoid needing to recompute many.
- constexpr size_t kMaxSavedFrames = 256;
- std::unique_ptr<ArtMethodDexPcPair[]> saved_frames(new ArtMethodDexPcPair[kMaxSavedFrames]);
- FetchStackTraceVisitor count_visitor(const_cast<Thread*>(this),
- &saved_frames[0],
- kMaxSavedFrames);
- count_visitor.WalkStack();
- const uint32_t depth = count_visitor.GetDepth();
- const uint32_t skip_depth = count_visitor.GetSkipDepth();
-
- // Build internal stack trace.
- BuildInternalStackTraceVisitor<kTransactionActive> build_trace_visitor(soa.Self(),
- const_cast<Thread*>(this),
- skip_depth);
- if (!build_trace_visitor.Init(depth)) {
- return nullptr; // Allocation failed.
- }
- // If we saved all of the frames we don't even need to do the actual stack walk. This is faster
- // than doing the stack walk twice.
- if (depth < kMaxSavedFrames) {
- for (size_t i = 0; i < depth; ++i) {
- build_trace_visitor.AddFrame(saved_frames[i].first, saved_frames[i].second);
- }
- } else {
- build_trace_visitor.WalkStack();
- }
- mirror::ObjectArray<mirror::Object>* trace = build_trace_visitor.GetInternalStackTrace();
- if (kIsDebugBuild) {
- ObjPtr<mirror::PointerArray> trace_methods = build_trace_visitor.GetTraceMethodsAndPCs();
- // Second half of trace_methods is dex PCs.
- for (uint32_t i = 0; i < static_cast<uint32_t>(trace_methods->GetLength() / 2); ++i) {
- auto* method = trace_methods->GetElementPtrSize<ArtMethod*>(
- i, Runtime::Current()->GetClassLinker()->GetImagePointerSize());
- CHECK(method != nullptr);
- }
- }
- return soa.AddLocalReference<jobject>(trace);
- }
2.1.3
我们回到上面Thread类中异常抛出的处理中,构造异常对象分析完了,我们再看下SetException到底在做什么,将一个tlsPtr_对象的exception变量进行赋值,这个tlsPtr_对象就是Thread中的一个结构体
- void Thread::SetException(ObjPtr<mirror::Throwable> new_exception) {
- CHECK(new_exception != nullptr);
- // TODO: DCHECK(!IsExceptionPending());
- tlsPtr_.exception = new_exception.Ptr();
- }
- struct PACKED(sizeof(void*)) tls_ptr_sized_values {
- tls_ptr_sized_values() : card_table(nullptr), exception(nullptr), stack_end(nullptr),
- managed_stack(), suspend_trigger(nullptr), jni_env(nullptr), tmp_jni_env(nullptr),
- self(nullptr), opeer(nullptr), jpeer(nullptr), stack_begin(nullptr), stack_size(0),
- deps_or_stack_trace_sample(), wait_next(nullptr), monitor_enter_object(nullptr),
- top_handle_scope(nullptr), class_loader_override(nullptr), long_jump_context(nullptr),
- instrumentation_stack(nullptr), debug_invoke_req(nullptr), single_step_control(nullptr),
- stacked_shadow_frame_record(nullptr), deoptimization_context_stack(nullptr),
- frame_id_to_shadow_frame(nullptr), name(nullptr), pthread_self(0),
- last_no_thread_suspension_cause(nullptr), checkpoint_function(nullptr),
- thread_local_start(nullptr), thread_local_pos(nullptr), thread_local_end(nullptr),
- thread_local_limit(nullptr),
- thread_local_objects(0), mterp_current_ibase(nullptr), mterp_default_ibase(nullptr),
- mterp_alt_ibase(nullptr), thread_local_alloc_stack_top(nullptr),
- thread_local_alloc_stack_end(nullptr),
- flip_function(nullptr), method_verifier(nullptr), thread_local_mark_stack(nullptr),
- async_exception(nullptr) {
- std::fill(held_mutexes, held_mutexes + kLockLevelCount, nullptr);
- }
-
- // The biased card table, see CardTable for details.
- uint8_t* card_table;
-
- // The pending exception or null.
- mirror::Throwable* exception;
-
- // The end of this thread's stack. This is the lowest safely-addressable address on the stack.
- // We leave extra space so there's room for the code that throws StackOverflowError.
- uint8_t* stack_end;
-
- // The top of the managed stack often manipulated directly by compiler generated code.
- ManagedStack managed_stack;
-
- // In certain modes, setting this to 0 will trigger a SEGV and thus a suspend check. It is
- // normally set to the address of itself.
- uintptr_t* suspend_trigger;
-
- // Every thread may have an associated JNI environment
- JNIEnvExt* jni_env;
-
- // Temporary storage to transfer a pre-allocated JNIEnvExt from the creating thread to the
- // created thread.
- JNIEnvExt* tmp_jni_env;
-
- // Initialized to "this". On certain architectures (such as x86) reading off of Thread::Current
- // is easy but getting the address of Thread::Current is hard. This field can be read off of
- // Thread::Current to give the address.
- Thread* self;
-
- // Our managed peer (an instance of java.lang.Thread). The jobject version is used during thread
- // start up, until the thread is registered and the local opeer_ is used.
- mirror::Object* opeer;
- jobject jpeer;
-
- // The "lowest addressable byte" of the stack.
- uint8_t* stack_begin;
-
- // Size of the stack.
- size_t stack_size;
- ....省略部分代码
- // Sampling profiler and AOT verification cannot happen on the same run, so we share
- // the same entry for the stack trace and the verifier deps.
- union DepsOrStackTraceSample {
- DepsOrStackTraceSample() {
- verifier_deps = nullptr;
- stack_trace_sample = nullptr;
- }
- // Pointer to previous stack trace captured by sampling profiler.
- std::vector<ArtMethod*>* stack_trace_sample;
- // When doing AOT verification, per-thread VerifierDeps.
- verifier::VerifierDeps* verifier_deps;
- } deps_or_stack_trace_sample;
-
- // The next thread in the wait set this thread is part of or null if not waiting.
- Thread* wait_next;
-
-
- StackedShadowFrameRecord* stacked_shadow_frame_record;
-
- // Deoptimization return value record stack.
- DeoptimizationContextRecord* deoptimization_context_stack;
-
- // For debugger, a linked list that keeps the mapping from frame_id to shadow frame.
- // Shadow frames may be created before deoptimization happens so that the debugger can
- // set local values there first.
- FrameIdToShadowFrame* frame_id_to_shadow_frame;
-
- // A cached copy of the java.lang.Thread's name.
- std::string* name;
- // A cached pthread_t for the pthread underlying this Thread*.
- pthread_t pthread_self;
- // If no_thread_suspension_ is > 0, what is causing that assertion.
- const char* last_no_thread_suspension_cause;
- // Pending checkpoint function or null if non-pending. If this checkpoint is set and someone\
- // requests another checkpoint, it goes to the checkpoint overflow list.
- Closure* checkpoint_function GUARDED_BY(Locks::thread_suspend_count_lock_);
- // Pending barriers that require passing or NULL if non-pending. Installation guarding by
- // Locks::thread_suspend_count_lock_.
- // They work effectively as art::Barrier, but implemented directly using AtomicInteger and futex
- // to avoid additional cost of a mutex and a condition variable, as used in art::Barrier.
- AtomicInteger* active_suspend_barriers[kMaxSuspendBarriers];
- // Thread-local allocation pointer. Moved here to force alignment for thread_local_pos on ARM.
- uint8_t* thread_local_start;
- // thread_local_pos and thread_local_end must be consecutive for ldrd and are 8 byte aligned for
- // potentially better performance.
- uint8_t* thread_local_pos;
- uint8_t* thread_local_end;
- // Thread local limit is how much we can expand the thread local buffer to, it is greater or
- // equal to thread_local_end.
- uint8_t* thread_local_limit;
- size_t thread_local_objects;
- // Entrypoint function pointers.
- // TODO: move this to more of a global offset table model to avoid per-thread duplication.
- JniEntryPoints jni_entrypoints;
- QuickEntryPoints quick_entrypoints;
- ....省略部分代码
- } tlsPtr_;
2.1.4
这样子我们就分析完了第一部分,异常的抛出处理过程,抛出了异常,肯定需要处理异常啊?我们回到前面的代码逻辑,POSSIBLY_HANDLE_PENDING_EXCEPTION这个就是开始处理异常,
art/runtime/interpreter/interpreter_switch_impl.cc
- case Instruction::DIV_INT: {
- PREAMBLE();
- bool success = DoIntDivide(shadow_frame, inst->VRegA_23x(inst_data),
- shadow_frame.GetVReg(inst->VRegB_23x()),
- shadow_frame.GetVReg(inst->VRegC_23x()));
- POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
-
它的实现在这里
art/runtime/interpreter/interpreter_switch_impl.cc
- #define HANDLE_PENDING_EXCEPTION() HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instrumentation)
-
-
- #define HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instr) \
- do { \
- DCHECK(self->IsExceptionPending()); \
- self->AllowThreadSuspension(); \
- if (!MoveToExceptionHandler(self, shadow_frame, instr)) { \
- /* Structured locking is to be enforced for abnormal termination, too. */ \
- DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame); \
- if (interpret_one_instruction) { \
- /* Signal mterp to return to caller */ \
- shadow_frame.SetDexPC(dex::kDexNoIndex); \
- } \
- ctx->result = JValue(); /* Handled in caller. */ \
- return; \
- } else { \
- int32_t displacement = \
- static_cast<int32_t>(shadow_frame.GetDexPC()) - static_cast<int32_t>(dex_pc); \
- inst = inst->RelativeAt(displacement); \
- } \
- } while (false)
-
其中有个方法
MoveToExceptionHandler,这个返回boolean类型的值,我们看下这里是怎么处理的
art/runtime/interpreter/interpreter_common.cc
- bool MoveToExceptionHandler(Thread* self,
- ShadowFrame& shadow_frame,
- const instrumentation::Instrumentation* instrumentation) {
- self->VerifyStack();
- StackHandleScope<2> hs(self);
- Handle<mirror::Throwable> exception(hs.NewHandle(self->GetException()));
- if (instrumentation != nullptr &&
- instrumentation->HasExceptionThrownListeners() &&
- self->IsExceptionThrownByCurrentMethod(exception.Get())) {
- // See b/65049545 for why we don't need to check to see if the exception has changed.
- instrumentation->ExceptionThrownEvent(self, exception.Get());
- }
- //FindCatchBlock去寻找异常处理块
- bool clear_exception = false;
- uint32_t found_dex_pc = shadow_frame.GetMethod()->FindCatchBlock(
- hs.NewHandle(exception->GetClass()), shadow_frame.GetDexPC(), &clear_exception);
- //注意这里,代表找不到异常处理,需要进行栈的回溯
- if (found_dex_pc == dex::kDexNoIndex) {
- if (instrumentation != nullptr) {
- if (shadow_frame.NeedsNotifyPop()) {
- instrumentation->WatchedFramePopped(self, shadow_frame);
- }
- // Exception is not caught by the current method. We will unwind to the
- // caller. Notify any instrumentation listener.
- instrumentation->MethodUnwindEvent(self,
- shadow_frame.GetThisObject(),
- shadow_frame.GetMethod(),
- shadow_frame.GetDexPC());
- }
- return false;
- } else {
- // 代表找到异常处理的地方,比如catch块,然后设置相关的found_dex_pc。
- shadow_frame.SetDexPC(found_dex_pc);
- if (instrumentation != nullptr && instrumentation->HasExceptionHandledListeners()) {
- self->ClearException();
- instrumentation->ExceptionHandledEvent(self, exception.Get());
- if (UNLIKELY(self->IsExceptionPending())) {
- // Exception handled event threw an exception. Try to find the handler for this one.
- return MoveToExceptionHandler(self, shadow_frame, instrumentation);
- } else if (!clear_exception) {
- self->SetException(exception.Get());
- }
- } else if (clear_exception) {
- self->ClearException();
- }
- return true;
- }
- }
代码中一些流程,我简单进行了备注,FindCatchBlock我们去看下实现
- uint32_t ArtMethod::FindCatchBlock(Handle<mirror::Class> exception_type,
- uint32_t dex_pc, bool* has_no_move_exception) {
- // Set aside the exception while we resolve its type.
- Thread* self = Thread::Current();
- StackHandleScope<1> hs(self);
- Handle<mirror::Throwable> exception(hs.NewHandle(self->GetException()));
- self->ClearException();
- // Default to handler not found.
- uint32_t found_dex_pc = dex::kDexNoIndex;
- // Iterate over the catch handlers associated with dex_pc.
- CodeItemDataAccessor accessor(DexInstructionData());
- for (CatchHandlerIterator it(accessor, dex_pc); it.HasNext(); it.Next()) {
- dex::TypeIndex iter_type_idx = it.GetHandlerTypeIndex();
- // Catch all case
- if (!iter_type_idx.IsValid()) {
- found_dex_pc = it.GetHandlerAddress();
- break;
- }
- // Does this catch exception type apply?
- ObjPtr<mirror::Class> iter_exception_type = ResolveClassFromTypeIndex(iter_type_idx);
- if (UNLIKELY(iter_exception_type == nullptr)) {
- // Now have a NoClassDefFoundError as exception. Ignore in case the exception class was
- // removed by a pro-guard like tool.
- // Note: this is not RI behavior. RI would have failed when loading the class.
- self->ClearException();
- // Delete any long jump context as this routine is called during a stack walk which will
- // release its in use context at the end.
- delete self->GetLongJumpContext();
- LOG(WARNING) << "Unresolved exception class when finding catch block: "
- << DescriptorToDot(GetTypeDescriptorFromTypeIdx(iter_type_idx));
- } else if (iter_exception_type->IsAssignableFrom(exception_type.Get())) {
- found_dex_pc = it.GetHandlerAddress();
- break;
- }
- }
- if (found_dex_pc != dex::kDexNoIndex) {
- const Instruction& first_catch_instr = accessor.InstructionAt(found_dex_pc);
- *has_no_move_exception = (first_catch_instr.Opcode() != Instruction::MOVE_EXCEPTION);
- }
- // Put the exception back.
- if (exception != nullptr) {
- self->SetException(exception.Get());
- }
- return found_dex_pc;
- }
里面的逻辑无非是寻找CatchHandler来处理,比如下面这段代码,0x0025 - 0x0027的代码逻辑,被0x0028处catch住,
所以虚拟机能找到对应的catch块来处理。
- try {
- int i = 9 / 0;
- } catch (Exception e) {
- Log.i("jin", "exception : " + e.getMessage(), new Exception());
- }
-
-
- 0023: const/16 v0, #int 9 // #9
- 0025: div-int/lit8 v0, v0, #int 0 // #00
- 0027: goto 0048 // +0021
- 0028: move-exception v0
- 0029: new-instance v1, Ljava/lang/StringBuilder; // type@0dcc
- 002b: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@fd11
- 002e: const-string v2, "exception : " // string@554a
- 0030: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@fd1a
- 0033: invoke-virtual {v0}, Ljava/lang/Exception;.getMessage:()Ljava/lang/String; // method@fcaa
- 0036: move-result-object v2
- 0037: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@fd1a
- 003a: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@fd21
- 003d: move-result-object v1
- 003e: new-instance v2, Ljava/lang/Exception; // type@0db2
- 0040: invoke-direct {v2}, Ljava/lang/Exception;.<init>:()V // method@fca9
- 0043: const-string v3, "jin" // string@6a65
- 0045: invoke-static {v3, v1, v2}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I // method@0139
- 0048: return-void
- catches : 1
- 0x0025 - 0x0027
- Ljava/lang/Exception; -> 0x0028
-
我们继续,如果有catch块能处理异常我们分析完了一部分,是怎么跳转到catch块执行的吗,如果没有catch块怎么办呢?
其实,我们在函数回溯后,会返回到art_quick_to_interpreter_bridge中执行,有一个RETURN_OR_DELIVER_PENDING_EXCEPTION的宏,
art/runtime/arch/arm64/quick_entrypoints_arm64.S
- ENTRY art_quick_to_interpreter_bridge
- SETUP_SAVE_REFS_AND_ARGS_FRAME // Set up frame and save arguments.
-
- // x0 will contain mirror::ArtMethod* method.
- mov x1, xSELF // How to get Thread::Current() ???
- mov x2, sp
-
- // uint64_t artQuickToInterpreterBridge(mirror::ArtMethod* method, Thread* self,
- // mirror::ArtMethod** sp)
- bl artQuickToInterpreterBridge
-
- RESTORE_SAVE_REFS_AND_ARGS_FRAME // TODO: no need to restore arguments in this case.
- REFRESH_MARKING_REGISTER
-
- fmov d0, x0
-
- RETURN_OR_DELIVER_PENDING_EXCEPTION
- END art_quick_to_interpreter_bridge
-
- //宏的实现
- .macro RETURN_OR_DELIVER_PENDING_EXCEPTION_REG reg
- ldr \reg, [xSELF, # THREAD_EXCEPTION_OFFSET] // Get exception field.
- cbnz \reg, 1f
- ret
- 1:
- DELIVER_PENDING_EXCEPTION
- .endm
-
- //最终实现
- .macro DELIVER_PENDING_EXCEPTION_FRAME_READY
- mov x0, xSELF
-
- // Point of no return.
- bl artDeliverPendingExceptionFromCode // artDeliverPendingExceptionFromCode(Thread*)
- brk 0 // Unreached
- .endm
宏的最终实现是
bl artDeliverPendingExceptionFromCode ,也就是需要跳转到这个artDeliverPendingExceptionFromCode去执行
art/runtime/entrypoints/quick/quick_throw_entrypoints.cc
- extern "C" NO_RETURN void artDeliverPendingExceptionFromCode(Thread* self)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ScopedQuickEntrypointChecks sqec(self);
- self->QuickDeliverException();
- }
最终是调用Thread的QuickDeliverException方法,
- void Thread::QuickDeliverException() {
- // Get exception from thread.
- ObjPtr<mirror::Throwable> exception = GetException();
- CHECK(exception != nullptr);
- if (exception == GetDeoptimizationException()) {
- artDeoptimize(this);
- UNREACHABLE();
- }
-
- ReadBarrier::MaybeAssertToSpaceInvariant(exception.Ptr());
-
- // This is a real exception: let the instrumentation know about it.
- instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
- if (instrumentation->HasExceptionThrownListeners() &&
- IsExceptionThrownByCurrentMethod(exception)) {
- // Instrumentation may cause GC so keep the exception object safe.
- StackHandleScope<1> hs(this);
- HandleWrapperObjPtr<mirror::Throwable> h_exception(hs.NewHandleWrapper(&exception));
- instrumentation->ExceptionThrownEvent(this, exception.Ptr());
- }
- // Does instrumentation need to deoptimize the stack?
- // Note: we do this *after* reporting the exception to instrumentation in case it
- // now requires deoptimization. It may happen if a debugger is attached and requests
- // new events (single-step, breakpoint, ...) when the exception is reported.
- if (Dbg::IsForcedInterpreterNeededForException(this)) {
- NthCallerVisitor visitor(this, 0, false);
- visitor.WalkStack();
- if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) {
- // method_type shouldn't matter due to exception handling.
- const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
- // Save the exception into the deoptimization context so it can be restored
- // before entering the interpreter.
- PushDeoptimizationContext(
- JValue(),
- false /* is_reference */,
- exception,
- false /* from_code */,
- method_type);
- artDeoptimize(this);
- UNREACHABLE();
- } else {
- LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
- << visitor.caller->PrettyMethod();
- }
- }
- // Don't leave exception visible while we try to find the handler, which may cause class
- // resolution.
- ClearException();
- QuickExceptionHandler exception_handler(this, false);
- exception_handler.FindCatch(exception); //找到exception处理的地方
- exception_handler.UpdateInstrumentationStack();
- if (exception_handler.GetClearException()) {
- // Exception was cleared as part of delivery.
- DCHECK(!IsExceptionPending());
- } else {
- // Exception was put back with a throw location.
- DCHECK(IsExceptionPending());
- // Check the to-space invariant on the re-installed exception (if applicable).
- ReadBarrier::MaybeAssertToSpaceInvariant(GetException());
- }
- exception_handler.DoLongJump(); //跳转到异常处理的地址
- }
里面最重要的几个地方,FindCatch()和DoLongJump(),找到相应Exception处理的catch块,并且去完成真正的跳转。
- void QuickExceptionHandler::DoLongJump(bool smash_caller_saves) {
- // Place context back on thread so it will be available when we continue.
- self_->ReleaseLongJumpContext(context_);
- context_->SetSP(reinterpret_cast<uintptr_t>(handler_quick_frame_));
- CHECK_NE(handler_quick_frame_pc_, 0u);
- context_->SetPC(handler_quick_frame_pc_);
- context_->SetArg0(handler_quick_arg0_);
- if (smash_caller_saves) {
- context_->SmashCallerSaves();
- }
- context_->DoLongJump();
- UNREACHABLE();
- }
如果找不到catch块处理,就会去通过DetachCurrentThread()和Destroy()去完成
- void Thread::Destroy() {
- Thread* self = this;
- DCHECK_EQ(self, Thread::Current());
-
- if (tlsPtr_.jni_env != nullptr) {
- {
- ScopedObjectAccess soa(self);
- MonitorExitVisitor visitor(self);
- // On thread detach, all monitors entered with JNI MonitorEnter are automatically exited.
- tlsPtr_.jni_env->monitors_.VisitRoots(&visitor, RootInfo(kRootVMInternal));
- }
- // Release locally held global references which releasing may require the mutator lock.
- if (tlsPtr_.jpeer != nullptr) {
- // If pthread_create fails we don't have a jni env here.
- tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.jpeer);
- tlsPtr_.jpeer = nullptr;
- }
- if (tlsPtr_.class_loader_override != nullptr) {
- tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.class_loader_override);
- tlsPtr_.class_loader_override = nullptr;
- }
- }
-
- if (tlsPtr_.opeer != nullptr) {
- ScopedObjectAccess soa(self);
- // We may need to call user-supplied managed code, do this before final clean-up.
- HandleUncaughtExceptions(soa);
- RemoveFromThreadGroup(soa);
- Runtime* runtime = Runtime::Current();
- if (runtime != nullptr) {
- runtime->GetRuntimeCallbacks()->ThreadDeath(self);
- }
- }
- ...省略部分代码
- }
-
-
- void Thread::HandleUncaughtExceptions(ScopedObjectAccessAlreadyRunnable& soa) {
- if (!IsExceptionPending()) {
- return;
- }
- ScopedLocalRef<jobject> peer(tlsPtr_.jni_env, soa.AddLocalReference<jobject>(tlsPtr_.opeer));
- ScopedThreadStateChange tsc(this, kNative);
-
- // Get and clear the exception.
- ScopedLocalRef<jthrowable> exception(tlsPtr_.jni_env, tlsPtr_.jni_env->ExceptionOccurred());
- tlsPtr_.jni_env->ExceptionClear();
-
- // Call the Thread instance's dispatchUncaughtException(Throwable)
- tlsPtr_.jni_env->CallVoidMethod(peer.get(),
- WellKnownClasses::java_lang_Thread_dispatchUncaughtException,
- exception.get());
-
- // If the dispatchUncaughtException threw, clear that exception too.
- tlsPtr_.jni_env->ExceptionClear();
- }
-
最终通过jni调用Thread的dispatchUncaughtException方法执行,也就是回到了我们最初的java堆栈中。
在机器码中,原始的字节码从Throw指令会被翻译成HThrow IR,
art/compiler/optimizing/nodes.h
- class HThrow FINAL : public HTemplateInstruction<1> {
- public:
- HThrow(HInstruction* exception, uint32_t dex_pc)
- : HTemplateInstruction(kThrow, SideEffects::CanTriggerGC(), dex_pc) {
- SetRawInputAt(0, exception);
- }
-
- bool IsControlFlow() const OVERRIDE { return true; }
-
- bool NeedsEnvironment() const OVERRIDE { return true; }
-
- bool CanThrow() const OVERRIDE { return true; }
-
- bool AlwaysThrows() const OVERRIDE { return true; }
-
- DECLARE_INSTRUCTION(Throw);
-
- protected:
- DEFAULT_COPY_CONSTRUCTOR(Throw);
- };
art/compiler/optimizing/code_generator_arm64.cc
- void InstructionCodeGeneratorARM64::VisitThrow(HThrow* instruction) {
- codegen_->InvokeRuntime(kQuickDeliverException, instruction, instruction->GetDexPc());
- CheckEntrypointTypes<kQuickDeliverException, void, mirror::Object*>();
- }
-
- /*
- * Called by managed code, saves callee saves and then calls artThrowException
- * that will place a mock Method* at the bottom of the stack. Arg1 holds the exception.
- */
art/runtime/arch/arm64/quick_entrypoints_arm64.S
ONE_ARG_RUNTIME_EXCEPTION art_quick_deliver_exception, artDeliverExceptionFromCode
最终会调用artDeliverExceptionFromCode方法,里面最终调用的就是Thread的QuickDeliverException方法,和之前分析的流程重合了,就不重复描述了。
- extern "C" NO_RETURN void artDeliverExceptionFromCode(mirror::Throwable* exception, Thread* self)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- /*
- * exception may be null, in which case this routine should
- * throw NPE. NOTE: this is a convenience for generated code,
- * which previously did the null check inline and constructed
- * and threw a NPE if null. This routine responsible for setting
- * exception_ in thread and delivering the exception.
- */
- ScopedQuickEntrypointChecks sqec(self);
- if (exception == nullptr) {
- self->ThrowNewException("Ljava/lang/NullPointerException;", "throw with null exception");
- } else {
- self->SetException(exception);
- }
- self->QuickDeliverException();
- }
其实异常的处理就两步,1 抛异常, 2 处理异常,要么catch块处理,要么uncaughtExceptionHandler处理。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。