AndFix热修复原理分析与手写实现
什么是AndFix?
AndFix是阿里推出的热修复框架,热修复是针对线上的出现的轻量级bug,在不进行版本更新的情况下进行修复
优点
无需更新版本,即时生效。更新体积小
缺点
版本和厂商手机不兼容问题
原理
由补丁类的classLoader加载补丁类,在native层针对不同Android架构中的不同的ArtMethod结构调用对应的replaceMethod方法按照定义好的ArtMethod结构一一替换方法的所有信息如所属类、访问权限、代码内存地址等。
手写实现
1.自定义注解,将类名和包名传给
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodReplace {
String className();
String methodName();
}
2.将自己打好的补丁(dex文件),下载到本地,将补丁中的方法替换之前的方法
public class DexManager {
private static final DexManager ourInstance = new DexManager();
public static DexManager getInstance() {
return ourInstance;
}
private DexManager() {
}
private Context mContext;
/**
* 首先要调用该方法
*
* @param context
*/
public void setContext(Context context) {
this.mContext = context;
}
/**
* 加载dex文件
*
* @param file 修复好的补丁包(dex文件)
*/
public void loadDex(File file) {
try {
DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
new File(mContext.getCacheDir(), "opt").getAbsolutePath(),
Context.MODE_PRIVATE);
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
//获取类名
String className = entries.nextElement();
//加载类
// Class<?> aClass = Class.forName(className);//不适用!只能加载安装了的App中的class
Class fixedClass = dexFile.loadClass(className, mContext.getClassLoader());
if (null != fixedClass){
fixClass(fixedClass);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 修复class(里面的method)
* 核心
* @param fixedClass
*/
private void fixClass(Class fixedClass) {
//获取methods
Method[] fixedMethods = fixedClass.getDeclaredMethods();
for (Method fixMethod : fixedMethods) {
//
MethodReplace methodReplace = fixMethod.getAnnotation(MethodReplace.class);
if (null == methodReplace){
continue;
}
//找到注解了(要修复的方法)
String className = methodReplace.className();
String methodName = methodReplace.methodName();
if (!TextUtils.isEmpty(className)&&!TextUtils.isEmpty(methodName)){
try {
//获取带bug的方法所在的class
Class<?> bugClass = Class.forName(className);
//获取带bug的方法
Method bugMethod = bugClass.getDeclaredMethod(methodName,
fixMethod.getParameterTypes());
//fixMethod + bugMethod
//接下来替换对应ArtMethod结构体成员
replace(bugMethod, fixMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private native void replace(Method bugMethod,Method fixMethod);
//不要忘记
static {
System.loadLibrary("native-lib");
}
}
3.利用native层,将ArtMethod 结构调用对应的replaceMethod方法按照定义好的ArtMethod结构,替换方法的所有的内容
#include <jni.h>
#include <string>
#include "art_5_1.h"
/**
* 底层核心代码
* 替换ArtMethod结构体中的字段
* Method>ArtMethod>获取字段
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_netease_andfix_DexManager_replace(JNIEnv *env, jobject thiz, jobject bug_method,
jobject fix_method) {
//获取带bug的Method的ArtMethod
art::mirror::ArtMethod *bugArtMethod = reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(
bug_method));
//获取修复好的Method的ArtMethod
art::mirror::ArtMethod *fixArtMethod = reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(
fix_method));
//size_t artMethodSize = aMethod-bMethod;
//memcpy(bugArtMethod,fixArtMethod , artMethodSize);
reinterpret_cast<art::mirror::Class*>(fixArtMethod->declaring_class_)->class_loader_ =
reinterpret_cast<art::mirror::Class*>(bugArtMethod->declaring_class_)->class_loader_; //for plugin classloader
reinterpret_cast<art::mirror::Class*>(fixArtMethod->declaring_class_)->clinit_thread_id_ =
reinterpret_cast<art::mirror::Class*>(bugArtMethod->declaring_class_)->clinit_thread_id_;
reinterpret_cast<art::mirror::Class*>(fixArtMethod->declaring_class_)->status_ = reinterpret_cast<art::mirror::Class*>(bugArtMethod->declaring_class_)->status_-1;
//for reflection invoke
reinterpret_cast<art::mirror::Class*>(fixArtMethod->declaring_class_)->super_class_ = 0;
bugArtMethod->declaring_class_ = fixArtMethod->declaring_class_;
bugArtMethod->dex_cache_resolved_types_ = fixArtMethod->dex_cache_resolved_types_;
bugArtMethod->access_flags_ = fixArtMethod->access_flags_ | 0x0001;
bugArtMethod->dex_cache_resolved_methods_ = fixArtMethod->dex_cache_resolved_methods_;
bugArtMethod->dex_code_item_offset_ = fixArtMethod->dex_code_item_offset_;
bugArtMethod->method_index_ = fixArtMethod->method_index_;
bugArtMethod->dex_method_index_ = fixArtMethod->dex_method_index_;
bugArtMethod->ptr_sized_fields_.entry_point_from_interpreter_ =
fixArtMethod->ptr_sized_fields_.entry_point_from_interpreter_;
bugArtMethod->ptr_sized_fields_.entry_point_from_jni_ =
fixArtMethod->ptr_sized_fields_.entry_point_from_jni_;
bugArtMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_ =
fixArtMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
//小米5:5.1系统,改了ArtMethod结构体字段, 增加了一个number字段
// bugArtMethod->declaring_class_ = fixArtMethod->declaring_class_;
//// 在内存上是下面:
//// (uint32_t*)(bugArtMethod + 0) = (uint32_t*)(fixArtMethod + 0);
// //小米5,第一个字段增加为 number后, 49行代码实际执行的操作是:
// bugArtMethod->number = fixArtMethod->declaring_class_;
}
附:android 5.0jni源码
#include <string.h>
#include <jni.h>
#include <stdio.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <stdint.h> /* C99 */
namespace art {
namespace mirror {
class Object {
public:
// The number of vtable entries in java.lang.Object.
static constexpr size_t kVTableLength = 11;
static uint32_t hash_code_seed;
uint32_t klass_;
uint32_t monitor_;
};
class Class : public Object {
public:
// Interface method table size. Increasing this value reduces the chance of two interface methods
// colliding in the interface method table but increases the size of classes that implement
// (non-marker) interfaces.
static constexpr size_t kImtSize = 64; //IMT_SIZE;
// defining class loader, or NULL for the "bootstrap" system loader
uint32_t class_loader_;
// For array classes, the component class object for instanceof/checkcast
// (for String[][][], this will be String[][]). NULL for non-array classes.
uint32_t component_type_;
// DexCache of resolved constant pool entries (will be NULL for classes generated by the
// runtime such as arrays and primitive classes).
uint32_t dex_cache_;
// Short cuts to dex_cache_ member for fast compiled code access.
uint32_t dex_cache_strings_;
// static, private, and <init> methods
uint32_t direct_methods_;
// instance fields
//
// These describe the layout of the contents of an Object.
// Note that only the fields directly declared by this class are
// listed in ifields; fields declared by a superclass are listed in
// the superclass's Class.ifields.
//
// All instance fields that refer to objects are guaranteed to be at
// the beginning of the field list. num_reference_instance_fields_
// specifies the number of reference fields.
uint32_t ifields_;
// The interface table (iftable_) contains pairs of a interface class and an array of the
// interface methods. There is one pair per interface supported by this class. That means one
// pair for each interface we support directly, indirectly via superclass, or indirectly via a
// superinterface. This will be null if neither we nor our superclass implement any interfaces.
//
// Why we need this: given "class Foo implements Face", declare "Face faceObj = new Foo()".
// Invoke faceObj.blah(), where "blah" is part of the Face interface. We can't easily use a
// single vtable.
//
// For every interface a concrete class implements, we create an array of the concrete vtable_
// methods for the methods in the interface.
uint32_t iftable_;
// Descriptor for the class such as "java.lang.Class" or "[C". Lazily initialized by ComputeName
uint32_t name_;
// Static fields
uint32_t sfields_;
// The superclass, or NULL if this is java.lang.Object, an interface or primitive type.
uint32_t super_class_;
// If class verify fails, we must return same error on subsequent tries.
uint32_t verify_error_class_;
// Virtual methods defined in this class; invoked through vtable.
uint32_t virtual_methods_;
// Virtual method table (vtable), for use by "invoke-virtual". The vtable from the superclass is
// copied in, and virtual methods from our class either replace those from the super or are
// appended. For abstract classes, methods may be created in the vtable that aren't in
// virtual_ methods_ for miranda methods.
uint32_t vtable_;
// Access flags; low 16 bits are defined by VM spec.
uint32_t access_flags_;
// Total size of the Class instance; used when allocating storage on gc heap.
// See also object_size_.
uint32_t class_size_;
// Tid used to check for recursive <clinit> invocation.
pid_t clinit_thread_id_;
// ClassDef index in dex file, -1 if no class definition such as an array.
// TODO: really 16bits
int32_t dex_class_def_idx_;
// Type index in dex file.
// TODO: really 16bits
int32_t dex_type_idx_;
// Number of instance fields that are object refs.
uint32_t num_reference_instance_fields_;
// Number of static fields that are object refs,
uint32_t num_reference_static_fields_;
// Total object size; used when allocating storage on gc heap.
// (For interfaces and abstract classes this will be zero.)
// See also class_size_.
uint32_t object_size_;
// Primitive type value, or Primitive::kPrimNot (0); set for generated primitive classes.
uint32_t primitive_type_;
// Bitmap of offsets of ifields.
uint32_t reference_instance_offsets_;
// Bitmap of offsets of sfields.
uint32_t reference_static_offsets_;
// State of class initialization.
int32_t status_;
// TODO: ?
// initiating class loader list
// NOTE: for classes with low serialNumber, these are unused, and the
// values are kept in a table in gDvm.
// InitiatingLoaderList initiating_loader_list_;
// The following data exist in real class objects.
// Embedded Imtable, for class object that's not an interface, fixed size.
// ImTableEntry embedded_imtable_[0];
// Embedded Vtable, for class object that's not an interface, variable size.
// VTableEntry embedded_vtable_[0];
// Static fields, variable size.
// uint32_t fields_[0];
// java.lang.Class
static void *java_lang_Class_;
};
class ArtField : public Object {
public:
uint32_t declaring_class_;
int32_t access_flags_;
int32_t field_dex_idx_;
int32_t offset_;
};
//art 可以采用 解释模式 或者 AOT模式执行。
//解释模式就是取出dex code,逐条解释执行。这个时候回取这个方法的entry_point_from_interpreter_然后跳转执行。
//AOT模式是”Ahead of time”,在安装时将dex code 优化成机器码,运行时直接执行机器码执行。调用这个方法时会调用entry_point_from_quick_compiled_code_,然后跳转执行。
class ArtMethod : public Object {
public:
// uint32_t number;//小米5加的字段
// Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
// The class we are a part of.
uint32_t declaring_class_;
// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
//这是一个存放ArtMethod*的指针数组,通过它可以获得ArtMethod所在dex所有Method对应的ArtMethod*
uint32_t dex_cache_resolved_methods_;
// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
uint32_t dex_cache_resolved_types_;
// Access flags; low 16 bits are defined by spec.
//可以理解为该函数的标志位,如函数为public,private,static,native等。
uint32_t access_flags_;
/* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */
// Offset to the CodeItem.
//里面指向code_item指针,code_item存储的实际是dex当中的字节码.其用处本来是适配dalvik解释器,
//即无法编译成机器码的,用解释器来执行。
//最终可得到code_item地址,里面存储方法指令。但需先获取dex头地址
uint32_t dex_code_item_offset_;
// Index into method_ids of the dex file associated with this method.
//方法索引,主要作为寻址替换用
uint32_t dex_method_index_;
/* End of dex file fields. */
// Entry within a dispatch table for this method. For static/direct methods the index is into
// the declaringClass.directMethods, for virtual methods the vtable and for interface methods the
// ifTable.
uint32_t method_index_;
// Fake padding field gets inserted here.
// Must be the last fields in the method.
struct PtrSizedFields {
// Method dispatch from the interpreter invokes this pointer which may cause a bridge into
// compiled code.
//通过interpreter方式调用方法 解释执行入口。
void *entry_point_from_interpreter_;
// Pointer to JNI function registered to this method, or a function to resolve the JNI function.
//用于存储jni方法信息。
void *entry_point_from_jni_;
// Method dispatch from quick compiled code invokes this pointer which may cause bridging into
// portable compiled code or the interpreter.
//这里就是常见的ART Hook方法为,替换方法的入口点,
// 替换后的入口点,会重新准备栈和寄存器,执行hook的方法。
// 注意,这里指向的是汇编代码,运行的是已经预处理过的机器码。
void *entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;
static void *java_lang_reflect_ArtMethod_;
};
}
}
cmakeList
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib})
示例
public class MainActivity extends AppCompatActivity {
private Caculator caculator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
caculator = new Caculator();
}
public void caculator(View view) {
caculator.caculator(this);
}
public void fix(View view) {
DexManager.getInstance().setContext(this);
DexManager.getInstance().loadDex(
new File(Environment.getExternalStorageDirectory(), "out.dex"));
}
}
总结
AndFix 目前官方只支持到7.0。轻量级,性能损耗低,不许冷启动就能生效。而且由于厂商会修改ArtMethod,所以只支持原生系统,如果商用的话不支持选择AndFix