ART异常处理机制(3)

来源:互联网 发布:去哪里投诉淘宝网 编辑:程序博客网 时间:2024/06/07 00:54

在本篇介绍 NullPointerException在 ART种的实现。本篇基础:ART异常处理机制(1) - SIGSEGV信号的拦截和处理

在 ART异常处理机制1 中,我们已经知道了 ART会注册信号处理函数,优先尝试处理 SIGSEGV信号。ART中总共又 4 种 handler 享有优先处理 SIGSEGV信号的权利。

在 StackOverflowHandler 发现处理不了当前的 SIGSEGV后,再交给 NullPointerHandler 尝试处理。本篇主要分析下 NullPointerHandler 以及 NullPointerException 的检测以及抛出。

NullPointerHandler 的实现就在其Action函数当中:

bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info, void* context) {  if (!IsValidImplicitCheck(info)) {    return false;  }  // The code that looks for the catch location needs to know the value of the  // ARM PC at the point of call.  For Null checks we insert a GC map that is immediately after  // the load/store instruction that might cause the fault.  However the mapping table has  // the low bits set for thumb mode so we need to set the bottom bit for the LR  // register in order to find the mapping.  // Need to work out the size of the instruction that caused the exception.  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);  uint8_t* ptr = reinterpret_cast<uint8_t*>(sc->arm_pc);  uint32_t instr_size = GetInstructionSize(ptr);  uintptr_t gc_map_location = (sc->arm_pc + instr_size) | 1;  // Push the gc map location to the stack and pass the fault address in LR.  sc->arm_sp -= sizeof(uintptr_t);  *reinterpret_cast<uintptr_t*>(sc->arm_sp) = gc_map_location;  sc->arm_lr = reinterpret_cast<uintptr_t>(info->si_addr);  sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception_from_signal);  // Pass the faulting address as the first argument of  // art_quick_throw_null_pointer_exception_from_signal.  VLOG(signals) << "Generating null pointer exception";  return true;}

从前面几部分的分析我们可以知道一个ART虚拟机进程收到 SIGSEGV之后,会先经过 StackOverflowHandler 尝试能否处理这个SIGSEGV,假如StackOverflowHandler没有能够处理它的话,那么接下来一个想要尝试身手的就是NullPointerHandler了。与前面的 StackOverflowHandler 类似,它总要先判断下自己这两把刷子能不能刷的掉这个SIGSEGV,否则上来一通乱刷岂不乱套。

这个判断就是通过 IsValidImplicitCheck(info)这个函数来进行的:

  static bool IsValidImplicitCheck(siginfo_t* siginfo) {    // Our implicit NPE checks always limit the range to a page.    // Note that the runtime will do more exhaustive checks (that we cannot    // reasonably do in signal processing code) based on the dex instruction    // faulting.    return CanDoImplicitNullCheckOn(reinterpret_cast<uintptr_t>(siginfo->si_addr));  }
这个函数里有说明:隐式的Null Check 限制在一个page之内.

什么意思呢?接着看下去,其调用 CanDoImplicitNullCheckOn函数来判断,其参数 siginfo->si_addr 是指本次发生segment fult 时的 fault_addr,通过 SIGSEGV 信号的 siginfo_t 来传递。下面是其声明:

typedef struct siginfo {int si_signo;int si_errno;int si_code;        ....union {                ...struct {void __user *_addr; /* faulting insn/memory ref. */#ifdef __ARCH_SI_TRAPNOint _trapno;/* TRAP # which caused the signal */#endifshort _addr_lsb; /* LSB of the reported address */} _sigfault;                ....} _sifields;        ....} __ARCH_SI_ATTRIBUTES siginfo_t;#define si_addr_sifields._sigfault._addr
接下来真正判断的函数CanDoImplicitNullCheckOn就使用这个 fault_addr 来判断能否处理这个 SIGSEGV:

// Returns whether the given memory offset can be used for generating// an implicit null check.static inline bool CanDoImplicitNullCheckOn(uintptr_t offset) {  return offset < kPageSize;}
可以看到,判断的情况是,当 fault_addr 小于 4 KB 时,就认为需要处理这个SIGSEGV。需要关注的时,在此之前,已经经过 IsInGeneratedCode()函数判断过当前这个segment fault是否是发生在 generated code。当这两个条件都满足时,就判定为NullPointerException.

那么问题来了:

1.IsInGeneratedCode判断,说明了什么问题 ?

答:说明发生在 generated code中的 SIGSEGV 才会进行隐式的 NullPointer check,那么Interpreter模式的NullPointer Check应该就是另外一回事了。

2.为什么是 4KB,这个数字怎么来的 ?

答:目前还没不知道,但可以先推测一下。一般发生一个 NullPointerException 时,是我们准备访问一个对象的成员变量或者调用其成员函数时抛出的这个Exception。其一,访问成员变量时发生,因为成员变量在内存中的存放是紧挨着该对象的Object指针的,所以这个 4 KB 的意思是,Object 的大小一定是小于 4KB的?所以当发生NullPointer时,由于 Object 地址是0x0,访问成员时的 offset小于 4K,所以fault addr 一定小于 4K?感觉有点问题,Object大小应该可以大于 4KB 的,后续探究。其二,调用成员函数的情况,执行一个java对象的成员函数是这么个流程,先从对象地址处(class的对应对象的offset是0)取出其class地址,然后从改地址的一定偏移取出 ArtMethod地址,然后再从ArtMethod地址偏移几十个字节,获得对应函数的 quick code入口,然后跳转过去执行;那么如果当前object是null,在第一步从对象地址处取出 class 地址时,就会触发 segment fault,此时的 fault_addr = 0 (null) + 0 (offset) =0,显然是小于4K 的。

这两个问题可以留着思考与调查。我们现在假设当前 SIGSEGV 确实是 NullPointer 触发的(fault_addr < 4K),看下其接下来会怎么处理:

从NullPointerHandler::Action我们看到,接下来计算出 gc_map_location,并push到栈上,把 fault_addr 赋值给 lr,然后跳转
到 art_quick_throw_null_pointer_exception_from_signal 函数,准备抛出一个 NullPointer 异常:

ENTRY art_quick_throw_null_pointer_exception_from_signal    // The fault handler pushes the gc map address, i.e. "return address", to stack    // and passes the fault address in LR. So we need to set up the CFI info accordingly.    .cfi_def_cfa_offset __SIZEOF_POINTER__    .cfi_rel_offset lr, 0    push {r0-r12}                   @ 13 words of callee saves and args; LR already saved.    .cfi_adjust_cfa_offset 52    .cfi_rel_offset r0, 0    .cfi_rel_offset r1, 4    .cfi_rel_offset r2, 8    .cfi_rel_offset r3, 12    .cfi_rel_offset r4, 16    .cfi_rel_offset r5, 20    .cfi_rel_offset r6, 24    .cfi_rel_offset r7, 28    .cfi_rel_offset r8, 32    .cfi_rel_offset r9, 36    .cfi_rel_offset r10, 40    .cfi_rel_offset r11, 44    .cfi_rel_offset ip, 48    @ save all registers as basis for long jump context    SETUP_SAVE_EVERYTHING_FRAME_CORE_REGS_SAVED r1    mov r0, lr                      @ pass the fault address stored in LR by the fault handler.    mov r1, r9                      @ pass Thread::Current    bl  artThrowNullPointerExceptionFromSignal  @ (Thread*)END art_quick_throw_null_pointer_exception_from_signal
实际跳转到 artThrowNullPointerExceptionFromSignal:

// Installed by a signal handler to throw a NPE exception.extern "C" NO_RETURN void artThrowNullPointerExceptionFromSignal(uintptr_t addr, Thread* self)    REQUIRES_SHARED(Locks::mutator_lock_) {  ScopedQuickEntrypointChecks sqec(self);  ThrowNullPointerExceptionFromDexPC(/* check_address */ true, addr);  self->QuickDeliverException();}
这个函数的第一个参数 addr 是 fault addr,所以接下来就根据 addr来创建一个 NullPointerException,然后DeliverException即可.在 ThrowNullPointerExceptionFromDexPC 中:

  1. 找到当前正在执行的 ArtMethod,以及正在执行的 dex instruction
  2. 再次检测是否是真正的NullPointerException:再次检测 addr是否小于4K,以及针对不同数据访问的不同检测,比如IGET,IPUT指令,会检测要访问的成员的field_offset 是否与 fault addr相等
  3. 检测成功后,抛出NullPointerException,ErrorMessage根据dex instruction 类别来决定,比如instruction是在调用函数,message描述调用函数时发生了NullPointer;如果在访问对象成员,也给出相应的 Message
除了这种在从 Object 访问成员,或调用函数时由于NullPointer 触发的 SIGSEGV,从而在信号处理过程中被NullPointerHandler判断为 NullPointerException 的这种隐式的 Null Check,还有其他的 Null Check:

1.编译器在generated code中安插的 Null Check,举个例子:

    public Account[] getSharedAccounts(UserHandle user) {        try {            return mService.getSharedAccountsAsUser(user.getIdentifier());        } catch (RemoteException re) {            throw re.rethrowFromSystemServer();        }    }
编译到 OAT文件中如下:

  48: android.accounts.Account[] android.accounts.AccountManager.getSharedAccounts(android.os.UserHandle) (dex_method_idx=608)    DEX CODE:      0x0000: 5431 f914                | iget-object v1, v3, Landroid/accounts/IAccountManager; android.accounts.AccountManager.mService // field@5369      0x0002: 6e10 eec8 0400           | invoke-virtual {v4}, int android.os.UserHandle.getIdentifier() // method@51438      0x0005: 0a02                     | move-result v2      0x0006: 7220 cd03 2100           | invoke-interface {v1, v2}, android.accounts.Account[] android.accounts.IAccountManager.getSharedAccountsAsUser(int) // method@973      0x0009: 0c01                     | move-result-object v1      0x000a: 1101                     | return-object v1      0x000b: 0d00                     | move-exception v0      0x000c: 6e10 1ec7 0000           | invoke-virtual {v0}, java.lang.RuntimeException android.os.RemoteException.rethrowFromSystemServer() // method@50974      0x000f: 0c01                     | move-result-object v1      0x0010: 2701                     | throw v1
    CODE: (code_offset=0x01ac6ca5 size_offset=0x01ac6ca0 size=154)...      0x01ac6ca4: f5ad5c00sub     r12, sp, #8192      0x01ac6ca8: f8dcc000ldr.w   r12, [r12, #0]        StackMap [native_pc=0x1ac6cad] (dex_pc=0x0, native_pc_offset=0x8, dex_register_map_offset=0xffffffff, inline_info_offset=0xffffffff, register_mask=0x0, stack_mask=0b0000000000000000000000)      0x01ac6cac: e92d41e0push    {r5, r6, r7, r8, lr}      0x01ac6cb0: b087    sub     sp, sp, #28      0x01ac6cb2: 9000    str     r0, [sp, #0]      0x01ac6cb4: 910d    str     r1, [sp, #52]      0x01ac6cb6: 920e    str     r2, [sp, #56]      0x01ac6cb8: f8b9c000ldrh.w  r12, [r9, #0]  ; state_and_flags      0x01ac6cbc: f1bc0f00cmp.w   r12, #0      0x01ac6cc0: d12c    bne     +88 (0x01ac6d1c)      0x01ac6cc2: 698d    ldr     r5, [r1, #24]      0x01ac6cc4: b392    cbz     r2, +100 (0x01ac6d2c)      0x01ac6cc6: 460e    mov     r6, r1      0x01ac6cc8: 4611    mov     r1, r2      0x01ac6cca: 4617    mov     r7, r2      0x01ac6ccc: 6808    ldr     r0, [r1, #0]      0x01ac6cce: f8d000b8ldr.w   r0, [r0, #184]      0x01ac6cd2: f8d0e020ldr.w   lr, [r0, #32]      0x01ac6cd6: 47f0    blx     lr        StackMap [native_pc=0x1ac6cd9] (dex_pc=0x2, native_pc_offset=0x34, dex_register_map_offset=0x0, inline_info_offset=0xffffffff, register_mask=0xe0, stack_mask=0b0000000110000000000000)          v1: in register (5)[entry 0]          v3: in register (6)[entry 1]          v4: in register (7)[entry 2]      0x01ac6cd8: b36d    cbz     r5, +90 (0x01ac6d36)      0x01ac6cda: 4629    mov     r1, r5      0x01ac6cdc: 4602    mov     r2, r0      0x01ac6cde: 4680    mov     r8, r0      0x01ac6ce0: f2403ccdmovw    r12, #973      0x01ac6ce4: 6808    ldr     r0, [r1, #0]      0x01ac6ce6: f8d00084ldr.w   r0, [r0, #132]      0x01ac6cea: 6b40    ldr     r0, [r0, #52]      0x01ac6cec: f8d0e020ldr.w   lr, [r0, #32]      0x01ac6cf0: 47f0    blx     lr        StackMap [native_pc=0x1ac6cf3] (dex_pc=0x6, native_pc_offset=0x4e, dex_register_map_offset=0x3, inline_info_offset=0xffffffff, register_mask=0xe0, stack_mask=0b0000000110000000000000)          v1: in register (5)[entry 0]          v2: in register (8)[entry 3]          v3: in register (6)[entry 1]          v4: in register (7)[entry 2]      0x01ac6cf2: e010    b       +32 (0x01ac6d16)        StackMap [native_pc=0x1ac6cf5] (dex_pc=0xb, native_pc_offset=0x50, dex_register_map_offset=0xffffffff, inline_info_offset=0xffffffff, register_mask=0x0, stack_mask=0b0000000000000000000000)      0x01ac6cf4: f8d91084ldr.w   r1, [r9, #132]  ; exception      0x01ac6cf8: f04f0c00mov.w   r12, #0      0x01ac6cfc: f8c9c084str.w   r12, [r9, #132]      0x01ac6d00: 460d    mov     r5, r1      0x01ac6d02: 6808    ldr     r0, [r1, #0]      0x01ac6d04: f8d000e8ldr.w   r0, [r0, #232]      0x01ac6d08: f8d0e020ldr.w   lr, [r0, #32]      0x01ac6d0c: 47f0    blx     lr        StackMap [native_pc=0x1ac6d0f] (dex_pc=0xc, native_pc_offset=0x6a, dex_register_map_offset=0x6, inline_info_offset=0xffffffff, register_mask=0x20, stack_mask=0b0000000110000000000000)          v0: in register (5)[entry 0]          v3: in stack (52)[entry 4]          v4: in stack (56)[entry 5]      0x01ac6d0e: 4606    mov     r6, r0      0x01ac6d10: f8d9e2acldr.w   lr, [r9, #684]  ; pDeliverException      0x01ac6d14: 47f0    blx     lr        StackMap [native_pc=0x1ac6d17] (dex_pc=0x10, native_pc_offset=0x72, dex_register_map_offset=0x9, inline_info_offset=0xffffffff, register_mask=0x60, stack_mask=0b0000000110000000000000)          v0: in register (5)[entry 0]          v1: in register (6)[entry 1]          v3: in stack (52)[entry 4]          v4: in stack (56)[entry 5]      0x01ac6d16: b007    add     sp, sp, #28      0x01ac6d18: e8bd81e0pop     {r5, r6, r7, r8, pc}      0x01ac6d1c: 9103    str     r1, [sp, #12]      0x01ac6d1e: 9204    str     r2, [sp, #16]      0x01ac6d20: f8d9e2a8ldr.w   lr, [r9, #680]  ; pTestSuspend      0x01ac6d24: 47f0    blx     lr        StackMap [native_pc=0x1ac6d27] (dex_pc=0x0, native_pc_offset=0x82, dex_register_map_offset=0xc, inline_info_offset=0xffffffff, register_mask=0x0, stack_mask=0b0000000110000000011000)          v3: in stack (12)[entry 6]          v4: in stack (16)[entry 7]      0x01ac6d26: 9903    ldr     r1, [sp, #12]      0x01ac6d28: 9a04    ldr     r2, [sp, #16]      0x01ac6d2a: e7ca    b       -108 (0x01ac6cc2)      0x01ac6d2c: 9103    str     r1, [sp, #12]      0x01ac6d2e: 9204    str     r2, [sp, #16]      0x01ac6d30: f8d9e2bcldr.w   lr, [r9, #700]  ; pThrowNullPointer      0x01ac6d34: 47f0    blx     lr        StackMap [native_pc=0x1ac6d37] (dex_pc=0x2, native_pc_offset=0x92, dex_register_map_offset=0xe, inline_info_offset=0xffffffff, register_mask=0x20, stack_mask=0b0000000110000000011000)          v1: in register (5)[entry 0]          v3: in stack (12)[entry 6]          v4: in stack (16)[entry 7]      0x01ac6d36: 9003    str     r0, [sp, #12]      0x01ac6d38: f8d9e2bcldr.w   lr, [r9, #700]  ; pThrowNullPointer      0x01ac6d3c: 47f0    blx     lr        StackMap [native_pc=0x1ac6d3f] (dex_pc=0x6, native_pc_offset=0x9a, dex_register_map_offset=0x11, inline_info_offset=0xffffffff, register_mask=0xe0, stack_mask=0b0000000110000000000000)          v1: in register (5)[entry 0]          v2: in stack (12)[entry 6]          v3: in register (6)[entry 1]          v4: in register (7)[entry 2]

可以看到这个函数被编译出来的 quick code中,由两处显示的 Null Check,分别在下面两处代码处:
0x01ac6cc4: b392     cbz     r2, +100 (0x01ac6d2c) 
0x01ac6cd8: b36d     cbz     r5, +90 (0x01ac6d36)
第一处,是检查参数 user是否为 Null,因为要调用 user.getIdentifier(),如果user 是Null,则会跳转到 0x01ac6d2c处:
      0x01ac6d2c: 9103    str     r1, [sp, #12]      0x01ac6d2e: 9204    str     r2, [sp, #16]      0x01ac6d30: f8d9e2bcldr.w   lr, [r9, #700]  ; pThrowNullPointer      0x01ac6d34: 47f0    blx     lr
这里会先保存 this,user(此时是 null)到栈上,然后调用 thread->pThrowNullPointer 函数抛出一个NullPointer异常。
第二处,r5看起来是mService,因为要调用 mService.getSharedAccountsAsUser(),所以检测的 r5是 null,会跳转到 0x01ac6d36 处:
      0x01ac6d36: 9003    str     r0, [sp, #12]      0x01ac6d38: f8d9e2bcldr.w   lr, [r9, #700]  ; pThrowNullPointer      0x01ac6d3c: 47f0    blx     lr
这里保存的 user.getIdentifier()的返回值到栈上,然后也调用 thread->pThrowNullPointer 函数来抛出一个 NullPointer异常。
而代码中:
  qpoints->pThrowNullPointer = art_quick_throw_null_pointer_exception;
所以实际是跳转到  art_quick_throw_null_pointer_exception 函数来抛出异常的。
(补充:qpoints->pXXX 都是在ART  thread 创建后,Init()过程中初始化的,被赋值为 art_quick_throw_null_pointer_exception,而在quick code中,是从thread 的一定 offset 获取 qpoints->pXXX 的值,然后跳转过去,也就是说编译器在编译的时候病不知道具体实现函数的地址)
    /*     * Called by managed code to create and deliver a NullPointerException.     */NO_ARG_RUNTIME_EXCEPTION_SAVE_EVERYTHING art_quick_throw_null_pointer_exception, artThrowNullPointerExceptionFromCode
又会跳转到 artThrowNullPointerExceptionFromCode 函数:
// Called by generated code to throw a NPE exception.extern "C" NO_RETURN void artThrowNullPointerExceptionFromCode(Thread* self)    REQUIRES_SHARED(Locks::mutator_lock_) {  ScopedQuickEntrypointChecks sqec(self);  // We come from an explicit check in the generated code. This path is triggered  // only if the object is indeed null.  ThrowNullPointerExceptionFromDexPC(/* check_address */ false, 0U);  self->QuickDeliverException();}
接下来的事情就和上面的 artThrowNullPointerExceptionFromSignal() 函数基本相同了,只不过此处调用 ThrowNullPointerExceptionFromDexPC()函数时传递的参数是 false 和 0,也就是不用经过再次检测确认是否是 NullPointerException了,因为这个是显示的 Null Check,是明确的NPE,所以才进行Throw Exception的。
目前我们能确认的就是 编译器会在编译java 函数时适当的插入显示的 Null Check函数,什么是适当?牵扯到编译规则,后续分析编译器的时候再详细分析。
2.已提到的两种都是在 generated code中发生/检测的 Null Pointer Exception,所以代码如果解释执行的话,定然会在解释执行的过程进行 Null Check,如下这个三个函数里会直接调用 ThrowNullPointerExceptionFromDexPC()来抛出异常:
DoIGetQuick():在所有的 iget_xxx指令会调用这个函数,在这个函数内检查符合会跳转到 ThrowNullPointerExceptionFromDexPC()
DoIPutQuick():在所有的 iput_xxx指令会调用这个函数,在这个函数内检查符合会跳转到 ThrowNullPointerExceptionFromDexPC()
ThrowNullPointerExceptionFromInterpreter():这个函数只在 monitor_enter/exit,array_length,aget_xxx,aput_xxx,i/a_get_object 这些指令的时候才会判断并跳转到 ThrowNullPointerExceptionFromDexPC() 函数。
上面这些看起来都是 field 访问时的 NPE 检测和抛出。而函数调用时的 Null Check则比较隐秘,找了好久才找到:
dex 指令中的函数调用指令都是: invoke-virtual / invoke-direct / invoke-static 等,查找这些指令的实现,看在这个实现过程中是否进行了 Null检查:
看下  op_invoke_virtual.S
%include "arm/invoke.S" { "helper":"MterpInvokeVirtual" }
在 invoke.S中:
%default { "helper":"UndefinedInvokeHandler" }    /*     * Generic invoke handler wrapper.     */    /* op vB, {vD, vE, vF, vG, vA}, class@CCCC */    /* op {vCCCC..v(CCCC+AA-1)}, meth@BBBB */    .extern $helper    EXPORT_PC    mov     r0, rSELF    add     r1, rFP, #OFF_FP_SHADOWFRAME    mov     r2, rPC    mov     r3, rINST    bl      $helper    cmp     r0, #0    beq     MterpException    FETCH_ADVANCE_INST 3    bl      MterpShouldSwitchInterpreters    cmp     r0, #0    bne     MterpFallback    GET_INST_OPCODE ip    GOTO_OPCODE ip
所以这里会通过 bl $helper 指令跳转到 MterpInvokeVirtual() 函数,MterpInvokeVirtual() 的实现如下:
extern "C" size_t MterpInvokeVirtual(Thread* self,                                     ShadowFrame* shadow_frame,                                     uint16_t* dex_pc_ptr,                                     uint16_t inst_data)    REQUIRES_SHARED(Locks::mutator_lock_) {  JValue* result_register = shadow_frame->GetResultRegister();  const Instruction* inst = Instruction::At(dex_pc_ptr);  return DoFastInvoke<kVirtual>(      self, *shadow_frame, inst, inst_data, result_register);}
在 MterpInvokeVirtual 中又调用 DoFastInvoke<kVirtuall>() 函数:
template<InvokeType type>static inline bool DoFastInvoke(Thread* self,                                ShadowFrame& shadow_frame,                                const Instruction* inst,                                uint16_t inst_data,                                JValue* result) {  const uint32_t method_idx = inst->VRegB_35c();  const uint32_t vregC = inst->VRegC_35c();  ObjPtr<mirror::Object> receiver = (type == kStatic)      ? nullptr      : shadow_frame.GetVRegReference(vregC);  ArtMethod* sf_method = shadow_frame.GetMethod();  ArtMethod* const called_method = FindMethodFromCode<type, false>(      method_idx, &receiver, sf_method, self);  ....}
在 DoFastInvoke() 函数中,调用 FindMethodFormCode<kVirtual, false>() 函数来查找 invoke-virtual 想要调用的函数:
template<InvokeType type, bool access_check>inline ArtMethod* FindMethodFromCode(uint32_t method_idx,                                     ObjPtr<mirror::Object>* this_object,                                     ArtMethod* referrer,                                     Thread* self) {   ....  // Next, null pointer check.  if (UNLIKELY(*this_object == nullptr && type != kStatic)) {    if (UNLIKELY(resolved_method->GetDeclaringClass()->IsStringClass() &&                 resolved_method->IsConstructor())) {      // Hack for String init:      //      // We assume that the input of String.<init> in verified code is always      // an unitialized reference. If it is a null constant, it must have been      // optimized out by the compiler. Do not throw NullPointerException.    } else {      // Maintain interpreter-like semantics where NullPointerException is thrown      // after potential NoSuchMethodError from class linker.      ThrowNullPointerExceptionForMethodAccess(method_idx, type);      return nullptr;  // Failure.    }  }  ....}
在查找的过程中,就有个 Null check:假如当前的 this object 是 null,且这个invoke指令不是 invoke-static指令,且不是 String.<init>函数,那么就会抛出一个 NullPointerException。

到这里,我们知道了:

  1. 隐式的 Null Check,通过 artThrowNullPointerExceptionFromSignal 来抛出 NPE;
  2. 编译器安插到 generated code中的显式的 Null Check,通过 artThrowNullPointerExceptionFromCode 来抛出 NPE
  3. 在 Interpreter 执行过程中的 Null Check,通过 ThrowNullPointerExceptionFromDexPC(field访问时) 和 ThrowNullPointerExceptionForMethodAccess() 这两个函数来抛出 NPE

仍然遗留的问题有:

  1. Implicit Null Check 时的 4K 这个数字到底是怎么来的?
  2. 在 generated code中 Null Check的安插点并不多,那么显式的 Null Check 安插的规则是怎样的
  3. 根据 qpoints->pThrowNullPointer 引出的 thread quick entry points的知识,有兴趣的读者可以研究下
在 ART 中 NullPointerException 的研究先到此为止,另外java 的 ArrayIndexOutOfBoundsException 在generated code中是通过 pThrowArrayBounds抛出的。


原创粉丝点击