本文通过分析一个简单java类文件的字节码,希望借此能快速了解java类文件格式
为了分析字节码,必须有一个整体的格式如下:
以上面的表作为分析的基础,开始行动!
一段简单的java代码
1
2
3
4
5
6
7
8
9
|
package org.kaka.clazz;
public class TestClass {
private int m;
public int inc(){
return m+1;
}
}
|
查看字节码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
00000000 ca fe ba be 00 00 00 32 00 13 0a 00 04 00 0f 09 |.......2........|
00000010 00 03 00 10 07 00 11 07 00 12 01 00 01 6d 01 00 |.............m..|
00000020 01 49 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |.I...<init>...()|
00000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e |V...Code...LineN|
00000040 75 6d 62 65 72 54 61 62 6c 65 01 00 03 69 6e 63 |umberTable...inc|
00000050 01 00 03 28 29 49 01 00 0a 53 6f 75 72 63 65 46 |...()I...SourceF|
00000060 69 6c 65 01 00 0e 54 65 73 74 43 6c 61 73 73 2e |ile...TestClass.|
00000070 6a 61 76 61 0c 00 07 00 08 0c 00 05 00 06 01 00 |java............|
00000080 18 6f 72 67 2f 6b 61 6b 61 2f 63 6c 61 7a 7a 2f |.org/kaka/clazz/|
00000090 54 65 73 74 43 6c 61 73 73 01 00 10 6a 61 76 61 |TestClass...java|
000000a0 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 00 21 00 03 |/lang/Object.!..|
000000b0 00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 02 |................|
000000c0 00 01 00 07 00 08 00 01 00 09 00 00 00 1d 00 01 |................|
000000d0 00 01 00 00 00 05 2a b7 00 01 b1 00 00 00 01 00 |......*.........|
000000e0 0a 00 00 00 06 00 01 00 00 00 03 00 01 00 0b 00 |................|
000000f0 0c 00 01 00 09 00 00 00 1f 00 02 00 01 00 00 00 |................|
00000100 07 2a b4 00 02 04 60 ac 00 00 00 01 00 0a 00 00 |.*....`.........|
00000110 00 06 00 01 00 00 00 07 00 01 00 0d 00 00 00 02 |................|
00000120 00 0e |..|
00000122
|
step 1) java magic number
首先的四个字节0x ca fe ba be 即class文件的magic number
step2) java version
接下来的四个字节0x 00 00 00 32 即class文件的版本号(可参看class文件的版本号列表)
step3) 常量池
接下来是描述常量表的长度0x 00 13,一共是(19-1)项,人肉分析如下
第1项
tag: 0x 0a ,CONSTANT_Methodref_info即方法声明
index: 0x 00 04,指向常量池中CONSTANT_Class_info,见常量池第4项
index: 0x 00 0f,指向常量池中CONSTANT_NameAndType_info见常量池第15项
第2项
tag 0x 09,CONSTANT_Fieldref_info即字段声明
index: 0x 00 03,指向常量池中CONSTANT_Class_info,见常量池第3项
index: 0x 00 10,指向常量池中CONSTANT_NameAndType_info见常量池第16项
第3项
tag:0x 07,CONSTANT_Class_info即类声明
index: 0x 00 11,指向常量池中的CONSTANT_Utf8_info,第17项
第4项
tag:0x 07,CONSTANT_Class_info即类声明
index: 0x 00 12,指向常量池中的CONSTANT_Utf8_info,第18项
第5项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 01 ,长度为1个字节
bytes: 0x 6d, 内容为"m"
第6项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 01 ,长度为1个字节
bytes: 0x 49, 内容为"I"
第7项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 06 ,长度为6个字节
bytes: 0x 3c 69 6e 69 74 3e, 内容为"<init>"
第8项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 03 ,长度为3个字节
bytes: 0x 28 29 56 内容为"()V"
第9项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 04 ,长度为4个字节
bytes: 0x 43 6f 64 65 内容为"Code"
第10项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 0f ,长度为15个字节
bytes: 0x 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65内容为"LineNumberTable"
第11项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 03,长度为3个字节
bytes: 0x 69 6e 63 内容为"inc"
第12项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 03,长度为3个字节
bytes: 0x 28 29 49 内容为"()I"
第13项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 0a,长度为10个字节
bytes: 0x 53 6f 75 72 63 65 46 69 6c 65内容为"SourceFile"
第14项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 04,长度为14个字节
bytes: 0x 54 65 73 74 43 6c 61 73 73 2e 6a 61 76 61内容为"TestClass.java"
第15项
tag:0x 0c,CONSTANT_NameAndType_info即字段或者方法说明
index:0x 00 07 ,字段或者方法常量索引,见常量池第7项
index:0x 00 08,字段或者方法常量索引,见常量池第8项
第16项
tag:0x 0c,CONSTANT_NameAndType_info即字段或者方法说明
index:0x 00 05 ,字段或者方法常量索引,见常量池第5项
index:0x 00 06,字段或者方法常量索引,见常量池第6项
第17项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 18,长度为24个字节
bytes: 0x 6f 72 67 2f 6b 61 6b 61 2f 63 6c 61 7a 7a 2f 54 65 73 74 43 6c 61 73 73内容为"org/kaka/clazz/TestClass"
第18项
tag:0x 01,CONSTANT_Utf8_info即字符串说明
length: 0x 00 10,长度为16个字节
bytes: 0x 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 内容为"java/lang/Object"
可以看出
CONSTANT_Methodref_info、CONSTANT_Fieldref_info都 依赖于CONSTANT_Class_info、CONSTANT_NameAndType_info
CONSTANT_Class_info依赖于CONSTANT_Utf8_info,后者用于声明类的名字
CONSTANT_NameAndType_info依赖于两个CONSTANT_Utf8_info
第一个CONSTANT_Utf8_info用于说明方法或者字段的名字
第一个CONSTANT_Utf8_info用于说明方法的入参以及返回类型或者字段的类型
列举常量池结构
可以对比下javap的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
Compiled from "TestClass.java"
public class org.kaka.clazz.TestClass extends java.lang.Object
SourceFile: "TestClass.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method #4.#15; // java/lang/Object."<init>":()V
const #2 = Field #3.#16; // org/kaka/clazz/TestClass.m:I
const #3 = class #17; // org/kaka/clazz/TestClass
const #4 = class #18; // java/lang/Object
const #5 = Asciz m;
const #6 = Asciz I;
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Asciz LineNumberTable;
const #11 = Asciz inc;
const #12 = Asciz ()I;
const #13 = Asciz SourceFile;
const #14 = Asciz TestClass.java;
const #15 = NameAndType #7:#8;// "<init>":()V
const #16 = NameAndType #5:#6;// m:I
const #17 = Asciz org/kaka/clazz/TestClass;
const #18 = Asciz java/lang/Object;
{
public org.kaka.clazz.TestClass();
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();
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 7: 0
}
|
两者是一致的
step4) 访问标志
接下来的两个字节0x 00 21是由下标中的0x 00 01 | 0x 0020计算出来的,表类是public 的,且是jdk1.2以后的编译器编译出来的
step5) 类信息
头两个字节0x 00 03,指向常量池中的第3项,即类名
接下来的两个字节 0x 00 04 指向常量池中的第4项,即父类名
接下来的两个字节 0x 00 00 表示类实现的接口的个数,本例子中为0,如果有的n个话,后面还会有2n个字节的常量池索引
step 6)字段信息
头两个字节 0x 00 01 字段的个数,本例中为1
接下来就是1个field_info结构
头两个字节 0x 00 02 表示accss_flags,即private
接下来0x 00 05表示字段名,指向常量池的第5项,即本例中"m"
接下来的0x 00 06 表示字段的描述信息,指向常量池的第6项,即本例中"I" ,表示是int类型
接下来的0x 00 00 表示字段的属性信息(用于扩展或者补充说明字段信息)的个数,本例中为0,如果有n个的话,后面还会有n个attribute_info结构
step 7) 方法信息
头两个字节为0x 00 02,表示方法的个数,本例中为2
接下来是两个method_info结构
第1个method_info结构
头两个字节0x 00 01 表示accss_flags,即public 方法
接下来0x 00 07表示方法名,指向常量池的第7项,即本例中"<init>"
接下来的0x 00 08 表示方法的描述信息,指向常量池的第8项,即本例中"()V" ,表示是一个没有参数,返回类型为void的方法
接下来的0x 00 01表示方法的属性信息的个数,本例中为1
接下是1个attribute_info结构
0x 00 09表示属性名,指向常量池的第9项,即Code,表示方法的代码
0x 00 00 00 1d 表示该属性的长度,即29个字节
0x 00 01 表示max_stack
0x 00 01 表示max_locals
0x 00 00 00 05 表示code_length, 即该方法的代码为编译后为5个字节
0x 2a b7 00 01 b1 即代码
0x 00 00 表示 没有异常信息
0x 00 01 表示有一个属性信息
接下是1个attribute_info结构
0x 00 0a 表示属性名,指向常量池的第10项,即LineNumberTable,表示方法的行号
0x 00 00 00 06 表示该属性的长度,即6个字节
0x 00 01 表示有1行
0x 00 00 表示字节码为第0行
0x 00 03 表示上面的字节码为第0行对应源码中的第3行
第2个method_info结构
头两个字节00 01 表示accss_flags,即public 方法
接下来0x 00 0b表示方法名,指向常量池的第11项,即本例中"inc"
接下来的0x 00 0c 表示方法的描述信息,指向常量池的第12项,即本例中"()I" ,表示是一个没有参数,返回类型为int的方法
接下来的0x 00 01表示方法的属性信息的个数,本例中为1
接下是1个attribute_info结构
0x 00 09表示属性名,指向常量池的第9项,即Code,表示方法的代码
0x 00 00 00 1f 表示该属性的长度,即31个字节
0x 00 02 表示max_stack
0x 00 01 表示max_locals
0x 00 00 00 07 表示code_length, 即该方法的代码为编译后为7个字节
0x 2a b4 00 02 04 60 ac 即代码
0x 00 00 表示 没有异常信息
0x 00 01 表示有一个属性信息
接下是1个attribute_info结构
0x 00 0a 表示属性名,指向常量池的第10项,即LineNumberTable,表示方法的行号
0x 00 00 00 06 表示该属性的长度,即6个字节
0x 00 01 表示有1行
0x 00 00 表示字节码为第0行
0x 00 07 表示上面的字节码为第0行对应源码中的第7行
step 8) 属性信息
头两个字节为0x 00 01,表示有一个属性
接下是1个attribute_info结构
0x 00 0d表示属性名,指向常量池的第13项,即SourceFile,表示类的源文件
0x 00 00 00 02,属性长度,即接下来的字节个数
0x 00 0e 表示源文件名,指向常量池的第14项,即"TestClass.java"
小结
如果想分析类文件,大部分情况下按字节分析,直接使用javap即可,但如果知道字节码也可以更好的了解javap的结果
几个种要的结构需要了解,本文就不一一列举具体信息(这个网上到处都是)
method_info
会包含attribute_info
filed_info
会包含attribute_info
attribute_info
共有好几种attribute,本例中就用到了Code,LineNumberTable,SourceFile,每种不同的属性结构都不一样
特别Code类型的attribute_info,编译过后的字令码就存放在里面
另外还有LineNumberTable以及本例中未提到的LocalVariableTable都是和调试息息相关的
有可能会嵌套attribute_info