深刻淺出解讀 Java 虛擬機的差異測試技術

本文分享基於字節碼種子生成有效、可執行的字節碼文件變種,並用於 JVM 實現的差異測試。本文特別提出用於修改字節碼語法的classfuzz技術和修改字節碼語義的classming技術。上述變種技術系統性地操做和改變字節碼的語法、控制流和數據流,生成具備豐富語義的字節碼變種。進一步地,能夠在多個 JVM 產品上運行生成的字節碼變種,經過 JVM 驗證或執行行爲的差別以發現 JVM 缺陷乃至安全漏洞。本文整理自陳雨亭在 2018 年 12 月 22 日 GreenTea JUG Java Meetup 現場的演講速記。
今天我要報告的是咱們在過去幾年內針對 Java 虛擬機的測試工做。首先先作一下自我介紹,我是中國計算機學會系統軟件專委會委員陳雨亭,很是但願有同仁加入系統軟件專委會。
對於 Java 虛擬機測試的研究,實際上是一個偶然。早期,我作了一些軟件測試方面的工做,當時我更多關注於技術,包括基於規約的軟件測試、模型驅動的軟件測試、白盒測試、黑盒測試這些耳熟能詳的測試技術。在 2014 年到 2015 年之間,我開始關注那些可以發現真實問題的系統測試方面的工做,當時就作了 SSL 安全協議支撐軟件,包括 OPENSSL 這樣的一些軟件的測試。後來就想爲何不能作一些更復雜工做?好比能夠測試 Java 虛擬機,隨後就趕上了一個新的挑戰, Java 虛擬機的輸入是字節碼,對其測試某種意義上來講其實是在生成程序,這件事情也頗有挑戰。
<div id="n3wdry" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/png/168324/1546483351638-35ab03b8-68d2-4176-8325-89abdd3c5736.png" data-width="827">
  <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/png/168324/1546483351638-35ab03b8-68d2-4176-8325-89abdd3c5736.png" width="827" />
</div>

 

咱們在 JVM 測試方面作了兩項工做,實際上作了兩個工具:一個是classfuzz;一個是classming。
首先介紹一下背景,這個問題的背景仍是來自於 Java 虛擬機的跨平臺性,對於一樣的類來講,放在各個虛擬機上面跑,就須要有相同的運行結果。對於 Java 虛擬機,咱們就想能不能在裏面找到一些缺陷。實際上這個不是一個新概念。任何一個產品級的虛擬機在發佈以前都須要經過技術兼容包 TCK 的測試,那麼技術兼容包其實是由 Oracle 發佈的。這就引起了新問題,我不是 Oracle 的員工,我也沒有花錢去買 TCK,我該怎樣去測試一個已經發布了的產品級虛擬機?包括 OpenJDK 中的 HotSpot
或者IBM 的 OpenJ9。</div>
這裏面就同時衍生了兩個問題:
  •  
    怎麼樣去發現一個 JVM 缺陷或者是安全漏洞?

     

    •  
      怎樣生成一個有效的測試包?對於測試輸入,怎樣可以有更多的這樣的字節碼,或者產生更多可運行的應用程序並在虛擬機上再跑一跑?

       

      1.1 如何暴露出產品級虛擬機的缺陷
      對於第一個問題,即怎麼樣去暴露出一個產品及虛擬機的缺陷,這裏面在跑的時候就會發現有一個困難,這個困難就是在學術圈裏叫作「缺乏一個測試喻言」。若是要測一個 Java 虛擬機的話,咱們拿一個類過來跑跑,在一個 Java 虛擬機上面,會獲得一個真實的結果,這個時候咱們把真實結果和一個預期結果來比較一下,若是可以發現它們裏面的不一致,那麼這個就說明 Java 虛擬機出現了一些問題。

       

      <div id="k6y2au" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483408236-9d59042d-2544-4fd6-aa25-9fd52903027d.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483408236-9d59042d-2544-4fd6-aa25-9fd52903027d.jpeg" width="720" />
      </div>

       

      咱們的預期結果究竟是怎麼來的?實際上有一個 Java 虛擬機規範,假如 Java 虛擬機規範,它也是一個可以運行的機器,那麼它跑一跑,可以獲得一個運預期結果。可是實際上,咱們說 Java 虛擬機規範它自己也不能跑,因此這個事情實際上沒有很好的解決方案。後來,咱們意識到差異測試技術,這個也是 Java 虛擬機開發中,你們都採用的一個方法,也就是說有多個虛擬機,把一個類或者是應用拿到不一樣的虛擬機上去跑,比較它們之間的結果是否是有差異。

       

      若是這個你們結果都一致,那就很好,若是結果不一致,那麼就能夠去預測一下這裏面是否是有 Java 虛擬機的實現出了問題。
      1.2 如何得到一個有效的測試包
      對於第二個問題,就是怎麼樣可以有更加複雜的或者更加花樣繁多的字節碼來作測試?一開始,咱們嘗試去使用現實中大量的類,從網上甚至從 openJDK 裏面本身的包裏解壓出不少類文件,放在 JVM 不一樣版本上面去跑。這裏面的確仍是可以發現一些問題,一些不一致的現象,可是這個不一致更可能是兼容性問題。
      因而咱們很快就轉向了第二個技術,叫「領域感知的模糊測試技術」。模糊測試是應用在安全領域裏面的一個測試技術,它能夠幫助發現一些安全問題。好比說有一個文件,有個圖像,把這個圖像一位一位地變化,用以查看應用軟件是否比較健壯。若是把技術應用到 Java 虛擬機上面,就要作一些調整,這種調整是領域感知的 ,也就是說咱們知道 Java 字節碼它自己的一些特性,根據它的特性來作一些變化,這個工做更加泛,咱們有一個種子類,經過這個種子,咱們會把它變來變去,變成一堆的測試類,放到 Java 虛擬機上跑。
      <div id="selsgg" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483438172-fc3af688-ebb7-4d82-a652-35809ecd111e.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483438172-fc3af688-ebb7-4d82-a652-35809ecd111e.jpeg" width="720" />
      </div>

       

      這個工做咱們曾發在 PLDI2016,還有一個明年的 ICSE 上的工做,第一個是 classfuzz,第二個 classming。讓咱們對於 Java的類執行過程進行一個深刻了解,一開始作的工做比較偏向於上層,就是更多的去關注了 Java 類的是怎麼樣去導進來,怎麼樣連接,怎麼樣去初始化等等。這個是 classfuzz 的主要工做。後來作到必定的程度,咱們就轉向了下層,怎麼樣去作驗證,執行,這個時候就會去想類的執行會不會引起一些差異,我能不能在不一樣虛擬機上真的跑出一些不同的結果?
       
      <div id="ygt6ag" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483462686-aabb0aaf-f940-4f49-9b47-8723a87d9b45.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483462686-aabb0aaf-f940-4f49-9b47-8723a87d9b45.jpeg" width="720" />
      </div>

       

      Classfuzz

       

      下面,我就分別對這兩個工做進行介紹。classfuzz 是一個很簡單的一個想法,就有點像一開始最傳統的模糊測試的技術,對合法的 Java 字節碼文件,咱們想進行一個語法變種,變種之後,好比說對於 Java 類咱們獲得它的一個語法樹,去嘗試修改,好比說把 public 改爲 private,把文件名改一改,把這個函數名改一改,這樣的話能夠生成不少比較奇怪的類,把奇怪的類拿過來之後,就能夠去測試一下 Java 虛擬機的一個健壯性。
      <div id="ngm3de" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483478284-1e9e1dd2-9d2a-4387-83de-71d296d59df1.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483478284-1e9e1dd2-9d2a-4387-83de-71d296d59df1.jpeg" width="720" />
      </div>

       

      可是這個時候咱們很快就意識到,這個時候它有一個缺陷,咱們只是去挑戰了虛擬機的一個啓動過程,就是看看它的格式檢查對不對?去看看他的連接過程對不對,看看它的初始化對不對。Classfuzz 是 2016 年的一個論文,但一直到近期咱們仍是用它發現了一點問題。右邊是一個字節碼,固然這個字節碼比較繁雜,把這樣的一個類,放到 Open J9 和 HotSpot 上面跑, HotSpot 馬上就報了一個格式錯誤,那麼 Open J9 是屬於一個正常運行,這裏面是由於沒有 main  函數,可是它整體算是一個正常經過的類。後來咱們就研究了差異緣由,它的主要緣由就是這裏面它有一個
      flag,代表這個是一個接口文件 interface。那麼從規範上來講,若是接口 flag 被設定了之後,它就要同時去設一個 abstract 的 flag,因此 HotSpot 報了一個格式問題,這個是正確的。那麼 Open J9 上咱們就找到一個缺陷。</div>

       

      <div id="q646ke" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483496969-126615ab-4105-4d2f-9135-358e35667512.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483496969-126615ab-4105-4d2f-9135-358e35667512.jpeg" width="720" />
      </div>

       

      咱們把這個問題其實也報給了 Open J9,通過了幾輪反覆,他們很快就修復了,修復完了之後又引入了新的問題,又修復,大概就是這樣的一個過程。

       

      <div id="4f41fn" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483514160-a0029bb7-c338-4893-83ba-16410828e08b.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483514160-a0029bb7-c338-4893-83ba-16410828e08b.jpeg" width="720" />
      </div>

       

      經過 classfuzz 這樣一個技術,甚至能夠發現 Java 虛擬機規範裏面的二義性。那麼左邊是這樣的一個類,它這裏面有個 public abstract{},它表明的是類初始化函數。咱們或者是把某個方法名字改爲了  clinit,或者是把正常的類初始化函數前面加了一個abstract。那麼實際上 Open J9 和 HotSpot 又有了一個行爲上的差別。咱們回頭去看了一下緣由,這個是由於 Java 虛擬機規範的問題,Java 虛擬機規範裏是這樣說的,other methods named
      &lt;clinit&gt; in a class file are of noconsequence, 「除了類初始化這個函數之外,其餘的函數加上這種標識符 of no consequence」,這究竟是一個什麼含義?這個裏面你們就有誤解了。Hotspot 認爲它是一個常規的方法,可是 J9 認爲這裏面就是一個格式錯誤,這個就是你們對 of no consequence 會有認識上的不同。</div>

       

      <div id="t2nlan" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483534596-d1562e79-ac4f-4c0f-9243-a25a40cffd45.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483534596-d1562e79-ac4f-4c0f-9243-a25a40cffd45.jpeg" width="720" />
      </div>

       

       Classfuzz 框架

       

      接下來我來介紹一下 classfuzz 的框架,假設有個種子,進行了一個變種,變種結束之後,把變種類放到 Java七、Java八、Java九、J九、GCJ 上面一塊兒去跑。那麼就能夠經過一個類,生成了不少的變種文件,在不一樣虛擬機上面跑。這個裏面其實想隨機的變種,隨機生成不少的變種類,效果很是差。因而又引入了這樣一個過程:有一個選擇和測試的過程,有不少的變種算子,咱們研究怎麼樣去選擇更有效的變種算子,也選擇更加有表明性的一些類文件來作測試。

       

      <div id="dqa0ul" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483552031-90a9a3f9-2540-4bf4-a25f-509157f4912e.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483552031-90a9a3f9-2540-4bf4-a25f-509157f4912e.jpeg" width="720" />
      </div>

       

      那麼這裏面有幾個技術要點,因爲時間限制,我就簡單過一下。
      Classfuzz 的技術要點 1

       

      咱們設計了 129 個變種算子,其中 123 個是用來修改類的語法的,像我剛纔說的 public 改爲 private,刪掉一個名字,改掉一個函數名等等,或者刪掉一個函數等等。
      <div id="d64rve" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483567778-bfe91ec8-f981-49bc-ac57-e4555a540d64.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483567778-bfe91ec8-f981-49bc-ac57-e4555a540d64.jpeg" width="720" />
      </div>
      咱們還有 6 個修改語義,右邊是修改語義的一個簡單的辦法。咱們採用一個 Java 字節碼分析工具是 SOOT,這裏面它會把類轉成 Jimple 文件,那麼對 Jimple 文件的第 2 個語句和第 3 個語句,能夠給它順序顛倒一下。可是這個顛倒效果沒有那麼理想,不是說全部程序的字節碼都有一個前後關係。

       

      Classfuzz 的技術要點 2

       

      剛纔說到有 129 個變種算子,這個算子數其實挺多的。咱們的選擇性很是廣,那麼這裏面就採用了一個直覺,直覺是哪些變種算子更加有效,就讓它用的更加頻繁一點,因此採用了一個有點偏機器學習的一個算法,馬爾可夫鏈蒙特卡洛算法來選擇更加有效的算子。咱們預期會造成一個分佈,有些算子給它一些高的機率,有些給低的機率。實際分佈不是所預期的這樣,但整體上趨勢仍是比較接近的。

       

      Classfuzz的技術要點 3

       

      會有不少的測試類會被生成,這個時候怎麼樣去選擇一些有表明性的測試類?咱們採用了傳統測試裏面一個等價類劃分的技術,就把它們放到某個虛擬機上去跑,放到 Hotspot 上面,特別是 classloader 那一塊代碼,就收集一下它的行覆蓋率和分支覆蓋率,比較一下。這個時候馬上就有一個數字上的感受,假如這個數字不同,那麼就說明類在 Java 虛擬機裏面的處理邏輯是不同的,若是處理邏輯不同,那麼就應該說兩個類特性仍是不同的。若是有新生成的類的話,拿到 Java 虛擬機上跑,再來算一下它的覆蓋率,看看它是否是有表明性,這裏面表明性有兩個用途,第一個是用於多個
      Java 虛擬機差異測試,第二個是把它做爲新的種子來作變種,可以獲得新的變種。</div>

       

      <div id="uqzuql" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483598620-e6fedaf9-473d-426a-8d64-0bdedfe741db.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483598620-e6fedaf9-473d-426a-8d64-0bdedfe741db.jpeg" width="720" />
      </div>

       

      Classfuzz 的技術要點 4

       

      第四個技術要點是差異測試,咱們拿類到多個 Java 虛擬機上跑,去觀察它們的執行結果,試圖去分析究竟是在哪個階段所拋出的什麼問題。觀察在哪一個階段報了錯,爲何?當有幾個 JVM 的時候,就採用一個從衆原則推測哪一個 Java 虛擬機出錯了,這是差異測試的過程。

       

      <div id="swikue" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483613907-4eb58227-781b-4439-bf72-596672efc7b9.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483613907-4eb58227-781b-4439-bf72-596672efc7b9.jpeg" width="720" />
      </div>

       

      classfuzz 也發現了更多的 Java 虛擬機的這種區別,這裏面有一個變量叫 R0,咱們把 R0 的類型改了一下,從 map 改爲 String,也發現了虛擬機差異。咱們還發現 J9 和 Hotspot 的驗證方式不同,當導進來一個類的時候,HotSpot 會把全部的方法都會驗證一遍,可是 J9 就顯得比較 lazy 一點,它只是對未來有可能運行的方法會去作一個驗證,因此這個時候也有一個差異。那麼此外還發現 GU 缺乏維護,固然它如今更缺乏維護了。

       

      <div id="n2esyo" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483646303-554462b0-f407-441e-92d1-674f2ed27d5d.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483646303-554462b0-f407-441e-92d1-674f2ed27d5d.jpeg" width="720" />
      </div>

       

      咱們這個時候就意識到還會有不少的工做要作,因而就再接再礪,又作了下一個工做。classfuzz 並無可以深刻測試 Java 虛擬機的底層,咱們至始終在測的可能編譯那塊的同窗比較感興趣一點,可是對於研究運行時的同窗可能沒有那麼大的興趣,那麼主要的緣由就是咱們只是修改了語法,生成了不少格式正確或者不正確的字節碼文件,可是去運行的時候,除了不多數的可以修改語義操做的一些算子之外,生成的大部分的東西或者是被拒了,或者是它的執行和前面的一些類沒有什麼差異。這個時候咱們就思考這樣的一個事情,咱們是否是可以生成格式正確、可是語義不同的程序,語義不同也就是說你真的可以在Java虛擬機上跑,實際上語義不同的字節碼。

       

      這樣咱們可以測試兩個功能模塊:第一個,驗證器;第二個,它的執行功能,或者執行引擎。你們以爲可能就有點意思了。好,在作這兩件事情的時候,其實有一些執念:

       

      <li data-type="list-item" data-list-type="unordered-list">
        <div data-type="p">第一個執念,有不少同窗都學了編譯,那麼編譯原理裏面其實有不少程序分析和優化的算法。當時在作這件事情的時候,就很好奇,這麼多經典的算法在 Java 虛擬機實現當中,都正確地實現了嗎?咱們能不能在實現裏面,找到一個實現錯了的一個算法?</div>
      </li>
      <li data-type="list-item" data-list-type="unordered-list">
        <div data-type="p">第二個執念,是否是可以找到在兩個 Java 虛擬機上運行結果不同的程序?這個典型的就拿主流的 J9 和 Hotspot,在上面能不能用一樣字節碼,可以運行不同,還有爲何?好比執行的時候是否是還會有各類各樣奇怪的現象,例如 double free 等問題。 </div>
      </li>

       

       

      <div id="46pecg" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483672724-23157def-cbc8-4c90-b5d5-e049aadf2ec8.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483672724-23157def-cbc8-4c90-b5d5-e049aadf2ec8.jpeg" width="720" />
      </div>

       

      好,那麼接下來咱們 show 一點例子,幫助你們瞭解 Java 虛擬機的上述差異。

       

      <div id="hyeyvx" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483694031-3e20da89-374c-4024-9926-3636fbaf1b39.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483694031-3e20da89-374c-4024-9926-3636fbaf1b39.jpeg" width="720" />
      </div>

       

      右邊一小段代碼,那麼這兩段代碼咱們看是否是真的有什麼語義上的不一致?實際上左邊代碼是建立了一個對象,從棧頂拿出了一個元素,作了一個比較,對吧?右邊代碼表示從棧頂拿了一個元素,建立了一個對象,再作了比較。這兩個代碼語義實際上是如出一轍。一個是 o 等於 this,一個是 this 等於 o。這兩段代碼其實本質上都是錯誤代碼,由於咱們 new 完了之後其實沒有給對象初始化。可是到 Hotspot 和 J9 上面去運行的時候,Hotspot 給兩個都報了一個驗證錯誤,咱們就發現,J9 在很是罕見的狀況下,在某一個初始化函數裏面,若是你寫了代碼,它會經過驗證。實際上咱們抓住了一個缺陷。這是一個比較簡單的例子。

       

      那麼再來看比較複雜一點的例子,咱們說數據流分析可能實現錯了,那能不能找一找運行結果不同的程序?右邊是一個種子類,先建立了一個對象,初始化,把這個對象設爲空。接下來用 monitorenter 和 monitorexit。那麼 Jimple 正好反了一下。把它轉換爲類文件之後,Hotspot 和 J9 它是比較一致的,Hotspot 拋出了空指針異常,J9 也拋出了空指針異常。這是由於 Java 虛擬機規範裏面說,假如對象是空,咱們遇到的第一個 R0,由於是空的,那麼它應該拋一個空指針異常。

       

      <div id="0gspdf" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483721959-24be042f-389f-406c-aa1a-a63df8c8fb0a.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483721959-24be042f-389f-406c-aa1a-a63df8c8fb0a.jpeg" width="720" />
      </div>

       

      那麼接下來看看到底怎麼樣去修改代碼,發生了什麼?第一個乾的事情就是在裏面插入一個循環,直接跳到這,entermonitor R0,這個地方又作了一個循環回去,也就是說 entermonitorR0 會執行 20 遍, exitmonitor r0 執行了一遍。這個時候咱們發現這個 Hotspot 拋出了一個 IMSE,可是 J9 是正常執行。追究緣由,咱們發現這裏面其實有一個叫結構鎖的機制,假如一個Java  虛擬機要求去實現結構鎖這樣的一個機制,而且類違反告終構鎖規則,那麼就拋出一個 IMSE,Hotspot
      知足結構鎖機制,可是 J9 不要求,因此這裏面會造成一個差異,這是所發現的第一個差異。</div>

       

      <div id="zp0gns" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483741979-60b05b2f-77f3-47a2-aed1-8bf7680c5ea6.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483741979-60b05b2f-77f3-47a2-aed1-8bf7680c5ea6.jpeg" width="720" />
      </div>

       

      又去跑模糊測試,繼續去撞。這個時候從 new string,初始化以後,正好插入了一個 goto 語句到這,也就是說這是一個 new string r0,entermonitor r0,exitmonitor r0。Hotspot 就是正常的運行了,J9 就拋出了一個驗證錯誤。HotSpot 反饋說這應該是一個正確的例子,由於雖然 R0 沒有初始化,可是這個裏面沒有什麼危害,因此就能夠放過它。

       

      那麼 J9 就認爲它存在一個缺陷。實際上 Java 虛擬機規範裏是這樣說的,一個驗證器若是趕上一個沒有初始化的對象,在使用的時候應該要報一個驗證問題。好,那麼既然到這種狀況下面,entermonitor r0 它是使用了,就說明這個規則被違反了。又作了作,又撞了一個問題。entermonitor r0 這個東西是一個正常的對象,那麼 exitmonitor r0,這個時候 R0 是空對象,它原本應該匹配的,可是這個時候實際上變成空引用。

       

      <div id="hefceg" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483758078-491e9e88-8826-4260-9346-d1513a7add41.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483758078-491e9e88-8826-4260-9346-d1513a7add41.jpeg" width="720" />
      </div>

       

      J9 拋出了一個空指針異常,Hotspot 拋出了 IMSE。J9 的解釋很合理,由於從規範上來講,monitorexit null 應該拋出一個空指針異常。Hotspot 開發人員也找了好久,實際上發如今這作了一個優化,在這個時候 Hotspot 會拋出幾個異常,可是這個時候會作一個優化,把其餘異常都扔掉,留了一個 IMSE。可是因爲它們是拋的不同的異常,因爲這些異常能夠被分別捕獲,因此程序能夠產生不一樣的運行結果。針對於一樣的一個種子,咱們變化,會發現,這個程序它的運行還有點不太同樣。

       

      這個裏面還發現了一些,好比說 Hotspot 的不一樣版本之間也會有一些差異,當咱們的測試類比較複雜的時候,有控制流的歸併,有數組的訪問,有異常處理等等,它會趕上一些問題。

       

      <div id="43ggpo" data-type="image" data-display="block" data-align="" data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483773231-becfbfdd-9227-43cb-a907-ee89a780cdfd.jpeg" data-width="720">
        <img src="https://intranetproxy.alipay.com/skylark/lark/0/2019/jpeg/168324/1546483773231-becfbfdd-9227-43cb-a907-ee89a780cdfd.jpeg" width="720" />
      </div>

       

      技術方面,仍是採用類變種,意圖是生成語義不同的一些文件。怎麼樣算語義不同?也就是方法裏面可以被運行到的字節碼是不同的。那麼一個主要的思想就是要修改這個種子裏字節碼。咱們記錄哪些字節碼被運行到了,在裏邊去修改一下,去修改它的控制流,那麼修改完了控制流之後,它的數據流也可能發生改變,這個時候會出現很異常的控制流或者數據流,若是咱們把這種異常的狀況放到 Java 虛擬機上跑,有可能它的數據流分析會錯了,有可能其餘狀況也會出錯,這是很簡單的思想。

       

      咱們的技術要點,第一個會記錄一下哪些字節碼會被執行到,反正作一些插裝就能夠。第二個咱們要作一些變種,在每一個語句後面,就插入 goto。實際上除了 goto 以外,咱們還能夠插入 return、throw、lookupswitch,都是 Jimple 裏支持的。固然也能夠去用 ASM 插入更多可以修改控制流的指令。

       

      變種過程也是一個頻繁試錯的過程,實際上遵循了一個流程,變種出錯我就把它拒了,變種過程就是有個種子,變完了之後,決定要接收、拒絕等等,獲得新的類,繼續變種、接受、拒絕等。

       

      咱們差異測試主要是看看有什麼驗證問題,還有沒有可能會撞上系統崩潰,有沒有輸出差別,這種輸出差別並非由併發致使的,而由 Java 虛擬機實現上面的差別致使的。

       

      最後介紹一個例子。右邊有一個函數,R2 等於 new string,那麼在這 R2 是一個對象,這個 R2 被用了,因此理論上他不能經過驗證,由於 R2 被使用以前沒有被初始化,違反了 Java 虛擬機規範。可是在這個裏面 Hotspot 成功地拋出了驗證錯誤,可是 J9 沒有可以拒絕,說明驗證器出錯了。實際上你們能夠看一下這個問題是怎麼來的,其實就是植入了一個 goto,初始化中跳出去了,它正好 R2 就被使用了,這個時候就發現了這個問題。

       

      總結一下咱們的工做,咱們作了一個 Java 字節碼變種及 Java 虛擬機差異測試的一個技術方案,這個裏面能夠暴露出 Java 虛擬機的缺陷。進一步咱們但願去看看,既然有這麼多的變種,爲何不把它應用到內存管理當中,看看內存管理有什麼問題,看看性能有什麼問題,特別是變種有可能會對一些高強度的計算,進行反覆的迭代,反覆計算,那麼是否是可以發現性能方面的一些缺陷?這項工做是和如今在蘇黎世理工的蘇振東老師,九州大學的趙建軍教授,還有南洋理工的蘇亭,谷歌孫誠年一塊兒作的一項工做。那麼個人彙報就到這裏,謝謝你們。

 做者: jessie筱姜
原文連接
本文爲雲棲社區原創內容,未經容許不得轉載。算法

相關文章
相關標籤/搜索