ART异常处理机制(4) - throw & catch & finally实现

本篇从本质上解读 java exception 的throw,catch,finally,在ART种的实现。

1.Throw 的实现

先看下 throw exception的实现,还是从一个函数中来分析。

Java  CODE:

    public void delete() {
        throw new NullPointerException();
    }
class CODE:

  public void delete();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #11                 // class java/lang/NullPointerException
         3: dup
         4: invokespecial #12                 // Method java/lang/NullPointerException."":()V
         7: athrow
      LineNumberTable:
        line 20: 0
DEX CODE:

    DEX CODE:
      0x0000: 2200 0300                 | new-instance v0, java.lang.NullPointerException // type@3
      0x0002: 7010 0500 0000            | invoke-direct {v0}, void java.lang.NullPointerException.() // method@5
      0x0005: 2700                      | throw v0
QUICK CODE:

    CODE: (code_offset=0x00001154 size_offset=0x00001150 size=128)...
      0x00001154: d1400bf0  sub x16, sp, #0x2000 (8192)
      0x00001158: b940021f  ldr wzr, [x16]
        StackMap [native_pc=0x115c] (dex_pc=0x0, native_pc_offset=0x8, dex_register_map_offset=0xffffffff, inline_info_offset=0xffffffff, register_mask=0x0, stack_mask=0b0000000000000)
      0x0000115c: f81c0fe0  str x0, [sp, #-64]!
      0x00001160: a902d7f4  stp x20, x21, [sp, #40]
      0x00001164: f9001ffe  str lr, [sp, #56]
      0x00001168: 79400270  ldrh w16, [tr] ; state_and_flags
      0x0000116c: 350001d0  cbnz w16, #+0x38 (addr 0x11a4)
      0x00001170: aa0103f4  mov x20, x1
      0x00001174: aa0003e1  mov x1, x0
      0x00001178: 52800060  mov w0, #0x3
      0x0000117c: f940de7e  ldr lr, [tr, #440] ; pAllocObject
      0x00001180: d63f03c0  blr lr
        StackMap [native_pc=0x1184] (dex_pc=0x0, native_pc_offset=0x30, dex_register_map_offset=0x0, inline_info_offset=0xffffffff, register_mask=0x100000, stack_mask=0b0000000000000)
          v1: in register (20)  [entry 0]
      0x00001184: aa0003e1  mov x1, x0
      0x00001188: aa0103f5  mov x21, x1
      0x0000118c: 580001de  ldr lr, pc+56 (addr 0x11c4) (0x70678fa4 / 1885835172)
      0x00001190: 580001e0  ldr x0, pc+60 (addr 0x11cc) (0x6f52cbc0 / 1867697088)
      0x00001194: d63f03c0  blr lr
        StackMap [native_pc=0x1198] (dex_pc=0x2, native_pc_offset=0x44, dex_register_map_offset=0x2, inline_info_offset=0xffffffff, register_mask=0x300000, stack_mask=0b0000000000000)
          v0: in register (21)  [entry 1]
          v1: in register (20)  [entry 0]
      0x00001198: aa1503e0  mov x0, x21
      0x0000119c: f9426e7e  ldr lr, [tr, #1240] ; pDeliverException
      0x000011a0: d63f03c0  blr lr
        StackMap [native_pc=0x11a4] (dex_pc=0x5, native_pc_offset=0x50, dex_register_map_offset=0x2, inline_info_offset=0xffffffff, register_mask=0x300000, stack_mask=0b0000000000000)
          v0: in register (21)  [entry 1]
          v1: in register (20)  [entry 0]
      0x000011a4: 910073f0  add x16, sp, #0x1c (28)
      0x000011a8: a93f0600  stp x0, x1, [x16, #-16]
      0x000011ac: f9426a7e  ldr lr, [tr, #1232] ; pTestSuspend
      0x000011b0: d63f03c0  blr lr
        StackMap [native_pc=0x11b4] (dex_pc=0x0, native_pc_offset=0x60, dex_register_map_offset=0x4, inline_info_offset=0xffffffff, register_mask=0x0, stack_mask=0b0000000100000)
          v1: in stack (20) [entry 2]
      0x000011b4: 910073f0  add x16, sp, #0x1c (28)
      0x000011b8: a97f0600  ldp x0, x1, [x16, #-16]
      0x000011bc: 17ffffed  b #-0x4c (addr 0x1170)
      0x000011c0: 580000bf  ldr xzr, pc+20 (addr 0x11d4) (0x0 / 0)
      0x000011c4: 70678fa4  adr x4, #+0xcf1f7 (addr 0xd03bb)
      0x000011c8: 00000000  unallocated (Unallocated)
      0x000011cc: 6f52cbc0  unimplemented v0.8h, v30.8h, v2.h[5]
      0x000011d0: 00000000  unallocated (Unallocated)
可以看到 java 中的 throw exception代码,在class文件和dex文件都被分解成了 Exception的创建(包括new,和调用构造函数)和抛出。在 class中的抛出异常指令是 athrow,在DEX中是 throw v0.
而编译生成的 ART QICK CODE中,发现 throw 会通过调用 thread 的 quick entry points的pDeliverException实现。

  qpoints->pDeliverException = art_quick_deliver_exception;
    /*
     * 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.
     */
ONE_ARG_RUNTIME_EXCEPTION art_quick_deliver_exception, artDeliverExceptionFromCode
最终实现是在 artDeliverExceptionFromCode函数:

// Called by generated code to throw an exception.
extern "C" NO_RETURN void artDeliverExceptionFromCode(mirror::Throwable* exception, Thread* self)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  ScopedQuickEntrypointChecks sqec(self);
  if (exception == nullptr) {
    self->ThrowNewException("Ljava/lang/NullPointerException;", "throw with null exception");
  } else {
    self->SetException(exception);
  }
  self->QuickDeliverException();
}

把 NullPointerException对象设置到线程 self 的线程私有成员里,然后调用 QuickDeliverException抛出异常,这个函数在前面 几个小节里已经见到过,但都没有详细介绍,实际上真正Exception的抛出和 java 中 catch 代码的实现都在这个函数里实现。这个函数的执行涉及到 catch的实现,所以我们在下一小节cath实现里详细介绍它。

总结:java 的 throw在class和dex中分别使用 athrow指令实现,在ART QUICK CODE中通过 artDeliverExceptionFromCode函数实现。Catch Exception的检测是在 QuickDeliverException函数的流程中实现的。即,throw Exception的过程中,会检查是否有人 catch 这个Exception,如果有会跳转到 catch处的指令。


2. Catch的实现与 QuickDeliverException函数

还是从 Java 代码开始,我们从对比开始来看有没有 catch语句,反应在在dex指令上的区别:

没有catch的函数:

    public void test() {
        delete();
    }
对应的 DEX CODE:

 3: void Hello.test() (dex_method_idx=3)                                                                                                                                               
    DEX CODE:
      0x0000: 6e10 0100 0000            | invoke-virtual {v0}, void Hello.delete() // method@1
      0x0003: 0e00                      | return-void

有 catch语句的函数,但catch中什么都没有做:

    public void test() {
        try {
            delete();
        } catch (Exception e) {

        }
    }
对应的 DEX CODE:

  3: void Hello.test() (dex_method_idx=3)
    DEX CODE:
      0x0000: 6e10 0100 0100            | invoke-virtual {v1}, void Hello.delete() // method@1
      0x0003: 0e00                      | return-void
      0x0004: 0d00                      | move-exception v0
      0x0005: 28fe                      | goto -2
在catch语句中做了一些事情的函数:

    public void test() {
        try {
            delete();
        } catch (Exception e) {
            System.out.println("catched :" +e);
        }
    }
对应的 DEX CODE:

  3: void Hello.test() (dex_method_idx=3)
    DEX CODE:
      0x0000: 6e10 0100 0400            | invoke-virtual {v4}, void Hello.delete() // method@1
      0x0003: 0e00                      | return-void
      0x0004: 0d00                      | move-exception v0
      0x0005: 6201 0100                 | sget-object  v1, Ljava/io/PrintStream; java.lang.System.out // field@1
      0x0007: 2202 0600                 | new-instance v2, java.lang.StringBuilder // type@6
      0x0009: 7010 0700 0200            | invoke-direct {v2}, void java.lang.StringBuilder.() // method@7
      0x000c: 1a03 1100                 | const-string v3, "catched :" // string@17
      0x000e: 6e20 0900 3200            | invoke-virtual {v2, v3}, java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) // method@9
      0x0011: 0c02                      | move-result-object v2
      0x0012: 6e20 0800 0200            | invoke-virtual {v2, v0}, java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.Object) // method@8
      0x0015: 0c00                      | move-result-object v0
      0x0016: 6e10 0a00 0000            | invoke-virtual {v0}, java.lang.String java.lang.StringBuilder.toString() // method@10
      0x0019: 0c00                      | move-result-object v0
      0x001a: 6e20 0400 0100            | invoke-virtual {v1, v0}, void java.io.PrintStream.println(java.lang.String) // method@4
      0x001d: 28e6                      | goto -26
通过上面的3种情况的对比,我们可以发现:

1.catch的实现伴随着 move-exception这个 dalvik bytecode

2.在 try 块中发生的 Exception 会通过 move-exception指令保存到一个参数 v0 (或者vN)中

3.catch 块中的函数对应的指令紧跟在 move-exception 指令之后

上边是得出的一些结论,当然,这些结论并不仅仅靠这些对比得出,而是也从代码中进行过验证。

那么,到这里,我们还应该要有一些疑问,才能有目的的进行借来了的分析:

比如,在dex code中,0x0000处,调用完成 Hell.delete(),接下来的下一条指令就是 0x0003 处的 return-void 指令,那么按说,程序执行顺序执行,执行到这条指令就会从该函数返回,不会执行接下来的指令了,从 dex code,QUICK CODE中,都看不到能继续执行的可能跳转,那么持续是如何执行到 catch Exception的指令以及catch 块中的内容呢 ?这个秘密就隐藏在虚拟机的异常抛出流程的 QuickDeliverException阶段。

void Thread::QuickDeliverException() {
  // Get exception from thread.
  ObjPtr exception = GetException();
  ....
  // 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_handler.UpdateInstrumentationStack();
  exception_handler.DoLongJump();
}
这个代码要结合前面第一节的 artDeliverExceptionFromCode 函数来看了,因为这个函数就是从上面调用过来的。

在artDeliverExceptionFromCode函数中,先把 exception设置到 thread 的私有数据,然后才调用 QuickDeliverException函数。所以在这个函数中,先通过GetException() 把Exception从 thread私有数据中取出来,然后 ClearException()把它清空(防止后面有类加载的时候,导致类加载失败);接下来使用 QuickExceptionHander 来在调用栈(当前 frame和 caller frame)中查找有没有能够 catch 该 exception的 catch块,如果没有的话,会抛出异常导致进程挂掉,如果有能够 catch该Exception的catch块,那么接下来的执行流将跳转到对应函数的 catch块(catch块可能不在抛出Exception的函数,而在caller method中,比如我们的例子就是这样)去执行 catch 块中的代码。其实到这里,大体的实现原理已经说完了,接下来是具体的实现,有兴趣的同学可以看一下。

2.1 catch 块的查找

Catch块的查找工作主要是由如下代码完成:

  QuickExceptionHandler exception_handler(this, false);
  exception_handler.FindCatch(exception);
看FindCatch函数:

void QuickExceptionHandler::FindCatch(ObjPtr exception) {
  StackHandleScope<1> hs(self_);
  Handle exception_ref(hs.NewHandle(exception));

  // Walk the stack to find catch handler.
  CatchBlockStackVisitor visitor(self_, context_, &exception_ref, this);
  visitor.WalkStack(true);
  if (clear_exception_) {
    // Exception was cleared as part of delivery.
    DCHECK(!self_->IsExceptionPending());
  } else {
    // Put exception back in root set with clear throw location.
    self_->SetException(exception_ref.Get());
  }
  // If the handler is in optimized code, we need to set the catch environment.
  if (*handler_quick_frame_ != nullptr &&
      handler_method_header_ != nullptr &&
      handler_method_header_->IsOptimized()) {
    SetCatchEnvironmentForOptimizedHandler(&visitor);
  }
}

在这里通过 CatchBlockStackVisitor去遍历栈进行查找 Catch块。查找结束后,会保存一些数据到 QuickExceptionHander的成员数据内。

比如,如果查找到存在当前正在处理的这个Exception对应的catch块,且 catch 块的catch hander(后续会讲到)的中的dex pc指向的第一个dex指令是 move-exception vN(意思是需要使用 Exception对象,会把其放到vN上,以备使用),就需要把 Exception设置回thread私有成员里(FindCatch之前 clear掉了);

如果被catch,catch block第一条指令不是 move-exception,则不会再把exception设置回去,因为后续不需要使用了,且catch住了,不需要抛出;

当然,如果没有查找到对应的 catch 块,也需要重新把 Exception设置回去,这个异常对象在后面抛出的时候需要用到。

接下来是catch块查找使用的 CatchBlockStackVisitor 的实现:

  bool VisitFrame() OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
    ArtMethod* method = GetMethod();
    ....
    return HandleTryItems(method);
  }
这个VisitFrame是每个 StackVisitor或其子类需要实现的函数,在 WalkStack 的过程中,对每个frame都会调用该函数,且当VisitFrame返回 false的情况下,会停止 WalkStack过程。
这里我们看到查找的实现还在 HandleTryItems(method)函数中,这里简单介绍下 try item,我们不止在 Find Catch block吗,怎么又跑到 try item了?其实这里涉及到了dex文件。

我们可以想一想查找过程中存在的需要解决的问题:我们知道,我们Catch Exception的时候,一般比较精确的catch比较好,因为我们最好明确知道代码的走向。如果我们 throw了一个NullPointerException,而catch块中想要catch的是 IndexOutOfBoundsException,这样肯定不能匹配的,那么是如何区分的,catch块想要catch的Exception的种类的信息存放在哪?在比如一段代码可能存在多个catch block,这些信息又存放在哪?

很显然,在前面的DEX CODE中,我们并没有看到任何相关的信息。而真实的情况是,catch 块的信息都存放在dex文件中。我们再想一下,一个函数中可能有多个try-catch 块,那么我们肯定要区分每个 try 块包着的行号范围(在dex中是 dex pc的范围),不同的 try 块的范围不同,对应的 catch block也不同,所以这里又有两个需要记录的信息,所以dex文件中要有这两个信息。在dex文件中,记录这两个信息的数据结构分别叫 try item 和 catch hander。try item中记录了try块对应的dex pc起始和结束范围,以及对应的 catch hander在 catch handler list中的index。当代码运行找抛出一个Exception,在其中一个函数中查找 catch block时,需要先拿到当前函数执行到的 dex pc(在这个环境下是已知的),然后拿到 try item list,看看 list 中的哪个 try item的范围包含当前 dex pc,如果包含,说明发生exception的代码在当前的try 块范围内,然后再根据 try item结构的index,到 catch hander list 中找到对应 catch handler,而 catch handler数据结构中,则保存有想要 catch的 Exception的 type id,以及catch 块代码在dex code中的dex pc(在 catch到的情况下,会跳转到这里执行 catch块代码),根据这个type id,我们能知道想要 catch的是什么 Exception,然后只需要判断当前要抛出的exception类是否是想要catch的Exception或者其子类,如果match,则 catch成功,后续要转到 catch block对应的 dex pc 处,执行 catch block内的代码。

上面这些解释了 try item和 catch handler,而显然索引关系是从 try item来索引 catch handler,所以在上面 Find Catch Block 的时候会出来一个 HandleTryItems函数来完成 find catch的工作。

需要提一下,try item list和 catch handler list在dex文件中,是紧跟着函数的 byte code instructions存放的,下面是 test 函数中的情况,看的时候,关注一下最后一列的 comment:

ART异常处理机制(4) - throw & catch & finally实现_第1张图片


背景知识介绍完了,我们接下来继续看 HandleTryItems()函数的实现:

  bool HandleTryItems(ArtMethod* method)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    uint32_t dex_pc = DexFile::kDexNoIndex;
    if (!method->IsNative()) {
      dex_pc = GetDexPc();
    }
    if (dex_pc != DexFile::kDexNoIndex) {
      bool clear_exception = false;
      StackHandleScope<1> hs(GetThread());
      Handle to_find(hs.NewHandle((*exception_)->GetClass()));
      uint32_t found_dex_pc = method->FindCatchBlock(to_find, dex_pc, &clear_exception);
      exception_handler_->SetClearException(clear_exception);
      if (found_dex_pc != DexFile::kDexNoIndex) {
        exception_handler_->SetHandlerMethod(method);
        exception_handler_->SetHandlerDexPc(found_dex_pc);
        exception_handler_->SetHandlerQuickFramePc(
            GetCurrentOatQuickMethodHeader()->ToNativeQuickPc(
                method, found_dex_pc, /* is_catch_handler */ true));
        exception_handler_->SetHandlerQuickFrame(GetCurrentQuickFrame());
        exception_handler_->SetHandlerMethodHeader(GetCurrentOatQuickMethodHeader());
        return false;  // End stack walk.
      } else if (UNLIKELY(GetThread()->HasDebuggerShadowFrames())) {
        // We are going to unwind this frame. Did we prepare a shadow frame for debugging?
        size_t frame_id = GetFrameId();
        ShadowFrame* frame = GetThread()->FindDebuggerShadowFrame(frame_id);
        if (frame != nullptr) {
          // We will not execute this shadow frame so we can safely deallocate it.
          GetThread()->RemoveDebuggerShadowFrameMapping(frame_id);
          ShadowFrame::DeleteDeoptimizedFrame(frame);
        }
      }
    }
    return true;  // Continue stack walk.
  }
有了上面的背景知识,这个函数就简单了:

1. dex_pc = GetDexPc();获取当前函数中执行到的 dex_pc,用以判断是否在 try item 范围内

2. method->FindCatchBlock() 就是从dex file 的 try item list / catch handler list查找匹配的catch handler,若找到,则返回对应的 catch handler中记录的 catch block的dex pc

3.若FindCatchBlock成功,将能够catch该Exception的ArtMethod,catch block在该method中的dex pc / quick code pc,quick frame,oat quick method header记录到 QuickExceptionHandler中,以便后续使用

4.找到的话,直接返回 false,不再继续 walk stack,所以可知,java call中,Exception会被离抛出Exception点最近的那个匹配的 catch block Catch住,外层的调用就 catch 不到

再看一下 ArtMethod 的 FindCatchBlock 函数:

uint32_t ArtMethod::FindCatchBlock(Handle exception_type,
                                   uint32_t dex_pc, bool* has_no_move_exception) {
  const DexFile::CodeItem* code_item = GetCodeItem();
  // Set aside the exception while we resolve its type.
  Thread* self = Thread::Current();
  StackHandleScope<1> hs(self);
  Handle exception(hs.NewHandle(self->GetException()));
  self->ClearException();
  // Default to handler not found.
  uint32_t found_dex_pc = DexFile::kDexNoIndex;
  // Iterate over the catch handlers associated with dex_pc.
  for (CatchHandlerIterator it(*code_item, 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?
    mirror::Class* iter_exception_type = GetClassFromTypeIndex(iter_type_idx, true /* resolve */);
    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 != DexFile::kDexNoIndex) {
    const Instruction* first_catch_instr =
        Instruction::At(&code_item->insns_[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;
}
这里就是前面说的,从当前函数的 try item中进行匹配。需要关注的是,如果匹配上了,但 catch handler中记录的 dex pc指向的第一个指令是 move-exception,那么 has_no_move_exception置为 false,也就是前面传递过来的 clear_exception置为 false,在FindCatch之后还会设置给 thread 的私有成员,以便后续使用。而一般情况下,catch block的第一个指令都是 move-exception,所以这个值一般是 被设置为 false。

move-exception指令的解释:

0D | move-exception vx  |  Move the exception objectreference thrown during a method invocation into vx. | 0D19 - move-exception v25

到这里,Find Catch Block的原理即实现都介绍完毕,主要在于要理解 dex 文件中记录的 try item list 和 catch handler list。由此也可以知道,每次发生Exception的时候,必然要访问dex file。

接下一节来分析catch情况和无catch情况下,发生 Exception后,执行流的走向。


2.2 跳转到 catch 块或者抛出异常

这里需要继续回到上面分析的 QuickDeliverException 函数:

void Thread::QuickDeliverException() {
  // Get exception from thread.
  ObjPtr exception = GetException();
  ....
  // 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_handler.UpdateInstrumentationStack();
  exception_handler.DoLongJump();
}
在前面一小节,我们已经分析完了 FindCatch,那么接下来执行流有两个分支,一个是找到了catch block,一个是没有找到 catch block,我们来看下这两种情况下,执行流是怎么跳转的。

在FindCatch之后的 UpdateInstrumentationStack函数,一般情况下,其里面的代码条件为假,不会执行。

真正的跳转就在 exception_handler.DoLongJump();函数了:

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(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();
}
其中   self_->ReleaseLongJumpContext(context_);是把 QuickExceptionhandler初始化是创建的 context设置给 thread(释放旧的,保存新的);

SetSP()是把前面记录的 catch block所在的栈顶SP设置到 context;SetPC是把 catch handler指向的 catch block的 quick code地址传递给 context 的PC;

SetArg0()是在,DeoptimizeStack()场景下才能用到,在当前场景用不到,一般情况下都是 0;

默认参数 smash_caller_saves为 true,所以会调用 SmashCallerSaves()函数:

void ArmContext::SmashCallerSaves() {
  // This needs to be 0 because we want a null/zero return value.
  gprs_[R0] = const_cast(&gZero);
  gprs_[R1] = const_cast(&gZero);
  gprs_[R2] = nullptr;
  gprs_[R3] = nullptr;

  fprs_[S0] = nullptr;
  fprs_[S1] = nullptr;
  fprs_[S2] = nullptr;
  fprs_[S3] = nullptr;
  fprs_[S4] = nullptr;
  fprs_[S5] = nullptr;
  fprs_[S6] = nullptr;
  fprs_[S7] = nullptr;
  fprs_[S8] = nullptr;
  fprs_[S9] = nullptr;
  fprs_[S10] = nullptr;
  fprs_[S11] = nullptr;
  fprs_[S12] = nullptr;
  fprs_[S13] = nullptr;
  fprs_[S14] = nullptr;
  fprs_[S15] = nullptr;
}

功能就是把一些 caller save寄存器初始化为 nullptr。

我们可以看到这里,  gprs_[R0] = const_cast(&gZero);,而 static constexpr uint64_t gZero = 0;所以,在任何情况下R0/R1都是指向 gZero了。

且前面我们设置的 SP,PC并没有被清空。接下来调用 context 的DoLongJump函数:

void ArmContext::DoLongJump() {
  uintptr_t gprs[kNumberOfCoreRegisters];
  uint32_t fprs[kNumberOfSRegisters];
  for (size_t i = 0; i < kNumberOfCoreRegisters; ++i) {
    gprs[i] = gprs_[i] != nullptr ? *gprs_[i] : ArmContext::kBadGprBase + i;
  }
  for (size_t i = 0; i < kNumberOfSRegisters; ++i) {
    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : ArmContext::kBadFprBase + i;
  }
  DCHECK_EQ(reinterpret_cast(Thread::Current()), gprs[TR]);
  art_quick_do_long_jump(gprs, fprs);
}

  enum {
    kBadGprBase = 0xebad6070,
    kBadFprBase = 0xebad8070,
  };

可以看到,在跳转前的参数准备中,除了我们设置的 SP(指向 catch block所在函数的 sp) 和 PC (指向 catch block的第一条指令的 quick code 地址),R0(指向gZero地址,gZero的值可能被修改),R1(指向 gZero地址),其他都会被设置为非法的地址。

然后就跳转到 art_quick_do_long_jump函数:

    /*
     * On entry r0 is uint32_t* gprs_ and r1 is uint32_t* fprs_
     */
ARM_ENTRY art_quick_do_long_jump
    vldm r1, {s0-s31}     @ load all fprs from argument fprs_
    ldr  r2, [r0, #60]    @ r2 = r15 (PC from gprs_ 60=4*15)
    ldr  r14, [r0, #56]   @ (LR from gprs_ 56=4*14)
    add  r0, r0, #12      @ increment r0 to skip gprs_[0..2] 12=4*3
    ldm  r0, {r3-r13}     @ load remaining gprs from argument gprs_
    ldr  r0, [r0, #-12]   @ load r0 value
    mov  r1, #0           @ clear result register r1
    bx   r2               @ do long jump
END art_quick_do_long_jump
依次解释下:

第一条指令:把r1指向的内存中连续的 32*8 byte数据 load 到 s0-s30

第二条指令:由于 PC保存在 gprs_数组中,对应的寄存器是 r15,把它拿出来放到 r2

第三条指令:保存 LR(r14),这里的  LR应该是 ArmContext::kBadGprBase + 14 吧

第四条指令:跳过 r0,r1,r2

第五条指令:从 gprs_[3] 开始的 11*8 byte数据 load 到 r3-r13

第六条指令:保存 gprs_[R0] 到 r0,作为第一个参数

第七条指令:清空返回值寄存器

第八条指令:跳转到 r2,即 PC (catch block quick code 地址)

我们看下当前例子中,应该跳转到哪:

JAVA CODE:

    public void test() {
        try {
            delete();
        } catch (Exception e) {
            System.out.println("catched :" +e);
        }
    }
DEX CODE:

  3: void Hello.test() (dex_method_idx=3)
    DEX CODE:
      0x0000: 6e10 0100 0400            | invoke-virtual {v4}, void Hello.delete() // method@1
      0x0003: 0e00                      | return-void
      0x0004: 0d00                      | move-exception v0
      0x0005: 6201 0100                 | sget-object  v1, Ljava/io/PrintStream; java.lang.System.out // field@1
      0x0007: 2202 0600                 | new-instance v2, java.lang.StringBuilder // type@6
      0x0009: 7010 0700 0200            | invoke-direct {v2}, void java.lang.StringBuilder.() // method@7
      0x000c: 1a03 1100                 | const-string v3, "catched :" // string@17
      0x000e: 6e20 0900 3200            | invoke-virtual {v2, v3}, java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) // method@9
      0x0011: 0c02                      | move-result-object v2
      0x0012: 6e20 0800 0200            | invoke-virtual {v2, v0}, java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.Object) // method@8
      0x0015: 0c00                      | move-result-object v0
      0x0016: 6e10 0a00 0000            | invoke-virtual {v0}, java.lang.String java.lang.StringBuilder.toString() // method@10
      0x0019: 0c00                      | move-result-object v0
      0x001a: 6e20 0400 0100            | invoke-virtual {v1, v0}, void java.io.PrintStream.println(java.lang.String) // method@4
      0x001d: 28e6                      | goto -26
QUICK CODE:

    CODE: (code_offset=0x000011f4 size_offset=0x000011f0 size=360)...
      0x000011f4: d1400bf0  sub x16, sp, #0x2000 (8192)
      0x000011f8: b940021f  ldr wzr, [x16]
        StackMap [native_pc=0x11fc] (dex_pc=0x0, native_pc_offset=0x8, dex_register_map_offset=0xffffffff, inline_info_offset=0xffffffff, register_mask=0x0, stack_mask=0b00000000000000000000000000000)
      0x000011fc: f81a0fe0  str x0, [sp, #-96]!
      0x00001200: a903d7f4  stp x20, x21, [sp, #56]
      0x00001204: a904dff6  stp x22, x23, [sp, #72]
      0x00001208: f9002ffe  str lr, [sp, #88]
      0x0000120c: b9006be1  str w1, [sp, #104]
      0x00001210: 79400270  ldrh w16, [tr] ; state_and_flags
      0x00001214: 350007b0  cbnz w16, #+0xf4 (addr 0x1308)
      0x00001218: aa0003f4  mov x20, x0
      0x0000121c: aa0103f5  mov x21, x1
      0x00001220: b9400020  ldr w0, [x1]
      0x00001224: f9417000  ldr x0, [x0, #736]
      0x00001228: f940181e  ldr lr, [x0, #48]
      0x0000122c: d63f03c0  blr lr
        StackMap [native_pc=0x1230] (dex_pc=0x0, native_pc_offset=0x3c, dex_register_map_offset=0x0, inline_info_offset=0xffffffff, register_mask=0x200000, stack_mask=0b00100000000000000000000000000)
          v4: in register (21)  [entry 0]
      0x00001230: 14000031  b #+0xc4 (addr 0x12f4)

        StackMap [native_pc=0x1234] (dex_pc=0x4, native_pc_offset=0x40, dex_register_map_offset=0xffffffff, inline_info_offset=0xffffffff, register_mask=0x0, stack_mask=0b00000000000000000000000000000)
      0x00001234: b9408a74  ldr w20, [tr, #136] ; exception
      0x00001238: b9008a7f  str wzr, [tr, #136] ; exception
      0x0000123c: f94003e1  ldr x1, [sp]
      0x00001240: f9401020  ldr x0, [x1, #32]
      0x00001244: b9401c00  ldr w0, [x0, #28]
      0x00001248: 340006a0  cbz w0, #+0xd4 (addr 0x131c)

      0x0000124c: 1101e010  add w16, w0, #0x78 (120)
      0x00001250: 88dffe10  ldar w16, [x16]
      0x00001254: 71002a1f  cmp w16, #0xa (10)
      0x00001258: 5400062b  b.lt #+0xc4 (addr 0x131c)
      0x0000125c: b942f415  ldr w21, [x0, #756]
      0x00001260: 528000c0  mov w0, #0x6
      0x00001264: f940de7e  ldr lr, [tr, #440] ; pAllocObject
      0x00001268: d63f03c0  blr lr
      .....
在这个例子中,catch handler中记录的dex pc是0x4 (前面第二节中贴的哪个图片的最后一行记录着这个值),而其对应的 quick code地址是:0x00001234,所以上面 art_quick_do_long_jump 函数最后一条指令 bx r2就是跳转到 0x00001234处继续执行。

在0x00001234处,首先从 thread中取出 Exception 到 w20,然后把thread中Exception清空,在后续调用中会用到这个 Exception。

      0x00001268: d63f03c0  blr lr

        StackMap [native_pc=0x126c] (dex_pc=0x7, native_pc_offset=0x78, dex_register_map_offset=0x2, inline_info_offset=0xffffffff, register_mask=0x300000, stack_mask=0b00100000000000000000000000000)
          v0: in register (20)  [entry 1]
          v1: in register (21)  [entry 0]
          v4: in stack (104)    [entry 2]
      0x0000126c: aa0003e1  mov x1, x0
      0x00001270: aa0103f6  mov x22, x1
      0x00001274: 580006de  ldr lr, pc+216 (addr 0x134c) (0x70695d34 / 1885953332)
      0x00001278: 580006e0  ldr x0, pc+220 (addr 0x1354) (0x6f51a820 / 1867622432)
      0x0000127c: d63f03c0  blr lr

        StackMap [native_pc=0x1280] (dex_pc=0x9, native_pc_offset=0x8c, dex_register_map_offset=0x5, inline_info_offset=0xffffffff, register_mask=0x700000, stack_mask=0b00100000000000000000000000000)
          v0: in register (20)  [entry 1]
          v1: in register (21)  [entry 0]
          v2: in register (22)  [entry 3]
          v4: in stack (104)    [entry 2]
      0x00001280: b0000002  adrp x2, #+0x1000 (addr 0x2000)
      0x00001284: b940c442  ldr w2, [x2, #196]
      0x00001288: 34000562  cbz w2, #+0xac (addr 0x1334)
      0x0000128c: aa1603e1  mov x1, x22
      0x00001290: aa0203f7  mov x23, x2
      0x00001294: b9400020  ldr w0, [x1]
      0x00001298: f9427c00  ldr x0, [x0, #1272]
      0x0000129c: f940181e  ldr lr, [x0, #48]
      0x000012a0: d63f03c0  blr lr

      ******************************************************
        StackMap [native_pc=0x12a4] (dex_pc=0xe, native_pc_offset=0xb0, dex_register_map_offset=0x8, inline_info_offset=0xffffffff, register_mask=0xf00000, stack_mask=0b00100000000000000000000000000)
          v0: in register (20)  [entry 1]
          v1: in register (21)  [entry 0]
          v2: in register (22)  [entry 3]
          v3: in register (23)  [entry 4]
          v4: in stack (104)    [entry 2]
      0x000012a4: aa1403e2  mov x2, x20     ;把Exception作为第三个参数传递给StringBuilder.apend(java.lang.String)函数
      0x000012a8: aa0003e1  mov x1, x0      ;此时x0是 StringBuilder对象,把他作为 apend函数低2个参数
      0x000012ac: aa0103f6  mov x22, x1
      0x000012b0: b9400020  ldr w0, [x1]    ;获取 StringBuilder对象class

        StackMap [native_pc=0x12b4] (dex_pc=0x12, native_pc_offset=0xc0, dex_register_map_offset=0xb, inline_info_offset=0xffffffff, register_mask=0x0, stack_mask=0b00000000000000000000000000000)
          v0: in register (20)  [entry 1]
          v1: in register (21)  [entry 0]
          v2: in register (0)   [entry 5]
          v3: in register (23)  [entry 4]
          v4: in stack (104)    [entry 2]
      0x000012b4: f9427800  ldr x0, [x0, #1264]  ;找到 StringBuilder的 append(java.lang.Object)函数,作为第一个参数
      0x000012b8: f940181e  ldr lr, [x0, #48]    ;获取该函数 quick code入口
      0x000012bc: d63f03c0  blr lr               ;跳转到quick code执行
      ******************************************************
      ....
这样,第一个跳转分支,跳转到 catch block的执行就完成了,且在catch block中使用这个 Exception对象。

到这里,还没有看到找不到匹配的 catch块情况下的执行流,明天继续分析

还有,貌似少了一个 finally啊。。。。







你可能感兴趣的:(Android虚拟机,ART,异常处理,throw-catch)