胖虎的逆向之路 01——动态加载和类加载机制详解

  • 一、前言
  • 二、类的加载器
    • 1. 双亲委派模式
    • 2. Android 中的类加载机制
      • 1)Android 基本类的预加载
      • 2)Android类加载器层级关系及分析
      • 3)BootClassLoader
      • 4)Class文件加载
      • 5)PathClassLoader
      • 6)DexClassLoader
      • 7) BaseDexClassLoader的加载过程
      • 8) ClassLoader的loadClass()加载
      • 9)ClassLoader的findLoadedClass()加载
  • 三、文章总结
  • 四、参考文献

一、前言

之前一直了解到加壳脱壳,直接使用Fart等脱壳工具进行的,停留在知其然不知其所以然的层次,所以以此准备进行Android 基础理论的学习中,首先要深入理解类加载器和动态加载二者之间的关系,本文记录了类加载器和动态加载之间的关系和原理,由于作者能力有限,会尽力的详细讲解两者之间的关系,如本文中有任何错误,烦请指正,感谢~


二、类的加载器

Android中的类加载器机制与JVM一样遵循双亲委派(双亲加载)模式

双亲委派/双亲加载 都是指的同一个机制,只是叫法不同而已…

1. 双亲委派模式

先来看一下业内人员对于双亲委派机制解释

1)当加载.class文件时,以递归的形式逐级向上委托给父加载器ParentClassLoader加载,如果加载过了,就不用再加载一遍
2)如果父加载器没有加载过,继续委托给父加载器去加载,一直到这条链路的顶级,顶级ClassLoader如果没有加载过,则尝试加载,加载失败,则逐级向下交还调用者加载

说的很简洁明了,但是对于不理解的同学可能还是比较难懂,这里我大概画了个图(画风扭曲,谨慎观看…)

由上图结合文字描述应该大概差不多可以看懂的吧?
那么再次大白话讲一下:

1)先检查当前的ClassLoader是否已经加载过class文件,使用findLoadedClass 方法,如果已经加载的话就直接返回
2)如果当前的ClassLoader没有加载过,并且存在父类(判断当前不是顶级的ClassLoader),则委派父类去加载,用parent.loadClass(name,false)方法,此时会向上传递,然后去父级ClassLoader中循环第1步,一直到顶级ClassLoader
(3) 如果父ClassLoader没有加载,则尝试本级ClassLoader加载,如果加载失败了就会向下传递,交给调用方式实现.class文件的加载

到这里,大概能明白了吧?

好吧,到这里还不明白的话,看来我对自己的文笔期望过高了,以下是我复制过来的一段话(来自于百度 Google )

我们要加载一个class文件,我们定义了一个CustomerClassLoader类加载器:
(1)首先会判断自己的CustomerClassLoader否加载过,如果加载过直接返回,
(2)如果没有加载过则会调用父类PathClassLoader去加载,该父类同样会判断自己是否加载过,如果没有加载过则委托给父类BootClassLoader去加载,
(3)这个BootClassLoader是顶级classLoader,同样会去判断自己有没有加载过,如果也没有加载过则会调用自己的findClass(name)去加载,
(4)如果顶级BootClassLoader加载失败了,则会把加载这个动作向下交还给PathClassLoader,
(5)这个PathClassLoader也会尝试去调用findClass(name);去加载,如果加载失败了,则会继续向下交还给CustomClassLoader来完成加载,这整个过程感觉是一个递归的过程,逐渐往上然后有逐渐往下,直到加载成功
其实这个String.class在系统启动的时候已经被加载了,我们自己定义一个CustomerClassLoader去加载,其实也是父类加载的

那么为什么要有这么一个东西来限制class的文件加载呢?

(1) 防止同一个.class文件重复加载
(2) 对于任意一个类确保在虚拟机中的唯一性.由加载它的类加载器和这个类的全类名一同确立其在Java虚拟机中的唯一性
(3) 保证.class文件不被篡改,通过委派方式可以保证系统类的加载逻辑不被篡改

知识点来了昂, 如何确保一个类的唯一性?

不仅仅是全类名,还要是加载该类的类加载器和这个类的全类名一同确定了在jvm中的为唯一性


2. Android 中的类加载机制

1)Android 基本类的预加载

首先看一下Dalvik虚拟机启动相关(图是抄来的)

大白话讲起来是这样的:

  1. Bootloader 启动(电源按键启动)到
  2. kernel 启动idle进程后
  3. Nativate层执行了Init进程后
  4. 解析执行了init.rc,在其内部又
  5. 调用了app_process(Xposed的基础就是替换了app_process),然后进入到
  6. framework层,产生了zygote进程,当有其他app进程启动时,该进程的孵化都是zygote中进行的,最后我们的
  7. 各项system_server进程启动, 结束

当然我们简略了很多流程,例如zygote native进程的主要工作,这些暂不细讲,以后会单开一个文章来讲下zygote~


回到我们的类加载里面来咯…

2)Android类加载器层级关系及分析

下图是层级关系示意图(不用猜,我百度的),清晰了表示各加载器之间的关系和层级,请看~

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 引入)

emm如果浅尝辄止,到这我觉得就ok了,下面将会是具体的细节,准备好了吗(摩拳擦掌)


3)BootClassLoader

启动类的加载器,用于记载zygote 进程已经预加载的基本类,可以推测他只需要从缓存中加载,这是基类ClassLoader终得一个内部类,由于包的访问权限,所以应用层没有办法直接访问

我们看一哈他的源码

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);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {return Class.classForName(name, false, null);}// ...省略@Overrideprotected Class<?> loadClass(String className, boolean resolve)throws ClassNotFoundException {Class<?> clazz = findLoadedClass(className);if (clazz == null) {clazz = findClass(className);}return clazz;}// ...省略}
}

在源码分析中,我们可以看到BootClassLoader没有父类加载器,再缓存中取不到类的时候,是直接调用自己的findclass方法, findClass()方法调用Class.classForName方法,而ZygoteInit.preloadClasses()中,加载基本类是Class.forName()

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;// ...省略
}

看源码可以发现,预加载时,ZygoteInit.preloadClasses()中调用Class.forName(),实际是指定BootClassLoader为类加载器,并且对BootClassLoader进程初始化,仅需要一次
总之,通过 Class.forName() 或者 Class.classForName() 可以且仅可以直接加载基本类,一旦基本类预加载后,对于应用程序而言,我们虽然不能直接访问BootClassLoader,但可以通过Class.forName/Class.classForName加载
意思是可以通过Class.forName 获取到基本类的实例?

看下大概的流程

从Zygote启动,到创建vm,初始化积累dex文件,zygoteinit进行preload预加载基本类,到孵化各应用Appp进程加载基本类及一些相关类;
无论是系统类加载器(PathClassLoader)还是自定义的类加载器(DexClassLoader),最顶层的祖先加载器默认是 BootClassLoader,与 JVM 一样,保证了基本类的类型安全

类的加载基本告一阶段,下面歇息五分钟…再来看Class文件加载

Thread{sleep(5_000)} …


4)Class文件加载

1.通过Class.forName()方法动态加载
2.通过ClassLoader.loadClass()方法动态加载
类的加载分为3个步骤:1.装载(Load),2.链接(Link),3.初始化(Intialize)


类加载的时机:

1.隐式加载:
(1)创建一个类的实例,耶尔就是new一个对象
(2)访问某个类或者接口的静态变量,或者对该静态变量赋值
(3)调用类的静态方法
(4)反射Class.forName(“android.app.ActivityThread”)
(5)初始化一个类的子类(会首先初始化子类的父类)
2.显式加载:
(1)使用LoadClass()加载
(2)使用forName()加载

显式加载中有两种办法,两种办法有些许不同噢

(1)ClassLoader.loadclasss 方法可以加载一个类,但是不会触发类的初始化,也就是说不会对类中的静态变量、代码块进行初始化操作
(2)Class.forName 方法不但会加载一个类,还会触发类的初始化阶段,对这个类的静态变量、代码块进行初始化(会执行代码块)


5)PathClassLoader

PathClassLoader主要用于系统和app的类加载器,其中optimizedDirectory为null, 采用默认目录/data/dalvik-cache/

PathClassLoader 是作为应用程序的系统类加载器,也是在 Zygote 进程启动的时候初始化的(基本流程为:ZygoteInit.main() -> ZygoteInit.forkSystemServer() -> ZygoteInit.handleSystemServerProcess() -> ZygoteInit.createPathClassLoader()。在预加载基本类之后执行),所以每一个 APP 进程从 Zygote 中 fork 出来之后都自动携带了一个 PathClassLoader,它通常用于加载 apk 里面的 .dex 文件

每一个App进程从zygote中孵化出来之后,都自动携带了一个pathClassLoader,通常用于加载apk里面的.dex 文件

6)DexClassLoader

可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载, 但必须是app私有可写目录来缓存odex文件. 能够加载系统没有安装的apk或者jar文件, 因此很多热修复和插件化方案都是采用DexClassLoader

public class
DexClassLoader extends BaseDexClassLoader {public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);}
}

总结:
我们看源码可以发现DexClassLoader与PathClassLoader都继承于BaseDexClassLoader,这两个类只是提供了自己的构造函数,没有额外的实现
区别:
DexClassLoader提供了optimizedDirectory,而PathClassLoader则没有,optimizedDirectory正是用来存放odex文件的地方,所以可以利用DexClassLoader实现动态加载


7) BaseDexClassLoader的加载过程

暂时略过(我还没太明白)


8) ClassLoader的loadClass()加载

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) {//如果没有加载过,则调用parent的类加载递归加载该类,若已加载则直接返回clazz = parent.loadClass(className, false);if (clazz == null) {//还没加载,则调用当前类加载器来加载clazz = findClass(className);}}return clazz;}
}

该方法的加载流程如下:
(1)判断当前类加载器是否已经加载过指定类,若已加载则直接返回,否则继续执行;
(2)调用parent的类加载递归加载该类,检测是否加载,若已加载则直接返回,否则继续执行;
(3)调用当前类加载器,通过findClass加载。

9)ClassLoader的findLoadedClass()加载

protected final Class<?> findLoadedClass(String name) {ClassLoader loader;if (this == BootClassLoader.getInstance())loader = null;elseloader = this;return VMClassLoader.findLoadedClass(loader, name);
}

总而言之,如下图所示

三、文章总结

搞了半天,还是没全弄明白,今天的脑细胞就到这了,诸位新年愉快

四、参考文献

https://bbs.kanxue.com/thread-271538.htm

【胖虎的逆向之路】01——动态加载和类加载机制详解相关推荐

  1. unwrap函数c语言实现,AppDomain与Assembly的动态加载与卸载代码详解

    为了将问题描述清楚,我们先来看一个例子.在这个例子中,WinForm上有一个按钮,当用户点击这个按钮后,就会装载一个已经存在的Assembly,并且在界面的Label控件上显示出这个Assembly的 ...

  2. 【胖虎的逆向之路】03——Android一代壳脱壳办法罗列实操

    [胖虎的逆向之路]03--Android脱壳办法罗列&脱壳原理详解 [胖虎的逆向之路]01--动态加载和类加载机制详解 [胖虎的逆向之路]02--Android整体加壳原理详解&实现 ...

  3. 【胖虎的逆向之路】04——脱壳(一代壳)原理脱壳相关概念详解

    [胖虎的逆向之路]04--脱壳(一代壳)原理&脱壳相关概念详解 [胖虎的逆向之路]01--动态加载和类加载机制详解 [胖虎的逆向之路]02--Android整体加壳原理详解&实现 [胖 ...

  4. 【胖虎的逆向之路】02——Android整体加壳原理详解实现

    [胖虎的逆向之路](02)--Android整体加壳原理详解&实现 Android Apk的加壳原理流程及详解 文章目录 [胖虎的逆向之路](02)--Android整体加壳原理详解& ...

  5. 【胖虎的逆向之路】如何绕过 Android11新特性之 “包的可见性“

    前言 距离Android11 发布已经过去了,当初我有大概了解过一些Android 11上的行为变更,总体变化虽然不少,但是要求我们必须去适配的地方并不算多.对于我而言可能需要注意的是文件相关权限,譬 ...

  6. 【胖虎的逆向之路】Android 7.0 上Magisk配合Xposed的相关问题

    基础环境 1.Android 7.1.0(硬件小米6 sagit): 2.Magisk V23.0 3.Xposed (由Magisk-模块-搜索下载) 安装 首先android刷机.Magisk R ...

  7. 百度地图 路书动态加载规划

    <html lang="en"> <head><meta charset="utf-8"/><title>路书& ...

  8. android 动态 dex,Android 动态加载dex

    首先如果仅仅是因为64K method的问题可以直接看这里DexGuard.Proguard.Multi-dex给出的解决方案. 本文主要讨论从编译层面,dex动态加载器选择层面以及安全层面讨论dex ...

  9. 在.Net framework中动态加载Assembly的loadFromRemoteSources配置

    简介 在插件类型的应用开发中,我们可能会在程序中动态加载一个assembly文件,创建其中的类对象并使用. 这时,就涉及到了CAS(code access security)和信任沙盒. 一般,我们的 ...

最新文章

  1. 网管型交换机比普通交换机有哪些明显优势
  2. POJ_3262 Protecting the Flowers 【贪心】
  3. 3.3 1!到n!的和
  4. 我的Android进阶之旅------Android利用温度传感器实现带动画效果的电子温度计
  5. 自己动手写符合自己业务需求的eslint规则
  6. linux安装python3.7的步骤_centos7安装python3 的三种方式
  7. Linux 双网卡绑定方法
  8. 汇编语言定时器转化为c语言,不用定时器和汇编语言,只用C语言实现精确无误的延时...
  9. application.properties文件配置详解(核心属性和Web属性) ——Spring Boot配置
  10. java 静态绑定_java的动态绑定和静态绑定
  11. Ubuntu默认Python版本选择
  12. lua开发/ 腾讯 Bugly / 截屏 / 遮罩
  13. 小赛毛游C记——分支和循环语句(2)
  14. [每日一氵] openCV drawMatches 函数中 flag 用法
  15. 强者的系统:高观点下的人生
  16. 2018-2019-2 20175216张雪原 实验四《Android程序设计》实验报告
  17. Glide学习(二)—缓存策略
  18. 【2022年】安装vm虚拟机unbuntu 服务器版
  19. 微信哪个电话能转人工服务器,如何联系微信人工客服?掌握好窍门,只需30秒可接通,亲测有效...
  20. 惠普HP Deskjet Ink Advantage 3540 打印机驱动

热门文章

  1. request进行搜索引擎关键词提交
  2. 文华财经(第一面)HR面
  3. 信息熵、条件熵、联合熵、互信息和条件互信息
  4. 5. 第五阶段 测试开发技术 - JAVA
  5. matlab经典实例,BP神经网络matlab实例(简单而经典)
  6. Qt电子白板 画板 画笔 毛笔 钢笔 蜡笔 2D/3D图形 音视频播放
  7. 零售行业运用机器学习,实现在线推荐和定向营销(人工智能系列)
  8. 高德地图放大Marker icon
  9. 奥迪J518方向锁通病维修
  10. iview table 横向拖动表格内容滚动