2012-11-12 21 views
7

我正在處理的遺留項目包含一些外部庫,其形式爲一組二進制jar文件。我們決定爲了進行分析和修補,我們希望收到這個庫的源代碼,使用它們來構建新的二進制文件,經過詳細和足夠長的迴歸測試後切換到這些二進制文件。如何檢查二進制文件是否由特定源構建

假設我們已經檢索並建立了源(我實際上是在規劃階段)。在進行真正的測試之前,我希望執行一些「兼容性檢查」,以排除源代表與「舊」二進制文件中的內容顯着不同的可能性。

使用javap工具我能夠提取用於編譯的JDK版本(至少我相信它是JDK的版本)。它說,二進制文件是使用主版本46和次版本0構建的。根據this article它映射到JDK 1.2。

假設相同的JDK將用於源編譯。

問題是: 有沒有驗證的可靠的和可能有效的方法,如果這兩個二進制文件是由相同的源代碼構建的?我想知道所有方法簽名和類定義是否相同,以及方法實現的大部分或全部是否相同/相似。

這個庫很大,所以我認爲反編譯的二進制文件的詳細分析可能不是一個選項。

+0

反射('java.lang.reflect')應爲類和方法簽名做,但不執行。 – SJuan76

+0

如何比較兩個二進制文件的MD5哈希值? – sp00m

+1

爲了將來的參考,最簡單的方法就是使用像Git,Subversion或Mercurial這樣的版本控制系統,然後在您的系統中包含版本號和/或變更集ID jar,比如在清單文件中。 – Brian

回答

1

我建議一個多階段的過程:

應用先前建議Jardiff或類似的,看看是否有任何API差異。如果可能的話,選擇一個可以選擇報告私有方法等的工具。實際上,即使公共API沒有變化,Java中的任何實質性實現更改都可能會改變某些方法和類。

如果您有API匹配,請使用指定的編譯器編譯幾個隨機選擇的文件,反編譯結果和原始類文件,並比較結果。如果它們匹配,則將相同的過程應用於更大和更大的代碼體,直到找到不匹配或檢查了所有內容。

反編譯代碼的差異更有可能爲您提供關於差異性質的線索,並且比實際的類文件更容易篩選非顯着差異。

如果您遇到不匹配,請分析它。這可能是由於你不關心的事情。如果是這樣,請嘗試構建一個腳本,刪除該差異形式並恢復編譯和比較過程。如果得到廣泛的不匹配,請嘗試編譯器參數,如優化。如果對編譯器參數的調整消除差異,請繼續進行批量比較。此階段的目標是找到編譯器參數和反編譯的代碼過濾器的組合,這些代碼過濾器在示例文件上產生匹配,並將其應用於庫的批量比較。

如果您在反編譯的代碼中無法獲得相當接近的匹配,那麼您可能沒有正確的源代碼。即便如此,如果你有一個API匹配,可能值得構建你的系統並使用編譯結果運行你的測試。如果您的測試至少與您從源代碼構建的版本一樣運行,請繼續使用它。

+0

我決定使用你的大部分建議。謝謝 :) –

0

這裏有各種各樣的JAR比較工具。一個曾經是相當不錯的是Jardiff。我有一段時間沒有使用它,但我確定它仍然可用。在同一空間還有一些商業產品可以滿足您的需求。

0

Jardiff Perception提到的是一個好的開始,但是沒有辦法在理論上100%確實地做到這一點。這是因爲可以使用不同的編譯器和不同的編譯器配置和優化級別編譯相同的源代碼。所以沒有辦法比較類和方法簽名之外的二進制代碼(字節碼)。

你是什麼意思的「類似實施」的方法?我們假設一個聰明的編譯器會丟棄一個else的情況,因爲它發現這個情況可能不是真的。兩者是否相似?是和不是.. :-)

最好的方法去恕我直言是設置非常好的迴歸測試用例,檢查您的庫的每個關鍵功能。這可能是一種恐怖,但長期來看可能比尋找bug更便宜。這一切都取決於你在這個項目中的未來計劃。不是一個簡單的容易的決定。

0

對於方法簽名,請使用像jardiff這樣的工具。

對於實現的相似性,您必須迴歸一個瘋狂的猜測。比較操作碼級的字節碼可能依賴於編譯器並導致大量的錯誤否定。如果是這種情況,您可以使用LineNumberTable來回退比較類的方法。

它爲您提供了每種方法的行號列表(只要類文件已使用調試標誌進行編譯,這在舊的或商業庫中經常會丟失)。

如果兩個類文件是從相同的源代碼編譯的,那麼至少每個方法的行號應該完全匹配。

可以使用庫,比如Apache BCEL檢索LineNumberTable:

// import org.apache.bcel.classfile.ClassParser; 
    JavaClass fooClazz = new ClassParser("Foo.class").parse(); 
    for(Method m : fooClazz.getMethods()) 
    { 
    LineNumberTable lnt = m.getLineNumberTable(); 
    LineNumber[] tab = lnt.getLineNumberTable(); 
    for(LineNumber ln : tab) 
    { 
     System.out.println(ln.getLineNumber()); 
    } 
    } 
相關問題