本文是以The Java® Virtual Machine Specification Java SE 8 Edition 文档中的内容为依据编写,算得上是一篇学习日记,如有错误,恳请指正。
public class TestC{
private int cc = 13;
public void printCC(){
System.out.println(cc);
}
}
下面的图是将上面的ava文件编译后的class文件的16进制内容,(使用的工具是Hex Editor)
记这个图为
图-0
下面的内容是使用 javap -verbose TestC命令生成的辅助内容,摘取了和class文件相关的内容贴出,记为 表-1(虽然是截图~)
图-0和表-0对后面的class文件格式解读比较重要,这里先贴出,后面会使用到这里的案例文件来辅助理解
下面就正式的进入Class文件的格式说明了
表-1.1
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
简要说明;u1表示表示1个字节,u2表示2个字节,u4表示4个字节;class文件的内容排列就如同上表所示
从表上看,class文件的前4个字节是一个magic,其实是一个标记,标记这个文件是一个class文件,这是class文件的安全验证之一(也就是说可以吧.class文件的后缀修改~~,只要不改这个标记就可以被虚拟机识别),这个magic有一个固定值 CAFEBABE(查看图-0);使用这个值据说是和某个coffee品牌有关~
表-1.1后面的四个字节记录的是class文件的版本号,这个class文件的版本号是0x00000034;minor_version是0,major_version是52,如果虚拟机的版本低于52,那么虚拟机会拒绝执行
表-1.1版本信息后面的两个字节表示常量表常量的数量。图-0中的值为0x001E;也就是说TestC.class文件的常量表中有30个常量,但是看表-0,Constant pool的数量却只有29个,这是为什么呢?这是设计者考虑到有这样一类值,不引用任何一个常量池项目,所以把0位置空出来,让这类值指向这个位置,所以常量池的技术是从1开始的!有别于数组等计数方式!!!
常量池之后就是类的结构信息,类的AccessFlag(public private static final 等修饰),其父类,实现的接口等信息
之后就是field和method,以及sourcefile等属性信息
这里对其他的信息做个简单的概括,后面会详细分析
常量表格式,通用的格式如下
All constant_pool table entries have the following general format:
cp_info {
u1 tag;
u1 info[];
}
一个u1长度的tag表示这个常量的类型;
info的格式依据不同的常量类型而有所不同,但都可以用一个info数组来表示。
下表(表-1.2)展示tag值及其对应的常量类型
表-1.2
Constant Type | value |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
我们先看Method这种常量类型的info格式
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
这是一个方法的info
tag 的值为10,
class_index是常量表中的位置,是一个class类型的值,表示这个方法所属于的类;
name_and_type_index,是方法的声明,包括方法名,方法参数和返回值。
看案例里的常量表里的第一个常量就是一个方法info;
先从图-0里看,其方法的字节为 0x0A00060010
tag = 0x0A; 10
class_index = 0x0006; // 值6,即指向了常量表的6号常量
name_and_type_index = 0x0010;// 值 16,指向常量表16号常量
我们看(表-0)里常量表的6 和16,发现6是一个class 其值指向 23(23是一个utf-8,值为java/lang/Object),说明类是java/lang/Object,
常量表中(从表-0看)的16号常量是NameAndType类型的,其值指向常量表的9和10
9和10是utf-8常量,值分别为 < init >和 ()V
从表-0里看后面的注释就很容易知道,这个方法具体是哪个方法
这第一个常量是一个方法,是类java/lang/Object.的,方法名”< init >”:签名()V
后面的其他常量格式我就不一一分析了,这里只是贴出其格式及其说明()
class属性常量
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
tag的值为7,name_index的值是这个class所在常量表的位置
字段属性常量 tag = 0x09
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
接口方法属性常量 tag = 0x0B
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
The class_index item of a CONSTANT_Methodref_info structure must be a class type, not an interface type.
The class_index item of a CONSTANT_InterfaceMethodref_info structure must be an interface type.
这里需要注意的是CONSTANT_InterfaceMethodref_info里的class_index必须是个interface类型~,CONSTANT_Methodref_info的class_index必须是class类型;
String类型常量 tag = 0x08;
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
tag = 0x03
CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}
tag = 0x04
CONSTANT_Float_info {
u1 tag;
u4 bytes;
}
tag = 0x05
CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
tag = 0x06
CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
注:CONSTANT_Long_info和CONSTANT_Double_info是有一些坑的,这里暂不填,有兴趣的可以去查看jvm8的技术文档
tag = 0x0C ;通常是对方法和常量的声明说明
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
tag = 0x01 ;这应该是常量表里最常见的了
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
这里也有个小坑,从’\u0001’ to ‘\u007F’ 可以用单个字节表示就好,但是在 ‘\u0080’ to ‘\u07FF’的字符就必须使用2个字节表示
x: | 1 | 1 | 0 | bits 10 - 6 |
y: | 1 | 0 | bits 5-0 |
最后的值为:
((x & 0x1f) << 6) + (y & 0x3f)
当然还有更变态的字符需要用3个字节表示的:’\u0800’ to ‘\uFFFF’
x: | 1 | 1 | 1 | 0 | bits 15 - 12 |
y: | 1 | 0 | bits 11-6 | ||
z: | 1 | 0 | bits 5-0 |
其值为
((x & 0xf) << 12) + ((y & 0x3f) << 6) + (z & 0x3f)
最后还有超变态的字符U+FFFF (so-called supplementary characters) ,需要使用6个字节来表示,这就不具体解释了,平常基本不会用到==!
tag = 0x0F;
CONSTANT_MethodHandle_info {
u1 tag;
u1 reference_kind;
u2 reference_index;
}
tag = 0x10;
CONSTANT_MethodHandle_info {
u1 tag;
u2 descriptor_index;
}
tag = 0x10;
CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
当前类的属性,常量池结束后,后面的两个字节表示访问标志:这个类是否是一个接口,public private,是否abstract,是否final等具体如下表(表-1.3)
表-1.3
标志名称 | 值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_FINAL | 0x0010 | Declared final; no subclasses allowed. |
ACC_SUPER | 0x0020 | Treat superclass methods specially when invoked by the invokespecial instruction. JDK1.0.2之后编译出来的这个值都为true |
ACC_INTERFACE | 0x0200 | Is an interface, not a class. |
ACC_ABSTRACT | 0x0400 | Declared abstract; must not be instantiated. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code.(这个类并不是用户代码产生) |
ACC_ANNOTATION | 0x2000 | Declared as an annotation type. |
ACC_ENUM | 0x4000 | Declared as an enum type. |
我们可以看到TestC的类的访问标志为 0x0021;说明是一个public 的类(我们反过来推导,依据这个类是个public的类,没有其他的访问标志,那么其访问标志依据上表得出是0021,在图0中搜索0021就知道常量表在哪里结束了,当然读者也可以一个个常量的查询过去直到常量表结束–!)
关于类的描述这里就不过多解释了,看下图(当前类是5,查看表-0,得知是com/pxr/jvm/TestC, 父类是 6,查看表-0,得知是java/lang/Object)
字段表的格式如下:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中access_flags的属性值如表-1.4
表-1.4
标志名称 | 值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private; usable only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static. |
ACC_FINAL | 0x0010 | Declared final; never directly assigned to after object construction (JLS §17.5). |
ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached. |
ACC_TRANSIENT | 0x0080 | Declared transient; not written or read by a persistent object manager. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code.(是否是编译器自动生成的) |
ACC_ENUM | 0x4000 | Declared as an element of an enum. |
name_index和descriptor_index则指向常量表中的某个值
这有个属性attribute_info,可见后面属性表的(1.6)分析
看当前案例中的字段属性值:
图-1.4
说明当前类中只有一个属性,其access_flag为0x0002(看表-1.4得知为 private),name为常量表中7,描述为常量表中的8;
查询表-0;可知这个属性是 private int cc; (I 就是int )
方法的格式如下
表-1.5
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中access_flags的属性值如表-1.5
表-1.5.1
标志名称 | 值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private; accessible only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static. |
ACC_FINAL | 0x0010 | Declared final; must not be overridden (§5.4.5). |
ACC_SYNCHRONIZED | 0x0020 | Declared synchronized; invocation is wrapped by a monitor use. |
ACC_BRIDGE | 0x0040 | A bridge method, generated by the compiler. |
ACC_VARARGS | 0x0080 | Declared with variable number of arguments.方法是否接受不定参数 |
ACC_NATIVE | 0x0100 | Declared native; implemented in a language other than Java. |
ACC_ABSTRACT | 0x0400 | Declared abstract; no implementation is provided. |
ACC_STRICT | 0x0800 | Declared strictfp; floating-point mode is FP- strict. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code.方法是否是由编译器产生的 |
看图-1.4,字段表后面就是方法表,第一个u2表示方法个数,0x0002说明有两个方法(很显然,TestC类中有一个默认的构造方法和printCC方法)
我们看第一个方法,截图中的灰色背景都是这个方法相关的,下面一个个解释
属性 | 值 | 说明 |
---|---|---|
access_flags | 0x0001 | 查看表-1.5.1,这是个public的方法 |
name_index | 0x0009 | 方法的名称在常量表中指向第9个常量,其值为< init >,说明是构造方法~~ |
descriptor_index | 0x000A | 方法的说明指向常量表中的第10个常量,方法的描述 ()V |
attributes_count | 0x0001 | 该方法一个属性,0x000B是属性的类型,这是个code属性(详见1.6属性表),其长度为 0x00000027,即code属性长度为39个字节 |
属性表的通用格式如下
表-1.6
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
我们先看code属性表
code属性表也算比较复杂,其格式如下
表-1.6.1
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
注意表-1.6中是一个属性表的统一表现形式。
表-1.6中的
u1 info[attribute_length];
相当于 code 属性表中的
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
下面我们就以案例代码分析一下code属性表
图-1.6
按照表1.6.1的规则 构造方法中max_stack为2,max_locals为1,code_length为 0x0000000B = 10 个字节,code 代码为 2AB700012A100DB50002B1;后面的2个字节 0000 说明code属性表中没有异常表, 0001说明code属性表中还有一个属性,该属性的name_index 为000C(查看表0得知为LineNumberTable属性,该属性长度为0x0000000A,属性为 00020000 00030004 0005)
所有属性里以code属性最为复杂,所以这里就只分析这个code属性,其他属性把格式列出,以供参考
常量属性
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}
InnerClasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
{ u2 inner_class_info_index;
u2 outer_class_info_index;
u2 inner_name_index;
u2 inner_class_access_flags;
} classes[number_of_classes];
}
inner_class_access_flags的对应值表如下
属性 | 值 | 说明 |
---|---|---|
ACC_PUBLIC | 0x0001 | Marked or implicitly public in source. |
ACC_PRIVATE | 0x0002 | Marked private in source. |
ACC_PROTECTED | 0x0004 | Marked protected in source. |
ACC_STATIC | 0x0008 | Marked or implicitly static in source. |
ACC_FINAL | 0x0010 | Marked final in source. |
ACC_INTERFACE | 0x0200 | Was an interface in source. |
ACC_ABSTRACT | 0x0400 | Marked or implicitly abstract in source. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
ACC_ANNOTATION | 0x2000 | Declared as an annotation type. |
ACC_ENUM | 0x4000 | Declared as an enum type. |
EnclosingMethod_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 class_index;
u2 method_index;
}
Synthetic_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
Signature_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 signature_index;
}
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
SourceDebugExtension_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 debug_extension[attribute_length];
}
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
} local_variable_table[local_variable_table_length];
}
LocalVariableTypeTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_type_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 signature_index;
u2 index;
} local_variable_type_table[local_variable_type_table_length];
}
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
RuntimeVisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
annotation的属性列表
annotation {
u2 type_index;
u2 num_element_value_pairs;
{ u2 element_name_index;
element_value value;
} element_value_pairs[num_element_value_pairs];
}
衍生的 element_value属性格式
element_value {
u1 tag;
union {
u2 const_value_index;
{ u2 type_name_index;
u2 const_name_index;
} enum_const_value;
u2 class_info_index;
annotation annotation_value;
{ u2 num_values;
element_value values[num_values];
} array_value;
} value;
}
RuntimeInvisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
RuntimeVisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}
RuntimeInvisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}
RuntimeVisibleTypeAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
type_annotation annotations[num_annotations];
}
RuntimeInvisibleTypeAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
type_annotation annotations[num_annotations];
}
BootstrapMethods_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_bootstrap_methods;
{ u2 bootstrap_method_ref;
u2 num_bootstrap_arguments;
u2 bootstrap_arguments[num_bootstrap_arguments];
} bootstrap_methods[num_bootstrap_methods];
}
MethodParameters_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 parameters_count;
{ u2 name_index;
u2 access_flags;
} parameters[parameters_count];
}
基本上这些属性很多都不会用到,在本文中,需要使用的就是code属性。
这一步的目的就是修改class文件,当调用TestC的printCC方法时,不仅仅打印出13这个数字,还要打印出另一个字符串。
修改常量表要注意两点:
1.修改常量表头数量值
2.新增的常量从常量表尾部添加,否则容易出错
需要注意的是我们加入的是一个String类型的常量,但是看1.2.5 CONSTANT_String_info结构的时候发现,
string_index
The value of the string_index item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_Utf8_info structure (§4.4.7) representing the sequence of Unicode code points to which the String object is to be initialized.
string_index 它是指向常量表的一个位置,实际上所有的字符串在常量表中都是以 utf-8存储的。
所以往常量表里添加字符串至少需要添加两个常量,一个String类型,一个Utf-8类型
String类型的值 08 001F;(因为当前的常量表里已经有了29个常量了,所以这个String类型的常量是第30个,而存储这个String的值的Utf-8类型常量的常量池编号就是31 也就是 0x001F)
utf-8类型的值 01 0002 6161 (01类型,0002 长度,6161 值)查询asc-2码表 a对应的16进制值是 0x61
修改后的TestC.class文件内容如下
运行javap -verbose TestC 查看class文件内容发现,aa字符串已经加入到常量表里了
1.修改code属性表中的 attribute_length和code_length
2.编写需要加入的代码( System.out.println("aa") )的字节码
我们按照code属性表那一节的方法先把printCC的核心code字节码挖出来,如下图所示
实际上printCC的执行字节码就是
B20003 2AB40002 B60004B1
查询字节码指令集
B2:获取指定类的静态域,并将其压入栈顶,
所以 B2 0003就是获取常量表中编号3的常量,查询图-2.2发现就是 java/lang/System.out:这个静态域
2A:将第一个引用类型本地变量推送到栈顶
B4 0002:获取0002的值,并将其压入栈顶,查询图-2.2 常量表0002的值就是 cc 字段
B6 0004:B6 调用实例方法(0004,println方法)
B1: 返回
所以B20003 2AB40002 B60004B1这段字节码就是 System.out.println(cc);
当然也可以通过javap -verbose TestC 查看详细:
编写System.out.println(cc)字节码:
实际上把 B20003 2AB40002 B60004 直接拿过来,把 B4 0002 压入栈顶的值替换成我们的String 0x001E 就行了,不是么?
B200032AB4001EB60004
我们把这段代码加入之后,注意还需要修改code 和 attribute的长度 ,这段字节码长度是 10(B1是返回,所以不需要重复加入),所以做如下修改
运行发现出错了。。
仔细检查发现,这里调用的是println(int )方法,所以还需要在常量池中吧这个方法给声明出来,这一步暂时留给读者完成
上节说到的一个错误,这节来修正了
1.需要加入字符串
2.需要加入方法
3.插入代码
这3个步骤我就不重复了,插入的代码需要查询虚拟机字节码指令表
插入代码这块需要有个坑
jvm8对每个成员方法都加入了localVariableTable这个属性,记录每个变量的作用域
通常会有个this指针,作用域是方法的长度,所以修改了方法长度,这个长度也要同步修改,具体修改参见下图(注:因昨日生成的文件未保存,所以这个新的class文件是使用jvm8编译的,内容可能不大一样)
图中,常量表的修改我就不标注了,因为比较简单。
大功告成!