Soot 静态分析框架(七)模块分析

Java 9里开始支持模块化,以一个独立的开源项目jigsaw而来, 具体可以参考链接,

https://openjdk.java.net/projects/jigsaw/  同时也可以参考JSR376标准

 

1. module-info 分析

在模块化的时候,需要构建module_info.java来声明模块之间的关系,

在Module声明里面定义了requires ,exports, opens, uses, provides 5种类型,关于这几种类型的作用就不在这里详细介绍了。

在字节码种并没有单独的opcode,只是用一种固定的顺序进行定义,以requires ,exports, opens, uses, provides这个顺序来排序

我们来看一下以下的一段bytecode

2                                       // requires

    #8,0                                    // pointto

    #0

    #10,8000                                // "java.base" ACC_MANDATED

    #0

  0                                       // exports

  0                                       // opens

  1                                       // uses

    #12                                     // testRequire/service

  1                                       // provides

    #12                                     // testRequire/service with ... 1

      #14                                     // ... with testRequire/ServiceImpl

第一个2表示requires,同时表示有几个requires,下面的缩进行代表着有哪2个,#10,8000表示的是8000表示的是16进制的ACC_Flag 修饰符

其中requiresACC独有的修饰符(TRANSITIVESTATIC

SYNTHETIC,MANDATED是共用的ACC修饰符

每个Flag定义的值为

TRANSITIVE

0x20

STATIC

0x40

SYNTHETIC

0x1000

MANDATED

0x8000

其本质上是通过bit位来设置不同的值的合集

第二个0代表着exports,第三个0代表着opens第四个1代表着uses,第五个1代表着provides,当值为非0的时候下一行就会缩进,代表着共有几个这样的类型,格式比较简单。但是我们从这种结构看出只能增加新的类型,同时类型顺序无法修改和删除,否则很难向下兼容。

这里简单的提一下这几个关键字

uses, provides,这个主要是对Java的SPI机制

Opens 主要是对Java的类反射机制,都是很有针对性的对Java提供的原生态的能力进行了管控。

 

2.Soot模块支持

Soot 的原来主版本目前并不支持模块分析,在Soot的Java9的branch孵化,目前已经合到主版本

 

2.1 Soot设置模块

Soot 需要显示的设置模块的相关信息

Options.v().set_soot_modulepath(path);

Soot会通过设置的模块的路径去加载类

需要注意的是:基础模块java.base

你不需要设置基础模块的地址,Soot里面会去自己寻找基础模块,如何寻找请参考下面的章节。

 

Soot 本身是使用运行的jdk 加载基础模块的,那就是说Soot的加载基础模块是绑定你的运行的jdk的,你无法通过设置模块相关参数来设置基础模块,通常基础模块都被打包成了jmod文件,Soot解析jmod文件是通过运行soot的jdk来解析的,jmod基础类的读取就的依赖运行jdk的能力,如果你想用一个运行jdk的版本来分析不同的版本jdk基础类,就会变的困难,不过好在java的基础类是向下兼容的,确保升级到最新的版本,可以确保能分析低版本的基础类。Soot内部会添加VIRTUAL_FS_FOR_JDK这个值到modulepath中去,用来标示基础类的路径。

如果你运行的Soot的jdk是低版本的,或许还有条路可以试试,解开jmod文件,设置到你的模块路径下,这种方式适合在不高于jdk8的版本方式上,同时你还的设置:

Options.v().set_prepend_classpath(false);

      

 

 

 

5.2 Soot 加载模块

ModulePathSourceLocator是查找模块路径和类文件的核心模块:

  1. 模块名字和模块的路径
  2. 获取模块下的类文件

通过ModulePathSourceLocator.

FoundFile lookUpInModulePath(String filename)方法去查找你想要获取的模块里的class

  1. Soot首先会去查找模块所绑定的路径,在soot开始分析的时候,初始化参数里并没有设置模块的路径,只能通过顺序查找你定义的modulepath下的模块路径
  2. 顺序解析模块路径下的模块:简单解析路径下的所有模块的module-info文件,只是为了获取模块的定义,并分析该路径下的模块以及该模块下的所有的类,构建模块和class的关系,(当然这个时候并不会去解析class),而是构建className和模块的关系,但是soot在这里并没有缓存这个关系,而是构建和缓存了模块名字和路径的关系,直到找到模块的路径

 

   3. 深度解析该模块所在路径下的module-info 文件,分析requires的部分,构建模块间的依赖,分析依赖模块下的module-info,细节可参考5.2.5注:在Java虚拟机编译中,会对自定义的module-info的类,在编译过程中自动添加java.base的模块依赖,默认依赖java.base的基本模块。在这种场景下,soot会分析基础模块java.base的module-info, 通过查找基础模块的路径(重复步骤a,b), 找到java.base的module-info进行解析

    4. 找到模块所对应的class文件,将FoundFile返回,如何找到所对应的模块请参考下面

 

 

5.3 模块的依赖关系

 

Java使用模块的方式进行类的管控,Soot里在LoadClass的时候需要准确的找到模块里的类,以确定该类是否能被模块加载,核心代码在ModuleUtil里findModuleThatExports方法,其原则如下:

Java里的module管控是基于Package的,只需要鉴权Package就足够。

  1. 首先分析要加载的模块的Module-info,该模块下的包是否包含该Package
  2. 如果不包含,读取该模块的Requires的模块,对依赖的模块进行module-info的分析
  3. 如果该module-info的export里包含该package,一种是export to 指定到该模块,如果没有to代表所有模块都可以使用。
  4. 链式访问requires transitive模块,对模块的module-info进行分析,分析逻辑和步骤3一致,直到链路的最后一层模块

 

5.4  如何获取模块下的class文件

 

通过ModulePathSourceLocator可以获取模块下的class文件,这里涉及到模块的两种表示方式

1.  自定义的模块:打包成jar或者直接class,这和原来获取jar下的class一样,区别主要是需要解析module-info.class类,获取类的目的是为了获取模块的依赖关系,同时也需要加载require的模块。

2. Java的基础类:清楚Java 模块的知道Java9将原来的rt.jar 替换成了模块的方式,在java_home下会有一个jmods的目录,里面保存着将rt.jar 拆解成多个不同的jmod文件,在Java下面我们可以使用jmod的命令来查看jmod文件的内容。Soot 并没有使用jmod去解析默认的jdk下的基础的这些jmod文件,而是通过jrt的方式去加载jmod,也就是

jrt:/

的方式通过NIO File 去解析文件,其本质是通过lib下的jrt-fs.jar去解析,所以Soot会先去判断一下该文件是否存在。如果存在,Soot会将jrt:/加到需要分析的模块逻辑中。

注意:如果是jar的方式,分析module只需要直接设置路径就可以了,而如果是jmod 的方式,需要jrt:/的方式去分析

 

jrt的访问方式默认的起始路径是以modules,也就是对应的jmods目录

jrt:/modules/

访问JRT的方式可以使用NIO的Path,Path是支持jrt的方式,而传统的File是不支持jrt的路径访问方式,这里要特别注意。

 

 

 

5.5 加载分析类

在前面的段落里提到过Soot的核心函数Scene,对模块的解析,Soot提供了新的ModuleScene 来支持,当加载class的时候,在模块的方式下需要使用ModuleScene来加载类,代码如下:

public static SootClass loadClass(String name, boolean main, String module) {

      SootClass c = ModuleScene.v().loadClassAndSupport(name, Optional.of(module));

      c.setApplicationClass();

      if (main)

         Scene.v().setMainClass(c);

      return c;

   }

需要使用name和模块名字来加载class,而原来的方式只需要提供class的名字

 

private static SootClass loadClass(String name, boolean main) {

      SootClass c = Scene.v().loadClassAndSupport(name);

      c.setApplicationClass();

      if (main)

         Scene.v().setMainClass(c);

      return c;

   }

 

5.6 加载模块特有的module-info类

解析模块下的类,Soot 沿用原来的框架思路通过Scene来进行加载,本质上使用SootResolver来进行class解析,而不同的是定义了新ModuleScene类来进行加载,使用SootModuleResolver 对模块下的class解析,思路类似:先构建class的引用,然后加到工作队列中,最后获取工作队列进行类的解析(ResolveClass),具体流程可以参考下面的图:

 

在分析Module-info的类的bytecode的时候,Soot继续沿用了objweb的asm解析框架,使用opcode v7.1的版本,可以支持最高java13的版本。和解析普通的类不一样的是Soot在SootClassBuilder通过构建SootModuleInfoBuilder 去实现asm提供的ModuleVisitor的接口。对Module-info.class里单独定义了SootModuleInfo类,而不是常见的SootClass,当然SootModuleInfo继承SootClass。

可见下图:

 

 

在上图可以看到SootModuleInfo里只是封装了export,open,require 这3种类型,具体的实现是在SootModuleInfoBuilder的代码中,Soot目前不支持uses, provides这2种类型,就是SPI的模式。

 

5.6.1 关键字requires

关键字requires里,是支持version有多个版本,在代码中是无法声明的,但是在编译的字节码里是声明的。

Module:

  #5,0                                    // "com.example.hello"

  #0

  1                                       // requires

    #6,8000                                 // "java.base" ACC_MANDATED

    #7                                      // 11.0.5

  1                                       // exports

    #8,0                                    // com/example/hello

  0                                       // opens

  0                                       // uses

  0                                       // provides

上面的例子里#7 11.0.5 代表的就是requires的版本,虽然require是有版本的,但是java里并不是必须的,我们可以从openjdk的项目声明看出:

http://openjdk.java.net/projects/jigsaw/spec/reqs/02#version-selection

虽然module里requires有明确的版本,但并没有希望在module里去严格执行版本管控,而是依赖于构建工具。模块系统只需要校验所选的模块集合满足每个模块的依赖即可,无需关注版本。在这个思路上Soot是一致的,Soot并没有对module-info中提供的版本构建版本的依赖分析,也就是并没有对依赖进行版本管控。

 

5.6.2 关键字exports

       export 代表着允许被外部模块访问的package,同时exports 也和to搭档进行外部模块的指定

 

在上图中,在soot里exportPackage是个列表,每个列表里的key是packageName,而value是指定的多个模块名字。当没有没有指定to的时候,value里面soot保存的是EVERYONE_MODULE代表着任何模块都可以使用。

 

 

 

 

 

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页