java反編譯工具

jeremylai發表於2021-08-13

編譯和反編譯

程式語言分成高階語言和低階語言。低階語言如機器語言、組合語言。這類語言直接用計算機指令編寫命令,不需要編譯。這些語言機器能看到懂,但是程式設計師讀起來很費勁。而我們平時經常用的語言C、Java、Python屬於高階語言,這些語言程式設計師能看的懂。而機器是看不懂的。

簡單的總結為:高階語言就是程式設計師認識的語言,而低階語言是機器認識的語言。而把高階語言轉成低階語言這個過程就是編譯,而反編譯就是把低階語言轉成高階語言。
有了反編譯,我們就可以看懂Java編譯器生成的位元組碼,比如Synchronized的實現原理(監聽器monitor)、列舉、語法糖、泛型,這些都需要用到反編譯工具。

javap

javap是jdk自帶的反編譯命令,可以對程式碼進行反編譯,但是反編譯的並不是java檔案。

使用格式

javap <options> <classes>

常用: javap -c 類名

-help  --help  -?        輸出此用法訊息
-version                 版本資訊
-v  -verbose             輸出附加資訊
-l                       輸出行號和本地變數表
-public                  僅顯示公共類和成員
-protected               顯示受保護的/公共類和成員
-package                 顯示程式包/受保護的/公共類和成員 (預設)
-p  -private             顯示所有類和成員
-c                       對程式碼進行反彙編
-s                       輸出內部型別簽名
-sysinfo                 顯示正在處理的類的系統資訊 (路徑, 大小, 日期, MD5 雜湊)
-constants               顯示最終常量
-classpath <path>        指定查詢使用者類檔案的位置
-cp <path>               指定查詢使用者類檔案的位置
-bootclasspath <path>    覆蓋引導類檔案的位置

下面寫一段synchronized程式碼:

public class SynchronizedTest {

	private int count = 0;

	public  void addOne() {
		synchronized (SynchronizedTest.class) {
			count++;
		}
	}
}

執行編譯和反編譯命令

javac SynchronizedTest .java
javap -c SynchronizedTest.class

直接用記事本開啟SynchronizedTest.class檔案是一堆亂碼檔案,用sublime開啟是一串數字

cafe babe 0000 0034 0017 0a00 0400 1209
0003 0013 0700 1407 0015 0100 0563 6f75
6e74 0100 0149 0100 063c 696e 6974 3e01
0003 2829 5601 0004 436f 6465 0100 0f4c
696e 654e 756d 6265 7254 6162 6c65 0100
0661 6464 4f6e 6501 000d 5374 6163 6b4d
6170 5461 626c 6507 0014 0700 1507 0016

反編譯後的程式碼:

public class com.SynchronizedTest {
  public com.SynchronizedTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_0
       6: putfield      #2                  // Field count:I
       9: return

  public void addOne();
    Code:
       0: ldc           #3                  // class com/yyw/oil/web/admin/controller/purchase/SynchronizedTest
       2: dup
       3: astore_1
       4: monitorenter
       5: aload_0
       6: dup
       7: getfield      #2                  // Field count:I
      10: iconst_1
      11: iadd
      12: putfield      #2                  // Field count:I
      15: aload_1
      16: monitorexit
      17: goto          25
      20: astore_2
      21: aload_1
      22: monitorexit
      23: aload_2
      24: athrow
      25: return
    Exception table:
       from    to  target type
           5    17    20   any
          20    23    20   any
}

javap並沒有將位元組碼反編譯成成java檔案,而是生成一種另一種能看的懂得位元組碼。可以看出被synchronized修飾的程式碼包含 monitorenter 和 monitorexit。synchronized 底層依賴著兩個指令來實現同步, 這裡看起來比較晦澀難懂。

CFR

官網上下載jar,執行如下命令:

java -jar cfr-0.151.jar SynchronizedTest.class

得到反編譯java檔案:

public class SynchronizedTest {
    private int count = 0;

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    public void addOne() {
        Class<SynchronizedTest> clazz = SynchronizedTest.class;
        synchronized (SynchronizedTest.class) {
            ++this.count;
            // ** MonitorExit[var1_1] (shouldn't be in output)
            return;
        }
    }
}

CFR還帶有一些引數:

引數 註釋
--decodeenumswitch (boolean) 去除switch對列舉支援的語法糖
--decodelambdas (boolean) 去除lambda表示式的語法糖
--decodestringswitch (boolean) 去除switch string支援的語法糖

其餘引數可使用如下命令檢視:

 java -jar cfr-0.151.jar --help 

idea

使用idea生成class檔案,用idea開啟class檔案即可。idea是絕大多數Java程式設計師使用的編輯器,使用idea開啟檔案比較方便、快捷。

參考

Java程式碼的編譯與反編譯那些事兒
javap的使用

相關文章