参考书目:《Java虚拟机规范》/第四章:class文件解析

不同语言编写的源文件(e.g. java, groovy, kotlin, scala)编译后生成的.class字节码文件都能在JVM上运行。

不同语言能在JVM上运行的本质:

android 字节码hook_常量池

 

小端&大端

android 字节码hook_Code_02

 

android 字节码hook_android 字节码hook_03

大端模式:低地址存放数据的高位,高地址存放数据的低位。

小端模式:低地址存放数据的低位,高地址存放数据的高位。

字节码在JVM(计算机)内存中是小端存储的。在写JVM时需要用大端模式读取。证明:

void JavaVirtualMachine::parse_single_class(JavaClassContent *class_content) {
    printf(“%X \n”, *(int*)class_content->get_content_ptr());
    printf(“%X\n”, htonl(*(int*)class_content->get_content_ptr())); //htonl: 转换为大端模式
    …
}

输出:BEBAFECA  CAFEBABE

原因:小端;大端

 

 

描述符

根据 Class文件结构中常量池中11种数据类型的结构总表, CONSTANT_Methodref_Info、CONSTANT_InterfaceMethodref_Info和CONSTANT_NameAndType_Info中都含有CONSTANT_NameAndType_info。而CONSTANT_NameAndType_Info中又包含名称和描述符。

数据类型的描述符

android 字节码hook_android 字节码hook_04

void类型 V e.g. ()v 类型为v的方法

引用类型 e.g. String的描述符: Ljava/lang/String;

数组类型 e.g. byte数组的描述符: [B

         e.g. String数组的描述符: [Ljava/lang/String; 

方法的描述符

参数数据类型的描述符)返回值的描述符

如果有多个参数,描述符用逗号分隔

e.g.  javap –verbose输出中查看到main方法的描述符

public static void main(java.lang.String[]);

  descriptor: ([Ljava/lang/String;]V //main方法的描述符

e.g.  某个方法的描述符

([[Ljava/lang/String;. I, Ljava/AA/B;)Ljava/lang/String;

原形:String XXX(String[][], int, BB)

 

 

实验:手动解析字节码

Example java code:

public class Simple {
    static int a = 10;
    public static void main(String[] args) {}
}

使用hexdump -C查看字节码文件,再打开jclasslib对比确认手动解析结果是否正确。

class文件的组成部分

android 字节码hook_android 字节码hook_05

 uX:占X个字节。

!:字节长度不确定,需要动态计算。

 (u4): 值为ca fe ba be。每个Java class文件都以0xCAFEBABE开头。用于判断一个文件是否为合格class文件。如果不是则不执行。

 

次版本号 (u2) & 主版本号 (u2):JDK的版本对应的major、minor号。

android 字节码hook_Code_06

e.g.  00000000  ca fe ba be 00 00 00 34

次版本号为0,主版本号为16进制34转为10进制52,所以表示jdk版本为1.8。jclasslib查看genral information可证。

 

常量池大小 (u2):常量池个数。

*注意:常量池最小index不是0而是1。真实常量池个数=字节码文件中常量池个数-1。

e.g. 00 19

00 19->25,真实常量池个数为25-1=24。 Jclasslib中查看Constant Pool可证。

 

常量池:每个数据结构tag都占前1个字节。根据tag值对应数据类型的结构解析其中存储的信息。

CONSTANT Fieldref_info {
    u1 tag;
    u2 class_index; 
    u2 name_and_type_index;
}

CONSTANT_Methodref_info {
     u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}
…

常量池中的11种数据结构类型:

android 字节码hook_android 字节码hook_07

e.g.  解析常量池

常量池中第1个元素:

00000000  ca fe ba be 00 00 00 34  00 19 0a 00 04 00 15

tag:0a==10 –> CONSTANT_Methodref

class_index: 00 04 == 4 –> 04位置还没解析出来,这里只能放符号引用,后面才能转为直接引用

name_and_type:

Jclasslib中查看Constant Pool中第1个元素可证。

常量池中第2个元素:

00000000  ca fe ba be 00 00 00 34  00 19 0a 00 04 00 15 09

00000010  00 03 00 16

tag: 09 ==9 -> CONSTANT_Fieldref_info

class_index:

name_and_type:

Jclasslib中查看Constant Pool中第2个元素可证。查看静态常量池也可证明符号引用了第3个元素,因为#2 field的值为#3。

常量池中第3个元素:

00000010  00 03 00 16 07 00 17 07 00 18 01 00 01 6101 00

tag: 07 ==7 -> CONSTANT_Class_Info

name_index:

常量池中第4个元素:

00000010  00 03 00 16 07 00 17 07 00 18

tag: 07 ==7 -> CONSTANT_Class_Info

常量池中第5个元素:

00000010  00 03 00 16 07 00 17 07 00 18 01 00 01 61

tag: 01 ==1 -> CONSTANT_Utf8_info,说明是String类型

length: 00 01==1 –> 长度为1,所以byte只往后取1位

bytes:

 

access flags (u2): 

android 字节码hook_Code_08

通过或运算解析access flag值的含义.e.g. 0x11表示public final

e.g.  解析access flag

00000120  61 6e 67 2f 4f 62 6a 65  63 74 00 21

0x0021 -> public

 

this_class (u2): 当前类

e.g.  解析this class

00000120  61 6e 67 2f 4f 62 6a 65  63 74 00 21 00 03

查看静态常量池,当前类是常量池中第3个元素

 

super_class (u2):

e.g.  解析super class

00000120  61 6e 67 2f 4f 62 6a 65  63 74 00 21 00 03 00 04

查看静态常量池,super class是常量池中第4个元素

android 字节码hook_android 字节码hook_09

 

 

 

interfaces_count (u2): 接口数量

e.g.  解析Interfaces_count

00000130  00 00

接口数量是0。

*一个类最多可以实现多少个接口?

- 65535。//因为接口数量占2个字节=2*8=16位,二进制16位数最大为1111 1111 1111 1111=十进制2^16-1。

 

interfaces[] (!): 如果接口数量是0,则该区域在字节码文件中不会出现。

 

fields_count[] (u2): fields(类的属性)的数量

e.g.  解析fields数量

00000130  00 00 00 01

 

fields_info (!): field在常量池中的存储方式。

field_info {
    u2 access_flags;
    u2 name_index; //指向常量池
    u2 descriptor_index;
    u2 attributes_count; //属性数量,
    attribute_info attributes[attributes_count]; //属性内容
}

attributes指的是field的属性(e.g. 加了final就会有ConstantValue属性)。如果attributes_count为0,attributes区域在字节码文件中就不会出现。

e.g.  解析一个field

先前fields_count的解析结果说明该类中只有一个field。

00000130  00 00 00 01 00 08 00 05  00 06 00 00

第1个属性:

access_flags:

//jclasslib查看对应field可证0008为static:

android 字节码hook_描述符_10


name_index:

descriptor_index:

attribute_count:

attributes:

 

methods_count (u2):方法数量

e.g.解析方法数量

00000130  00 00 00 01 00 08 00 05  00 06 00 00 00 03

-> 有3个方法:静态方法clint、默认构造方法init、main。

 

methods_info (!):方法

method_info {
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count; //属性、Code、Exception
    attribute_info attributes[attributes_count]; 
}

如果attributes_count为0,attributes区域在字节码文件中就不会出现。

Code_attribute {
    u2 attribute_name_index; //即为method_info中解析出的attributes值
    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]; //Code属性的属性
}

exceptions分别在method_info和Code_attribute中都有记录,可验证得知throws异常和try catch异常在字节码中有不同的表示。

Code_attribute中有max_stack和max_locals,说明操作数栈和局部变量表的大小在编译时就已经确定。

Jclasslib打开/Methods/方法名/Code,在specific info查看指令内容。

Code的属性包括LineNumberTable(代码行号,存储Java代码所在行数)和LocalVariableTable(局部变量表,存储参数和局部变量),Jclasslib打开/Methods/方法名/Code可查。

e.g.解析第1个方法

00000130  00 00 00 01 00 08 00 05  00 06 00 00 00 03 00 01

00000140  00 07 00 08 00 01 00 09

access_flag:

name_index:

descriptor_index:

attr_count: 00 01

attributes:

name_index==7 -> 常量池中第7个元素为CONSTANT_Utf8_info, String为<init>,说明是构造方法。

Attributes==9 -> 第1个属性为Code,按照Code_attribute结构继续解析:

e.g. 解析init方法的code属性

00000140  00 07 00 08 00 01 00 09  00 00 00 2f 00 01 00 01

00000150  00 00 00 05 2a b7 00 01  b1 00 00 00 02

attribute_name_index:
attribute_length:

max_stack: 00 01 -- 操作数栈大小

max_locals: 00 01  -- 局部表大小

code_length:

code: 2a b7 00 01 b1  -- 方法体(字节码指令)

exception_table_length:

exception_table:

attributes_count:

方法体:

  0x2a == 42 -> aload_0

  …

  0xb1 == 177 -> return

e.g. Jclasslib->Methods/<init>/Code点开方法体中的指令科与字节码对应

android 字节码hook_描述符_11

android 字节码hook_Code_12

根据attributes_count确定code有2个attributes后,继续解析code的attributes:

00000150  00 00 00 05 2a b7 00 01  b1 00 00 00 02 00 0a 00

00000160  00 00 06 00 01 00 00 00  07

Code的第1个属性:

attr_name_index:

Code第1个属性的attr_name_index==10, jclasslib查看常量池第10个元素为CONSTANT_Utf8_info, 其String值为LineNumberTable。所以第1个属性是代码行号表。

确定当前code属性为LineNumberTable后,根据LineNumberTable的结构继续解析:

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];
}

attr_name_index:

attribute_length:

line_number_table_length:

start_pc:

line_number: 00 07 ]

Code的第2个属性:

00000160  00 00 06 00 01 00 00 00  07 00 0b 00 00 00 0c 00

00000170  01 00 00 00 05 00 0c 00  0d 00 00

attr_name_index:

Code第2个属性的attr_name_index==11, jclasslib查看常量池第11个元素为CONSTANT_Utf8_info, 其String值为LocalVariableTable。所以第2个属性是局部变量表。

确定当前code属性为LocalVariableTable后,根据LocalVariableTable的结构继续解析:

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];
}

attr_name_index:

attribute_length:

local_variable_table_length: 00 01

[ start_pc: 00 00

   length: 00 05

name_index:

descriptor_index:

index: 00 00 ]

 

attributes_count (u2)

 

attributes[] (!): 

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}