一、前言
最近一直在学习Android 加壳和脱壳,在进行Android加壳和脱壳的学习中,第一步便是深入理解类加载器和动态加载二者之间的关系,本文详细的介绍了类加载器和动态加载之间的关系和原理,之所以详细的讲解两者之间的关系,一是学习脱壳和加壳的需要,二是为后面Android插件化漏洞挖掘的讲解做铺垫。
二、类加载器
Android中的类加载器机制与JVM一样遵循双亲委派模式
1.双亲委派模式
(1)双亲委派模式定义
1 2
|
(1)加载.class文件时,以递归的形式逐级向上委托给父加载器ParentClassLoader加载,如果加载过了,就不用再加载一遍 (2)如果父加载器没有加载过,继续委托给父加载器去加载,一直到这条链路的顶级,顶级ClassLoader如果没有加载过,则尝试加载,加载失败,则逐级向下交还调用者加载
|
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ Class<?> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { }
if (c == null) { c = findClass(name); } } return c; }
|
1 2 3 4
|
代码解释: (1)先检查自己是否已经加载过class文件,用findLoadedClass方法,如果已经加载了直接返回 (2)如果自己没有加载过,存在父类,则委派父类去加载,用parent.loadClass(name,false)方法,此时会向上传递,然后去父加载器中循环第1步,一直到顶级ClassLoader (3) 如果父类没有加载,则尝试本级classLoader加载,如果加载失败了就会向下传递,交给调用方式实现.class文件的加载
|
(2)双亲委派模式加载流程
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
1 2 3 4 5 6 7
|
我们要加载一个class文件,我们定义了一个CustomerClassLoader类加载器: (1)首先会判断自己的CustomerClassLoader否加载过,如果加载过直接返回, (2)如果没有加载过则会调用父类PathClassLoader去加载,该父类同样会判断自己是否加载过,如果没有加载过则委托给父类BootClassLoader去加载, (3)这个BootClassLoader是顶级classLoader,同样会去判断自己有没有加载过,如果也没有加载过则会调用自己的findClass(name)去加载, (4)如果顶级BootClassLoader加载失败了,则会把加载这个动作向下交还给PathClassLoader, (5)这个PathClassLoader也会尝试去调用findClass(name);去加载,如果加载失败了,则会继续向下交还给CustomClassLoader来完成加载,这整个过程感觉是一个递归的过程,逐渐往上然后有逐渐往下,直到加载成功 其实这个String.class在系统启动的时候已经被加载了,我们自己定义一个CustomerClassLoader去加载,其实也是父类加载的
|
(3)双亲委派的作用
1 2 3
|
(1) 防止同一个.class文件重复加载 (2) 对于任意一个类确保在虚拟机中的唯一性.由加载它的类加载器和这个类的全类名一同确立其在Java虚拟机中的唯一性 (3) 保证.class文件不被篡改,通过委派方式可以保证系统类的加载逻辑不被篡改
|
2. Android中类加载机制
(1)Android基本类预加载
我们了解Android基本类预加载,首先我们回顾上文的Dalvik虚拟机启动相关:
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
我们执行app_process程序,进入main函数里面,然后进行AndroidRuntime::start
:
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
1 2 3 4
|
Zygote native 进程主要工作: (1)创建虚拟机–startVM (2)注册JNI函数–startReg (3)通过JNI知道Java层的com.android.internal.os.ZygoteInit 类,调用main 函数,进入java 世界
|
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
然后进入Java层:
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
1 2 3 4 5 6 7 8
|
Zygote总结: (1)解析init.zygote.rc中的参数,创建AppRuntime并调用AppRuntime.start()方法 (2)调用AndroidRuntime的startVM()方法创建虚拟机,再调用startReg()注册JNI函数 (3)通过JNI方式调用ZygoteInit.main(),第一次进入Java世界 (4)registerZygoteSocket()建立socket通道,zygote作为通信的服务端,用于响应客户端请求 (5)preload()预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率 (6)通过startSystemServer(),fork得力帮手system_server进程,也是Java Framework的运行载体(下面讲到system server再详细讲解) (7)调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作
|
Android的类加载机制和JVM一样遵循双亲委派模式,在dalvik/art启动时将所有Java基本类和Android系统框架的基本类加载进来,预加载的类记录在/frameworks/base/config/preloaded-classes
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
android.R$styleable android.accessibilityservice.AccessibilityServiceInfo$1 android.accessibilityservice.AccessibilityServiceInfo android.accessibilityservice.IAccessibilityServiceClient$Stub$Proxy android.accessibilityservice.IAccessibilityServiceClient$Stub android.accessibilityservice.IAccessibilityServiceClient android.accounts.AbstractAccountAuthenticator$Transport android.accounts.AbstractAccountAuthenticator android.accounts.Account$1 android.accounts.Account ...
java.lang.Short java.lang.StackOverflowError java.lang.StackTraceElement java.lang.StrictMath java.lang.String$1 java.lang.String$CaseInsensitiveComparator java.lang.String java.lang.StringBuffer java.lang.StringBuilder java.lang.StringFactory java.lang.StringIndexOutOfBoundsException java.lang.System$PropertiesWithNonOverrideableDefaults java.lang.System java.lang.Thread$1 ...
|
这些类只需要在Zygote进程启动时加载一遍就可以了,后续没一个APP或Android运行时环境的进程,都是从Zygote中fork出来,天然保留了加载过的类缓存
ZygoteInit.preload()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
static void preload(TimingsTraceLog bootTimingsTraceLog) { preloadClasses(); }
private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime();
InputStream is; try { is = new FileInputStream(PRELOADED_CLASSES); } catch (FileNotFoundException e) { Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + "."); return; }
try { BufferedReader br = new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
int count = 0; String line; while ((line = br.readLine()) != null) { line = line.trim(); if (line.startsWith("#") || line.equals("")) { continue; }
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line); try { Class.forName(line, true, null); count++; } catch (Throwable t) { } }
} catch (IOException e) { Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e); } finally { } }
|
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
(2)Android类加载器层级关系及分析
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
1 2 3 4 5 6 7 8
|
Android中的ClassLoader类型分为系统ClassLoader和自定义ClassLoader。其中系统ClassLoader包括3种是BootClassLoader、DexClassLoader、PathClassLoader (1)BootClassLoader:Android平台上所有Android系统启动时会使用BootClassLoader来预加载常用的类 (2)BaseDexClassLoader:实际应用层类文件的加载,而真正的加载委托给pathList来完成 (3)DexClassLoader:可以加载dex文件以及包含dex的压缩文件(apk,dex,jar,zip),可以安装一个未安装的apk文件,一般为自定义类加载器 (4)PathClassLoader:可以加载系统类和应用程序的类,通常用来加载已安装的apk的dex文件
补充: Android 提供的原生加载器叫做基础类加载器,包括:BootClassLoader,PathClassLoader,DexClassLoader,InMemoryDexClassLoader(Android 8.0 引入),DelegateLastClassLoader(Android 8.1 引入)
|
<1> BootClassLoader
1
|
启动类加载器,用于加载 Zygote 进程已经预加载的基本类,可以推测它只需从缓存中加载。这是基类 ClassLoader 的一个内部类,是包访问权限,所以应用程序无权直接访问
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
public abstract class ClassLoader {
class BootClassLoader extends ClassLoader { private static BootClassLoader instance;
public static synchronized BootClassLoader getInstance() { if (instance == null) { instance = new BootClassLoader(); }
return instance; }
public BootClassLoader() { super(null); }
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { return Class.classForName(name, false, null); }
@Override protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(className);
if (clazz == null) { clazz = findClass(className); }
return clazz; }
} }
|
1 2 3
|
源码分析: 我们可以看见,BootClassLoader没有父加载器,在缓存取不到类是直接调用自己的findClass()方法 findClass()方法调用Class.classForName()方法,而ZygoteInit.preloadClasses()中,加载基本类是Class.forName()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
ublic final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {
public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName(className, true, ClassLoader.getClassLoader(caller)); }
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { if (loader == null) { loader = BootClassLoader.getInstance(); } Class<?> result; try { result = classForName(name, initialize, loader); } catch (ClassNotFoundException e) { Throwable cause = e.getCause(); if (cause instanceof LinkageError) { throw (LinkageError) cause; } throw e; } return result; }
static native Class<?> classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException;
}
|
1 2
|
我们可以发现,预加载时,ZygoteInit.preloadClasses()中调用Class.forName(),实际是指定BootClassLoader为类加载器,且只需要在预加载的时候进行类初始化,只需要一次 总之,通过 Class.forName() 或者 Class.classForName() 可以且仅可以直接加载基本类,一旦基本类预加载后,对于应用程序而言,我们虽然不能直接访问BootClassLoader,但可以通过Class.forName/Class.classForName加载
|
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
无论是系统类加载器(PathClassLoader)还是自定义的类加载器(DexClassLoader),最顶层的祖先加载器默认是 BootClassLoader,与 JVM 一样,保证了基本类的类型安全
Class文件加载:
1 2 3
|
1.通过Class.forName()方法动态加载 2.通过ClassLoader.loadClass()方法动态加载 类的加载分为3个步骤:1.装载(Load),2.链接(Link),3.初始化(Intialize)
|
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
类加载时机:
1 2 3 4 5 6 7 8 9
|
1.隐式加载: (1)创建类的实例,也就是new一个对象 (2)访问某个类或接口的静态变量,或者对该静态变量赋值 (3)调用类的静态方法 (4)反射Class.forName("android.app.ActivityThread") (5)初始化一个类的子类(会首先初始化子类的父类) 2.显示加载: (1)使用LoadClass()加载 (2)使用forName()加载
|
1 2 3
|
Class.forName 和 ClassLoader.loadClass加载有何不同: (1)ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作) (2)Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作
|
<2> PathClassLoader
主要用于系统和app的类加载器,其中optimizedDirectory为null, 采用默认目录/data/dalvik-cache/
1
|
PathClassLoader 是作为应用程序的系统类加载器,也是在 Zygote 进程启动的时候初始化的(基本流程为:ZygoteInit.main() -> ZygoteInit.forkSystemServer() -> ZygoteInit.handleSystemServerProcess() -> ZygoteInit.createPathClassLoader()。在预加载基本类之后执行),所以每一个 APP 进程从 Zygote 中 fork 出来之后都自动携带了一个 PathClassLoader,它通常用于加载 apk 里面的 .dex 文件
|
1 2 3 4 5 6 7 8 9 10
|
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); }
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); } }
|
<3> DexClassLoader
可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载, 但必须是app私有可写目录来缓存odex文件. 能够加载系统没有安装的apk或者jar文件, 因此很多热修复和插件化方案都是采用DexClassLoader
1 2 3 4 5 6 7 8 9
|
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), librarySearchPath, parent); } }
|
1 2 3 4
|
总结: 我们可以发现DexClassLoader与PathClassLoader都继承于BaseDexClassLoader,这两个类只是提供了自己的构造函数,没有额外的实现 区别: DexClassLoader提供了optimizedDirectory,而PathClassLoader则没有,optimizedDirectory正是用来存放odex文件的地方,所以可以利用DexClassLoader实现动态加载
|
<4> BaseDexClassLoader
1 2 3 4 5 6 7 8 9
|
public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); } }
|
1 2 3 4
|
dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用:分割; optimizedDirectory: 优化后dex文件存在的目录, 可以为null; libraryPath: native库所在路径列表;当有多个路径则采用:分割; ClassLoader:父类的类加载器
|
BaseDexClassLoader会初始化dexPathList,收集dex文件和Native文件动态库
初始化:
DexPathList:
该类主要用来查找Dex、SO库的路径,并这些路径整体呈一个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
final class DexPathList { private Element[] dexElements; private final List<File> nativeLibraryDirectories; private final List<File> systemNativeLibraryDirectories;
final class DexPathList { public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ... this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
this.nativeLibraryDirectories = splitPaths(libraryPath, false); this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions); ... } }
|
1 2 3
|
DexPathList初始化过程,主要收集以下两个变量信息: (1)dexElements: 根据多路径的分隔符“;”将dexPath转换成File列表,记录所有的dexFile (2)nativeLibraryPathElements: 记录所有的Native动态库, 包括app目录的native库和系统目录的native库
|
makePathElements:
1 2 3 4
|
private static Element[] makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions) { return makeDexElements(files, optimizedDirectory, suppressedExceptions, null); }
|
makeDexElements:
makeDexElements方法的作用是获取一个包含dex文件的元素集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader) { return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false); }
private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) { Element[] elements = new Element[files.size()]; int elementsPos = 0; for (File file : files) { if (file.isDirectory()) { elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); DexFile dex = null; if (name.endsWith(DEX_SUFFIX)) { dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex != null) { elements[elementsPos++] = new Element(dex, null); } } else { dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex == null) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } if (dex != null && isTrusted) { dex.setTrusted(); } } else { System.logW("ClassLoader referenced unknown path: " + file); } } if (elementsPos != elements.length) { elements = Arrays.copyOf(elements, elementsPos); }
return elements; }
|
该方法的主要功能是创建Element数组
loadDexFile:
加载DexFile文件,而且会把优化后的dex文件缓存到对应目录
1 2 3 4 5 6 7 8 9 10
|
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException { if (optimizedDirectory == null) { return new DexFile(file, loader, elements); } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements); } }
|
DexFile:
用来描述Dex文件,Dex的加载以及Class的查找都是由该类调用它的native方法完成的
1 2 3 4 5 6 7 8 9 10
|
DexFile(File file, ClassLoader loader, DexPathList.Element[] elements) throws IOException { this(file.getPath(), loader, elements); }
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException { mCookie = openDexFile(fileName, null, 0, loader, elements); mInternalCookie = mCookie; mFileName = fileName; }
|
openDexFile:
1 2 3 4 5 6 7 8
|
private static Object openDexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException { return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags, loader, elements); }
|
1 2 3 4 5 6
|
此时参数取值说明: sourceName为PathClassLoader构造函数传递的dexPath中以分隔符划分之后的文件名; outputName为null; flags = 0 loader为null; elements为makeDexElements()过程生成的Element数组;
|
openDexFileNative:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
static jobject DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName ATTRIBUTE_UNUSED, jint flags ATTRIBUTE_UNUSED, jobject class_loader, jobjectArray dex_elements) { ScopedUtfChars sourceName(env, javaSourceName); if (sourceName.c_str() == nullptr) { return 0; } Runtime* const runtime = Runtime::Current(); ClassLinker* linker = runtime->GetClassLinker(); std::vector<std::unique_ptr<const DexFile>> dex_files; std::vector<std::string> error_msgs; const OatFile* oat_file = nullptr;
dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(), class_loader, dex_elements, &oat_file, &error_msgs);
if (!dex_files.empty()) { jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files); ... return array; } else { ... return nullptr; } }
|
这样就完成了dex的加载过程,而BaseDexClassLoader
派生出两个子类加载器:PathClassLoader
和DexClassLoader
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
Android中如果parent类加载器加载不到类,最终还是会调用ClassLoader对象自己的findClass()方法
loadClass()加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public abstract class ClassLoader {
public Class<?> loadClass(String className) throws ClassNotFoundException { return loadClass(className, false); }
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(className);
if (clazz == null) { clazz = parent.loadClass(className, false);
if (clazz == null) { clazz = findClass(className); } } return clazz; } }
|
1 2 3 4
|
该方法的加载流程如下: (1)判断当前类加载器是否已经加载过指定类,若已加载则直接返回,否则继续执行; (2)调用parent的类加载递归加载该类,检测是否加载,若已加载则直接返回,否则继续执行; (3)调用当前类加载器,通过findClass加载。
|
findLoadedClass:
[-> ClassLoader.java]
1 2 3 4 5 6 7 8
|
protected final Class<?> findLoadedClass(String name) { ClassLoader loader; if (this == BootClassLoader.getInstance()) loader = null; else loader = this; return VMClassLoader.findLoadedClass(loader, name); }
|
findClass:
[-> BaseDexClassLoader.java]
1 2 3 4 5 6 7
|
public class BaseDexClassLoader extends ClassLoader { protected Class<?> findClass(String name) throws ClassNotFoundException { Class c = pathList.findClass(name, suppressedExceptions); ... return c; } }
|
DexPathList.findClass:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
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; } } } return null; }
|
1 2 3 4
|
代码解释: 一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组 dexElements。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面 热修复原理: 现在很多热修复技术就是把修复的dex文件放在DexPathList中Element[]数组的前面,这样就实现了修复后的Class抢先加载了,达到了修改bug的目的
|
DexFile.loadClassBinaryName:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public final class DexFile {
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { return defineClass(name, loader, mCookie, suppressed); }
private static Class defineClass(String name, ClassLoader loader, Object cookie, List<Throwable> suppressed) { Class result = null; try { result = defineClassNative(name, loader, cookie); } catch (NoClassDefFoundError e) { if (suppressed != null) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed != null) { suppressed.add(e); } } return result; } }
|
defineClassNative()这是native方法
defineClassNative:
[-> dalvik_system_DexFile.cc]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader, jobject cookie) { std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie); if (dex_files.get() == nullptr) { return nullptr; }
ScopedUtfChars class_name(env, javaName); if (class_name.c_str() == nullptr) { return nullptr; }
const std::string descriptor(DotToDescriptor(class_name.c_str())); const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str())); for (auto& dex_file : *dex_files) { const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash); if (dex_class_def != nullptr) { ScopedObjectAccess soa(env); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); class_linker->RegisterDexFile(*dex_file); StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> class_loader( hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader))); mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash, class_loader, *dex_file, *dex_class_def); if (result != nullptr) { return soa.AddLocalReference<jclass>(result); } } } return nullptr; }
|
在native层创建目标类的对象并添加到虚拟机列表
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
我们继续分析Native层可以发现:
1 2
|
DexFile.defineClassNative() 的实现在 /art/runtime/native/dalvik_system_DexFile.cc,最终由 ClassLinker.DefineClass() 实现 Class.classForName() 的实现在 /art/runtime/native/java_lang_Class.cc,最终由 ClassLinker.FindClass() 实现
|
ClassLinker核心原理:
1 2
|
先从已加载类的 class_table 中查询,若找到则直接返回;若找不到则说明该类是第一次加载,则执行加载流程,其中可能需要穿插加载依赖的类,加载完成后将其缓存到 class_table 中 在 ClassLinker 中,会维护两类 class_table,一类针对基本类,一类针对其它的类。class_table 是作为缓存已经加载过的类的缓冲池。不管以什么样的方式去加载类,都需要先从 class_table 中先进行查询以提高加载性能
|
ClassLinker 在加载类的时候遇到该类依赖的类,进行穿插加载依赖类:
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
我们总结BaseDexClassLoader初始化和加载原理:
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
Android类加载详细流程:
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
3.案例
(1)验证类加载器
我们验证App中的MainActivity类加载器和系统类String类的类加载器:
1 2 3 4
|
ClassLoader thisclassloader = MainActivity.class.getClassLoader(); ClassLoader StringClassloader = String.class.getClassLoader(); Log.e("ClassLoader1","MainActivity is in" + thisclassloader.toString()); Log.e("ClassLoader1","String is in" + StringClassloader.toString());
|
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
我们可以明显发现PathClassLoader
加载已安装的APK
类加载器,而BootClassLoader
加载系统预安装的类
(2)遍历父类加载器
1 2 3 4 5 6 7 8
|
public static void printClassLoader(ClassLoader classLoader) { Log.e("printClassLoader","this->"+ classLoader.toString()); ClassLoader parent = classLoader.getParent(); while (parent!=null){ Log.i("printClassLoader","parent->"+parent.toString()); parent = parent.getParent(); } }
|
(3)验证双亲委派机制
1 2 3 4 5 6 7
|
try { Class StringClass = thisclassloader.loadClass("java.lang.String"); Log.e("ClassLoader1","load StringClass!"+thisclassloader.toString()); } catch (ClassNotFoundException e) { e.printStackTrace(); Log.e("ClassLoader1","load MainActivity fail!"+thisclassloader.toString()); }
|
我们使用PathClassLoader
去加载 String.class类,还是可以加载成功,因为双亲委派的机制
(4)动态加载
这里我借用网上寒冰大佬动态加载的案例,来进一步讲述使用DexClassLoader类实现简单的动态加载插件dex,并验证ClassLoader的继承关系
我们先编写一个测试类文件,然后生成dex文件
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
我们先将dex文件放到模拟器的sdcard/下
![Android加壳与脱壳(1)——深入理解类加载器和动态加载]()
我们新建一个程序,然后编写主程序的代码,并授权sd读取权限
1 2
|
Context appContext = this.getApplication(); testDexClassLoader(appContext,"/sdcard/classes.dex");
|
1 2
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
然后我们编写类加载器代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
private void testDexClassLoader(Context context, String dexfilepath) { File optfile = context.getDir("opt_dex",0); File libfile = context.getDir("lib_dex",0);
ClassLoader parentclassloader = MainActivity.class.getClassLoader(); ClassLoader tmpclassloader = context.getClassLoader(); DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),parentclassloader);
Class clazz = null; try { clazz = dexClassLoader.loadClass("com.example.test.TestClass"); } catch (ClassNotFoundException e) { e.printStackTrace(); } if(clazz!=null){ try { Method testFuncMethod = clazz.getDeclaredMethod("test02"); Object obj = clazz.newInstance(); testFuncMethod.invoke(obj); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
}
|
(5)获得类列表
我们通过getClassNameList来获取类列表
1
|
private static native String[] getClassNameList(Object cookie);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
public static void getClassListInClassLoader(ClassLoader classLoader){ try { Class BaseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader"); Field pathListField = BaseDexClassLoader.getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathListObj = pathListField.get(classLoader);
Class DexElementClass = Class.forName("dalvik.system.DexPathList"); Field DexElementFiled = DexElementClass.getDeclaredField("dexElements"); DexElementFiled.setAccessible(true); Object[] dexElementObj = (Object[]) DexElementFiled.get(pathListObj); Class Element = Class.forName("dalvik.system.DexPathList$Element"); Field dexFileField = Element.getDeclaredField("dexFile"); dexFileField.setAccessible(true); Class DexFile =Class.forName("dalvik.system.DexFile"); Field mCookieField = DexFile.getDeclaredField("mCookie"); mCookieField.setAccessible(true); Field mFiledNameField = DexFile.getDeclaredField("mFileName"); mFiledNameField.setAccessible(true); Method getClassNameListMethod = DexFile.getDeclaredMethod("getClassNameList",Object.class); getClassNameListMethod.setAccessible(true);
for(Object dexElement:dexElementObj){ Object dexfileObj = dexFileField.get(dexElement); Object mCookiedobj = mCookieField.get(dexfileObj); String mFileNameobj = (String) mFiledNameField.get(dexfileObj); String[] classlist = (String[]) getClassNameListMethod.invoke(null,mCookiedobj); for(String classname:classlist){ Log.e("classlist",classLoader.toString()+"-----"+mFileNameobj+"-----"+classname); } }
} catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
|
三、实验总结
花了一段时间,断断续续总算把这篇类加载器和动态加载的帖子写完了,从中学习到了很多,这里如果有什么错误,就请各位大佬指正了。
四、参考文献
1 2 3 4 5 6
|
http://gityuan.com/2017/03/19/android-classloader/ https://www.jianshu.com/p/7193600024e7 https://www.jianshu.com/p/ff489696ada2 https://www.jianshu.com/p/363a4ad0489d https://github.com/huanzhiyazi/articles/issues/30 https://juejin.cn/post/6844903940094427150#heading-12
|
- source:security-kitchen.com
评论