alibaba dexposed初步解析

alibaba新出了一个非侵入的aop库,感觉不错,那么楼主这次就来学习一下这个库的具体应用,原理以及可以达到的效果。

这里先给出对应的github工程传送门:https://github.com/alibaba/dexposed

1.首先来讲讲,dexposed的具体用法怎么用,怎么引入到我们的工程中来。

这个其实在dexposed的github工程上说明的很清楚,这里我来重述下。

首先我们要将其引入到工程中:

native_dependencies {
    artifact ‘com.taobao.dexposed:dexposed_l:0.2+:armeabi‘
    artifact ‘com.taobao.dexposed:dexposed:0.2+:armeabi‘
}
dependencies {
    compile files(‘libs/dexposedbridge.jar‘)
}

看到libdexposed.so和libdexposed_l.so,可能会想到他们的差异是什么,看loadDexposedLIb这个方法其实很容易看出来:

private static boolean loadDexposedLib(Context context) {
        try {
            if(VERSION.SDK_INT != 10 && VERSION.SDK_INT != 9) {
                if(VERSION.SDK_INT > 19) {
                    System.loadLibrary("dexposed_l");
                } else {
                    System.loadLibrary("dexposed");
                }
            } else {
                System.loadLibrary("dexposed2.3");
            }

            return true;
        } catch (Throwable var2) {
            return false;
        }
    }

在sdk版本大于19,及为5.0以上时,会去调用dexposed_l,否则会调用dexposed,至于为什么需要两套不同的native实现,后面会解释到。

集成到我们的工程后,如果想应用它,只需要简单地一句话:

public class MyApplication extends Application {

    @Override public void onCreate() {
        // Check whether current device is supported (also initialize Dexposed framework if not yet)
        if (DexposedBridge.canDexposed(this)) {
            // Use Dexposed to kick off AOP stuffs.
            ...
        }
    }
    ...
}

参考git上给出的代码,官方应该是推荐在application层去调用这个方法。值得注意的是DexposedBridge.canDexposed(this)是一个对应boolean类型返回值的函数,若为false则是不能去实现hook函数的,所以要注意记录这个返回值,以便再其他地方调用hook时用来判断是否可以执行。

2.那么我们既然已经将其引入了工程,接下来就是研究怎么去使用这个库了

Example 1: Attach a piece of code before and after all occurrences of Activity.onCreate(Bundle).

    // 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 {
            // "thisObject" keeps the reference to the instance of target class.
            Activity instance = (Activity) param.thisObject;

            // The array args include all the parameters.
            Bundle bundle = (Bundle) param.args[0];
            Intent intent = new Intent();
            // XposedHelpers provide useful utility methods.
            XposedHelpers.setObjectField(param.thisObject, "mIntent", intent);

            // Calling setResult() will bypass the original method body use the result as method return value directly.
            if (bundle.containsKey("return"))
                param.setResult(null);
        }

        // To be invoked after Activity.onCreate()
        @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            XposedHelpers.callMethod(param.thisObject, "sampleMethod", 2);
        }
    });

Example 2: Replace the original body of the target method.

    DexposedBridge.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodReplacement() {

        @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
            // Re-writing the method logic outside the original method context is a bit tricky but still viable.
            ...
        }

    });

以上两个example是git上给出的两个最基础的用法。其他用法的延生都会基于这两个基础的用法。

example1中,我们可以看到,用dexposed可以实现不改变原函数的执行,但是在原函数执行前后去做一些其他的额外处理,例如改变入参和返回值等等的一些事情。

example2中,则是可以将原有要执行的函数替换成一个我们需要的新的执行函数。

基于以上两个实现,我们可以衍生出什么样的应用场景呢?有人说hook api具体可以实现什么,全凭使用者的想象力,实际上确实如此。

官方列出的几种应用场景正式当今比较需要的:

  • 典型的 AOP 编程
  • 仪表化 (测试,性能监控等等)
  • 在线热修复(重要,关键,安全漏洞等等)
  • SDK hooking,更好的开发体验

3.既然要应用一个库,我们必须对它的实现以及原理有一定的了解

下面是一段比较官方的介绍:

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优点:

1、系统性能的显著提升。

2、应用启动更快、运行更快、体验更流畅、触感反馈更及时。

3、更长的电池续航能力。

4、支持更低的硬件。

ART缺点:

1、更大的存储空间占用,可能会增加10%-20%。

2、更长的应用安装时间。

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

google在android4.4之后的版本都用art取代了dalvik,所以要hook android4.4以后的版本就必须去适配art虚拟机的机制。这也解释了上面为什么会有dexposed和dexposed_l两个so。目前官方表示,为了适配art的dexposed_l只是beta版,所以最好不要在正式的线上产品中使用它。

接下来,稍微来分析下对应的jar包的java部分代码结构:

其实很简洁,DexposedBrigde是主要的功能调用类,XposedHelpers则是一个反射功能类,其他则为一些辅助类。

工程用来hook的方法DexposedBridge.findAndHookMethod,那么这里来看看这个函数java部分的执行过程:

public static Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
        if(parameterTypesAndCallback.length != 0 && parameterTypesAndCallback[parameterTypesAndCallback.length - 1] instanceof XC_MethodHook) {
            XC_MethodHook callback = (XC_MethodHook)parameterTypesAndCallback[parameterTypesAndCallback.length - 1];
            <span style="color:#ff0000;">Method m = XposedHelpers.findMethodExact(clazz, methodName, parameterTypesAndCallback);</span>
            Unhook unhook = hookMethod(m, callback);
            if(!(callback instanceof XC_MethodKeepHook) && !(callback instanceof XC_MethodKeepReplacement)) {
                ArrayList var6 = allUnhookCallbacks;
                synchronized(allUnhookCallbacks) {
                    allUnhookCallbacks.add(unhook);
                }
            }

            return unhook;
        } else {
            throw new IllegalArgumentException("no callback defined");
        }
    }

首先会通过XposedHelpers这个反射工具类的findMethodExact方法来找到对应的Method。

然后会执行hookMethod:

public static Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
        if(!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor)) {
            throw new IllegalArgumentException("only methods and constructors can be hooked");
        } else {
            boolean newMethod = false;
            Map declaringClass = hookedMethodCallbacks;
            DexposedBridge.CopyOnWriteSortedSet callbacks;
            synchronized(hookedMethodCallbacks) {
                callbacks = (DexposedBridge.CopyOnWriteSortedSet)hookedMethodCallbacks.get(hookMethod);
                if(callbacks == null) {
                    callbacks = new DexposedBridge.CopyOnWriteSortedSet();
                    hookedMethodCallbacks.put(hookMethod, callbacks);
                    newMethod = true;
                }
            }

            callbacks.add(callback);
            if(newMethod) {
                Class declaringClass1 = hookMethod.getDeclaringClass();
                int slot = runtime == 1?XposedHelpers.getIntField(hookMethod, "slot"):0;
                Class[] parameterTypes;
                Class returnType;
                if(hookMethod instanceof Method) {
                    parameterTypes = ((Method)hookMethod).getParameterTypes();
                    returnType = ((Method)hookMethod).getReturnType();
                } else {
                    parameterTypes = ((Constructor)hookMethod).getParameterTypes();
                    returnType = null;
                }

                DexposedBridge.AdditionalHookInfo additionalInfo = new DexposedBridge.AdditionalHookInfo(callbacks, parameterTypes, returnType, (DexposedBridge.AdditionalHookInfo)null);
                <span style="color:#ff0000;">hookMethodNative(hookMethod, declaringClass1, slot, additionalInfo);</span>
            }

            callback.getClass();
            return new Unhook(callback, hookMethod);
        }
    }

这里我们需要给hookMethodNative传递的参数分别是1.根据反射定位到得要hook得Method;2.声明定义Method的class;3.slot标记runtime的标记符;4.包含函数入参,返回值类型以及对应回调的自定义类。然后就完成了对应的hook,java端的代码还是比较简单清晰的。至于native中的具体实现,楼主就不在这里详细解析了。这里可以给出hookMethodNative对应的c代码。

static void com_taobao_android_dexposed_DexposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect,
            jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) {
    // Usage errors?
    if (declaredClassIndirect == NULL || reflectedMethodIndirect == NULL) {
        dvmThrowIllegalArgumentException("method and declaredClass must not be null");
        return;
    }

    // Find the internal representation of the method
    ClassObject* declaredClass = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect);
    Method* method = dvmSlotToMethod(declaredClass, slot);
    if (method == NULL) {
        dvmThrowNoSuchMethodError("could not get internal representation for method");
        return;
    }

    if (dexposedIsHooked(method)) {
        // already hooked
        return;
    }

    // Save a copy of the original method and other hook info
    DexposedHookInfo* hookInfo = (DexposedHookInfo*) calloc(1, sizeof(DexposedHookInfo));
    memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct));
    hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect));
    hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect));

    // Replace method with our own code
    SET_METHOD_FLAG(method, ACC_NATIVE);
    method->nativeFunc = &dexposedCallHandler;
    method->insns = (const u2*) hookInfo;
    method->registersSize = method->insSize;
    method->outsSize = 0;

    if (PTR_gDvmJit != NULL) {
        // reset JIT cache
        MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull) = true;
    }
}

至于有兴趣研究的童鞋可以去github上查看完整代码,并深入研究,这里涉及比较多得android底层知识。

4.既然也了解部分的实现和原理,dexposed还有一个最吸引人的地方就是拥有热补丁的功能

android开发人员都知道,对于android这种客户端的应用,一旦出现线上bug,唯一的解决方法就是发修复包来升级修复,这是很不灵活,而且体验很差的。那么能够在不发版的情况下,动态修复线上bug是被急切需求的。下面来演示一下如何应用dexposed来修复线上的bug。

github上有给出相应的例子,楼主在此基础上进行了拓展和完善。

首先在你得app中添加此方法:

// Run taobao.patch apk
    public void runPatchApk() {
        if (android.os.Build.VERSION.SDK_INT == 21) {
            return;
        }
        if (!isSupport) {
            Log.d("dexposed", "This device doesn't support dexposed!");
            return;
        }
        File cacheDir = <span style="color:#ff0000;">getExternalCacheDir();</span>
        if (cacheDir != null) {
            String fullpath = cacheDir.getAbsolutePath() + File.separator + "patch.apk";
            PatchResult result = <span style="color:#ff0000;">PatchMain.load(this, fullpath, null);</span>
            if (result.isSuccess()) {
                Log.e("Hotpatch", "patch success!");
            } else {
                Log.e("Hotpatch", "patch error is " + result.getErrorInfo());
            }
        }
    }

其中用于存放patch.apk,即补丁文件的文件夹路径可按自己需求定义。PatchMain.load实现的主要功能是去遍历patch.apk中的ipatch类,并调用其中的handlePatch方法。注意:最好将该方法加在application的oncreate方法中,保证初始化程序时已打上补丁。那么当然你得app必须有一个去后台检查是否需要下载补丁的接口,如若需要最好用静默下载的方式,进行静默修复。下载完成后记得,动态调用runPatchApk这个方法,否则补丁不会生效。

那么再来看看对应的patch.apk工程应该是怎么构建的。

只需要引入dexposedbridge.jar和patchloader.jar,然后com.taobao.patch的包名下,去添加对应的Ipatch类即可。

那么我们想象线上版本出现了图片显示有问题,我们怎么去应用dexposed的热补丁功能去修复呢。

public class ViewPatch implements IPatch {
    @Override
    public void handlePatch(PatchParam patchParam) throws Throwable {
        Class<?> cls = null;
        try {
            cls = patchParam.context.getClassLoader()
                    .loadClass("com.husor.mizhe.activity.SplashActivity");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }

        DexposedBridge.findAndHookMethod(cls, "initView",
                new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        Activity mainActivity = (Activity) param.thisObject;
                        ImageView bottomView = (ImageView) XposedHelpers.getObjectField(mainActivity, "iv_start_bottom");
                        bottomView.setImageResource(0x7f020175);
                    }
                });
    }
}

我们先通过loadClass来定位到出问题的class,然后通过hook的方法,将SplashActivity的initView方法中设置图片错误的imageview,设置成我们想要得正确地resourceId.

总结:

这套库在java层面,主要应用到得是Java的反射机制,而且在应用这套库hook函数时,也会对使用者的反射知识有较高的要求,尤其在热补丁的应用场景下,只能通过反射去获取应用对应的函数和成员变量,以及对它们进行相应的操作。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-02 19:54:25

alibaba dexposed初步解析的相关文章

OpenStack Neutron DVR L2 Agent的初步解析(二)

声明: 本博客欢迎转载,但请保留原作者信息! 作者:林凯 团队:华为杭州OpenStack团队 OpenStack Juno版本已正式发布,这是这个开源云平台的10个版本,在Juno版的Neutron模块中真正引入了分布式路由(DVR)的实现,现在就让我们来初步看下分布式路由是怎么样工作的. 在OpenStack Neutron DVR L2 Agent的初步解析 (一)中我们已经知道DVR是怎么样工作的,现在就我们就来看下具体DVR是怎么样创建起来并且生效进行工作的. L2用Plugin与L3

【NXP开发板应用—智能插排】2.初步解析example之GPI

[前言] 首先感谢深圳市米尔科技有限公司举办的这次活动并予以本人参加这次活动的机会,以往接触过嵌入式,但那都是皮毛,最多刷个系统之类的,可以说对于嵌入式系统开发这件事情是相当非常陌生的,这次活动为我提供了一个非常好的入门和学习嵌入式的机会,同时在社区及技术群内也得到了很大的帮助和鼓励,这也是我不断前行和进步的动力,在这里一并致谢了.下面我附上本次学习心得及过程. 米尔MYS-6ULX单板机介绍: MYS-6ULX是一款以NXP i.MX6UL/i.MX6ULL处理器,Cortex-A7内核为核心

OpenStack Neutron DVR L2 Agent的初步解析 (一)

声明: 本博客欢迎转载,但请保留原作者信息! 作者:林凯 团队:华为杭州OpenStack团队 OpenStack Juno版本已正式发布,这是这个开源云平台的10个版本,在Juno版的Neutron模块中真正引入了分布式路由(DVR)的实现,现在就让我们来初步看下分布式路由是怎么样工作的. 分布式路由怎么工作? 为了实现分布式路由,L3和L2 agent将需要工作在计算节点内.今天,L3 agent运行在网络节点,但DVR提议,L3agent会在计算节点上运行.L2 agent将继续工作在计算

scrapy初步解析源码即深度使用

scrapy深度爬虫 ——编辑:大牧莫邪 本章内容 深度爬虫概述 scrapy Spider实现的深度爬虫 scrapy CrawlSpdier实现的深度爬虫 案例操作 课程内容 1. 深度爬虫概述 爬虫程序,主要是用与数据采集处理的一种网络程序,在操作过程中针对指定的url地址进行数据请求并根据需要采集数据,但是在实际项目开发过程中,经常会遇到目标url地址数量不明确的情况,如之前的章节中提到的智联招聘项目,不同的岗位搜索到的岗位数量不一定一致,也就意味着每个工作搜索到的工作岗位列表页面的数量

android XML动画初步解析(activity界面之间跳转demo)

上一篇文章android 简单地设置Activity界面的跳转动画讲了简单的activity界面之间的跳转,并且使用的是android内置的一些动画,此章就小提一下如何自己写一些动画来进行跳转. 按例,还是上一下效果:(结尾附上源码)     要自己写动画,首先要对动画的一些属性有一定了解: interpolator:被用来修饰动画效果,定义动画的变化率,可以使存在的动画效果accelerated(加速),decelerated(减速),repeated(重复),bounced(弹跳)等. an

HTTP协议初步解析

一.什么是HTTP协议 HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,定义了Web客户端向Web服务器请求Web页面的方式,以及服务器向客户端传送Web页面的方式. 定义中的名词解释: 什么是超文本? 超文本是用超链接的方法,将各种不同空间的文字信息组织在一起的网状文本. 什么是应用层协议? 网络的设计者使用分层的方式组织网络协议以及实现这些协议的硬件和软件,历史上比较重要的一个协议组织模型是五层因特网协议栈,从顶

$_SERVER 初步解析

当请求地址为 localhost/drupal/node/2?name=qinqiu $_SERVER['SCRIPT_NAME'] = /drupal/index.php 即代码所在文档的目录和文件名 $_SERVER['REQUEST_URI'] = /drupal/node/2?name=qinqiu 即全部的请求字符串 但不包含http_host $_SERVER['QUERY_STRING'] = name=qinqiu 即问号后面的字符串 $_SERVER['SCRIPT_FILEN

Android热修复框架AndFix原理解析及使用

一.前言 最近腾讯弄出一个Tinker热修复框架,那么本文先不介绍这个框架,先来介绍一下阿里的一个热修复框架AndFix,这个框架出来已经很长时间了,但是看网上没有太多非常详细的讲解,这里就来做一次分析.正好项目中要使用到.首先这个框架是开源的:https://github.com/alibaba/AndFix 其实在最早的时候我已经分析了阿里的另外一个热修复框架:Dexposed框架,还不了解的同学可以点击这里查看:Dexposed框架原理解析以及使用 当时介绍这个框架的时候发现他的实现原理很

[磁盘数据分析] 实现解析特定分区体系(DOS分区体系)的主引导记录扇区

近期学习了硬盘的结构以及分区体系,以DOS分区体系为例.磁盘的第一个扇区(0-512字节)被称为引导扇区(Boot Sector).内含有主引导记录(MBR).ji计算机启动并完成自检后,首先会寻找磁盘的MBR扇区并读取其中的引导记录,然后将系统控制权交给它. 我的任务是初步解析MBR的内容.判断分区类型.定位所有主分区以及它们的大小. 通过阅读数据取证入门名著"File System Forensic Analysis"获取DOS分区体系下的MBR的数据结构: 数据结构以及类的声明如