Class字節碼文件結構詳解

MatLez 8年前發布 | 11K 次閱讀 Java開發

來自: http://my.oschina.net/lixin91/blog/627767


Class字節碼中有兩種數據類型:

  1. 字節數據直接量:這是基本的數據類型。共細分為u1、u2、u4、u8四種,分別代表連續的1個字節、2個字節、4個字節、8個字節組成的整體數據。

  2. 表:表是由多個基本數據或其他表,按照既定順序組成的大的數據集合。表是有結構的,它的結構體現在,組成表的成分所在的位置和順序都是已經嚴格定義好的。

Class字節碼總體結構如下:

具體詳解請參考http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html

我在這里要說明幾個細節問題:

  1. 為什么說常量表的數量是constant_pool_count-1,且索引從1開始而不是0。其實根本原因在于,索引為0也是一個常量(保留常量),只不過它不存在常量表,這個常量就對應null值。因此加上這個系統保留常量,常量個數共為constant_pool_count個,但是常量表數量要減1。

  2. 在常量池中,如果存在long型或double型字面量,它們會占用兩個連續索引。比如:假設一個類中只有一個int型字面量1和一個double型字面量1(當然這種假設是不可能的,因為總會有類名字面量等),則常量池個數為3,而不是2。這正是因為double字面量占用了兩個連續的索引。

接下來,貼出一個小demo來展示如何讀取字節碼:

ClassParser負責把握Class字節碼整體結構的解析。

package com.lixin;

import java.io.IOException;
import java.io.InputStream;

public class ClassParser {

    private InputStream in;

    public ClassParser(InputStream in) {
        this.in = in;
    }

    public void parse() throws IOException {
        // 魔數
        magicNumber();
        // 主次版本號
        version();
        // 常量池
        constantPool();
        // 類或接口修飾符
        accessFlag();
        // 繼承關系(當前類、父類、父接口)
        inheritence();
        // 字段集合
        fieldList();
        // 方法集合
        methodList();
        // 屬性集合
        attributeList();
    }

    private void attributeList() throws IOException {
        line();
        int attrLength = StreamUtils.read2(in);
        System.out.println("共有"+attrLength+"個屬性");
        for (int i=0;i<attrLength;i++) {
            line();
            attribute();
        }
    }
    private void attribute() throws IOException {
        int nameIndex = StreamUtils.read2(in); 
        int length = StreamUtils.read4(in); 
        byte[] info = StreamUtils.read(in, length);
        System.out.println("nameIndex:"+nameIndex);
        System.out.println("length:"+length);
        System.out.println("info:"+info);
    }

    private void methodList() throws IOException {
        int length = StreamUtils.read2(in);
        System.out.println("共有"+length+"個方法");
        for (int i=0;i<length;i++)
            method();
    }

    private void method() throws IOException {
        System.out.println("---------------------");
        int accessFlag = StreamUtils.read2(in);
        int nameIndex = StreamUtils.read2(in);
        int descriptorIndex = StreamUtils.read2(in);
        System.out.println("accessFlag:"+accessFlag);
        System.out.println("nameIndex:"+nameIndex);
        System.out.println("descriptorIndex:"+descriptorIndex);
        attributeList();
    }

    private void fieldList() throws IOException {
        line();
        int length = StreamUtils.read2(in);
        System.out.println("共有"+length+"個字段");
        for (int i=0;i<length;i++) {
            System.out.println("-----------------------------");
            int accessFlag = StreamUtils.read2(in);
            int nameIndex = StreamUtils.read2(in);
            int descriptorIndex = StreamUtils.read2(in);
            System.out.println("accessFlag:"+accessFlag);
            System.out.println("nameIndex:"+nameIndex);
            System.out.println("descriptorIndex:"+descriptorIndex);
            attributeList();
        }
    }



    private void inheritence() throws IOException {
        line();
        int thisClassRef = StreamUtils.read2(in);
        int superClassRef = StreamUtils.read2(in);
        System.out.println("thisClassRef:"+thisClassRef);
        System.out.println("superClassRef:"+superClassRef);
        int interfaceLen = StreamUtils.read2(in);
        System.out.println("接口數量:"+interfaceLen);
        for (int i=0;i<interfaceLen;i++) {
            int interfaceRef = StreamUtils.read2(in);
            System.out.println("interfaceRef:"+interfaceRef);
        }
    }

    private void accessFlag() throws IOException {
        line();
        int accessFlag = StreamUtils.read2(in);
        System.out.println("accessFlag:0x"+Integer.toHexString(accessFlag)+"("+accessFlag+")");
    }

    private void constantPool() throws IOException {
        new ConstantPoolParser(in).constPool();
    }

    private void version() throws IOException {
        line();
        int minorVersion = StreamUtils.read2(in);
        int majorVersion = StreamUtils.read2(in);
        System.out.println("版本:"+majorVersion+"."+minorVersion);
    }

    private void magicNumber() throws IOException {
        line();
        int magic = StreamUtils.read4(in);
        System.out.println("魔數:"+Integer.toHexString(magic).toUpperCase());
    }

    private void line() {
        System.out.println("----------------------");
    }
}

ConstPoolParser負責常量池的解析(因為常量池表較多,且數據量也較大,因此單獨拉出來解析)

package com.lixin;

import java.io.IOException;
import java.io.InputStream;

public class ConstPoolParser {

    public static final int Utf8_info = 1;
    public static final int Integer_info = 3;
    public static final int Float_info = 4;
    public static final int Long_info = 5;
    public static final int Double_info = 6;
    public static final int Class_info = 7;
    public static final int String_info = 8;
    public static final int Fieldref_info = 9;
    public static final int Methodref_info = 10;
    public static final int InterfaceMethodref_info = 11;
    public static final int NameAndType_info = 12;
    public static final int MethodHandle_info = 15;
    public static final int MethodType_info = 16;
    public static final int InvokeDynamic_info = 18;

    private InputStream in;

    public ConstPoolParser(InputStream in) {
        this.in = in;
    }

    public void constPool() throws IOException {
        line();
        int length = StreamUtils.read2(in);
        System.out.println("共有"+length+"個常量");
        boolean doubleBytes = false;
        for (int i = 1; i < length; i++) {
            if (doubleBytes) {
                doubleBytes = false;
                continue;
            }
            line();
            System.out.println("常量索引:"+i);
            int flag = StreamUtils.read1(in);
//          System.out.println("標志:"+flag);

            switch (flag) {
            case Utf8_info:
                utf8Info();
                continue;
            case Integer_info:
                integerInfo();
                continue;
            case Float_info:
                floatInfo();
                continue;
            case Long_info:
                doubleBytes = true;
                longInfo();
                continue;
            case Double_info:
                doubleBytes = true;
                doubleInfo();
                continue;
            case Class_info:
                classInfo();
                continue;
            case String_info:
                stringInfo();
                continue;
            case Fieldref_info:
                fieldrefInfo();
                continue;
            case Methodref_info:
                methodrefInfo();
                continue;
            case InterfaceMethodref_info:
                interfaceMethodrefInfo();
                continue;
            case NameAndType_info:
                nameAndTypeInfo();
                continue;
            case MethodHandle_info:
                methodHandleInfo();
                continue;
            case MethodType_info:
                methodTypeInfo();
                continue;
            case InvokeDynamic_info:
                invokeDynamicInfo();
                continue;
            default:
                System.err.println(flag);
                throw new RuntimeException("unknown");
            }
        }
    }

    private void line() {
        System.out.println("----------------------");
    }

    private void utf8Info() throws IOException {
        int length = StreamUtils.read2(in);
        byte[] buf = StreamUtils.read(in, length);
        String s = new String(buf,0,buf.length);
        System.out.println("utf8Info表:");
        System.out.println("值:"+s);
    }

    private void integerInfo() throws IOException {
        System.out.println("integerInfo表:");
        int value = StreamUtils.read4(in);
        System.out.println("值:"+value);
    }

    private void floatInfo() throws IOException {
        System.out.println("floatInfo表:");
        int value = StreamUtils.read4(in);
        float f = Float.intBitsToFloat(value);
        System.out.println("值:"+f);
    }

    private void longInfo() throws IOException {
        System.out.println("longInfo表:");
        long value = StreamUtils.read8(in);
        System.out.println("值:"+value);
    }

    private void doubleInfo() throws IOException {
        System.out.println("doubleInfo表:");
        long value = StreamUtils.read8(in);
        double d = Double.longBitsToDouble(value);
        System.out.println("值:"+d);


    }

    private void classInfo() throws IOException {
        System.out.println("classInfo表:");
        int index = StreamUtils.read2(in);
        System.out.println("index:" + index);
    }

    private void stringInfo() throws IOException {
        System.out.println("stringInfo表:");
        int index = StreamUtils.read2(in);
        System.out.println("index:" + index);
    }

    private void fieldrefInfo() throws IOException {
        int classIndex = StreamUtils.read2(in);
        int nameAndTypeIndex = StreamUtils.read2(in);
        System.out.println("fieldrefInfo表:");
        System.out.println("classIndex:" + classIndex);
        System.out.println("nameAndTypeIndex:" + nameAndTypeIndex);
    }

    private void methodrefInfo() throws IOException {
        int classIndex = StreamUtils.read2(in);
        int nameAndTypeIndex = StreamUtils.read2(in);
        System.out.println("methodrefInfo表:");
        System.out.println("classIndex:" + classIndex);
        System.out.println("nameAndTypeIndex:" + nameAndTypeIndex);
    }

    private void interfaceMethodrefInfo() throws IOException {
        int classIndex = StreamUtils.read2(in);
        int nameAndTypeIndex = StreamUtils.read2(in);
        System.out.println("interfaceMethodrefInfo表:");
        System.out.println("classIndex:" + classIndex);
        System.out.println("nameAndTypeIndex:" + nameAndTypeIndex);
    }

    private void nameAndTypeInfo() throws IOException {
        int nameIndex = StreamUtils.read2(in);
        int typeIndex = StreamUtils.read2(in);
        System.out.println("nameAndTypeInfo表:");
        System.out.println("nameIndex:" + nameIndex);
        System.out.println("typeIndex:" + typeIndex);
    }

    private void methodHandleInfo() throws IOException {
        int referenceKind = StreamUtils.read1(in);
        int referenceIndex = StreamUtils.read2(in);
        System.out.println("methodHandleInfo表:");
        System.out.println("referenceKind:"+referenceKind);
        System.out.println("referenceIndex:"+referenceIndex);
    }

    private void methodTypeInfo() throws IOException {
        System.out.println("methodTypeInfo表:");
        int descriptorIndex = StreamUtils.read2(in);
        System.out.println("descriptorIndex:"+descriptorIndex);
    }

    private void invokeDynamicInfo() throws IOException {
        int bootstrapMethodAttrIndex = StreamUtils.read2(in);
        int nameAndTypeIndex = StreamUtils.read2(in);
        System.out.println("bootstrapMethodAttrIndex:"+bootstrapMethodAttrIndex);
        System.out.println("nameAndTypeIndex:"+nameAndTypeIndex);
    }
}

StreamUtils負責從輸入字節流中讀取數據

package com.lixin;

import java.io.IOException;
import java.io.InputStream;

public class StreamUtils {

    public static int read1(InputStream in) throws IOException {
        return in.read() & 0xff;
    }

    public static int read2(InputStream in) throws IOException{
        return (read1(in) << 8) | read1(in);
    }

    public static int read4(InputStream in) throws IOException {
        return (read2(in) <<16) | read2(in);
    }

    public static long read8(InputStream in) throws IOException {
        long high = read4(in) & 0xffffffffl;
        long low  = read4(in) & 0xffffffffl;
        return (high << 32) | (low);
    }

    public static byte[] read(InputStream in,int length) throws IOException {
        byte[] buf = new byte[length];
        in.read(buf, 0, length);
        return buf;
    }
}

TestClass為待解析的目標類,讀者可以任意改寫此類來多做實驗

package com.lixin;

public class TestClass {

    private int a = 5;
    protected char c = 'c';
    double x = 1.1;
    long y = 111;

    public void show() {

    }
}

測試方法入口:

package com.lixin;

import java.io.InputStream;

/**
 * 程序入口
 * @author lixin
 *
 */
public class App {

    public static void main(String[] args) throws Exception {
        InputStream in = Class.class.getResourceAsStream("/com/lixin/TestClass.class");
        ClassParser parser = new ClassParser(in);
        parser.parse();
    }


}

最后,我們可以使用jdk中的javap進行字節碼反編譯,來對比我們的讀取與反編譯結果差別,用于查錯。

javap -v TestClass.class >./out.txt

 本文由用戶 MatLez 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!