能看懂的字節碼-上

大綱

前言

上篇文章經過Java的跨平臺性引出了字節碼文件,這篇文章就是解讀字節碼文件到底存了哪些東西java

這個分析真的很難懂也很繞,不過若是你掌握了字節碼的分析方法,怎麼看字節碼,那麼就挺容易看懂的,但願你有耐心看下去。由於我以爲這應該算是很是詳細並且簡單的分析。我儘可能作到詳細解讀,書上不少隱藏的沒說的也儘可能給大家講出來,畢竟對於初學者確實有些坑會卡好久git

能夠和《深刻理解Java虛擬機》第二版一塊兒看,不過書上的例子和我實際運行的不同,大家看到的應該也是我這種,可是並不影響咱們找到查看字節碼的方法。畢竟方法是互通的github

這裏說的字節碼文件和Class文件是一個東西,文章中可能混用,請必定記住spring


個人全部文章同步更新與Github--Java-Notes,想了解JVM,HashMap源碼分析,spring相關,劍指offer題解(Java版),能夠點個star。能夠看個人github主頁,天天都在更新喲。安全

邀請您跟我一同完成 repo數據結構


Class文件結構

任何一個Class文件都對應着惟一一個類或接口的定義信息,但反過來講,類或接口並不必定都得定義在文件裏(譬如類或接口也能夠經過類加載器直接生成)。(若是不懂這個也沒必要記)jvm

須要記的

  • Class文件是一組 8位字節爲基礎的二進制流(後面會介紹),各個數據之間排列很是緊湊ide

  • Class文件採用相似於C語言結構體僞結構來存儲數據,只有兩種數據類型工具

    • 無符號
      • 無符號數屬於基本的數據類型,以u1,u2,u4,u8來表示一個字節,兩個字節,以此類推
      • 是複合結構。可能由多個無符號數或者其餘表做爲數據項構成的類型,習慣性的以"_info"結尾
    • 你能夠把整個Class文件當作是一張表

Class文件格式

class文件一共只由這麼多個東西組成源碼分析

請必定必定必定!!!!要記住這個表,字節碼文件順序徹底按照這個表進行的,我文章的順序也是按照這個寫的,並且這個表後面文章中會常常用到

嚴格限定,不可更改

上圖Class文件中的數據項,不管順序是數量,甚至於數據存儲的字節序這樣的細節,都是被嚴格限定的。哪一個字節表明什麼含義,長度是多少,前後順序如何,都不容許改變

準備

程序

package com.swu.leosanqing;

public class TestClass {

    private int m;

    public int inc(){
        return m+1;
    }
}

複製代碼

軟件

一個能用16進制查看二進制文件的軟件,好比我用的Ultra Edit

打開命令行

  • 使用Javac命令,把相應程序編譯成Class文件

  • 使用 javap -verbose命令,打開剛剛編譯成的Class文件

  • 使用剛剛的軟件打開Class文件

剛剛javap -verbose命令打開的文件是這樣,這個後面對比的時候會用到,裏面的名詞參考上面的結構表

Classfile /Users/zhuerchong/Desktop/code/idea/jvm/classFileTest/src/com/swu/leosanqing/TestClass.class
  Last modified 2019-4-22; size 294 bytes
  MD5 checksum b72816f8ed65e104c061eed5275b81e9
  Compiled from "TestClass.java"
public class com.swu.leosanqing.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#16         // com/swu/leosanqing/TestClass.m:I
   #3 = Class              #17            // com/swu/leosanqing/TestClass
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               TestClass.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               com/swu/leosanqing/TestClass
  #18 = Utf8               java/lang/Object
{
  public com.swu.leosanqing.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 8: 0
}
SourceFile: "TestClass.java"

複製代碼

魔數

概念

對應上面的 magic,他是標識class文件的基礎,通常文件的二進制流都會有 魔數,這個是相應的軟件識別文件的前提。

咱們常說的後綴名,基本上只是電腦識別歸類以及給人看,或者給默認的軟件解析,省事。你改了後綴名,並不會影響他的二進制內容。並且若是是要確保安全,在作軟件的文件上傳功能的時候,必定是要讀取內容判斷魔數而不是判斷後綴名

/** JPG */
	JPEG("FFD8FF"),
	
	/** PNG */
	PNG("89504E47"),
	
	/** GIF */
	GIF("47494638"),
複製代碼

一個JPG文件的十六進制碼

**就像是你給狗起了我的名,難道他就是人了嗎?**你只有改了他的DNA,他纔有可能成爲人。名字就對應後綴名,DNA就對應魔數

還記得我以前提到的,Class是以8位字節爲基礎的二進制文件嗎?若是咱們用十六進制來看,那麼兩位就至關因而一個字節,也就是u1。

因此咱們看的時候,是兩位兩位一塊兒看,上面提到魔數一共有4位(正確的應該是4個字節)。

結果他也真的是4位,

Class文件的魔數

CAFE BABE,這個魔數是否是很浪漫?有沒有想到和Java的名字相關(Java也是一種咖啡)?可是這個魔數是在 Java還在叫Oak的時候就已經肯定了下來,或許後面更名Java的時候參考了魔數

版本號

緊接着魔數的後面的兩個數據是版本號,分別都爲u2類型,也就是要看2個字節。

前者表示次版本號,後者表示主版本號。

0x34轉換成十進制是52,而主版本號是從45開始,**JDK1.1以後的每一個JDK大版本發佈主版本號向上加1.**因此52表示JDK1.8.

高版本的JDK能向下兼容,可是低版本的不能向上兼容,即便文件格式並未發生任何變化,虛擬機也拒絕編譯。因此JDK1.8能支持45.0-52.65535的class文件

常量池

常量池是一個大篇章,我看了很長時間才弄懂,由於一開始沒有看懂他是怎麼看的那個各類數據表,怎麼看他的數據結構體。

範圍

除了我框出來的部分,剩下的都是常量池的內容,

有什麼

常量池中主要存放兩大類:

  • 字面量
    • 文本字符串
    • 聲明爲final的常量值
    • 等….
  • 符號引用
    • 類和結構的全限定名
    • 字段的名稱和描述符
    • 方法的名稱和描述符

怎麼看

還記得那個表嗎

咱們看到主版本號以後就是常量池的入口了,頭兩個字節是常量池的大小。咱們看到是0x13,換算成十進制是19,也就是一共有18個常量池的數據(注意,不是從0開始,0單獨有用,索引就是1-18)。我就帶你們查看下有哪些數據,你從咱們用 javap -verbose弄出來的也能夠看出來。

在看字節碼以前,咱們得現弄明白常量池中都有哪些項目類型,一共有14個(JDK1.7,1.8以後增長了沒有我也不知道,不過不影響咱們閱讀和理解字節碼)

他們的共同特色是,表開始的第一位是一個u1類型的標誌位(tag)

  • 咱們看到第一個是0x0A,由他們的特徵知道他是標誌位(不清楚的,你可能忘了或者沒看到我圖片上面的一句話),

  • 那麼咱們就轉換成十進制,就是 10。

  • 查看圖片,找到標誌位是10 的那個常量表。

而後咱們知道這個表的結構體一共有三個數據項構成。咱們再根據這個來找後面的字節碼是啥意思。

根據表結構,咱們知道他後面兩個都是u2類型的,因此後面的都是兩個兩個看。下一個是0x0004,根據表的定義,他是指向CONSTANT_Class_info的索引。咱們再去找CONSTANT_Class_info,他是這樣的結構

因爲已經肯定了數據的表結構,因此他並無tag,因此0x0004就表示後面的index,即指向全限定名的常量項目的索引,而他指向第四個常量。(咱們如今還沒分析到第四個常量,可是咱們能夠經過以前命令行裏的內容找到第四個常量表明啥)

因此它其實是指向類的全限定名-java/lang/Object

咱們也能夠經過 javap -verbose 這個來驗證咱們剛剛翻譯過來的字節碼到底對不對,咱們看到,確實沒有問題。

咱們再看他的第三個數據項,0x000F,再根據剛剛的方法,咱們找到 常量池中第15個常量爲 NameAndType類型,而且指向第7和第8 個常量。

後面的翻譯方法跟這個同樣,我就再也不囉嗦了你也能夠根據我說的方法一一去驗證,直到把常量池中的所有翻譯完。

最後

若是你想看懂字節碼,知道他們表明什麼意思,那你最好反覆看下個人這個文章,跟我個人方法一一驗證一下,弄懂該怎麼去看字節碼的數據結構體。由於後面的訪問標誌、類索引和接口索引、字段表集合等等,Class文件的內容都是要採用這種方法翻譯。

想要看剩下的都是哪些內容,能夠看個人下篇,能看懂的字節碼-下。由於這篇主要是講如何看懂字節碼的方法,該用到哪些工具,怎麼驗證本身翻譯的對不對。

下篇預告

字節碼中的:

  • 訪問標誌

  • 類索引、父類索引和接口索引集合

  • 字段表結合

  • 方法表集合

  • 屬性表集合

相關文章
相關標籤/搜索