深入淺出解讀 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 的框架,假設有個種子,進行了一個變種,變種結束以後,把變種類放到 Java7、Java8、Java9、J9、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 虛擬機的缺陷。進一步我們希望去看看,既然有這麼多的變種,爲什麼不把它應用到內存管理當中,看看內存管理有什麼問題,看看性能有什麼問題,特別是變種有可能會對一些高強度的計算,進行反覆的迭代,反覆計算,那麼是不是能夠發現性能方面的一些缺陷?這項工作是和現在在蘇黎世理工的蘇振東老師,九州大學的趙建軍教授,還有南洋理工的蘇亭,谷歌孫誠年一起做的一項工作。那麼我的彙報就到這裏,謝謝大家。


       

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