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

news2025/1/10 1:50:08

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

  • 一、前言
  • 二、类的加载器
    • 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);
        }
 
        @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;
        }
 
        // ...省略
    }
}

在源码分析中,我们可以看到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;
    else
        loader = this;
    return VMClassLoader.findLoadedClass(loader, name);
}

总而言之,如下图所示

在这里插入图片描述

三、文章总结

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

四、参考文献

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143992.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从 Redshift 迁移数据到 DolphinDB

AWS Redshift 是最早的云数据仓库之一&#xff0c;为用户提供完全托管的 PB 级云中数据仓库服务。用户可以使用标准 SQL 和现有的商业智能工具&#xff0c;经济高效地进行数据分析。但 AWS Redshift 上手难度较大&#xff0c;对知识储备要求较高&#xff0c;设计和优化相当复杂…

PCB设计中的屏蔽罩设计

屏蔽罩是一个合金金属罩&#xff0c;是减少显示器辐射至关重要的部件&#xff0c;应用在MID或VR产品中可以有效的减少模块与模块之间的相互干扰&#xff0c;如图3-54所示&#xff0c;常见于主控功能模块和电源模块及Wifi模块之间的隔离。图3-54 屏蔽罩的使用 01 屏蔽罩夹子一般…

前端沙箱浅析

前言 沙箱&#xff0c;即sandbox。 通常解释为&#xff1a;沙箱是一种安全机制&#xff0c;为运行中的程序提供隔离环境。常用于执行未经测试或者不受信任的程序或代码&#xff0c;它会为待执行的程序创建一个独立的执行环境&#xff0c;内部程序的执行不会影响外部程序的运行…

Go第 7 章:数组与切片

Go第 7 章&#xff1a;数组与切片 7.1 为什么需要数组 7.2 数组介绍 数组可以存放多个同一类型数据。数组也是一种数据类型&#xff0c;在 Go 中&#xff0c;数组是值类型。 7.3 数组的快速入门 我们使用数组的方法来解决养鸡场的问题. 7.4 数组定义和内存布局 对上图的总…

QA | 关于信号发生器的扫频功能,您了解多少?

在上期文章中&#xff0c;我们介绍了可编程信号发生器使用中的相关问题&#xff0c;那么关于便携式信号发生器的扫频功能您是否有很多问题呢&#xff0c;今天我们将围绕信号源扫频功能详细解答大家感兴趣的几个问题&#xff0c;快来看看吧&#xff01;Q1&#xff1a;信号源是否…

Linux操作系统--文件管理(保姆级教程)

文件系统类型的含义 文件系统类型式指文件在存储介质上存放及存储的组织方法和数据结构。 Linux采用虚拟文件系统技术&#xff08;virtual file system)-VFS 一个世纪的文件系统想要被Linux支持&#xff0c;就必须提供一个符合VFS标准的接口&#xff0c;才能与VFS协同工作&am…

线程的创建与同步

线程的创建与同步线程的概念与实现方式线程的概念进程线程的区别线程使用线程相关的接口函数多线程代码线程并发线程的实现方式线程的同步信号量互斥锁读写锁条件变量线程的安全线程与fork线程的概念与实现方式 线程的概念 进程是正在执行的程序。线程是进程内部的一条执行路…

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《4》

这篇主要了解语义分割(semantic segmentation)&#xff0c;语义分割是分类中的一个核心知识点&#xff0c;而且这些语义区域的标注和预测都是像素级的。在语义分割中有两个很相似的重要问题&#xff0c;需要注意下&#xff1a;图像分割(image segmentation)&#xff1a;将图像分…

一文解决用C语言实现一个链表(全都是细节)

目录前言单链表1.链表中的结点2.链表中的常见操作&#xff08;1&#xff09;相关声明格式&#xff08;2&#xff09;常见操作的实现&#xff08;定义&#xff09;&#xff08;5&#xff09;测试前言 链表是指数据使用一个一个的结点连接起来的数据结构&#xff0c;这样的数据结…

(框架)Deepracer Local - 001: 搭建本地环境

Deepracer - 阿里云1. 安装环境2. 预安装脚本3. 从 github 下载 deepracer 代码 并初始化4. 首次运行deepracer1. 安装环境 推荐本地环境: Ubuntu (如果windowns必要的话&#xff0c;就装双系统&#xff0c;我的台式机就是双系统) 云环境: 阿里云&#xff0c;配置如下&#xf…

python简单介绍及基础知识(二)

♥️作者&#xff1a;小刘在这里 ♥️每天分享云计算网络运维课堂笔记&#xff0c;疫情之下&#xff0c;你我素未谋面&#xff0c;但你一定要平平安安&#xff0c;一 起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放&#xff0c;…

Codeforces Round #839 (Div. 3)(A~F)

A. AB?给出长度为3的字符串&#xff0c;计算字符串表示的表达式的值。思路&#xff1a;。AC Code&#xff1a;#include <bits/stdc.h>typedef long long ll; const int N 2e5 5; int t; std::string s;int main() {std::ios::sync_with_stdio(false);std::cin.tie(0);…

立即放弃 TypeScript 的 17 个理由

如果你和我一样&#xff0c;你可能会因为被迫而使用 Typescript。你的公司决定它会成为未来的语言&#xff0c;所以你被迫学习它。起初&#xff0c;您很高兴使用 Typescript。你知道它有很大的潜力&#xff0c;可以帮助你制作更强大的应用程序。但在使用了一段时间后&#xff0…

3.深度学习前的预备知识

3.预备知识 目录 数据操作 N维数组创建数组访问元素 数据预处理读取数据集 处理缺失值转换为张量格式小结 练习线性代数 标量向量矩阵张量张量算法的基本性质降维非降维求和点积矩阵-向量积矩阵-矩阵乘法范数范数和目标 微积分 导数和微分偏导数梯度链式法则 自动微分 一个简…

万字讲解!进阶指针!

今天我们来看进阶指针&#xff0c;还没有看过初阶指针的话建议先看看初阶 (3条消息) 初阶指针---从入门到入坟_KLZUQ的博客-CSDN博客 目录 1. 字符指针 2. 指针数组 3. 数组指针 3.1 数组指针的定义 3.2 &数组名VS数组名 3.3 数组指针的使用 4. 数组参数、指针参数 …

使用Sivarc使PLC程序标准化

前言 由于公司最近做的项目都是同样的&#xff0c;并且都采用S7-1500/S7-1200 与G120 系列做为主控系统&#xff0c;所以我一直在思考一个问题&#xff1a;如何标准化并且快速的编程调试。这样可以极大的缩短项目的调试周期&#xff0c;减少公司工程成本&#xff0c;同时也免去…

英伟达发布528.02 WHQL 新驱动,支持4070 Ti

昨日&#xff0c;英伟达正式发布RTX 4070 Ti显卡&#xff0c;并马不停蹄发布了528.02 WHQL 驱动&#xff0c;支持4070 Ti&#xff0c;新硬件新驱动一次性齐活。 RTX 4070 Ti显卡在光线追踪游戏中的性能表现良好&#xff0c;在现代游戏如《瘟疫传说:安魂曲》&#xff0c;《战锤…

php如何接收支付宝应用网关发送的post请求

php如何接收支付宝应用网关发送的POST请求方式,参数又是GET请求的数据格式配置支付宝应用网关如何接收支付宝异步通知(应用网关接收请求)将&连接的参数分割成数组实例&#xff1a;难点配置支付宝应用网关 首先要在服务器上写一个接口,然后将接口的访问地址设置在支付宝应用…

Java中常用API总结(2)—— System类(含实例解读)

System类一、前言二、概述1.API帮助文档2.概述3.使用方式三、常用方法1.获取当前时间所对应的毫秒值1️⃣格式2️⃣实例3️⃣具体应用2.终止当前正在运行的Java虚拟机1️⃣格式2️⃣实例3.进行数值元素copy1️⃣格式2️⃣实例3️⃣注意事项四、结语一、前言 本文将讲述System类…

springsecurity认证流程

Authentication AuthenticationManager : 认证管理器 实现类&#xff1a; ProviderManager AuthenticationProvider &#xff1a; 实现类: DaoAuthenticationProviderRememberMeAuthenticationProvider 方法: authenticate()supports() : 判断当前AuthenticationProvider是…