中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中怎么實現代碼編譯和反編譯

發布時間:2021-06-11 15:44:17 來源:億速云 閱讀:152 作者:Leah 欄目:編程語言

這篇文章將為大家詳細講解有關Java中怎么實現代碼編譯和反編譯,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

編程語言

在介紹編譯和反編譯之前,我們先來簡單介紹下編程語言(Programming Language)。編程語言(Programming Language)分為低級語言(Low-level Language)和高級語言(High-level Language)。

機器語言(Machine Language)和匯編語言(Assembly Language)屬于低級語言,直接用計算機指令編寫程序。

而C、C++、Java、Python等屬于高級語言,用語句(Statement)編寫程序,語句是計算機指令的抽象表示。

舉個例子,同樣一個語句用C語言、匯編語言和機器語言分別表示如下:

Java中怎么實現代碼編譯和反編譯

計算機只能對數字做運算,符號、聲音、圖像在計算機內部都要用數字表示,指令也不例外,上表中的機器語言完全由十六進制數字組成。最早的程序員都是直接用機器語言編程,但是很麻煩,需要查大量的表格來確定每個數字表示什么意思,編寫出來的程序很不直觀,而且容易出錯,于是有了匯編語言,把機器語言中一組一組的數字用助記符(Mnemonic)表示,直接用這些助記符寫出匯編程序,然后讓匯編器(Assembler)去查表把助記符替換成數字,也就把匯編語言翻譯成了機器語言。

但是,匯編語言用起來同樣比較復雜,后面,就衍生出了Java、C、C++等高級語言。

什么是編譯

上面提到語言有兩種,一種低級語言,一種高級語言。可以這樣簡單的理解:低級語言是計算機認識的語言、高級語言是程序員認識的語言。

那么如何從高級語言轉換成低級語言呢?這個過程其實就是編譯。

從上面的例子還可以看出,C語言的語句和低級語言的指令之間不是簡單的一一對應關系,一條a=b+1;語句要翻譯成三條匯編或機器指令,這個過程稱為編譯(Compile),由編譯器(Compiler)來完成,顯然編譯器的功能比匯編器要復雜得多。用C語言編寫的程序必須經過編譯轉成機器指令才能被計算機執行,編譯需要花一些時間,這是用高級語言編程的一個缺點,然而更多的是優點。首先,用C語言編程更容易,寫出來的代碼更緊湊,可讀性更強,出了錯也更容易改正。

將便于人編寫、閱讀、維護的高級計算機語言所寫作的源代碼程序,翻譯為計算機能解讀、運行的低階機器語言的程序的過程就是編譯。負責這一過程的處理的工具叫做編譯器

現在我們知道了什么是編譯,也知道了什么是編譯器。不同的語言都有自己的編譯器,Java語言中負責編譯的編譯器是一個命令:javac

javac是收錄于JDK中的Java語言編譯器。該工具可以將后綴名為.java的源文件編譯為后綴名為.class的可以運行于Java虛擬機的字節碼。

當我們寫完一個HelloWorld.java文件后,我們可以使用javac HelloWorld.java命令來生成HelloWorld.class文件,這個class類型的文件是JVM可以識別的文件。通常我們認為這個過程叫做Java語言的編譯。其實,class文件仍然不是機器能夠識別的語言,因為機器只能識別機器語言,還需要JVM再將這種class文件類型字節碼轉換成機器可以識別的機器語言。

什么是反編譯

反編譯的過程與編譯剛好相反,就是將已編譯好的編程語言還原到未編譯的狀態,也就是找出程序語言的源代碼。就是將機器看得懂的語言轉換成程序員可以看得懂的語言。Java語言中的反編譯一般指將class文件轉換成java文件。

有了反編譯工具,我們可以做很多事情,最主要的功能就是有了反編譯工具,我們就能讀得懂Java編譯器生成的字節碼。如果你想問讀懂字節碼有啥用,那么我可以很負責任的告訴你,好處大大的。比如我的博文幾篇典型的原理性文章,都是通過反編譯工具得到反編譯后的代碼分析得到的。如深入理解多線程(一)——Synchronized的實現原理、深度分析Java的枚舉類型—-枚舉的線程安全性及序列化問題、Java中的Switch對整型、字符型、字符串型的具體實現細節、Java的類型擦除等。我最近在GitChat寫了一篇關于Java語法糖的文章,其中大部分內容都用到反編譯工具來洞悉語法糖背后的原理。

Java反編譯工具

本文主要介紹3個Java的反編譯工具:javap、jad和cfr

javap

javap是jdk自帶的一個工具,可以對代碼反編譯,也可以查看java編譯器生成的字節碼。javap和其他兩個反編譯工具最大的區別是他生成的文件并不是java文件,也不像其他兩個工具生成代碼那樣更容易理解。拿一段簡單的代碼舉例,如我們想分析Java 7中的switch是如何支持String的,我們先有以下可以編譯通過的源代碼:

public class switchDemoString {
 public static void main(String[] args) {
 String str = "world";
 switch (str) {
  case "hello":
  System.out.println("hello");
  break;
  case "world":
  System.out.println("world");
  break;
  default:
  break;
 }
 }
}

執行以下兩個命令:

javac switchDemoString.java
javap -c switchDemoString.class

生成代碼如下:

public class com.hollis.suguar.switchDemoString {
 public com.hollis.suguar.switchDemoString();
 Code:
 0: aload_0
 1: invokespecial #1   // Method java/lang/Object."<init>":()V
 4: return

 public static void main(java.lang.String[]);
 Code:
 0: ldc  #2   // String world
 2: astore_1
 3: aload_1
 4: astore_2
 5: iconst_m1
 6: istore_3
 7: aload_2
 8: invokevirtual #3   // Method java/lang/String.hashCode:()I
 11: lookupswitch { // 2
  99162322: 36
  113318802: 50
  default: 61
  }
 36: aload_2
 37: ldc  #4   // String hello
 39: invokevirtual #5   // Method java/lang/String.equals:(Ljava/lang/Object;)Z
 42: ifeq  61
 45: iconst_0
 46: istore_3
 47: goto  61
 50: aload_2
 51: ldc  #2   // String world
 53: invokevirtual #5   // Method java/lang/String.equals:(Ljava/lang/Object;)Z
 56: ifeq  61
 59: iconst_1
 60: istore_3
 61: iload_3
 62: lookupswitch { // 2
   0: 88
   1: 99
  default: 110
  }
 88: getstatic #6   // Field java/lang/System.out:Ljava/io/PrintStream;
 91: ldc  #4   // String hello
 93: invokevirtual #7   // Method java/io/PrintStream.println:(Ljava/lang/String;)V
 96: goto  110
 99: getstatic #6   // Field java/lang/System.out:Ljava/io/PrintStream;
 102: ldc  #2   // String world
 104: invokevirtual #7   // Method java/io/PrintStream.println:(Ljava/lang/String;)V
 107: goto  110
 110: return
}

我個人的理解,javap并沒有將字節碼反編譯成java文件,而是生成了一種我們可以看得懂字節碼。其實javap生成的文件仍然是字節碼,只是程序員可以稍微看得懂一些。如果你對字節碼有所掌握,還是可以看得懂以上的代碼的。其實就是把String轉成hashcode,然后進行比較。

個人認為,一般情況下我們會用到javap命令的時候不多,一般只有在真的需要看字節碼的時候才會用到。但是字節碼中間暴露的東西是最全的,你肯定有機會用到,比如我在分析synchronized的原理的時候就有是用到javap。通過javap生成的字節碼,我發現synchronized底層依賴了ACC_SYNCHRONIZED標記和monitorenter、monitorexit兩個指令來實現同步。

jad

jad是一個比較不錯的反編譯工具,只要下載一個執行工具,就可以實現對class文件的反編譯了。還是上面的源代碼,使用jad反編譯后內容如下:

命令:jad switchDemoString.class

public class switchDemoString
{
 public switchDemoString()
 {
 }
 public static void main(String args[])
 {
 String str = "world";
 String s;
 switch((s = str).hashCode())
 {
 default:
  break;
 case 99162322:
  if(s.equals("hello"))
  System.out.println("hello");
  break;
 case 113318802:
  if(s.equals("world"))
  System.out.println("world");
  break;
 }
 }
}

看,這個代碼你肯定看的懂,因為這不就是標準的java的源代碼么。這個就很清楚的可以看到原來字符串的switch是通過equals()和hashCode()方法來實現的。

但是,jad已經很久不更新了,在對Java7生成的字節碼進行反編譯時,偶爾會出現不支持的問題,在對Java 8的lambda表達式反編譯時就徹底失敗。

CFR

jad很好用,但是無奈的是很久沒更新了,所以只能用一款新的工具替代他,CFR是一個不錯的選擇,相比jad來說,他的語法可能會稍微復雜一些,但是好在他可以work。

如,我們使用cfr對剛剛的代碼進行反編譯。執行一下命令:

java -jar cfr_0_125.jar switchDemoString.class --decodestringswitch false

得到以下代碼:

public class switchDemoString {
 public static void main(String[] arrstring) {
 String string;
 String string2 = string = "world";
 int n = -1;
 switch (string2.hashCode()) {
  case 99162322: {
  if (!string2.equals("hello")) break;
  n = 0;
  break;
  }
  case 113318802: {
  if (!string2.equals("world")) break;
  n = 1;
  }
 }
 switch (n) {
  case 0: {
  System.out.println("hello");
  break;
  }
  case 1: {
  System.out.println("world");
  break;
  }
 }
 }
}

通過這段代碼也能得到字符串的switch是通過equals()和hashCode()方法來實現的結論。

相比Jad來說,CFR有很多參數,還是剛剛的代碼,如果我們使用以下命令,輸出結果就會不同:

java -jar cfr_0_125.jar switchDemoString.class

public class switchDemoString {
 public static void main(String[] arrstring) {
 String string;
 switch (string = "world") {
  case "hello": {
  System.out.println("hello");
  break;
  }
  case "world": {
  System.out.println("world");
  break;
  }
 }
 }
}

所以--decodestringswitch表示對于switch支持string的細節進行解碼。類似的還有--decodeenumswitch、--decodefinally、--decodelambdas等。在我的關于語法糖的文章中,我使用--decodelambdas對lambda表達式警進行了反編譯。 源碼:

public static void main(String... args) {
 List<String> strList = ImmutableList.of("Hollis", "公眾號:Hollis", "博客:www.hollischuang.com");

 strList.forEach( s -> { System.out.println(s); } );
}

java -jar cfr_0_125.jar lambdaDemo.class --decodelambdas false反編譯后代碼:

public static /* varargs */ void main(String ... args) {
 ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
 strList.forEach((Consumer<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
}

private static /* synthetic */ void lambda$main$0(String s) {
 System.out.println(s);
}

典型的應對策略有以下幾種:

  • 隔離Java程序

    • 讓用戶接觸不到你的Class文件

  • 對Class文件進行加密

    • 提到破解難度

  • 代碼混淆

    • 將代碼轉換成功能上等價,但是難于閱讀和理解的形式

關于Java中怎么實現代碼編譯和反編譯就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

阳春市| 都兰县| 桂林市| 上饶市| 和硕县| 台北市| 当雄县| 桃源县| 乌兰浩特市| 涟源市| 锦州市| 墨竹工卡县| 长汀县| 温泉县| 古丈县| 旺苍县| 湾仔区| 扶余县| 河北省| 浦江县| 抚州市| 顺昌县| 襄城县| 徐闻县| 雷州市| 永川市| 通城县| 泌阳县| 长垣县| 玉环县| 平山县| 玛沁县| 罗定市| 海宁市| 花莲县| 琼海市| 镇赉县| 楚雄市| 嘉祥县| 子洲县| 曲沃县|