【个人笔记四】ART系统执行类方法流程分析

本文详细分析了Android ART系统中类方法的执行流程,包括入口点初始化、解释器与本地机器指令的切换、Dex Cache的使用、类方法的延迟链接等关键环节。通过讲解函数如artInterpreterToCompiledCodeBridge、GetQuickToInterpreterBridge等,揭示了ART如何在运行时决定方法的执行方式,并解释了Dex Cache在方法调用中的重要作用,以及类方法的调用如何通过Dex Cache进行延时解析和优化。
摘要由CSDN通过智能技术生成

接上一篇文章:【个人笔记三】ART系统类和方法加载分析

  • 在ART上用YAHFA、Legend以及一个java层实现的Andix: http://weishu.me/2017/03/20/dive-into-art-hello-world/,发现除了framework层的类(如telephonymanager)和应用中的类有效外,对于java核心库的类(如IOBridge和Class等)的hook都无效,所以我就以telephonymanager和IOBridge这两个类为例,试图从编译解析加载等角度分析这两者的区别以及造成hook结果不同的原因,如果有大牛能指点一二的话,不胜感激。。。

上一章分析了如何链接OAT文件中的类和方法到内存中,通过LinkCode对每个方法进行了装载,设置好了它们的执行入口。这一章则先跟一下每个执行入口entrypoint的初始化流程,了解了这些entrypoint的初始化,然后跟一下Dex Cache,再继续通过LinkCode入手,分析一下不同方法设置的entrypoint的含义,最后再跟方法调用的流程。

首先分析entrypoint的初始化,老规矩,先放一张整体流程图,比较简单:
entrypoint的初始化
在前面Android运行时ART加载OAT文件的过程分析中,我们提到了ART运行时的启动和初始化过程。其中的一个初始化过程便是将主线程关联到ART运行时去,如下所示:

bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) {
  ......
  java_vm_ = new JavaVMExt(this, runtime_options);
  ......
  Thread* self = Thread::Attach("main", false, nullptr, false);
  ......
  return true;  
}

这个函数定义在文件art/runtime/runtime.cc中。
在Runtime类的成员函数Init中,通过调用Thread类的静态成员函数Attach将当前线程,也就是主线程,关联到ART运行时去。在关联的过程中,就会初始化一个外部库函数调用跳转表。

Thread类的静态成员函数Attach的实现如下所示:

Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_group, bool create_peer) {
  Runtime* runtime = Runtime::Current();
  ......
  Thread* self;
  {
    MutexLock mu(nullptr, *Locks::runtime_shutdown_lock_);
    if (runtime->IsShuttingDownLocked()) {
      LOG(ERROR) << "Thread attaching while runtime is shutting down: " << thread_name;
      return nullptr;
    } else {
      Runtime::Current()->StartThreadBirth();
      self = new Thread(as_daemon);
      bool init_success = self->Init(runtime->GetThreadList(), runtime->GetJavaVM());
      Runtime::Current()->EndThreadBirth();
      if (!init_success) {
        delete self;
        return nullptr;
      }
    }
  }
  ......

  return self;

这个函数定义在文件art/runtime/thread.cc中。
在Thread类的静态成员函数Attach中,最重要的就是创建了一个Thread对象来描述当前被关联到ART运行时的线程。创建了这个Thread对象之后,马上就调用它的成员函数Init来对它进行初始化。

Thread类的成员函数Init的实现如下所示:

bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_env_ext) {
  ......
  InitTlsEntryPoints();
  ......

  if (jni_env_ext != nullptr) {
    DCHECK_EQ(jni_env_ext->vm, java_vm);
    DCHECK_EQ(jni_env_ext->self, this);
    tlsPtr_.jni_env = jni_env_ext;
  } else {
    tlsPtr_.jni_env = JNIEnvExt::Create(this, java_vm);
    if (tlsPtr_.jni_env == nullptr) {
      return false;
    }
  }
  ......
  return true;

这个函数定义在文件art/runtime/thread.cc中。
Thread类的成员函数Init除了给当前的线程创建一个JNIEnvExt(tlsPtr_.jni_env)对象来描述它的JNI调用接口之外,还通过调用另外一个成员函数InitTlsEntryPoints来初始化一个外部库函数调用跳转表。

Thread类的成员函数InitTlsEntryPoints的实现如下所示:

void Thread::InitTlsEntryPoints() {
  // Insert a placeholder so we can easily tell if we call an unimplemented entry point.
  uintptr_t* begin = reinterpret_cast<uintptr_t*>(&tlsPtr_.interpreter_entrypoints);
  uintptr_t* end = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(&tlsPtr_.quick_entrypoints) +
      sizeof(tlsPtr_.quick_entrypoints));
  for (uintptr_t* it = begin; it != end; ++it) {
    *it = reinterpret_cast<uintptr_t>(UnimplementedEntryPoint);
  }
  InitEntryPoints(&tlsPtr_.interpreter_entrypoints, &tlsPtr_.jni_entrypoints, &tlsPtr_.quick_entrypoints);
}

这个函数定义在文件art/runtime/thread.cc中。
Thread类的tlsPtr_中定义了三个成员变量interpreter_entrypoints_、jni_entrypoints_和quick_entrypoints_,如下所示:

  struct PACKED(4) tls_ptr_sized_values {
    ......

    // Entrypoint function pointers.
    // TODO: move this to more of a global offset table model to avoid per-thread duplication.
    InterpreterEntryPoints interpreter_entrypoints;
    JniEntryPoints jni_entrypoints;
    QuickEntryPoints quick_entrypoints;
    ......
  };

Thread类的tlsPtr_声明定义在文件art/runtime/thread.h中。
Thread类将外部库函数调用跳转表划分为4个,其中,interpreter_entrypoints_描述的是解释器要用到的跳转表,jni_entrypoints_描述的是JNI调用相关的跳转表,而quick_entrypoints_描述的是Quick后端生成的本地机器指令要用到的跳转表,相比5.0源码这里也删除了portable_entrypoints_。

回到Thread类的成员函数InitTlsEntryPoints中,它通过调用一个全局函数InitEntryPoints来初始化上述的4个跳转表。全局函数InitEntryPoints的实现是和CPU体系结构相关的,因为跳转表里面的函数调用入口是用汇编语言来实现的。

我们以ARM体系架构为例,来看全局函数InitEntryPoints的实现,如下所示:

void InitEntryPoints(InterpreterEntryPoints* ipoints, JniEntryPoints* jpoints,
                     QuickEntryPoints* qpoints) {
  // Interpreter
  ipoints->pInterpreterToInterpreterBridge = artInterpreterToInterpreterBridge;
  ipoints->pInterpreterToCompiledCodeBridge = artInterpreterToCompiledCodeBridge;

  // JNI
  jpoints->pDlsymLookup = art_jni_dlsym_lookup_stub;

  // Alloc
  ResetQuickAllocEntryPoints(qpoints);

  // Cast
  qpoints->pInstanceofNonTrivial = artIsAssignableFromCode;
  qpoints->pCheckCast = art_quick_check_cast;

  // DexCache
  qpoints->pInitializeStaticStorage = art_quick_initialize_static_storage;
  qpoints->pInitializeTypeAndVerifyAccess = art_quick_initialize_type_and_verify_access;
  qpoints->pInitializeType = art_quick_initialize_type;
  qpoints->pResolveString = art_quick_resolve_string;

  // Field
  qpoints->pSet8Instance = art_quick_set8_instance;
  qpoints->pSet8Static = art_quick_set8_static;
  qpoints->pSet16Instance = art_quick_set16_instance;
  qpoints->pSet16Static = art_quick_set16_static;
  qpoints->pSet32Instance = art_quick_set32_instance;
  qpoints->pSet32Static = art_quick_set32_static;
  qpoints->pSet64Instance = art_quick_set64_instance;
  qpoints->pSet64Static = art_quick_set64_static;
  qpoints->pSetObjInstance = art_quick_set_obj_instance;
  qpoints->pSetObjStatic = art_quick_set_obj_static;
  qpoints->pGetByteInstance = art_quick_get_byte_instance;
  qpoints->pGetBooleanInstance = art_quick_get_boolean_instance;
  qpoints->pGetShortInstance = art_quick_get_short_instance;
  qpoints->pGetCharInstance = art_quick_get_char_instance;
  qpoints->pGet32Instance = art_quick_get32_instance;
  qpoints->pGet64Instance = art_quick_get64_instance;
  qpoints->pGetObjInstance = art_quick_get_obj_instance;
  qpoints->pGetByteStatic = art_quick_get_byte_static;
  qpoints->pGetBooleanStatic = art_quick_get_boolean_static;
  qpoints->pGetShortStatic = art_quick_get_short_static;
  qpoints->pGetCharStatic = art_quick_get_char_static;
  qpoints->pGet32Static = art_quick_get32_static;
  qpoints->pGet64Static = art_quick_get64_static;
  qpoints->pGetObjStatic = art_quick_get_obj_static;

  // Array
  qpoints->pAputObjectWithNullAndBoundCheck = art_quick_aput_obj_with_null_and_bound_check;
  qpoints->pAputObjectWithBoundCheck = art_quick_aput_obj_with_bound_check;
  qpoints->pAputObject = art_quick_aput_obj;
  qpoints->pHandleFillArrayData = art_quick_handle_fill_data;

  // JNI
  qpoints->pJniMethodStart = JniMethodStart;
  qpoints->pJniMethodStartSynchronized = JniMethodStartSynchronized;
  qpoints->pJniMethodEnd = JniMethodEnd;
  qpoints->pJniMethodEndSynchronized = JniMethodEndSynchronized;
  qpoints->pJniMethodEndWithReference = JniMethodEndWithReference;
  qpoints->pJniMethodEndWithReferenceSynchronized = JniMethodEndWithReferenceSynchronized;
  qpoints->pQuickGenericJniTrampoline = art_quick_generic_jni_trampoline;

  // Locks
  qpoints->pLockObject = art_quick_lock_object;
  qpoints->pUnlockObject = art_quick_unlock_object;

  // Math
  qpoints->pIdivmod = __aeabi_idivmod;
  qpoints->pLdiv = __aeabi_ldivmod;
  qpoints->pLmod = __aeabi_ldivmod;  // result returned in r2:r3
  qpoints->pLmul = art_quick_mul_long;
  qpoints->pShlLong = art_quick_shl_long;
  qpoints->pShrLong = art_quick_shr_long;
  qpoints->pUshrLong = art_quick_ushr_long;
  if (kArm32QuickCodeUseSoftFloat) {
    qpoints->pFmod = fmod;
    qpoints->pFmodf = fmodf;
    qpoints->pD2l = art_d2l;
    qpoints->pF2l = art_f2l;
  } else {
    qpoints->pFmod = art_quick_fmod;
    qpoints->pFmodf = art_quick_fmodf;
    qpoints->pD2l = art_quick_d2l;
    qpoints->pF2l = art_quick_f2l;
  }

  // Intrinsics
  qpoints->pIndexOf = art_quick_indexof;
  qpoints->pStringCompareTo = art_quick_string_compareto;
  qpoints->pMemcpy = memcpy;

  // Invocation
  qpoints->pQuickImtConflictTrampoline = art_quick_imt_conflict_trampoline;
  qpoints->pQuickResolutionTrampoline = art_quick_resolution_trampoline;
  qpoints->pQuickToInterpreterBridge = art_quick_to_interpreter_bridge;
  qpoints->pInvokeDirectTrampolineWithAccessCheck =
      art_quick_invoke_direct_trampoline_with_access_check;
  qpoints->pInvokeInterfaceTrampolineWithAccessCheck =
      art_quick_invoke_interface_trampoline_with_access_check;
  qpoints->pInvokeStaticTrampolineWithAccessCheck =
      art_quick_invoke_static_trampoline_with_access_check;
  qpoints->pInvokeSuperTrampolineWithAccessCheck =
      art_quick_invoke_super_trampoline_with_access_check;
  qpoints->pInvokeVirtualTrampolineWithAccessCheck =
      art_quick_invoke_virtual_trampoline_with_access_check;

  // Thread
  qpoints->pTestSuspend = art_quick_test_suspend;

  // Throws
  qpoints->pDeliverException = art_quick_deliver_exception;
  qpoints->pThrowArrayBounds = art_quick_throw_array_bounds;
  qpoints->pThrowDivZero = art_quick_throw_div_zero;
  qpoints->pThrowNoSuchMethod = art_quick_throw_no_such_method;
  qpoints->pThrowNullPointer = art_quick_throw_null_pointer_exception;
  qpoints->pThrowStackOverflow = art_quick_throw_stack_overflow;

  qpoints->pDeoptimize = art_quick_deoptimize;

  // Read barrier
  qpoints->pReadBarrierJni = ReadBarrierJni;
}

这个函数定义在文件art/runtime/arch/arm/entrypoints_init_arm.cc中。
从函数InitEntryPoints的实现就可以看到生成的本地机器指令要使用到的外部库函数调用跳转表的初始化过程了。例如,如果在生成的本地机器指令中,需要调用一个JNI函数,那么就需要通过art_jni_dlsym_lookup_stub函数来间接地调用,以便可以找到正确的JNI函数来调用。

此外,我们还可以看到,解释器要用到的跳转表只包含了两项,分别是artInterpreterToInterpreterBridge和artInterpreterToCompiledCodeBridge。前者用来从一个解释执行的类方法跳到另外一个也是解释执行的类方法去执行,后者用来从一个解释执行的类方法跳到另外一个以本地机器指令执行的类方法去执行。

剩下的其它代码均是用来初始化Quick后端生成的本地机器指令要用到的跳转表,它包含的项非常多,但是根据注释可以划分为Alloc(对象分配)、Cast(类型转换)、DexCache(Dex缓访问)、Field(成员变量访问)、FillArray(数组填充)、JNI(JNI函数调用)、Locks(锁)、Math(数学计算)、Intrinsics(内建函数调用)、Invocation(类方法调用)、Thread(线程操作)和Throws(异常处理)等12类。

有了这些跳转表之后,当我们需要在生成的本地机器指令中调用一个外部库提供的函数时,只要找到用来描述当前线程的Thread对象,然后再根据上述的三个跳转表在该Thread对象内的偏移位置,那么就很容易找到所需要的跳转项了。

接下来我们现来看另外一个知识点,它们是涉及到类方法的执行方式,也就是是通过解释器来执行,还是直接以本地机器指令来执行,以及它们之间是如何穿插执行的。
在前面Android运行时ART加载类和方法的过程分析这篇文章中,我们提到,在类的加载过程中,需要对类的各个方法进行链接,实际上就是确定它们是通过解释器来执行,还是以本地机器指令来直接执行,如下所示:

void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class,
                           uint32_t class_def_method_index) {
  Runtime* const runtime = Runtime::Current();
  if (runtime->IsAotCompiler()) {
    // The following code only applies to a non-compiler runtime.
    return;
  }
  // Method shouldn't have already been linked.
  DCHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr);
  if (oat_class != nullptr) {
    // Every kind of method should at least get an invoke stub from the oat_method.
    // non-abstract methods also get their code pointers.
    const OatFile::OatMethod oat_method = oat_class->GetOatMethod(class_def_method_index);
    oat_method.LinkMethod(method);
  }

  // Install entry point from interpreter.
  bool enter_interpreter = NeedsInterpreter(method, method->GetEntryPointFromQuickCompiledCode());
  if (enter_interpreter && !
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值