Android Thermal Repair Principle and Implementation Scheme Learning

Keywords: Android Java github network

This article is mainly about QQ Spatial Team Introduction of Android App Hot Patch Dynamic Repair Technology Principle and implementation learning.

Be based on Introduction of Android App Hot Patch Dynamic Repair Technology There are many hot-fix frameworks that are open source on github, roughly:

https://github.com/dodola/HotFix
https://github.com/jasonross/...
https://github.com/bunnyblue/...

principle

  • Prevent related classes from being labeled CLASS_ISPREVERIFIED when the app is packaged;
  • Put bug-free classes into a patch.jar package;
  • Load the class.dex file from the sdcard package through DexClassLoader;
  • Through Java's reflection mechanism, the classes.dex file in patch.jar is dynamically added to the dexElements array indirectly referenced by the BaseDexClassLoader object.Make BaseDexClassLoader load classes.dex in patch.jar to complete bug fixing when loading classes.

CLASS_ISPREVERIFIED PROBLEM

Questions about CLASS_ISPREVERIFIED Introduction of Android App Hot Patch Dynamic Repair Technology Detailed description is provided.

According to the above article, when the verify option is turned on at virtual machine startup, the class directly referenced (first-level relationship) to the static method, private method, constructor of the related class is in the same dex file, then the class is marked with CLASS_ISPREVERIFIED.

If hot fixes are used, prevent this class from being labeled CLASS_ISPREVERIFIED.

Note that the class is blocking the referencer, that is, suppose there is a class called LoadBugClass in the app that references BugClass internally.After publishing, you find that there is a write error in the BugClass. If you want to publish a new BugClass class, you need to prevent the LoadBugClass class from being marked as CLASS_ISPREVERIFIED.

That is, before generating an apk, you need to prevent the related classes from being labeled CLASS_ISPREVERIFIED.For how to prevent this, the article above makes it clear that LoadBugClass can reference another DEX file in its construction method, such as a class in hack.dex.

There are actually two things:

  • 1. Prevent related classes from labeling CLASS_ISPREVERIFIED when the app is packaged.
  • 2. Dynamically changing dexElements indirectly referenced by BaseDexClassLoader objects;

DexClassLoader and PathClassLoader

DexClassLoader
The source comment for DexClassLoader reads as follows:

A class loader that loads classes from {@code .jar} and {@code .apk} files containing a {@code classes.dex} entry. This can be used to execute code not installed as part of an application.
You can see that DexClassLoader can be used to load classes.dex files from within files of type.jar and.apk.Can be used to execute non-installed program code.

Hot fixes take advantage of the ability of DexClassLoader to load classes.dex files from external files.

DexClassLoader.java

package dalvik.system;
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

PathClassLoader

The source comment for PathClassLoader reads as follows:

Provides a simple {@link ClassLoader} implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).

As you can see, Android uses PathClassLoader as its loader for system and application classes.And this class can only load apk files that have been installed on Android system.

The Android system uses PathClassLoader as its class loader to load classes from the apk.

PathClassLoader.java

package dalvik.system;
public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}

Here, we learned that classes.dex can be loaded from sdcard's patch.jar through DexClassLoader:

  • The Android system uses PathClassLoader as its class loader to load classes from the apk.
  • DexClassLoader can load classes.dex from external files of type.jar and.apk.

BaseDexClassLoader

As mentioned earlier, the classes.dex file in patch.jar is dynamically added to the dexElements array indirectly referenced by the BaseDexClassLoader object through Java's reflection mechanism.Make BaseDexClassLoader load classes.dex in patch.jar to complete bug fixing when loading classes.

Here's how to reflect into the dexElements array indirectly referenced by the BaseDexClassLoader class.

BaseDexClassLoader.java Source

package dalvik.system;
public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;
    ...
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
    ...
}

BaseDexClassLoader loads classes by findClass, by pathList attribute, and by calling the findClass method of the DexPathList class. Next, take a look at the findClass method of the DexPathList class.

DexPathList.java

package dalvik.system;
final class DexPathList {
    private final Element[] dexElements;
    ...
    public Class findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;

            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }
    ...
}

An array of dexElements is found here.
Through the reflection mechanism, the class.dex file in the patch.jar package is added to the front of the dexElements array to complete the bug hot fix.
BaseDexClassLoader loops the dexElements array when loading classes, first loading the dex files that are in the front of the dexElements array, and then completing the bug fix if the corresponding classes are found in the dex files that are in the front of the dexElements array without loading the dex files that are in the back.

Code implementation that prevents related classes from being uploaded to CLASS_ISPREVERIFIED

adopt https://github.com/dodola/Hot....

The general process is:
Before the dx tool executes, modify the LoadBugClass.class file, add System.out.println (dododola.hackdex.AntilazyLoad.class) to its construction, and continue the packaging process.Note: The class AntilazyLoad.class is independent of hack.dex.

There are two questions:

  • How to modify a.class file (inject code through javassist)
  • How to make.class file modifications occur before dx

How to modify a class file for a class (inject code through javassist)

Create the following new classes:

Referenced class AntilazyLoad.java (class included in hack.jar)

package dodola.hackdex;
public class AntilazyLoad {}

Class BugClass.java to fix

package dodola.hotfix;
public class BugClass
{
    public String bug()
    {
        return "bug class";
    }
}

Reference class LoadBugClass.java

package dodola.hotfix;
public class LoadBugClass
{
    public String getBugString()
    {
        BugClass bugClass = new BugClass();
        return bugClass.bug();
    }
}

What you need to do here is add:

System.out.println(dodola.hackdex.AntilazyLoad.class)

You can do this through javassist, about javassist

JAVAssist
Javassist Learning Summary

For injecting code into the LoadBugClass.class construction method, there is a Test.java below

Test.java

package dodola.hotfix;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
public class Test {
    public static void main(String[] args) {
        try {
            //Find the.class file from the file path
            // class File Path
            String path = "D:\\work_space\\ws\\Android_Test\\app\\build\\intermediates\\classes\\debug\\dodola\\hotfix";
            ClassPool classes = ClassPool.getDefault();
            classes.appendClassPath(path);
            // Locate LoadBugClass.class
            CtClass c = classes.get("dodola.hotfix.LoadBugClass");
            // Insert code into construction method
            CtConstructor ctConstructor = c.getConstructors()[0];
            ctConstructor.insertBefore("System.out.println(dodola.hackdex.AntilazyLoad.class);");
            // output file
            c.writeFile(path + "/output");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Note: Imported in build.gradle

compile 'org.javassist:javassist:3.18.2-GA'

run runs Test.java, injecting code into LoadBugClass.class.
Below is a comparison of LoadBugClass.class before and after injection

How to make.class file modifications occur before dx

open https://github.com/dodola/Hot...

apply plugin: 'com.android.application'

// Construction Method Injection Code
task('processWithJavassist') << {
    // The directory in which the.class resides after the project has been compiled
    String classPath = file('build/intermediates/classes/debug')
    // Call the process method of PatchClass.java to inject code into the.class file construction method
    dodola.patch.PatchClass.process(
            // The directory in which the.class resides after the project has been compiled
            classPath,
            // The directory where hackdex's. class is located
            project(':hackdex').buildDir.absolutePath + '/intermediates/classes/debug')

}
android {
    ...
    // Inject code into.class before executing dx command
    applicationVariants.all { variant ->
        variant.dex.dependsOn << processWithJavassist 
    }
}
dependencies {
    ...
}

Make AntilazyLoad a hack_dex.jar package

After the code injection of "System.out.println (dododola.hack dex.AntilazyLoad.class);", the AntilazyLoad.class is also packaged into a hack_dex.jar package and loaded into the program through DexClassLoader, otherwise classes injected into the code, such as LoadBugClass, throw a ClassNotFoundException exception at runtime.

hack_dex.jar package generation

Corresponding command behavior:

jar cvf hack.jar dodola/hackdex/*
dx --dex --output hack_dex.jar hack.jar 

Load the.Dex file from hack_dex.jar

HotfixApplication.java

package dodola.hotfix;
import java.io.File;
import android.app.Application;
import android.content.Context;
import dodola.hotfixlib.HotFix;
//1. dexClassLoader loads hackdex.jar(dodola.hackdex.AntilazyLoad)
//2. DEX file in hack_dex, dynamically added to the dexElements array indirectly referenced by the BaseDexClassLoader object
public class HotfixApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //
        File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "hackdex_dex.jar");
        // Copy the hackdex_dex.jar file under assets to the path / data/data/packagename/dex/hackdex_dex.jar
        Utils.prepareDex(this.getApplicationContext(), dexPath, "hackdex_dex.jar");
        // Dynamically add hack_dex to the dexElements array indirectly referenced by the BaseDexClassLoader object
        HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.AntilazyLoad");
        // Load dododola.hackdex.AntilazyLoad
        try {
            this.getClassLoader().loadClass("dodola.hackdex.AntilazyLoad");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

patch method in HotFix.java file

//Add patch.dex dynamically to the dexElements array indirectly referenced by the BaseDexClassLoader object
public static void patch(Context context, String patchDexFile, String patchClassName) {
    // dex file exists
    if (patchDexFile != null && new File(patchDexFile).exists()) {
        try {
            // Ali Equipment
            if (hasLexClassLoader()) {
                injectInAliyunOs(context, patchDexFile, patchClassName);
            }
            // Non-Ali Equipment
            else if (hasDexClassLoader()) {
                injectAboveEqualApiLevel14(context, patchDexFile, patchClassName);
            } else {
                injectBelowApiLevel14(context, patchDexFile, patchClassName);
            }
        } catch (Throwable th) {
        }
    }
}

injectAboveEqualApiLevel14 method in HotFix.java file

private static void injectAboveEqualApiLevel14(Context context, String str, String str2)
        throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    // PathClassLoader
    PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
    // ---------------------------------
    // Merge Array
    Object a = combineArray(
            // Array of dexElements in PathClassLoader
            getDexElements(getPathList(pathClassLoader)),
            // After DexClassLoader loads the local dex file, the dexElements array
            getDexElements(getPathList(new DexClassLoader(str, context.getDir("dex", 0).getAbsolutePath(), str, context.getClassLoader())))
    );
    //----------dexElements array assignment---------
    // Get pathList
    Object a2 = getPathList(pathClassLoader);
    setField(a2, a2.getClass(), "dexElements", a);
    //---------------Load str2 class---------
    pathClassLoader.loadClass(str2);
}

Reference resources:
https://zhuanlan.zhihu.com/p/...
http://blog.csdn.net/lmj62356...

Posted by RagingEagle on Tue, 26 Nov 2019 20:06:11 -0800