Preliminary Analysis of alibaba dexposed, alibabadexposed

Source: Internet
Author: User

Preliminary Analysis of alibaba dexposed, alibabadexposed

Alibaba has a new non-intrusive aop library, which feels good. This time, the landlord will learn the specific application, principles, and effects of this library.


Here we first give the corresponding github project portal: https://github.com/alibaba/dexposed


1. First, let's talk about how to use dexposed and how to introduce it to our project.

This is actually clearly stated in dexposed's github project. I will repeat it here.

First, we need to introduce it into the project:

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')}

When we see libdexposed. so and libdexposed_l.so, we may think about their differences. It is easy to see the loadDexposedLIb method:

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;        }    }

If the sdk version is later than 19 and later than 5.0, dexposed_l will be called; otherwise, dexposed will be called. The following explains why two sets of native implementations are required.


After being integrated into our project, if you want to apply it, you only need one simple sentence:

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.            ...        }    }    ...}
Refer to the Code provided on git. The official recommendation is to call this method at the application layer. It is worth noting that DexposedBridge. canDexposed (this) is a function that corresponds to the return value of the boolean type. If it is false, the hook function cannot be implemented. Therefore, you must record the return value, so that the hook can be called elsewhere to determine whether it can be executed.


2. Now that we have introduced it into the project, the next step is to study how to use this library.

Example 1: Attach a piece of code before and after all occurrencesActivity.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.            ...        }    });
The above two examples are the two most basic usage in git. The extension of other usage will be based on these two basic usage.

In example1, we can see that dexposed can be used to execute the original function without changing, but some additional processing is performed before and after the original function is executed, such as changing input parameters and return values.

In example2, the original function to be executed can be replaced with a new execution function we need.


Based on the above two implementations, what application scenarios can we derive? Some people say that the specific implementation of the hook api depends on the user's imagination. In fact, this is true.

The following scenarios are officially required:

  • Typical AOP Programming

  • Instrumentation (testing, performance monitoring, etc)

  • Online hotfix (important, key, and security vulnerabilities)

  • SDK hooking for better development experience


3. To apply a library, we must have a certain understanding of its implementation and principles.

The following is an official introduction:

The AOP principle in Dexposed comes from Xposed. In a Dalvik virtual machine, it is mainly implemented by changing the definition of a method object method in the Dalvik virtual machine, the specific method is to change the type of the method to native and link the implementation of this method to a general Native Dispatch method. This Dispatch method calls back a unified processing method to the Java end through JNI, and finally calls the before and after functions in the unified processing method to implement AOP. Currently, the Art virtual machine is implemented by changing the entry function of an ArtMethod.

From the above, we can know the basic implementation process. In addition, we mentioned two concepts: the Dalvik Virtual Machine and the Art virtual machine. Here we will do a little bit of science.


What is Dalvik:

Dalvik is a Java virtual machine designed by Google for the Android platform. Dalvik virtual machine is one of the core components of the Android mobile device platform developed by Google and other vendors. It supports conversion. dex (Dalvik Executable) format Java application running ,. dex format is a compression format designed for Dalvik, suitable for systems with limited memory and processor speed. Dalvik is optimized to allow instances of multiple virtual machines to run simultaneously in limited memory, and each Dalvik application is executed as an independent Linux Process. Independent processes can prevent all programs from being shut down when the Virtual Machine crashes.


What is ART:
The Android operating system has matured. Google's Android team began to focus on some underlying components, one of which was responsible for the Dalvik runtime of the application program. Google developers have spent two years developing a faster execution efficiency, higher power-saving alternative to ART runtime. ART represents Android Runtime, which processes application execution In a completely different way than Dalvik. Dalvik relies on a Just-In-Time (JIT) compiler to interpret bytecode. The application code compiled by the developer needs to run on the user's device through an interpreter. This mechanism is not efficient, but it makes it easier for applications to run on different hardware and architectures. ART has completely changed this practice. During application installation, bytecode is pre-compiled to the machine language. This mechanism is called Ahead-Of-Time (AOT) compilation. After removing the interpreted code, the application execution will be more efficient and start faster.

 
Advantages of ART:
1. A significant improvement in system performance.
2. faster application startup, faster operation, smoother experience, and more timely touch feedback.
3. longer battery endurance.
4. Support lower hardware.

Disadvantages of ART:
1. A larger storage space may increase by 10%-20%.
2. Longer application installation time.

In general, the function of ART is to change the space for time ".


Google replaced dalvik with art in Versions later than Android 4.4. Therefore, to hook Versions later than Android 4.4, you must adapt to the mechanism of the art virtual machine. This also explains why dexposed and dexposed_l exist above. According to the official website, dexposed_l is only a beta version to adapt to art, so it is best not to use it in official online products.


Next, analyze the java code structure of the corresponding jar package:



In fact, DexposedBrigde is the main function calling class. XposedHelpers is a reflection function class, while others are some auxiliary classes.

The method DexposedBridge. findAndHookMethod used by the project to hook. Let's take a look at the java execution process of this function:

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");        }    }

First, the findMethodExact Method of the reflection tool class XposedHelpers will be used to find the corresponding Method.


Then the hookMethod will be executed:

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);        }    }

Here, the parameters that need to be passed to hookMethodNative are 1. method to be hooked according to reflection locating; 2. declare the class that defines the Method; 3. slot indicates the runtime identifier. 4. contains function input parameters, return value types, and custom classes for the corresponding callback. Then the corresponding hook is completed, and the java code is relatively simple and clear. As for the specific implementation in native, the author will not elaborate on it here. The c code corresponding to hookMethodNative is provided here.

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;    }}
For children's shoes that are interested in research, you can go to github to view the complete code and conduct in-depth research. This involves a lot of knowledge at the underlying android layer.

4. Now that I understand some of the implementations and principles, dexposed has the hot Patching feature, which is the most attractive.

Android Developers all know that the only solution to an online bug occurs for android client applications is to release a repair package to upgrade and fix it. This is not flexible and has a poor experience. In this case, it is urgent to dynamically fix online bugs without releasing a version. The following shows how to apply dexposed to fix online bugs.

Corresponding examples are provided on github, on which the landlord has expanded and improved.


First, add this method to your 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());            }        }    }

It is used to store and release patch.apk, that is, the folder path of the patch file can be defined as needed. The main function of patchmain.load is to go to the ipatch class in patch.apk and call the handlePatch method. Note: It is best to add this method to the oncreate method of the application to ensure that the program has been patched during initialization. Of course, your app must have an interface to go to the background to check whether the patch needs to be downloaded. If necessary, it is best to use the silent Download Method for silent repair. After the download is complete, remember to dynamically call the runPatchApk method, otherwise the patch will not take effect.


Let's take a look at how the patch.apk project is constructed.


You only need to introduce dexposedbridge. jar and patchloader. jar, and then add the corresponding Ipatch class under the com. taobao. patch package name.


So we think there is a problem with the image display in the online version. How can we fix it by applying the hot Patching feature of 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);                    }                });    }}

We first use loadClass to locate the problematic class, and then use the hook method to set the incorrect imageview in the initView method of SplashActivity to the correct resourceId.


Summary:

At the java level, this library is mainly applied to the reflection mechanism of Java. When applying this library hook function, it also has high requirements on the user's reflection knowledge, especially in hot Patching application scenarios, you can only obtain the corresponding functions and member variables of the application through reflection, and perform corresponding operations on them.


Copyright Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.