1 背景
熟悉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住就完了。
2 虚拟机层探究一下
本文以Android9.0虚拟机为例,针对上面异常代码的处理流程,对虚拟机层探究一下。
2.1 解释器模式
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 *Error.
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堆栈中。
2.2 机器码模式
在机器码中,原始的字节码从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();
}
3 总结
其实异常的处理就两步,1 抛异常, 2 处理异常,要么catch块处理,要么uncaughtExceptionHandler处理。