热修复 DexPosed AOP Xposed MD

时间:2023-03-10 05:21:09
热修复 DexPosed AOP Xposed MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

dexposed

GitHub

重要提醒:

  • 1、此方案不支持ART(Android 5.0 及之后的Android版本),这一点是致命的!
  • 2、此方案不支持Dalvik 3.0(专为平板设计的Android版本)
  • 3、只支持 ARM 架构

基于此,注定它会逐步失声,再多的优点也是徒劳。

dexposed enable 'god' mode上帝视角 for single android application.

热修复 DexPosed AOP Xposed MD

基本实现原理简介

Dexposed中的AOP原理来自于Xposed

在Dalvik虚拟机下,主要是通过改变一个方法对象方法在Dalvik虚拟机中的定义来实现,具体做法就是将该方法的类型改变为native并且将这个方法的实现链接到一个通用的 Native Dispatch 方法上。

这个 Dispatch 方法通过JNI回调到Java端的一个统一处理方法,最后在统一处理方法中调用 before、after 函数来实现AOP。

在Art虚拟机上目前也是是通过改变一个 ArtMethod 的入口函数来实现。

Dalvik 和 ART 虚拟机

什么是Dalvik:

Dalvik是Google公司自己设计用于Android平台的Java虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。

Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为一个独立的 Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。

什么是ART:

Android操作系统已经成熟,Google的Android团队开始将注意力转向一些底层组件,其中之一是负责应用程序运行的Dalvik运行时。因此Google开发了更快执行效率更高更省电的替代ART运行时,ART代表 Android Runtime,其处理应用程序执行的方式完全不同于Dalvik。

Dalvik是依靠一个Just-In-Time (JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。

ART则完全改变了这套做法,在应用安装时就预编译字节码到机器语言,这一机制叫Ahead-Of-Time (AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。

ART优点:

  • 系统性能的显著提升。
  • 应用启动更快、运行更快、体验更流畅、触感反馈更及时。
  • 更长的电池续航能力。
  • 支持更低的硬件。

ART缺点:

  • 更大的存储空间占用,可能会增加10%-20%。
  • 更长的应用安装时间

总的来说ART的功效就是“空间换时间”。

加载 so 库的逻辑

google在android4.4之后的版本都用art取代了dalvik,所以要 hook android4.4以后的版本就必须去适配art虚拟机的机制。这也解释了为什么会有dexposeddexposed_l两个so。

目前官方表示,为了适配art的dexposed_l只是beta版,所以最好不要在正式的线上产品中使用它。

public synchronized static boolean canDexposed(Context context) {
if (!DeviceCheck.isDeviceSupport(context)) {
return false;
}
return loadDexposedLib(context); //load xposed lib for hook.
}
private static boolean loadDexposedLib(Context context) {
try {
if (android.os.Build.VERSION.SDK_INT > 19){
System.loadLibrary("dexposed_l");
} else if (android.os.Build.VERSION.SDK_INT == 10
|| android.os.Build.VERSION.SDK_INT == 9 || android.os.Build.VERSION.SDK_INT > 14){
System.loadLibrary("dexposed");
}
return true;
} catch (Throwable e) {
return false;
}
}

可以看到,在sdk版本大于19时,会去调用dexposed_l,否则会调用dexposed。

findAndHookMethod 的执行过程

jar包的java部分代码结构很简洁,DexposedBrigde是主要的功能调用类,XposedHelpers则是一个反射功能类,其他则为一些辅助类。

DexposedBrigde中用来hook的方法findAndHookMethod的执行过程大致为:

  • 首先会通过XposedHelpers这个反射工具类的findMethodExact方法来找到对应的Method。
  • 然后会执行hookMethod方法。
  • 然后里面会调用hookMethodNative方法,我们需要给hookMethodNative传递的参数分别是:根据反射定位到得要hook的Method、声明定义Method的class、slot标记runtime的标记符、包含函数入参,返回值类型以及对应回调的自定义类。
  • 这样就完成了对应的hook。

支持的版本

Dexposed 支持从 Android 2.3 到 4.4(不包括3.0)的所有 dalvik 运行时 arm 架构设备。稳定性已在我们的长期产品实践中得到证实。

Runtime Android Version Support
Dalvik 2.2 Not Test
Dalvik 2.3 Yes
Dalvik 3 No
Dalvik 4.0-4.4 Yes
ART 5 Testing
ART 5.1 No
ART M No

官方文档

What is it?

Dexposed is a powerful yet并且 non-invasive非侵入性的 runtime AOP (Aspect-oriented Programming) framework for Android app development, based on the work of open-source Xposed framework project.

Dexposed是一个功能强大但非侵入性的运行时AOP框架,适用于Android应用程序开发,基于开源Xposed框架项目。

The AOP of Dexposed is implemented purely纯粹的 non-invasive, without any annotation processor, weaver or bytecode rewriter. The integration is as simple as loading a small JNI library in just one line of code at the initialization phase初始化阶段 of your app.

Dexposed的AOP是纯粹非侵入性的,没有任何注释处理器,编织器或字节码重写器。 集成就像在应用程序初始化阶段只在一行代码中加载一个小JNI库一样简单。

Not only the code of your app, but also the code of Android framework that running in your app process can be hooked. This feature is extremely useful in Android development as we developers heavily rely on严重依赖 the fragmented old versions of Android platform (SDK).

不仅应用程序的代码,而且还可以hooked在您的应用程序进程中运行的Android框架的代码。 此功能在Android开发中非常有用,因为我们开发人员严重依赖分散的旧版Android平台(SDK)。

Together with dynamic class loading, a small piece of compiled Java AOP code can be loaded into the running app, effectively altering the behavior of the target app without restart.

结合动态类加载,可以将一小段编译好的Java AOP代码加载到正在运行的应用程序中,从而有效地改变目标应用程序的行为而无需重新启动。

Typical use-cases

典型使用场景

  • Classic AOP programming,典型的 AOP 编程
  • Instrumentation (for testing, performance monitoring and etc.),仪表化 (测试,性能监控等等)
  • Online hot patch to fix critical, emergent or security bugs,在线热补丁修复关键,紧急或安全漏洞
  • SDK hooking for a better development experience,可以 hooking SDK 以便能有更好的开发体验

集成

patchloader.jar 导入工程

Directly add dexposed aar to your project as compile libraries, it contains a jar file "dexposedbridge.jar" two so files "libdexposed.so libdexposed_l.so" from 'dexposed' directory.

直接将 dexposed aar 添加到您的项目作为编译库,它包含一个jar文件 “dexposedbridge.jar” 两个来自'dexposed'目录的 .so 文件 “libdexposed.so 和 libdexposed_l.so”。

注意:这里aar文件只包含了armeabi架构的so文件,所以不支持 x86 架构。

compile 'com.taobao.android:dexposed:0.1.1@aar'

初始化:

// Check whether current device is supported (also initialize Dexposed framework if not yet)
if (DexposedBridge.canDexposed(this)) {
Log.i("bqt", "Use Dexposed to kick off AOP stuffs.");
} else {
Log.i("bqt", "Not Supported");
}

基本使用

There are three injection注入 points for a given method: before, after, replace.

在指定方法前后注入逻辑

可以不改变原函数的执行,但是在原函数执行前后去做一些其他的额外处理,例如改变入参和返回值等等的一些事情。

// Target class, method with parameter types, followed by the hook callback (XC_MethodHook).
DexposedBridge.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() { // To be invoked before Activity.onCreate().
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Activity instance = (Activity) param.thisObject; //thisObject保持对目标类实例的引用
Bundle bundle = (Bundle) param.args[0]; //数组args包含所有参数
Intent intent = new Intent();
XposedHelpers.setObjectField(param.thisObject, "mIntent", intent); //XposedHelpers提供了很多实用的方法 if (bundle.containsKey("return"))
param.setResult(null); //调用setResult将绕过原始方法体,直接使用指定的参数作为方法返回值。
} // To be invoked after Activity.onCreate()
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedHelpers.callMethod(param.thisObject, "sampleMethod", 2);
}
});
DexposedBridge.findAndHookMethod(Log.class, "d", String.class, String.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam arg0) throws Throwable {
String tag = (String) arg0.args[0];
String msg = (String) arg0.args[1];
System.out.println(tag + "," + msg);
}
});

完全替换某一指定方法

可以将原有要执行的函数替换成一个我们需要的新的执行函数

DexposedBridge.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
Log.i("bqt", "在原始方法上下文之外重写方法逻辑有点棘手,但仍然可行");
return new Object();
}
});

用于热修复

使用的前提是,我们需先创建一个热修复工程,打包后在我们项目中动态下载下来。

因为里面基本没什么逻辑,所以包很小,不用担心网络和下载时间的影响

补丁项目参考:dexposed/sample/patchsample

该项目可以生成一个将由dexposedexamples运行的apk,patchloader.jar是从dexposedexamples的“com.taobao.patch”包中导出的。

原始项目参考:dexposed/sample/dexposedexamples

原始项目修复bug的核心逻辑:

File cacheDir = getExternalCacheDir();
if (cacheDir != null) {
String fullpath = cacheDir.getAbsolutePath() + File.separator + "patch.apk";//这个是我们下载到本地的热修复包
PatchResult result = PatchMain.load(this, fullpath, null);
if (result.isSuccess()) {
Log.i("bqt", "hotPath load apk success.");
} else {
Log.i("bqt", "hotPath load apk error. " + result.getErrorInfo());
}
}

最核心的逻辑其实就是这一行代码:

PatchResult result = PatchMain.load(this, fullpath, null);

2018-6-9