使用到的知识点:
- 1 注解
- 2 类加载机制
- 3 反射
- 4 so插件化原理
- 5 native层本地方法替换
原理:找到出现异常的方法,然后,在native层将已修复的方法(fixedMethod)替换出现异常的方法(wrongMethod)。
1.如何找到出现异常的方法?
首先,已修复的方法,是由服务器下发下来的。通过自定义注解,在已修复的方法中添加了要修复的错误方法的信息。其中包括错误方法所在的类信息,错误方法的方法名。
接下来需要通过DexFile 加载,已修复的dex文件。加载成功以后,dexFile.entries()遍历DexFile中的entries得到dex中的类的类名。
再通过dexFile.loadClass加载类。类加载成功以后,再通过反射遍历类中的方法,得到Replace注解信息,Replace注解中就包含了,异常方法wrongMethod的类和方法名信息。
自定义注解Replace.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Replace {
String clazz();
String method();
}
从服务器下发下来的,已修复的方法,具体在已修复的dex文件中。
public class Calculator {
@Replace(clazz = "com.fmtech.fmphix.Calculator", method = "calculate")
public int calculate(){
int i = 100;
int j = 10;
return i/j;
}
}
2.native层如何替换?
在dalvik虚拟机和art虚拟机中是两种不同的替换方案。
(1).在dalvik虚拟机中
需要将异常方法部分属性替换为已修复的方法的属性。这种方法太依赖于底层Method的数据结构。
(2).在art虚拟机中
直接将异常方法整体替换为已修复的方法即可(即ArtMethod内存拷贝整体替换
)。为什么可以这样做呢?
因为在Art虚拟机中,一个类中的方法在底层相应的内存分布是连续的。所以,只要知道wrongMethod的地址,和对应ArtMethod的结构体大小size就可以直接进行内存拷贝,以达到热修复的目的。
jclass jclazz = env->FindClass("com/fmtech/fmphix/NativeStructsModel");
size_t methodF1Id = (size_t)env->GetStaticMethodID(jclazz, "f1", "()V");
size_t methodF2Id = (size_t)env->GetStaticMethodID(jclazz, "f2", "()V");
int size = methodF2Id - methodF1Id;
memcpy(wrongMethod, rightMethod, size);
==================
Java层代码DexManager
public class DexManager {
private static DexManager sInstance = new DexManager();
private Context mContext;
public static DexManager getInstance(){
return sInstance;
}
public void setContext(Context context){
mContext = context;
}
public void loadDexFile(File file){
if(null == file || !file.exists()){
return;
}
try {
DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(), new File(mContext.getCacheDir(), "opt").getAbsolutePath(), Context.MODE_PRIVATE);
if(null == dexFile){
return;
}
Enumeration<String> entries = dexFile.entries();
while(entries.hasMoreElements()){
String className = entries.nextElement();
// Class clazz = Class.forName(className);//Only available for loaded class;
Class clazz = dexFile.loadClass(className, mContext.getClassLoader());
if(null != clazz){
fixClazz(clazz);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void fixClazz(Class fixedClazz){
if(null == fixedClazz){
return;
}
Method[] methods = fixedClazz.getDeclaredMethods();
for(Method rightMethod:methods){
Replace replace = rightMethod.getAnnotation(Replace.class);
if(null == replace){
continue;
}
String wrongClassName = replace.clazz();
String wrongMethodName = replace.method();
try {
Class clazz = Class.forName(wrongClassName);
//Find the wrong method
Method wrongMethod = clazz.getDeclaredMethod(wrongMethodName, rightMethod.getParameterTypes());
if(Build.VERSION.SDK_INT <= 18){
replace(Build.VERSION.SDK_INT, wrongMethod, rightMethod);
}else{
replaceArt(Build.VERSION.SDK_INT, wrongMethod, rightMethod);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
public native void replace(int sdkVersionCode, Method wrongMethod, Method rightMethod);
public native void replaceArt(int sdkVersionCode, Method wrongMethod, Method rightMethod);
}
Native层代码实现
#include <jni.h>
#include <string>
#include "dalvik.h"
#include "art_7_0.h"
//#include "art_method.h"//For Art 6.0
typedef Object *(*DvmDecodeIndirectRef)(void *thread, jobject jobj);
typedef void *(*DvmThreadSelf)();
DvmDecodeIndirectRef dvmDecodeIndirectRef;
DvmThreadSelf dvmThreadSelf;
extern "C" {
JNIEXPORT void JNICALL
Java_com_fmtech_fmphix_DexManager_replace(JNIEnv *env, jobject instance, jint sdkVersionCode,
jobject wrongMethodObj, jobject rightMethodObj) {
//找到Java方法在Native对应的Method结构体
Method *wrongMethod = (Method *) env->FromReflectedMethod(wrongMethodObj);
Method *rightMethod = (Method *) env->FromReflectedMethod(rightMethodObj);
/*接下来操作的主要目的:修改rightMethod第一个成员变量ClassObject的status为CLASS_INITIALIZED*/
/**
* 通过函数dlopen打开指定的动态链接库,得到句柄后,再通过dlsym函数获取动态库中的函数地址,
* 获得函数地址后进行调用
*/
void *dvm_handle = dlopen("libdvm.so", RTLD_NOW);
dvmDecodeIndirectRef = (DvmDecodeIndirectRef) dlsym(dvm_handle, sdkVersionCode > 10
? "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject"
:
"dvmDecodeIndirectRef");
dvmThreadSelf = (DvmThreadSelf) dlsym(dvm_handle, sdkVersionCode > 10 ? "_Z13dvmThreadSelfv"
: "dvmThreadSelf");
jclass methodClazz = env->FindClass("java/lang/reflect/Method");
jmethodID rightMethodId = env->GetMethodID(methodClazz, "getDeclaringClass",
"()Ljava/lang/Class;");
jobject obj = env->CallObjectMethod(rightMethodObj, rightMethodId);
ClassObject *clazz = (ClassObject *) dvmDecodeIndirectRef(dvmThreadSelf(), obj);
clazz->status = CLASS_INITIALIZED;
wrongMethod->accessFlags |= ACC_PUBLIC;
wrongMethod->methodIndex = rightMethod->methodIndex;
wrongMethod->jniArgInfo = rightMethod->jniArgInfo;
wrongMethod->registersSize = rightMethod->registersSize;
wrongMethod->outsSize = rightMethod->outsSize;
//方法参数原型
wrongMethod->prototype = rightMethod->prototype;
wrongMethod->insns = rightMethod->insns;
wrongMethod->nativeFunc = rightMethod->nativeFunc;
jthrowable exc = env->ExceptionOccurred();
if (exc) {
/* We don't do much with the exception, except thatwe print a debug message for it, clear it, andthrow a new exception. */
jclass newExcCls;
env->ExceptionDescribe();
env->ExceptionClear();
newExcCls = env->FindClass("java/lang/IllegalArgumentException");
if (newExcCls == NULL) {
/* Unable to find the exception class, give up. */
return;
}
env->ThrowNew(newExcCls, "thrown from C code");
}
}
JNIEXPORT void JNICALL
Java_com_fmtech_fmphix_DexManager_replaceArt(JNIEnv *env, jobject instance, jint sdkVersionCode,
jobject wrongMethodObj, jobject rightMethodObj) {
art::mirror::ArtMethod *wrongMethod = (art::mirror::ArtMethod *) env->FromReflectedMethod(
wrongMethodObj);
art::mirror::ArtMethod *rightMethod = (art::mirror::ArtMethod *) env->FromReflectedMethod(
rightMethodObj);
/*方式一、ArtMethod内存拷贝整体替换*/
jclass jclazz = env->FindClass("com/fmtech/fmphix/NativeStructsModel");
size_t methodF1Id = (size_t)env->GetStaticMethodID(jclazz, "f1", "()V");
size_t methodF2Id = (size_t)env->GetStaticMethodID(jclazz, "f2", "()V");
int size = methodF2Id - methodF1Id;
memcpy(wrongMethod, rightMethod, size);
// /*方式二、ArtMethod属性替换*/
// //方法所在类(即可以将一个类的方法指向另一个类的方法)
// wrongMethod->declaring_class_ = rightMethod->declaring_class_;
//
// /****Art 6.0 Start*****/
// //快捷访问方式
//// wrongMethod->dex_cache_resolved_methods_ = rightMethod->dex_cache_resolved_methods_;
// //
//// wrongMethod->dex_cache_resolved_types_ = rightMethod->dex_cache_resolved_types_;
// /****Art 6.0 End*****/
//
// //索引偏移量
// wrongMethod->dex_code_item_offset_ = rightMethod->dex_code_item_offset_;
// //方法索引
// wrongMethod->method_index_ = rightMethod->method_index_;
// //
// wrongMethod->dex_method_index_ = rightMethod->dex_method_index_;
//
// /****Art 6.0 Start*****/
// //ArtMethod方法入口
//// wrongMethod->ptr_sized_fields_.entry_point_from_interpreter_ = rightMethod->ptr_sized_fields_.entry_point_from_interpreter_;
// /****Art 6.0 End*****/
//
// wrongMethod->ptr_sized_fields_.entry_point_from_jni_ = rightMethod->ptr_sized_fields_.entry_point_from_jni_;
//
// //Art机器模式的入口
// wrongMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_ = rightMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
//
// /****Art 7.0 Start*****/
// wrongMethod->ptr_sized_fields_.entry_point_from_jni_ = rightMethod->ptr_sized_fields_.entry_point_from_jni_;
// wrongMethod->ptr_sized_fields_.dex_cache_resolved_types_ = rightMethod->ptr_sized_fields_.dex_cache_resolved_types_;
// wrongMethod->ptr_sized_fields_.dex_cache_resolved_methods_ = rightMethod->ptr_sized_fields_.dex_cache_resolved_methods_;
// wrongMethod->hotness_count_ = rightMethod->hotness_count_;
// /****Art 7.0 End*****/
}
}
说明:关于android-4.0.4_r1\dalvik\vm\Jni.cpp#dvmDecodeIndirectRef()函数;
通过dlopen动态链接so,调用dvmDecodeIndirect的依据是:DVM的源码位于dalvik/目录下,其中dalvik/vm目录下的内容是DVM的具体实现部分,它会被编译成libdvm.so;dalvik/libdex会被编译成libdex.a静态库,作为dex工具使用。
/*
* Convert an indirect reference to an Object reference. The indirect
* reference may be local, global, or weak-global.
*
* If "jobj" is NULL, or is a weak global reference whose reference has
* been cleared, this returns NULL. If jobj is an invalid indirect
* reference, kInvalidIndirectRefObject is returned.
*
* Note "env" may be NULL when decoding global references.
*/
Object* dvmDecodeIndirectRef(Thread* self, jobject jobj) {
......
}
网友评论