Andfix 原理分析


即時生效原理

app啟動到一半的時候,所有需要發生變更的類已經被加載過了,
在Android系統中是無法對一個已經加載的類進行卸載的。騰訊的Tinker的方案是讓ClassLoader去加載新的類,如果不重啟app,原有的類還在虛擬機中,就無法加載新的類。因此需要冷啟動后,搶先加載修復補丁中的新類,從而達到熱修復的目的。

AndFix采用的方法是直接在已加載的類中的native層替換掉原方法,是在原有類的基礎上進行修改的。

底層替換原理

每一個 Java 方法在 Art 虛擬機中都對應一個 ArtMethod,ArtMethod 記錄了該方法的所有信息,包括所屬類、訪問權限、代碼執行地址等。

通過env->FromReflectedMethod,可以由 Method 對象得到這個方法對應的ArtMethod的真正起始地址,然后強轉為 ArtMethod 指針,通過指針的操作對其成員屬性進行修改替換。

為什么這樣替換后就可以實現熱修復呢?
就需要先了解虛擬機方法調用的原理

虛擬機方法調用的原理

我的測試機為Android 5.1.1,所以我們以Android 5.1.1版本的源碼為例。

Android 5.1.1版本中的 ArtMethod 結構體路徑為:
art/runtime/mirror/art_method.h#560。其中最重要的字段就是 entry_point_from_interpreter_entry_point_from_quick_compiled_code_ ,從名字可以看出它們就是方法執行的入口。

Java代碼在Android中會被編譯成 Dex code,
Art虛擬機中可以采用解釋模式或AOT機器碼模式執行Dex Code。

  • 解釋模式:就是取出Dex Code,逐條解釋執行。如果方法的調用者是以解釋模式運行的,調用該方法時,就會獲取它的 entry_point_from_interpreter_ ,然后跳轉執行。
  • AOT模式:就會預編譯Dex code對應的機器碼,然后在運行期間直接執行機器碼,不需要逐條解釋執行dex code。如果方法的調用者是以AOT機器碼方式執行的,在調用該方法時就是跳轉到 entry_point_from_quick_compiled_code_ 中執行的。

那是不是替換方法的執行入口就可以了呢?
當然不是,無論是解釋模式還是AOT機器碼模式,在運行期間還會需要調用ArtMethod中的其他成員字段

源碼跟蹤驗證

類加載流程

  1. FindClass (/art/runtime/jni_internal.cc#599)
  2. FindClass (/art/runtime/class_linker.cc#2117)
  3. DefineClass (/art/runtime/class_linker.cc#2218)
  4. LoadClass (/art/runtime/class_linker.cc#2727)
  5. LoadClassMembers (/art/runtime/class_linker.cc#2767)
  6. LinkCode (/art/runtime/class_linker.cc#2627)
  7. LinkMethod (/art/runtime/oat_file.cc#595)
  8. NeedsInterpreter (/art/runtime/class_linker.cc#2525)
  9. UnregisterNative(/art/runtime/mirror/art_method.cc#364)
  10. UpdateMethodsCode(/art/runtime/instrumentation.cc#679)
  11. UpdateEntrypoints(/art/runtime/instrumentation.cc#85)

JNI類的靜態成員函數 FindClass

static jclass FindClass(JNIEnv* env, const char* name) {
    CHECK_NON_NULL_ARGUMENT(name);
    Runtime* runtime = Runtime::Current();
    ClassLinker* class_linker = runtime->GetClassLinker();
    std::string descriptor(NormalizeJniClassDescriptor(name));
    ScopedObjectAccess soa(env);
    mirror::Class* c = nullptr;
    if (runtime->IsStarted()) {
      StackHandleScope<1> hs(soa.Self());
      Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));
      c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);
    } else {
      c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());
    }
    return soa.AddLocalReference<jclass>(c);
  }

在ART虛擬機進程中,存在著一個 Runtime 單例,用來描述ART運行時。通過調用 Runtime 類的靜態成員函數 Current 可以獲得上述Runtime單例。獲得了這個單例之后,就可以調用它的成員函數 GetClassLinker 來獲得一個 ClassLinker 對象。

ClassLinker 對象是在創建ART虛擬機的過程中創建的,用來加載類以及鏈接類方法

首先判斷ART運行時是否已經啟動起來。如果已經啟動,那么就通過調用函數GetClassLoader來獲得當前線程所關聯的ClassLoader,并且以此為參數,調用前面獲得的ClassLinker對象的成員函數FindClass來加載由參數name指定的類。

如果ART運行時還沒有啟動,那么這時候只可以加載系統類。這個通過前面獲得的 ClassLinker 對象的成員函數 FindSystemClass 來實現的。

ClassLinker類的成員函數 FindClass

mirror::Class* ClassLinker::FindClass(Thread* self, const char* descriptor,
                                      Handle<mirror::ClassLoader> class_loader) {
  DCHECK_NE(*descriptor, '\0') << "descriptor is empty string";
  DCHECK(self != nullptr);
  self->AssertNoPendingException();
  if (descriptor[1] == '\0') {
    // only the descriptors of primitive types should be 1 character long, also avoid class lookup
    // for primitive classes that aren't backed by dex files.
    return FindPrimitiveClass(descriptor[0]);
  }
  const size_t hash = ComputeModifiedUtf8Hash(descriptor);
  // Find the class in the loaded classes table.
  mirror::Class* klass = LookupClass(descriptor, hash, class_loader.Get());
  if (klass != nullptr) {
    return EnsureResolved(self, descriptor, klass);
  }
  // Class is not yet loaded.
  if (descriptor[0] == '[') {
    return CreateArrayClass(self, descriptor, hash, class_loader);
  } else if (class_loader.Get() == nullptr) {
    // The boot class loader, search the boot class path.
    ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
    if (pair.second != nullptr) {
      return DefineClass(self, descriptor, hash, NullHandle<mirror::ClassLoader>(), *pair.first,
                         *pair.second);
    } else {
      // The boot class loader is searched ahead of the application class loader, failures are
      // expected and will be wrapped in a ClassNotFoundException. Use the pre-allocated error to
      // trigger the chaining with a proper stack trace.
      mirror::Throwable* pre_allocated = Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
      self->SetException(ThrowLocation(), pre_allocated);
      return nullptr;
    }
  } else if (Runtime::Current()->UseCompileTimeClassPath()) {
    // First try with the bootstrap class loader.
    if (class_loader.Get() != nullptr) {
      klass = LookupClass(descriptor, hash, nullptr);
      if (klass != nullptr) {
        return EnsureResolved(self, descriptor, klass);
      }
    }
    // If the lookup failed search the boot class path. We don't perform a recursive call to avoid
    // a NoClassDefFoundError being allocated.
    ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
    if (pair.second != nullptr) {
      return DefineClass(self, descriptor, hash, NullHandle<mirror::ClassLoader>(), *pair.first,
                         *pair.second);
    }
    // Next try the compile time class path.
    const std::vector<const DexFile*>* class_path;
    {
      ScopedObjectAccessUnchecked soa(self);
      ScopedLocalRef<jobject> jclass_loader(soa.Env(),
                                            soa.AddLocalReference<jobject>(class_loader.Get()));
      class_path = &Runtime::Current()->GetCompileTimeClassPath(jclass_loader.get());
    }
    pair = FindInClassPath(descriptor, hash, *class_path);
    if (pair.second != nullptr) {
      return DefineClass(self, descriptor, hash, class_loader, *pair.first, *pair.second);
    }
  } else {
    ScopedObjectAccessUnchecked soa(self);
    mirror::Class* klass = FindClassInPathClassLoader(soa, self, descriptor, hash, class_loader);
    if (klass != nullptr) {
      return klass;
    }
    ScopedLocalRef<jobject> class_loader_object(soa.Env(),
                                                soa.AddLocalReference<jobject>(class_loader.Get()));
    std::string class_name_string(DescriptorToDot(descriptor));
    ScopedLocalRef<jobject> result(soa.Env(), nullptr);
    {
      ScopedThreadStateChange tsc(self, kNative);
      ScopedLocalRef<jobject> class_name_object(soa.Env(),
                                                soa.Env()->NewStringUTF(class_name_string.c_str()));
      if (class_name_object.get() == nullptr) {
        DCHECK(self->IsExceptionPending());  // OOME.
        return nullptr;
      }
      CHECK(class_loader_object.get() != nullptr);
      result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(),
                                               WellKnownClasses::java_lang_ClassLoader_loadClass,
                                               class_name_object.get()));
    }
    if (self->IsExceptionPending()) {
      // If the ClassLoader threw, pass that exception up.
      return nullptr;
    } else if (result.get() == nullptr) {
      // broken loader - throw NPE to be compatible with Dalvik
      ThrowNullPointerException(nullptr, StringPrintf("ClassLoader.loadClass returned null for %s",
                                                      class_name_string.c_str()).c_str());
      return nullptr;
    } else {
      // success, return mirror::Class*
      return soa.Decode<mirror::Class*>(result.get());
    }
  }

  ThrowNoClassDefFoundError("Class %s not found", PrintableString(descriptor).c_str());
  return nullptr;
}

參數 descriptor 指向的是要加載的類的簽名,而參數 class_loader 指向的是一個類加載器。

首先是調用另外一個成員函數 LookupClass 來檢查參數 descriptor 指定的類是否已經被加載過。如果是的話,那么 ClassLinker 類的成員函數 LookupClass 就會返回一個對應的 Class 對象,這個 Class 對象接著就會返回給調用者,表示加載已經完成。

如果參數 descriptor 指定的類還沒有被加載過,這時候主要就是要看參數 class_loader 的值了。如果參數 class_loader 的值等于NULL,那么就需要調用 FindInClassPath 來在系統啟動類路徑尋找對應的類。一旦尋找到,那么就會獲得包含目標類的DEX文件,因此接下來就調用 ClassLinker 類的另外一個成員函數 DefineClass 從獲得的DEX文件中加載參數 descriptor 指定的類了。

知道了參數 descriptor 指定的類定義在哪一個DEX文件之后,就可以通過 ClassLinker 類的另外一個成員函數
DefineClass 來從中加載它了。

ClassLinker類的成員函數 DefineClass

mirror::Class* ClassLinker::DefineClass(Thread* self, const char* descriptor, size_t hash,
                                        Handle<mirror::ClassLoader> class_loader,
                                        const DexFile& dex_file,
                                        const DexFile::ClassDef& dex_class_def) {
  StackHandleScope<3> hs(self);
  auto klass = hs.NewHandle<mirror::Class>(nullptr);

  // Load the class from the dex file.
  if (UNLIKELY(!init_done_)) {
    // finish up init of hand crafted class_roots_
    if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangObject));
    } else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangClass));
    } else if (strcmp(descriptor, "Ljava/lang/String;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangString));
    } else if (strcmp(descriptor, "Ljava/lang/ref/Reference;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangRefReference));
    } else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangDexCache));
    } else if (strcmp(descriptor, "Ljava/lang/reflect/ArtField;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangReflectArtField));
    } else if (strcmp(descriptor, "Ljava/lang/reflect/ArtMethod;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangReflectArtMethod));
    }
  }

  if (klass.Get() == nullptr) {
    // Allocate a class with the status of not ready.
    // Interface object should get the right size here. Regular class will
    // figure out the right size later and be replaced with one of the right
    // size when the class becomes resolved.
    klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
  }
  if (UNLIKELY(klass.Get() == nullptr)) {
    CHECK(self->IsExceptionPending());  // Expect an OOME.
    return nullptr;
  }
  klass->SetDexCache(FindDexCache(dex_file));
  LoadClass(dex_file, dex_class_def, klass, class_loader.Get());
  ObjectLock<mirror::Class> lock(self, klass);
  if (self->IsExceptionPending()) {
    // An exception occured during load, set status to erroneous while holding klass' lock in case
    // notification is necessary.
    if (!klass->IsErroneous()) {
      klass->SetStatus(mirror::Class::kStatusError, self);
    }
    return nullptr;
  }
  klass->SetClinitThreadId(self->GetTid());

  // Add the newly loaded class to the loaded classes table.
  mirror::Class* existing = InsertClass(descriptor, klass.Get(), hash);
  if (existing != nullptr) {
    // We failed to insert because we raced with another thread. Calling EnsureResolved may cause
    // this thread to block.
    return EnsureResolved(self, descriptor, existing);
  }

  // Finish loading (if necessary) by finding parents
  CHECK(!klass->IsLoaded());
  if (!LoadSuperAndInterfaces(klass, dex_file)) {
    // Loading failed.
    if (!klass->IsErroneous()) {
      klass->SetStatus(mirror::Class::kStatusError, self);
    }
    return nullptr;
  }
  CHECK(klass->IsLoaded());
  // Link the class (if necessary)
  CHECK(!klass->IsResolved());
  // TODO: Use fast jobjects?
  auto interfaces = hs.NewHandle<mirror::ObjectArray<mirror::Class>>(nullptr);

  mirror::Class* new_class = nullptr;
  if (!LinkClass(self, descriptor, klass, interfaces, &new_class)) {
    // Linking failed.
    if (!klass->IsErroneous()) {
      klass->SetStatus(mirror::Class::kStatusError, self);
    }
    return nullptr;
  }
  self->AssertNoPendingException();
  CHECK(new_class != nullptr) << descriptor;
  CHECK(new_class->IsResolved()) << descriptor;

  Handle<mirror::Class> new_class_h(hs.NewHandle(new_class));

  /*
   * We send CLASS_PREPARE events to the debugger from here.  The
   * definition of "preparation" is creating the static fields for a
   * class and initializing them to the standard default values, but not
   * executing any code (that comes later, during "initialization").
   *
   * We did the static preparation in LinkClass.
   *
   * The class has been prepared and resolved but possibly not yet verified
   * at this point.
   */
  Dbg::PostClassPrepare(new_class_h.Get());

  return new_class_h.Get();
}

ClassLinker類有一個類型為bool的成員變量 init_done_,用來表示ClassLinker是否已經初始化完成。

如果ClassLinker正處于初始化過程,即其成員變量 init_done_ 的值等于false,并且參數 descriptor 描述的是特定的內部類,那么就將本地變量 klass 指向它們,其余情況則會通過成員函數 AllocClass 為其分配存儲空間,以便后面通過成員函數 LoadClass 進行初始化。

ClassLinker類的成員函數 LoadClass 用來從指定的DEX文件中加載指定的類。指定的類從DEX文件中加載完成后,需要通過另外一個成員函數 InsertClass 添加到 ClassLinker 的已加載類列表中去。如果指定的類之前已經加載過,即調用成員函數InsertClass得到的返回值不等于空,那么就說明有另外的一個線程也正在加載指定的類。這時候就需要調用成員函數EnsureResolved來保證(等待)該類已經加載并且解析完成。另一方面,如果沒有其它線程加載指定的類,那么當前線程從指定的DEX文件加載完成指定的類后,還需要調用成員函數LinkClass來對加載后的類進行解析。最后,一個類型為Class的對象就可以返回給調用者了,用來表示一個已經加載和解析完成的類。

ClassLinker類的成員函數 LoadClass

void ClassLinker::LoadClass(const DexFile& dex_file,
                            const DexFile::ClassDef& dex_class_def,
                            Handle<mirror::Class> klass,
                            mirror::ClassLoader* class_loader) {
  CHECK(klass.Get() != nullptr);
  CHECK(klass->GetDexCache() != nullptr);
  CHECK_EQ(mirror::Class::kStatusNotReady, klass->GetStatus());
  const char* descriptor = dex_file.GetClassDescriptor(dex_class_def);
  CHECK(descriptor != nullptr);

  klass->SetClass(GetClassRoot(kJavaLangClass));
  if (kUseBakerOrBrooksReadBarrier) {
    klass->AssertReadBarrierPointer();
  }
  uint32_t access_flags = dex_class_def.GetJavaAccessFlags();
  CHECK_EQ(access_flags & ~kAccJavaFlagsMask, 0U);
  klass->SetAccessFlags(access_flags);
  klass->SetClassLoader(class_loader);
  DCHECK_EQ(klass->GetPrimitiveType(), Primitive::kPrimNot);
  klass->SetStatus(mirror::Class::kStatusIdx, nullptr);

  klass->SetDexClassDefIndex(dex_file.GetIndexForClassDef(dex_class_def));
  klass->SetDexTypeIndex(dex_class_def.class_idx_);
  CHECK(klass->GetDexCacheStrings() != nullptr);

  const byte* class_data = dex_file.GetClassData(dex_class_def);
  if (class_data == nullptr) {
    return;  // no fields or methods - for example a marker interface
  }

  OatFile::OatClass oat_class;
  if (Runtime::Current()->IsStarted()
      && !Runtime::Current()->UseCompileTimeClassPath()
      && FindOatClass(dex_file, klass->GetDexClassDefIndex(), &oat_class)) {
    LoadClassMembers(dex_file, class_data, klass, class_loader, &oat_class);
  } else {
    LoadClassMembers(dex_file, class_data, klass, class_loader, nullptr);
  }
}

dex_file: 類型為DexFile,描述要加載的類所在的DEX文件。
dex_class_def: 類型為ClassDef,描述要加載的類在DEX文件里面的信息。
klass: 類型為Class,描述加載完成的類。
class_loader: 類型為ClassLoader,描述所使用的類加載器。

LoadClass的任務就是要用dex_file、dex_class_def、class_loader三個參數包含的相關信息設置到參數klass描述的Class對象去,以便可以得到一個完整的已加載類信息。

關鍵:

  • setClassLoader:將class_loader描述的ClassLoader設置到klass描述的Class對象中,即給每一個已加載的類關聯一個類加載器。
  • SetDexClassDefIndex:通過DexFile的成員函數GetIndexForClassDef獲得正在加載的類在Dex文件中的索引號,并設置到klass中

FindOatClass:從相應的OAT文件中找到與正在加載的類對應的一個OatClass結構體oat_class。這需要利用到上面提到的DEX類索引號,這是因為DEX類和OAT類根據索引號存在一一對應關系。

ClassLinker類的成員函數 LoadClassMembers

void ClassLinker::LoadClassMembers(const DexFile& dex_file,
                                   const byte* class_data,
                                   Handle<mirror::Class> klass,
                                   mirror::ClassLoader* class_loader,
                                   const OatFile::OatClass* oat_class) {
  // Load fields.
  ClassDataItemIterator it(dex_file, class_data);
  Thread* self = Thread::Current();
  if (it.NumStaticFields() != 0) {
    mirror::ObjectArray<mirror::ArtField>* statics = AllocArtFieldArray(self, it.NumStaticFields());
    if (UNLIKELY(statics == nullptr)) {
      CHECK(self->IsExceptionPending());  // OOME.
      return;
    }
    klass->SetSFields(statics);
  }
  if (it.NumInstanceFields() != 0) {
    mirror::ObjectArray<mirror::ArtField>* fields =
        AllocArtFieldArray(self, it.NumInstanceFields());
    if (UNLIKELY(fields == nullptr)) {
      CHECK(self->IsExceptionPending());  // OOME.
      return;
    }
    klass->SetIFields(fields);
  }
  for (size_t i = 0; it.HasNextStaticField(); i++, it.Next()) {
    StackHandleScope<1> hs(self);
    Handle<mirror::ArtField> sfield(hs.NewHandle(AllocArtField(self)));
    if (UNLIKELY(sfield.Get() == nullptr)) {
      CHECK(self->IsExceptionPending());  // OOME.
      return;
    }
    klass->SetStaticField(i, sfield.Get());
    LoadField(dex_file, it, klass, sfield);
  }
  for (size_t i = 0; it.HasNextInstanceField(); i++, it.Next()) {
    StackHandleScope<1> hs(self);
    Handle<mirror::ArtField> ifield(hs.NewHandle(AllocArtField(self)));
    if (UNLIKELY(ifield.Get() == nullptr)) {
      CHECK(self->IsExceptionPending());  // OOME.
      return;
    }
    klass->SetInstanceField(i, ifield.Get());
    LoadField(dex_file, it, klass, ifield);
  }

  // Load methods.
  if (it.NumDirectMethods() != 0) {
    // TODO: append direct methods to class object
    mirror::ObjectArray<mirror::ArtMethod>* directs =
         AllocArtMethodArray(self, it.NumDirectMethods());
    if (UNLIKELY(directs == nullptr)) {
      CHECK(self->IsExceptionPending());  // OOME.
      return;
    }
    klass->SetDirectMethods(directs);
  }
  if (it.NumVirtualMethods() != 0) {
    // TODO: append direct methods to class object
    mirror::ObjectArray<mirror::ArtMethod>* virtuals =
        AllocArtMethodArray(self, it.NumVirtualMethods());
    if (UNLIKELY(virtuals == nullptr)) {
      CHECK(self->IsExceptionPending());  // OOME.
      return;
    }
    klass->SetVirtualMethods(virtuals);
  }
  size_t class_def_method_index = 0;
  uint32_t last_dex_method_index = DexFile::kDexNoIndex;
  size_t last_class_def_method_index = 0;
  for (size_t i = 0; it.HasNextDirectMethod(); i++, it.Next()) {
    StackHandleScope<1> hs(self);
    Handle<mirror::ArtMethod> method(hs.NewHandle(LoadMethod(self, dex_file, it, klass)));
    if (UNLIKELY(method.Get() == nullptr)) {
      CHECK(self->IsExceptionPending());  // OOME.
      return;
    }
    klass->SetDirectMethod(i, method.Get());
    LinkCode(method, oat_class, dex_file, it.GetMemberIndex(), class_def_method_index);
    uint32_t it_method_index = it.GetMemberIndex();
    if (last_dex_method_index == it_method_index) {
      // duplicate case
      method->SetMethodIndex(last_class_def_method_index);
    } else {
      method->SetMethodIndex(class_def_method_index);
      last_dex_method_index = it_method_index;
      last_class_def_method_index = class_def_method_index;
    }
    class_def_method_index++;
  }
  for (size_t i = 0; it.HasNextVirtualMethod(); i++, it.Next()) {
    StackHandleScope<1> hs(self);
    Handle<mirror::ArtMethod> method(hs.NewHandle(LoadMethod(self, dex_file, it, klass)));
    if (UNLIKELY(method.Get() == nullptr)) {
      CHECK(self->IsExceptionPending());  // OOME.
      return;
    }
    klass->SetVirtualMethod(i, method.Get());
    DCHECK_EQ(class_def_method_index, it.NumDirectMethods() + i);
    LinkCode(method, oat_class, dex_file, it.GetMemberIndex(), class_def_method_index);
    class_def_method_index++;
  }
  DCHECK(!it.HasNext());
}

從參數dex_file描述的DEX文件中獲得正在加載的類的靜態成員變量和實例成員變量個數,并且為每一個靜態成員變量和實例成員變量都分配一個ArtField對象,接著通過ClassLinker類的成員函數LoadField對這些ArtField對象進行初始化。初始化得到的ArtField對象全部保存在klass描述的Class對象中。

從參數dex_file描述的DEX文件中獲得正在加載的類的直接成員函數和虛擬成員函數個數,并且為每一個直接成員函數和虛擬成員函數都分配一個ArtMethod對象,接著通過ClassLinker類的成員函數LoadMethod對這些ArtMethod對象進行初始化。初始好得到的ArtMethod對象全部保存在klass描述的Class對象中。

參數klass描述的Class對象包含了一系列的ArtField對象和ArtMethod對象,其中,ArtField對象用來描述成員變量信息,而ArtMethod用來描述成員函數信息。

接下來繼續分析LinkCode函數的實現,以便可以了解如何在一個OAT文件中找到一個DEX類方法的本地機器指令。

ClassLinker類的成員函數 LinkCode

void ClassLinker::LinkCode(Handle<mirror::ArtMethod> method, const OatFile::OatClass* oat_class,
                           const DexFile& dex_file, uint32_t dex_method_index,
                           uint32_t method_index) {
  if (Runtime::Current()->IsCompiler()) {
    // The following code only applies to a non-compiler runtime.
    return;
  }
  // Method shouldn't have already been linked.
  DCHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr);
#if defined(ART_USE_PORTABLE_COMPILER)
  DCHECK(method->GetEntryPointFromPortableCompiledCode() == nullptr);
#endif
  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(method_index);
    oat_method.LinkMethod(method.Get());
  }

  // Install entry point from interpreter.
  bool enter_interpreter = NeedsInterpreter(method.Get(),
                                            method->GetEntryPointFromQuickCompiledCode(),
#if defined(ART_USE_PORTABLE_COMPILER)
                                            method->GetEntryPointFromPortableCompiledCode());
#else
                                            nullptr);
#endif
  if (enter_interpreter && !method->IsNative()) {
    method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge);
  } else {
    method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);
  }

  if (method->IsAbstract()) {
    method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
#if defined(ART_USE_PORTABLE_COMPILER)
    method->SetEntryPointFromPortableCompiledCode(GetPortableToInterpreterBridge());
#endif
    return;
  }

  bool have_portable_code = false;
  if (method->IsStatic() && !method->IsConstructor()) {
    // For static methods excluding the class initializer, install the trampoline.
    // It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines
    // after initializing class (see ClassLinker::InitializeClass method).
    method->SetEntryPointFromQuickCompiledCode(GetQuickResolutionTrampoline());
#if defined(ART_USE_PORTABLE_COMPILER)
    method->SetEntryPointFromPortableCompiledCode(GetPortableResolutionTrampoline());
#endif
  } else if (enter_interpreter) {
    if (!method->IsNative()) {
      // Set entry point from compiled code if there's no code or in interpreter only mode.
      method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
#if defined(ART_USE_PORTABLE_COMPILER)
      method->SetEntryPointFromPortableCompiledCode(GetPortableToInterpreterBridge());
#endif
    } else {
      method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniTrampoline());
#if defined(ART_USE_PORTABLE_COMPILER)
      method->SetEntryPointFromPortableCompiledCode(GetPortableToQuickBridge());
#endif
    }
#if defined(ART_USE_PORTABLE_COMPILER)
  } else if (method->GetEntryPointFromPortableCompiledCode() != nullptr) {
    DCHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr);
    have_portable_code = true;
    method->SetEntryPointFromQuickCompiledCode(GetQuickToPortableBridge());
#endif
  } else {
    DCHECK(method->GetEntryPointFromQuickCompiledCode() != nullptr);
#if defined(ART_USE_PORTABLE_COMPILER)
    method->SetEntryPointFromPortableCompiledCode(GetPortableToQuickBridge());
#endif
  }

  if (method->IsNative()) {
    // Unregistering restores the dlsym lookup stub.
    method->UnregisterNative(Thread::Current());

    if (enter_interpreter) {
      // We have a native method here without code. Then it should have either the GenericJni
      // trampoline as entrypoint (non-static), or the Resolution trampoline (static).
      DCHECK(method->GetEntryPointFromQuickCompiledCode() == GetQuickResolutionTrampoline()
          || method->GetEntryPointFromQuickCompiledCode() == GetQuickGenericJniTrampoline());
    }
  }

  // Allow instrumentation its chance to hijack code.
  Runtime* runtime = Runtime::Current();
  runtime->GetInstrumentation()->UpdateMethodsCode(method.Get(),
                                                   method->GetEntryPointFromQuickCompiledCode(),
#if defined(ART_USE_PORTABLE_COMPILER)
                                                   method->GetEntryPointFromPortableCompiledCode(),
#else
                                                   nullptr,
#endif
                                                   have_portable_code);
}
  • 參數method表示要設置本地機器指令的類方法。
  • 參數oat_class表示類方法method在OAT文件中對應的OatClass結構體。
  • 參數dex_file表示在dex文件中對應的DexFile。
  • 參數dex_method_index表示DexFile中方法method的索引號。
  • 參數method_index表示類方法method的索引號。

通過參數 method_index 描述的索引號可以在 oat_class 表示的OatClass結構體中找到一個 OatMethod 結構體 oat_method。這個 OatMethod 結構描述了類方法method的本地機器指令相關信息,通過調用它的成員函數LinkMethod可以將這些信息設置到參數method描述的 ArtMethod 對象中去。

OatMethod 的成員函數 LinkMethod

void OatFile::OatMethod::LinkMethod(mirror::ArtMethod* method) const {
  CHECK(method != NULL);
#if defined(ART_USE_PORTABLE_COMPILER)
  method->SetEntryPointFromPortableCompiledCode(GetPortableCode());
#endif
  method->SetEntryPointFromQuickCompiledCode(GetQuickCode());
}

通過OatMethod類的成員函數 GetPortableCode 和 GetQuickCode 獲得 OatMethod 結構體中的 code_offset_ 字段,并且通過調用 ArtMethod 類的成員函數 SetEntryPointFromCompiledCode 設置到參數method描述的 ArtMethod 對象中去。OatMethod 結構體中的 code_offset_ 字段指向的是一個本地機器指令函數,這個本地機器指令函數正是通過翻譯參數method描述的類方法的DEX字節碼得到的。

OatMethod 的 GetPortableCode 函數
const void* GetPortableCode() const {
      // TODO: encode whether code is portable/quick in flags within OatMethod.
      if (kUsePortableCompiler) {
        return GetOatPointer<const void*>(code_offset_);
      } else {
        return nullptr;
      }
    }
OatMethod 的 GetQuickCode 函數
const void* GetQuickCode() const {
      if (kUsePortableCompiler) {
        return nullptr;
      } else {
        return GetOatPointer<const void*>(code_offset_);
      }
    }

ClassLinker類的全局函數 NeedsInterpreter

// Returns true if the method must run with interpreter, false otherwise.
static bool NeedsInterpreter(
    mirror::ArtMethod* method, const void* quick_code, const void* portable_code)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  if ((quick_code == nullptr) && (portable_code == nullptr)) {
    // No code: need interpreter.
    // May return true for native code, in the case of generic JNI
    // DCHECK(!method->IsNative());
    return true;
  }
#ifdef ART_SEA_IR_MODE
  ScopedObjectAccess soa(Thread::Current());
  if (std::string::npos != PrettyMethod(method).find("fibonacci")) {
    LOG(INFO) << "Found " << PrettyMethod(method);
    return false;
  }
#endif
  // If interpreter mode is enabled, every method (except native and proxy) must
  // be run with interpreter.
  return Runtime::Current()->GetInstrumentation()->InterpretOnly() &&
         !method->IsNative() && !method->IsProxyMethod();
}

檢查參數method描述的類方法是否需要通過解釋器執行。

在以下兩種情況下,一個類方法需要通過解釋器來執行:

  1. 沒有對應的本地機器指令,即參數quick_code和portable_code的值等于NULL。
  2. ART虛擬機運行在解釋模式中,并且類方法不是JNI方法,并且也不是代理方法。

因為JNI方法是沒有對應的DEX字節碼的,因此即使ART虛擬機運行在解釋模式中,JNI方法也不能通過解釋器來執行。至于代理方法,由于是動態生成的(沒有對應的DEX字節碼),因此即使ART虛擬機運行在解釋模式中,它們也不通過解釋器來執行。

調用Runtime類的靜態成員函數Current獲得的是描述ART運行時的一個Runtime對象。調用這個Runtime對象的成員函數GetInstrumentation獲得的是一個Instrumentation對象。這個Instrumentation對象是用來調試ART運行時的,通過調用它的成員函數InterpretOnly可以知道ART虛擬機是否運行在解釋模式中。

回到ClassLinker類的成員函數 LinkCode

如果調用函數NeedsInterpreter得到的返回值enter_interpreter等于true,而且不是Native方法,那么就意味著參數method描述的類方法需要通過解釋器來執行,這時候就將函數artInterpreterToInterpreterBridge設置為解釋器執行該類方法的入口點。否則的話,就將另外一個函數artInterpreterToCompiledCodeBridge設置為解釋器執行該類方法的入口點。

為什么我們需要為類方法設置解釋器入口點呢?根據前面的分析可以知道,在ART虛擬機中,并不是所有的類方法都是有對應的本地機器指令的,并且即使一個類方法有對應的本地機器指令,當ART虛擬機以解釋模式運行時,它也需要通過解釋器來執行。當以解釋器執行的類方法在執行的過程中調用了其它的類方法時,解釋器就需要進一步知道被調用的類方法是應用以解釋方式執行,還是本地機器指令方法執行。為了能夠進行統一處理,就給每一個類方法都設置一個解釋器入口點。需要通過解釋執行的類方法的解釋器入口點函數是artInterpreterToInterpreterBridge,它會繼續通過解釋器來執行該類方法。需要通過本地機器指令執行的類方法的解釋器入口點函數是artInterpreterToCompiledCodeBridge,它會間接地調用該類方法的本地機器指令。

判斷method是否是一個抽象方法。抽象方法聲明類中是沒有實現的,必須要由子類實現。因此抽象方法在聲明類中是沒有對應的本地機器指令的,它們必須要通過解釋器來執行。不過,為了能夠進行統一處理,我們仍然假裝抽象方法有對應的本地機器指令函數,只不過這個本地機器指令函數被設置為 GetQuickToInterpreterBridge。當函數 GetQuickToInterpreterBridge,就會自動進入到解釋器中去。

當method是一個非類靜態初始化函數(class initializer)的靜態方法時,我們不能直接執行翻譯其DEX字節碼得到的本地機器指令。這是因為類靜態方法可以在不創建類對象的前提下執行。這意味著一個類靜態方法在執行的時候,對應的類可能還沒有初始化好。這時候我們就需要先將對應的類初始化好,再執行相應的靜態方法。為了能夠做到這一點。我們就先調用GetResolutionTrampoline函數得到一個Tampoline函數,接著將這個Trampoline函數作為靜態方法的本地機器指令。這樣如果類靜態方法在對應的類初始化前被調用,就會觸發上述的Trampoline函數被執行。而當上述Trampoline函數執行時,它們先初始化好對應的類,再調用原來的類靜態方法對應的本地機器指令。按照代碼中的注釋,當一個類初始化完成之后,就可以調用函數ClassLinker::FixupStaticTrampolines來修復該類的靜態成員函數的本地機器指令,也是通過翻譯DEX字節碼得到的本地機器指令。這里需要注意的是,為什么類靜態初始化函數不需要按照其它的類靜態方法一樣設置Tampoline函數呢?這是因為類靜態初始化函數是一定保證是在類初始化過程中執行的。

當method需要通過解釋器執行時,那么當該類方法執行時,就不能執行它的本地機器指令,因此我們就先調用GetCompiledCodeToInterpreterBridge函數獲得一個橋接函數,并且將這個橋接函數假裝為類方法的本地機器指令。一旦該橋接函數被執行,它就會入到解釋器去執行類方法。通過這種方式,我們就可以以統一的方法來調用解釋執行和本地機器指令執行的類方法。

判斷method是否是一個JNI方法。如果是的話,那么就調用ArtMethod類的成員函數UnregisterNative來初始化它的JNI方法調用接口。

ArtMethod類的成員函數 UnregisterNative

void ArtMethod::UnregisterNative(Thread* self) {
  CHECK(IsNative() && !IsFastNative()) << PrettyMethod(this);
  // restore stub to lookup native pointer via dlsym
  RegisterNative(self, GetJniDlsymLookupStub(), false);
}

UnregisterNative實際上就是將一個JNI方法的初始化入口設置為通過調用函數GetJniDlsymLookupStub獲得的一個Stub。這個Stub的作用是,當一個JNI方法被調用時,如果還沒有顯示地注冊有Native函數,那么它就會自動從已加載的SO文件查找是否存在一個對應的Native函數。如果存在的話,就將它注冊為JNI方法的Native函數,并且執行它。這就是隱式的JNI方法注冊。

回到ClassLinker類的成員函數 LinkCode

最后調用Instrumentation類的成員函數UpdateMethodsCode檢查是否要進一步修改參數method描述的類方法的本地機器指令入口。

void Instrumentation::UpdateMethodsCode(mirror::ArtMethod* method, const void* quick_code,
                                        const void* portable_code, bool have_portable_code) {
  const void* new_portable_code;
  const void* new_quick_code;
  bool new_have_portable_code;
  if (LIKELY(!instrumentation_stubs_installed_)) {
    new_portable_code = portable_code;
    new_quick_code = quick_code;
    new_have_portable_code = have_portable_code;
  } else {
    if ((interpreter_stubs_installed_ || IsDeoptimized(method)) && !method->IsNative()) {
#if defined(ART_USE_PORTABLE_COMPILER)
      new_portable_code = GetPortableToInterpreterBridge();
#else
      new_portable_code = portable_code;
#endif
      new_quick_code = GetQuickToInterpreterBridge();
      new_have_portable_code = false;
    } else {
      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
      if (quick_code == class_linker->GetQuickResolutionTrampoline() ||
          quick_code == class_linker->GetQuickToInterpreterBridgeTrampoline() ||
          quick_code == GetQuickToInterpreterBridge()) {
#if defined(ART_USE_PORTABLE_COMPILER)
        DCHECK((portable_code == class_linker->GetPortableResolutionTrampoline()) ||
               (portable_code == GetPortableToInterpreterBridge()));
#endif
        new_portable_code = portable_code;
        new_quick_code = quick_code;
        new_have_portable_code = have_portable_code;
      } else if (entry_exit_stubs_installed_) {
        new_quick_code = GetQuickInstrumentationEntryPoint();
#if defined(ART_USE_PORTABLE_COMPILER)
        new_portable_code = GetPortableToInterpreterBridge();
#else
        new_portable_code = portable_code;
#endif
        new_have_portable_code = false;
      } else {
        new_portable_code = portable_code;
        new_quick_code = quick_code;
        new_have_portable_code = have_portable_code;
      }
    }
  }
  UpdateEntrypoints(method, new_quick_code, new_portable_code, new_have_portable_code);
}

Instrumentation類是用來調用ART運行時的。例如,當我們需要監控類方法的調用時,就可以往Instrumentation注冊一些Listener。這樣當類方法調用時,這些注冊的Listener就會得到回調。當Instrumentation注冊有相應的Listener時,它的成員變量instrumentation_stubs_installed_的值就會等于true。

總結
通過上述源碼跟蹤分析,一個類的加載過程完成了。加載完成后得到的是一個Class對象。這個Class對象關聯有一系列的 ArtField 對象和 ArtMethod 對象。其中,ArtField對象描述的是成員變量,而ArtMethod對象描述的是成員函數。對于每一個ArtMethod對象,它都有一個解釋器入口點和一個本地機器指令入口點。這樣,無論一個類方法是通過解釋器執行,還是直接以本地機器指令執行,我們都可以以統一的方式來進行調用。同時,理解了上述的類加載過程后,我們就可以知道,我們在Native層通過JNI接口FindClass查找或者加載類時,得到的一個不透明的jclass值,實際上指向的是一個Class對象。

類方法查找流程

  1. GetStaticMethodID (/art/runtime/jni_internal.cc#943)
  2. FindMethodID (/art/runtime/jni_internal.cc#140)

JNI類的靜態成員函數 FindClass

static jmethodID GetStaticMethodID(JNIEnv* env, jclass java_class, const char* name,
                                     const char* sig) {
    CHECK_NON_NULL_ARGUMENT(java_class);
    CHECK_NON_NULL_ARGUMENT(name);
    CHECK_NON_NULL_ARGUMENT(sig);
    ScopedObjectAccess soa(env);
    return FindMethodID(soa, java_class, name, sig, true);
}

參數 name 和 sig 描述的分別是要查找的類方法的名稱和簽名,而參數 java_class 是對應的類。參數 java_class 的類型是jclass,從前面類加載過程的分析可以知道,它實際上指向的是一個Class對象。

JNI類的靜態成員函數 FindMethodID

static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class,
                              const char* name, const char* sig, bool is_static)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  mirror::Class* c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class*>(jni_class));
  if (c == nullptr) {
    return nullptr;
  }
  mirror::ArtMethod* method = nullptr;
  if (is_static) {
    method = c->FindDirectMethod(name, sig);
  } else if (c->IsInterface()) {
    method = c->FindInterfaceMethod(name, sig);
  } else {
    method = c->FindVirtualMethod(name, sig);
    if (method == nullptr) {
      // No virtual method matching the signature.  Search declared
      // private methods and constructors.
      method = c->FindDeclaredDirectMethod(name, sig);
    }
  }
  if (method == nullptr || method->IsStatic() != is_static) {
    ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");
    return nullptr;
  }
  return soa.EncodeMethod(method);
}

執行過程:

  1. 將參數jni_class的值轉換為一個Class指針c,因此就可以得到一個Class對象,并且通過ClassLinker類的成員函數EnsureInitialized確保該Class對象描述的類已經初始化。
  2. Class對象c描述的類在加載的過程中,經過解析已經關聯上一系列的成員函數。這些成員函數可以分為兩類:Direct和Virtual。Direct類的成員函數包括所有的靜態成員函數、私有成員函數和構造函數,而Virtual則包括所有的虛成員函數。
  3. 經過前面的查找過程,如果都不能在Class對象c描述的類中找到與參數name和sig對應的成員函數,那么就拋出一個NoSuchMethodError異常。否則的話,就將查找得到的ArtMethod對象封裝成一個jmethodID值返回給調用者。

我們通過調用JNI接口GetStaticMethodID獲得的不透明jmethodID值指向的實際上是一個ArtMethod對象。
當我們獲得了一個ArtMethod對象之后,就可以輕松地得到它的本地機器指令入口,進而對它進行執行。

總結
當我們把舊方法(ArtMethod)的所有成員字段都替換為新方法(ArtMethod)的成員字段后,執行時所有的數據就可以保持和新方法的數據一致。這樣在所有執行到舊方法的地方,會獲取新方法的執行入口、所屬類型、方法索引號、以及所屬dex信息,然后像調用舊方法一樣,執行新方法的邏輯。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容