前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【胖虎的逆向之路】01——动态加载和类加载机制详解

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

作者头像
胖虎哥
发布2023-05-10 19:23:25
4660
发布2023-05-10 19:23:25
举报

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

一、前言

之前一直了解到加壳脱壳,直接使用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终得一个内部类,由于包的访问权限,所以应用层没有办法直接访问

我们看一哈他的源码

代码语言:javascript
复制
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()

代码语言:javascript
复制
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

代码语言:javascript
复制
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()加载

代码语言:javascript
复制
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()加载

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 胖虎的逆向之路 01——动态加载和类加载机制详解
  • 一、前言
  • 二、类的加载器
    • 1. 双亲委派模式
      • 2. Android 中的类加载机制
        • 1)Android 基本类的预加载
        • 2)Android类加载器层级关系及分析
        • 3)BootClassLoader
        • 4)Class文件加载
        • 5)PathClassLoader
        • 6)DexClassLoader
        • 7) BaseDexClassLoader的加载过程
        • 8) ClassLoader的loadClass()加载
        • 9)ClassLoader的findLoadedClass()加载
    • 三、文章总结
    • 四、参考文献
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档