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代表着任何模塊都可以使用。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章