NDK-热修复之Andfix详解

一、原理分析

1.即时生效原理

  app启动到一半的时候,所有需要发生变更的类已经被加载过了,在Android系统中是无法对一个已经加载的类进行卸载的。腾讯的Tinker的方案是让ClassLoader去加载新的类,如果不重启APP,原有的类还在虚拟机中,就无法加载新的类。因此需要冷启动后抢先加载修复补丁中的新类,从而达到修复的目的。
在这里插入图片描述
  Andfix,它的命名源于Android-HotFix的缩写。采用的方法是直接在已加载的类中的native层替换掉原方法,是在原有类的基础上进行修改的。

2.底层替换原理

  每一个Java方法在Art虚拟机中都对应一个ArtMethod,ArtMethod记录了该方法的所有信息,包括所属类、访问权限、代码执行地址等。
  通过env->FromReflectedMethod,可以由Method对象得到这个方法对应的ArtMethod的真正起始地址,然后强转为ArtMethod指针,通过指针的操作对其成员属性进行修改替换。

  为什么这样替换后就可以实现热修复呢?这就需要了解虚拟机调用方法的原理。

3.虚拟机调用方法的原理

(源码为 Android 5.1.1版本)
art/runtime/mirror/art_method.h#592

 struct PACKED(4) PtrSizedFields {
   
    void* entry_point_from_interpreter_;
    void* entry_point_from_jni_;
    void* entry_point_from_quick_compiled_code_;
#if defined(ART_USE_PORTABLE_COMPILER)
    void* entry_point_from_portable_compiled_code_;
#endif
  } ptr_sized_fields_;

  最重要的字段是entry_point_from_interpreter_和entry_point_from_quick_compiled_code_,它们是方法执行的入口。
  Java代码在Android中会被编译成Dex Code,Art虚拟机中可以采用解释器模式或AOT(Ahead of Time)机器码模式执行Dex Code。
解释模式:解释模式就是取出Dex Code,逐条解释执行。如果方法的调用者是以解释模式运行的,调用该方法时,就会获取它的entry_point_from_interpreter_,然后跳转执行。
AOT(Ahead Of Time):运行前编译。如果是AOT的方式,就会预编译Dex Code对应的机器码,然后在运行期间直接执行机器码,不需要逐条解释执行dex code。如果方法的调用者是以AOT机器码方式执行的,在调用该方法就会跳转到entry_point_from_quick_compiled_code_。
  那是不是替换到方法的执行入口就可以了呢?当然不是,无论是解释模式还是AOT机器码模式,在运行期间还会需要调用ArtMethod中的其他成员字段。

4.源码证明(类加载过程)

(0)整体流程

在这里插入图片描述

(1)AndroidRuntime.cpp

/frameworks/base/core/jni/AndroidRuntime.cpp

void AndroidRuntime::start(const char* className, const Vector<String8>& options)
	...省略部分代码
986    JNIEnv* env;
987    if (startVm(&mJavaVM, &env) != 0) {
988        return;
989    }
	...省略部分代码
1027    char* slashClassName = toSlashClassName(className);
1028    jclass startClass = env->FindClass(slashClassName);
	...省略部分代码
1027    char* slashClassName = toSlashClassName(className);
1028    jclass startClass = env->FindClass(slashClassName);
1029    if (startClass == NULL) {
1030        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
1031        /* keep going */
1032    } else {
1033        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
1034            "([Ljava/lang/String;)V");
1035        if (startMeth == NULL) {
1036            ALOGE("JavaVM unable to find main() in '%s'\n", className);
1037            /* keep going */
1038        } else {
1039            env->CallStaticVoidMethod(startClass, startMeth, strArray);
1040
1041#if 0
1042            if (env->ExceptionCheck())
1043                threadExitUncaughtException(env);
1044#endif
1045        }
1046    }
	...省略部分代码
}

  参数className的值等于"com.adnroid.internal.os.ZygoteInit",本地变量env是从调用另一个成员函数startVm创建的ART虚拟机获得的jni接口。函数的目标就是要找到一个名称为com.android.internal.os.ZygoteInit的类,以及它的静态成员函数main,然后就以这个函数为入口,开始运行ART虚拟机。
1028行:调用JNI接口FindClass加载com.android.internal.os.ZygoteInit类。
ZygoteInit类Main方法:/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
1033行:调用JNI接口GetStaticMethodID找到com.android.internal.os.ZygoteInit类的静态成员函数main。
1039行:调用JNI的接口CallStaticVoidMethod开始执行com.internal.os.ZygoteInit类的静态成员函数main。
  需要注意的是这里执行main方法的是本地机器指令,而不是dex字节码。

(2)env->FindClass

/art/runtime/jni_internal.cc#FindClass

589  static jclass FindClass(JNIEnv* env, const char* name) {
590    CHECK_NON_NULL_ARGUMENT(name);
591    Runtime* runtime = Runtime::Current();
592    ClassLinker* class_linker = runtime->GetClassLinker();
593    std::string descriptor(NormalizeJniClassDescriptor(name));
594    ScopedObjectAccess soa(env);
595    mirror::Class* c = nullptr;
596    if (runtime->IsStarted()) {
597      StackHandleScope<1> hs(soa.Self());
598      Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));
599      c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);
600    } else {
601      c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());
602    }
603    return soa.AddLocalReference<jclass>(c);
604  }

  在ART虚拟机运行过程中,存在着一个Runtime单例,用来描述ART运行时。通过调用Runtime的静态成员函数Current可以获得上述Runtime单例。获得这个单例后,就可以调用它的成员函数GetClassLinker来获得一个ClassLinker对象。ClassLinker对象是在创建ART虚拟机的过程中创建的,用来加载类以及连接类的方法。
  JNI静态成员函数FindClass首先判断ART运行时是否已经启动起来。
  如果已经启动,那么久通过调用函数GetClassLoader来获取当前线程所关联的ClassLoader,并且以此为参数,调用前面获得的ClassLinker对象的成员函数FindClass来加载由参数name指定的类。
  如果ART运行时还没有启动,那么这个时候只可以加载系统的类。这个通过前面获得的ClassLinker对象的成员函数FindSystemClass来实现。

(3)ClassLinker#FindClass

/art/runtime/class_linker.cc#FindClass

mirror::Class* ClassLinker::FindClass(Thread* self, const char* descriptor, Handle<mirror::ClassLoader> class_loader) {
	...省略部分代码 下方去除了注释
2129  mirror::Class* klass = LookupClass(descriptor, hash, class_loader.Get());
2130  if (klass != nullptr) {
2131    return EnsureResolved(self, descriptor, klass);
2132  }
2134  if (descriptor[0] == '[') {
2135    return CreateArrayClass(self, descriptor, hash, class_loader);
2136  } else if (class_loader.Get() == nullptr) {
2138    ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
2139    if (pair.second != nullptr) {
2140      return DefineClass(self, descriptor, hash, NullHandle<mirror::ClassLoader>(), *pair.first,
2141                         *pair.second);
2142    } 
2150  }
	...省略部分代码
}

  参数descriptor指向的是要加载的类的签名,而参数class_loader指向的是一个类的加载器。
  首先是调用另外一个成员函数LookupClass来检查参数descriptor指定的类是否已经被加载过,如果是的话,那么ClassLinker类的成员函数LookupClass就会返回一个对应Class对象(代码中的klass),这个Class对象接着就会返回给调用者,表示加载已经完成。
  如果参数descriptor指定类还没有加载过,这时候主要看参数class_loader的值。如果class_loader的值为NULL,那么久需要调用FindInClassPath方法来在系统启动类路径寻找对应的类。
  上述的这种加载方式就是使用了双亲委托机制
  一旦寻找到,那么就会获得包含目标类的DEX文件,因此接下来调用ClassLinker类的另一个成员函数DefindClass,从获得的DEX文件中加载参数descriptor指定的类了。
这里调用FindInClassPath,实际要完成的工作是从ClassLinker的成员变量boot_class_path_描述的一系列的DEX文件中检查哪一个DEX文件包含有参数descriptor指定的类。
  所谓的系统启动类路径,其实就是一系列指定的由系统提供的DEX文件,这些DEX文件保存在ClassLinker类的成员变量boot_class_path_描述的一个向量中。

(4)ClassLinker#DefineClass

/art/runtime/class_linker.cc


2218mirror::Class* ClassLinker::DefineClass(Thread* self, const char* descriptor, size_t hash,
2219                                        Handle<mirror::ClassLoader> class_loader,
2220                                        const DexFile& dex_file,
2221                                        const DexFile::ClassDef& dex_class_def) {
....下方源码去除了注释了空行
2222  StackHandleScope<3> hs(self);
2223  auto klass = hs.NewHandle<mirror::Class>(nullptr);
2226  if (UNLIKELY(!init_done_)) {
2228    if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {
2229      klass.Assign(GetClassRoot(kJavaLangObject));
2230    } else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) {
2231      klass.Assign(GetClassRoot(kJavaLangClass));
2232    } else if (strcmp(descriptor, "Ljava/lang/String;") == 0) {
2233      klass.Assign(GetClassRoot(kJavaLangString));
2234    } else if (strcmp(descriptor, "Ljava/lang/ref/Reference;") == 0) {
2235      klass.Assign(GetClassRoot(kJavaLangRefReference));
2236    } else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {
2237      klass.Assign(GetClassRoot(kJavaLangDexCache));
2238    } else if (strcmp(descriptor, "Ljava/lang/reflect/ArtField;") == 0) {
2239      klass.Assign(GetClassRoot(kJavaLangReflectArtField));
2240    } else if (strcmp(descriptor, "Ljava/lang/reflect/ArtMethod;") == 0) {
2241      klass.Assign(GetClassRoot(kJavaLangReflectArtMethod));
2242    }
2243  }
2245  if (klass.Get() == nullptr) {
2250    klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
2251  }
2252  if (UNLIKELY(klass.Get() == nullptr)) {
2253    CHECK(self->IsExceptionPending());  // Expect an OOME.
2254    return nullptr;
2255  }
2256  klass->SetDexCache(FindDexCache(dex_file));
2257  LoadClass(dex_file, dex_class_def, klass, class_loader.Get());
2258  ObjectLock<mirror::Class> lock(self, klass);
2259  if (self->IsExceptionPending()) {
2262    if (!klass->IsErroneous()) {
2263      klass->SetStatus(mirror::Class::kStatusError, self);
2264    }
2265    return nullptr;
2266  }
2267  klass->SetClinitThreadId(self->GetTid());
2270  mirror::Class* existing = InsertClass(descriptor, klass.Get(), hash);
2271  if (existing != nullptr) {
2274    return EnsureResolved(self, descriptor, existing);
2275  }
2278  CHECK(!klass->IsLoaded());
2279  if (!LoadSuperAndInterfaces(klass, dex_file)) {
2281    if (!klass->IsErroneous()) {
2282      klass->SetStatus(mirror::Class::kStatusError, self);
2283    }
2284    return nullptr;
2285  }
2286  CHECK(klass->IsLoaded());
2288  CHECK(!klass->IsResolved());
2290  auto interfaces = hs.NewHandle<mirror::ObjectArray<mirror::Class>>(nullptr);
2291
2292  mirror::Class* new_class = nullptr;
2293  if (!LinkClass(self, descriptor, klass, interfaces, &new_class)) {
2295    if (!klass->IsErroneous()) {
2296      klass->SetStatus(mirror::Class::kStatusError, self);
2297    }
2298    return nullptr;
2299  }
2300  self->AssertNoPendingException();
2301  CHECK(new_class != nullptr) << descriptor;
2302  CHECK(new_class->IsResolved()) << descriptor;
2304  Handle<mirror::Class> new_class_h(hs.NewHandle(new_class));
2317  Dbg::PostClassPrepare(new_class_h.Get());
2319  return new_class_h.Get();
2320}

  C

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值