随笔-8  评论-67  文章-0  trackbacks-0
  从上一篇博客(Java Class文件解析)的分析可以看出,Class文件中的各项是按照一定的包含关系和次序关系存储的,因此Class文件可以从头到尾地被解析为各个项。下面请看一个解析实例:

这里我们以非常著名的HelloWorld为实例来分析Java Class文件。HelloWorld的源代码如下:

package bytecodeResearch;

public class HelloWorld {

    
//定义两个静态变量
    private static String str_1 = "Hello";
    
private static String str_2 = "World";
    
    
/**
     * 静态方法
     * 
@param str
     
*/

    
private static void Hello(String str)
    
{
        System.out.println(str);
    }
    
    
/**
     * 静态方法
     * 
@param str
     
*/

    
private static void World(String str)
    
{
        System.out.println(str);
    }

    
    
/**
     * 程序入口方法
     * 
@param args
     
*/

    
public static void main(String[] args)
    
{
        Hello(str_1);
        World(str_2);
    }

}

    编译单元HelloWorld.java文件经过编译器编译之后,将得到一个HelloWorld.class文件。需要说明的是,经过不同的编译器编译之后得到的HelloWorld.class文件可能不一样,本人选用的开发工具是Eclipse3.3-europa版本,Eclipse SDK自带的JDT工具内置了增量式Java编译器,这个编译器与javac完全兼容。本人以下的分析都是基于Eclipse自带的编译器编译得到的Class文件,如果有人用了Jikes编译器、GNU的编译器或者其它版本的javac编译器的话,得到的Class文件跟我的可能不完全一样的话,但是Class文件的格式肯定是一样的,因此分析的原理也都是一样的,即都是基于上一篇博客(Java Class文件解析)给出的ClassFile结构图示。

    经过Eclipse3.3编译得到的Class文件内容如下

  

 

HelloWorld.class文件的内容

00000000h: CA FE BA BE 00 00 00 31  00 31 07 00 02  01 00 1B (该类共有48个常量池表项)

00000010h: 62 79 74 65 63 6F 64 65 52 65 73 65 61 72 63 68 

00000020h: 2F 48 65 6C 6C 6F 57 6F 72 6C 64  07 00 04 01 00 

00000030h: 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 

00000040h: 74 01 00 05 73 74 72 5F 31 01 00 12 4C 6A 61 76 

00000050h: 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B  01 00  

00000060h: 05 73 74 72 5F 32 01 00 08 3C 63 6C 69 6E 69 74 

00000070h: 3E  01 00 03 28 29 56  01 00 04 43 6F 64 65  08 00   (10号常量池表项)

00000080h: 0C  01 00 05 48 65 6C 6C 6F  09 00 01 00 0E 0C 00 

00000090h: 05 00 06 08 00 10  01 00 05 57 6F 72 6C 64 09 00  

000000a0h: 01 00 12 0C 00 07 00 06 01 00 0F 4C 69 6E 65 4E 

000000b0h: 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63  

000000c0h: 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01   (20号常量池表项)

000000d0h: 00 06 3C 69 6E 69 74  3E  0A 00 03 00 17  0C 00 15 

000000e0h: 00 09 01 00 04 74 68 69 73 01 00 1D 4C 62 79 74 

000000f0h: 65 63 6F 64 65 52 65 73 65 61 72 63 68 2F 48 65 

00000100h: 6C 6C 6F 57 6F 72 6C 64 3B  01 00 15 28 4C 6A 61  

00000110h: 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29  

00000120h: 56  09 00 1C 00 1E 07 00 1D 01 00 10 6A 61 76 61 

00000130h: 2F 6C 61 6E 67 2F 53 79 73 74 65 6D  0C 00 1F 00   (30号常量池表项)

00000140h: 20 01 00 03 6F 75 74 01 00 15 4C 6A 61 76 61 2F  

00000150h: 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 0A 

00000160h: 00 22 00 24 07 00 23 01 00 13 6A 61 76 61 2F 69 

00000170h: 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D  0C 00 25  

00000180h: 00 1A 01 00 07 70 72 69 6E 74 6C 6E 01 00 03 73 

00000190h: 74 72  01 00 04 6D 61 69 6E 01 00 16  28 5B 4C 6A  

000001a0h: 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B   (40号常量池表项)

000001b0h: 29 56  0A 00 01 00 2A 0C 00 0C 00 1A 0A 00 01 00 

000001c0h: 2C  0C 00 10 00 1A 01 00 04 61 72 67 73 01 00 13 

000001d0h: 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 

000001e0h: 6E 67 3B 01 00 0A 53 6F 75 72 63 65 46 69 6C 65  

000001f0h: 01 00 0F 48 65 6C 6C 6F 57 6F 72 6C 64 2E 6A 61  

00000200h: 76 61  00 21 00 01 00 03 00 00  00 02 00 0A 00 05    (该类有0x0002field)

00000210h: 00 06  00 00  00 0A 00 07 00 06 00 00 00 05  00 08    (该类有0x0005method)

00000220h: 00 08 00 09 00 01  00 0A 00 00 00 33 00 01 00 00        (1method_info)

00000230h: 00 00 00 0B 12 0B B3 00 0D 12 0F B3 00 11 B1 00 

00000240h: 00 00 02 00 13 00 00 00 0E 00 03 00 00 00 06 00 

00000250h: 05 00 07 00 0A 00 03 00 14 00 00 00 02 00 00 00        (2method_info)

00000260h: 01  00 15 00 09  00 01  00 0A  00 00 00 2F 00 01 00 

00000270h: 01 00 00 00 05 2A B7 00 16 B1 00 00 00 02 00 13 

00000280h: 00 00 00 06 00 01 00 00 00 03 00 14 00 00 00 0C  

00000290h: 00 01 00 00 00 05 00 18 00 19 00 00  00 0A 00 0C    (3method_info)

000002a0h: 00 1A  00 01  00 0A 00 00 00 36  00 02 00 01 00 00 

000002b0h: 00 08 B2 00 1B 2A B6 00 21 B1 00 00 00 02 00 13 

000002c0h: 00 00 00 0A 00 02 00 00 00 0F 00 07 00 10 00 14  

000002d0h: 00 00 00 0C 00 01 00 00 00 08 00 26 00 06 00 00  

000002e0h: 00 0A 00 10 00 1A 00 01 00 0A 00 00 00 36 00 02     (4method_info)

000002f0h: 00 01 00 00 00 08 B2 00 1B 2A B6 00 21 B1 00 00  

00000300h: 00 02 00 13 00 00 00 0A 00 02 00 00 00 18 00 07 

00000310h: 00 19 00 14 00 00 00 0C 00 01 00 00 00 08 00 26  

00000320h: 00 06  00 00 00 09 00 27 00 28 00 01 00 0A 00 00    (5method_info)

00000330h: 00 3F  00 01 00 01 00 00 00 0D B2 00 0D B8 00 29 

00000340h: B2 00 11 B8 00 2B B1 00 00 00 02 00 13 00 00 00 

00000350h: 0E 00 03 00 00 00 21 00 06 00 22 00 0C 00 23 00 

00000360h: 14 00 00 00 0C 00 01 00 00 00 0D 00 2D 00 2E 00 

00000370h: 00 00 01 00 2F 00 00 00 02 00 30                              (该类有0x0001attribute)

 

 

     注:该Class的内容是经过解析的,即将.class文件的二进制字节流转换为16进制的数字字符流形式。由于UE查看Class文件时,用一个8位的16进制数来表示每一行起始字节对应于Class文件中的字节号,为了不造成视觉差异,本人写了一段程序来读写Class文件内容,其输出结果就是上面这段内容,每16个字节占一行,每个字节均拆解成216进制整数字符,每一行左端的8位的16进制整数就表示该行的起始字节对应于原Class文件中的字节号。这个程序贴在了我第一篇blog里--个读取Class文件的示例程序

    好了,闲言少叙!下面开始正式解析这个Class文件。

 

按照上一篇博客(Java Class文件解析)介绍的ClassFile结构图示以及对ClassFile结构的详细分析,可以将HelloWorld.class文件的各个项顺序地解析如下:

(1) 4个字节0xCAFEBABEmagic项的内容。

(2) 接下来的2个字节0x0000minor_version项的内容,即该Class文件的次版本号0

(3) 接下来的2个字节0x0031major_version项的内容,即该Class文件的主版本号为49。这个主版本号对应于J2SE5.0的编译器的编译结果。如果你用J2SE6.0的编译器来编译此HelloWorld.java程序的话,得到的主版本号应该是0x0032,即50

(4) 接下来的2个字节0x0031constant_pool_count项的内容,它表示接下来共有连续的49-1=48constant_pool表项。

(5) 紧接着constant_pool_count项后面的是常量池列表项。常量池列表项的长度是可变的,这是因为不同的常量池表项的格式是不一样的。

5.1)先来分析第一个常量池表项。上一篇博客(Java Class文件解析)中提到,每个常量池表项的具体格式是要根据其tag项(即该常量池表项的第一个字节)来决定。因此,constant_pool_count项后面的第一字节就是第一个常量池表项的tag项的内容,在这里为0x07cp_type表可知,tag值为0x07的对应CONSTANT_Class结构的常量池表项。再查阅CONSTANT_Class_info表,该表结构如下:

CONSTANT_Class_info {

            u1 tag;

            u2 name_index;

}

       根据《JVM Spec(2nded)中对此表的说明可知,1个字节的tag项就是刚才读取的值0x07,而后的2个字节的name_index项表示对一个CONSTANT_Utf8_info表的索引,该索引项包含了类或者接口的完全限定名称。对于HelloWorld.class这个类来说,第一个常量池表项就是一个CONSTANT_Class_info表的结构,其tag项的值是0x07,其name_index项的值是0x0002,即该常量池表项指向索引为0x0002的常量池表项。

5.2)再来分析第二个常量池表项。其tag项值为0x01,查阅cp_type表可知,该常量池表项是一个CONSTANT_ Utf8_info表的结构,然后我们查阅CONSTANT_ Utf8_info表,该表结构如下:

CONSTANT_Utf8_info {
         u1 tag;
         u2 length;
         u1 bytes[length];
 }

       根据《JVM Spec(2nded)中对此表的说明可知,1个字节的tag项的值就是0x012个字节的length项给出了后续的bytes数组的长度(字节数),在这里为0x001B,即其后续的27个字节均是该bytes数组的内容,它包含了按照变体UTF-8格式存储(不是标准的UTF-8格式啊,具体的差别请查阅那两个参考资料,都有说明)的字符串中的字符。按照此变体格式可以将这27个字节解析为“bytecodeResearch/HelloWorld”,这27个字符实际上就是该ClassFile的完全限定名称,这是为什么呢?因为第一个常量池表项是CONSTANT_Class_info结构的,其name_index项指向的常量池表项包含了类或者接口的完全限定名称,第二个常量池表项正是第一个常量池表项中name_index项所指向的常量池表项,因此“bytecodeResearch/HelloWorld”这27个字符就是该ClassFile的完全限定名称(注:是完全限定名称的内部形式,下同)

       5.3)再来分析第三个常量池表项。第二个常量池表项后的第一个字节为0x07,由cp_type表可知,tag值为0x07的对应CONSTANT_Class结构的常量池表项。类似于对第一个常量池表项的分析,0x07后面的两个字节为name_index项,其值为0x0004,即该常量池表项指向索引为0x0004的常量池表项,且该常量池是CONSTANT_Utf8_info表的结构

       5.4)下面来分析第四个常量池表项。该常量池表项的tag项值又是0x01,类似地,同第二个常量池表项的分析,该常量池表项也是一个CONSTANT_ Utf8_info表的结构,其length项值为0x0010,即其后续的bytes数组的长度为0x0010个字节。按照《JVM Spec(2nded)中关于变体UTF-8格式的定义,可以将这16个字节解析为“java/lang/Object”,这是Java类层次结构的根类Object类的完全限定名称。

       类似地,可以分析第5678910都是一个CONSTANT_ Utf8_info结构,分别表示“str_1”,“Ljava/lang/String”,“str_2”,“<clinit>”,“()V”,“Code

       5.5…….其他常量池表项的分析原理是类似的,这里就不赘述了。

       5.6)第四十八个常量池表项的解析。经过分析,第48个常量池起始于第000001f0h字节,终止于第00000202h字节。该常量池表项又是一个CONSTANT_Utf8_info表的结构,其15字节的bytes数组项的值可解析为“HelloWorld.java”。由后面的分析说明该常量池表项存储的是该类文件的SourceFile属性的信息。

       好了,该Class文件的常量池部分已经解析结束了!下面开始其它部分的解析:

       (6)   在常量池列表项后面的两个字节是该Java类型的access_flags,这里为0x0021。根据access_flags表可以查到,该值是0x00200x0001两者的和,即该类的修饰符为ACC_PUBLIC+ACC_SUPER,前者表示该类是public类型,后者表示采用invokespecial指令特殊处理对超类的调用。具体可以查阅两本参考资料中关于JVM指令集的描述J

       7)接下来的两个字节是this_class项,它是一个对常量池表项的索引,在这里值为0x0001,即它指向1号常量池表项,而1号常量池表项是一个CONSTANT_Class_info结构,它指向2号常量池表项,2号常量池表项的值为bytecodeResearch/HelloWorld,前面提到这是该Class文件的完全路径名称的内部形式,因此this_class即指bytecodeResearch/HelloWorld

       (8) 接下来的两个字节是super_class项,它是一个对常量池表项的索引,在这里值为0x0003, 即它指向3号常量池表项,查一下上面对3号常量池表项的分析,它指向4号常量池表项,而4号常量池表项包含的值为“java/lang/Object”,即super_class的实际值为“java/lang/Object”。说明我们分析的这个Class文件的超类是java.lang.Object

(9) 下面的两个字节是interfaces_count项,在这里的值为0x0000,这表示由该类直接实现或者由该接口所扩展的超接口的数量为0,因此该Class文件中的interfaces列表项也就不存在了。

(10)接下来的字节应该是field项的内容了。首先的两个字节是fields_count项,这里的值为0x0002,即该类声明了两个字段(变量),亦即该项之后的fields列表项的元素个数为2。由于fields列表项的类型为field_info,所以在fields_count项下面的字节是两个连续的field_info结构,下面来详细分析这两个具体的field_info结构;

10.1)第一个field_info,即第一个字段的相关信息。

10.1.1)首先的两个字节是第一个fieldaccess_flags项,在这里的值为0x000A,查阅field_access_flags表可知该access_flags项表示的是ACC_PRIVATE+ACC_STATIC,即该字段是由privatestatic修饰的。

10.1.2)接下来的两个字节是name_index项,在这里的值为0x0005,即该字段的简单名称由第5个常量池表项描述的,根据上一篇博客(Java Class文件解析)的分析可知,该常量池包含的信息为str_1,即该字段的名称为str_1

10.1.3)接下来的两个字节是descriptor_index项,在这里的值为0x0006,即该字段的描述符存储在6个常量池表项,根据上一篇博客(Java Class文件解析)的分析可知,这个字段的类型为“Ljava/lang/String”。在Class文件中,“L<classname>”表示一个类的实例,其中<classname>是这个内部形式的完全限定类名。

10.1.4)接下来的两个字节是attributes_count项,在这里的值为0x0000,即该字段没有附加的属性列表。因而也就不用讨论attributes[]项了。

10.2)第二个field_info,即第二个字段的相关信息。类似地,参照第一个字段信息的分析,我们很快就可以知道该字段的access_flags项为ACC_PRIVATE+ACC_STATIC,名字为str_2,类型描述符为“Ljava/lang/String”attributes_count的值为0x0000

(11)接下来的字节应该是method项的内容了。首先的两个字节是methods_count项,这里的值为0x0005,即该类声明了5个方法,亦即该项之后的methods列表项的元素个数为5。由于methods列表项的类型为method_info,所以在methods_count项下面的字节是5个连续的method_info结构,下面来详细分析这5个具体的method_info结构:

11.1)第1method_info结构,即第一个方法的相关信息,如方法名、描述符(即方法的返回值及参数类型)以及一些其它信息。根据method_info表分析接下来的字节码可以得到:

11.1.1access_flags项,值为0x0008,即给方法的访问修饰符为ACC_STATIC,它表示这是一个static方法。

11.1.2name_index项,值为0x0008,8号常量池表项存储的信息为<clinit>即该方法的名称为<clinit>。这是一个类与接口初始化方法,这个方法是由Java编译器编译源代码的时候产生的,Java编译器将该类的所有类变量初始化语句和所有类型的静态初始化器收集到一起,放到<clinit>方法中,该方法只能被JVM隐式地调用,专门用于把类型的静态变量设置为它们正确的初始值。

11.1.3descriptor_index项,值为0x0009,9号常量池表项存储的信息为()V,这表示该方法的没有参数,返回值为void

11.1.4attributes_count项,值为0x0001,即该方法有一个属性。查阅属性信息表的结构,如下所示:

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

由这个表,我们可以知道attributes_count项后面的是这个属性的attribute_name_index项,该项的值为0x000A,该属性的名字信息存储在第10号常量池表项里。查阅第10号常量池表项可知,该属性的名字为“Code”,然后我们查阅Code_attribute表,结构如下:

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

11.1.5Code_attribute项,该项包含了一个Java方法,或者实例初始化方法,或者类或接口初始化方法的JVM指令和辅助信息。每个JVM的实现都必须要识别Code属性,在每个method_info结构也必须确切地有一个Code属性。下面来具体分析这个属性;

11.1.5.1) attribute_name_index项,2个字节,该项的值为0x000A,查阅第10号常量池表型包含的信息后知该属性的名字为“Code”。

11.1.5.2attribute_length项,4个字节,值为0x00000033,这说明该属性的长度,出去初始的6个字节,还有0x33=51个字节。如果不愿意讨论接下去的51字节的话,可以直接跳过这51字节(00000226h-0000025eh字节),讨论下一个方法。

11.1.5.3max_stack项,2个字节,值为0x0001,这表示该方法执行中任何点操作数栈上字的最大个数为1

11.1.5.4max_locals项,2个字节,值为0x0000,这表示该方法使用的局部变量个数为0

11.1.5.5code_length项,4个字节,值为0x0000000B,这表示该方法的code数组中字节的总个数为11

11.1.5.6code[]项,由11.1.5.5)知,该方法的code数组共占11个字节。该code[]项给出了实现该方法的JVM代码的实际字节。例如第一个指令是0x12,这是ldc指令,这个指令表示将一个常量池表项压入栈,它需要一个操作数,而它后面的一个字节是0x0B,因此这条指令加上其操作数就表示将常量池中的第0x0B号表项压入栈。接下来的一个指令是0xB3,这是putstatic指令,这条指令表示设置类中静态变量的值。它需要两个操作数indexbyte1indexbyte2,这两个操作数均占一个字节,JVM执行putstatic执行时,会通过计算(indexbyte1<<8)|indexbyte2生成一个对常量池表项的索引,这里的参数为0x000x0D,运算结果是0x0D,因此这条指令的意思就是将操作数栈的当前栈顶元素赋值给0x0D号常量池表项所存储的字段(str_1),即完成对字段str_1的赋值。。同样,下面的五个字节的意思,就是将索引为0x0F的常量池表项压入操作数栈,并赋值给(0x00<<)|0x11=0x11号常量池表项中所存储的字段(str_2),即完成对字段str_2的赋值。该Code数组的最后一个字节是0xB1,这是一条不带操作数的指令return,它表示从方法中返回,返回值为void

11.1.5.7exception_table_length项,2个字节,值为0x0000,这表示该方法的异常处理器的个数为0。因此exception_table[ ]就没有必要讨论了。

11.1.5.8attributes_count项,2个字节,值为0x0002,这表示该方法Code属性具有两个属性。当前由Code属性定义和使用的两个属性是LineNumberTaleLocalVariableTable属性。

11.1.5.9attributes[ ]项,由于LineNumberTaleLocalVariableTable两个属性都包含了一些调试信息,但是两者都是可选属性,因此这里就不多讨论了。

11.2)第2method_info结构,即第2个方法的相关信息。第2个方法是实例初始化方法<init>,这段方法在Class文件中的字节编号为:0000025fh-0000029bh字节,感兴趣的朋友请继续分析下去,原理和第一个方法的分析是一样的。

11.3)第3method_info结构,即第3个方法的相关信息。第3个方法是该类的静态方法Hello,这段方法在Class文件中的字节编号为:0000029ch-000002dfh字节,感兴趣的朋友请继续分析下去,原理和第一个方法的分析是一样的。

11.4)第4method_info结构,即第4个方法的相关信息。第4个方法是该类的静态方法World,这段方法在Class文件中的字节编号为:000002e0h-00000323h字节,感兴趣的朋友请继续分析下去,原理和第一个方法的分析是一样的。

11.5)第5method_info结构,即第5个方法的相关信息。第5个方法是该类文件的入口main方法,这段方法在Class文件中的字节编号为:00000324h-00000370h字节,感兴趣的朋友请继续分析下去,原理和第一个方法的分析是一样的。

(12) attributes_count项,2个字节,该ClassFile的属性计数项,它的值为0x0001,表示在后续的attributes列表中的attributes_info表的总个数为1

(13) attributes[ ]项,该ClassFile的属性列表项,这是Class文件的最后一项了!由(12)知,该列表项只有一个表项。由attribute_info表结构

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

可知,attributes_count项后面的两个字节是attribute_name_index项,它的值为0x002F,它表示对常量池编号为0x002F的表项的一个索引。这个索引表项存储的信息为”SourceFile”,即该ClassFile属性的名称为SourceFile,该属性是一个可选的定长属性,对于给定的ClassFile结构的attributes列表中不能有多于一个的SourceFile属性;查阅SourceFile_attribute表可知,下面的4个字节为attribute_length项,其值为0x00000002,它表示在该项后面还有2个字节的信息。根据SourceFile_attribute表,最后的这两个字节是sourcefile_index项,该项的值是一个对CONSTANT_Utf8_info结构的常量池表项的索引,其信息表示的是该Class文件的源文件名称。在这里值为0x0030,根据上一篇博客(Java Class文件解析)的分析,第48号常量池表项存储的信息可解析为“HelloWorld.java”,这是该Class文件的源文件名称(不包括路径)

       好了,到此为止,该Class文件的实例已经全部解析完毕,大功告成:)


 

posted on 2008-02-03 13:03 独孤求败 阅读(4848) 评论(33)  编辑  收藏 所属分类: Java ByteCode

评论:
# re: 一个解析Java Class文件的实例 2008-02-03 13:55 | 小白鼠儿
很晕很深奥  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-03 14:14 | dennis
很好的文章,有兴趣研究这个的都会自己去找spec或者《inside jvm》
不过现在能沉下心来读你这篇文章的,我怕没几个  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-03 15:22 | 久城
很晕很迷糊......

看来我还需要看很多东西才能消化它.....  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-03 21:11 | 独孤求败
@dennis

呵呵...当然,我也是参考了这两本书,在上一篇blog里已经说了,我只是整理了一下,并且加进去一些自己的理解而已:)
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-03 21:12 | 独孤求败
@小白鼠儿

找那两本参考资料看看:)  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-03 21:12 | 独孤求败
@久城

找那两本参考资料看看:)  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-12 14:43 | loocky
值得研究一下看看,我相信不超过1%的人能都读完  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-14 14:18 | zhyiwww
我觉得,解析了class文件,然后能更深入的引导我们去理解那些东西,是值得我们去深入思考的问题。  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-15 02:14 | 独孤求败
呵呵...真正需要的人会看完的:)
@loocky
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-15 02:16 | 独孤求败
你说得很对,这只是入门级的技术blog
@zhyiwww
  回复  更多评论
  
# re: 一个解析Java Class文件的实例[未登录] 2008-02-15 10:00 | 屹砾
如果要做JVM或者研究JVM原理的话,做这个研究还是蛮有意义的。  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-15 10:11 | 独孤求败
赞同啊~@屹砾
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-02-17 12:36 | 83bbb
有点意思,  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-03-21 21:33 | 王跃峰
我就是仔细研究这个文章的人啊!
很好,很有用。
我最近也在解析class文件,就是不知道怎样获取类中的方法的参数名字。  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-03-25 17:06 | 王跃峰
不知道博主还在写吗?我研究了下,写了程序要获取到class中的method的参数名称,目前只能得到方法描述.
一般是这样的
create(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
我现在想知道参数的名字啊,常量池中有这个名字的
怎样通过method_info联系到哪个参数名称?
求老大赐教.mail:wang.yue.feng@163.com   回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-03-29 15:15 | 独孤求败
可以查阅Java System API
能解决的,Method,MethodDescriptor,ParameterDescriptor这三个类提供的方法应该就能解决你的问题:)
@王跃峰
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-03-30 12:30 | 王跃峰
谢谢大哥帮忙啊.

我试了下,好象也不行啊,ParameterDescriptor类里面根本没有表示参数名字的属性和方法.

另外,我也尝试用了两个分析class的工具jcfe和classediter,确实最后是按照classfile的文件结构解析的.但是在method_info中没有参数名称的迹象啊.参数类型这个是很明显的.

求教方法和它的参数是怎么对应的?

最后,考虑method_info中的code属性,这个里面不知能得到什么东西?  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-03-30 16:27 | 王跃峰
import java.beans.MethodDescriptor;
import java.beans.ParameterDescriptor;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
public class FileClassLoader extends ClassLoader {
public static final String drive = "d:/";
public static final String fileType = ".class";

public FileClassLoader() {
super();
}
public FileClassLoader(ClassLoader arg0) {
super(arg0);
}
public Class findClass(String name) {
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);
}
public byte[] loadClassData(String name) {
FileInputStream fis = null;
byte[] data = null;
try {
fis = new FileInputStream(new File(drive + name + fileType));

ByteArrayOutputStream baos = new ByteArrayOutputStream();

int ch = 0;
while ((ch = fis.read()) != -1) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
public static void main(String[] args) throws Exception {
FileClassLoader loader = new FileClassLoader();
Class objClass = loader.loadClass("HelloWorld", true);

Object obj = objClass.newInstance();

Method[] methods=objClass.getMethods();
for(Method m :methods)
{
System.out.println(m.getName());
MethodDescriptor md = new MethodDescriptor(m);
System.out.println(md.getParameterDescriptors());
//这里得到的参数描述都是null
//ParameterDescriptor[] PDS = md.getParameterDescriptors();
System.out.println("************************");
///
}
}
}

  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-03-30 19:26 | 王跃峰
上面代码用的反射肯定是不行了,目前就是看对应class的反编译结果,主要是不知道方法怎么和常量池里的参数名字联系起来.  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-04-03 00:22 | 独孤求败
我写了个程序验证了下,是不能获得方法的参数名字的...
后来,我查看了常量池,里面好像没有参数的名字啊,只有方法的参数类型描述,没有名字。不知道你是用什么工具看到的?我是看不到:)
我猜想,保存方法参数的名字也没有意义啊,只需保存参数的类型就可以了,因为调用一个方法时,只需验证其参数类型是否满足即可!
不值得,阁下想从class文中获得方法的参数名字做什么用?@王跃峰
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-04-03 18:16 | 王跃峰
谢谢老兄关注这个问题.
我想先反编译出原函数的样子,然后考虑反编译源代码.
常量池只要全部枚举,有参数名字的,就是顺序不一定是参数的顺序了,我考虑了很久,目前主要思路还是停留在考虑method_info的code属性的code里,我企图从这着字节码得到一些信息.
我看过jad反编译的代码,只有这个工具可以看到反编译函数的参数名字,我想在classfile里应该有个对应关系.

@独孤求败
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-04-03 18:22 | 王跃峰
关于枚举常量池,我这里有网上一位大哥开发的一个源码可以做到这一点.我也是企图在这个源代码上进一步研究.
@独孤求败
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-04-03 22:27 | 独孤求败
我用JAD试了一下,的确是你说的那样的,但是方法的形参名字是存在了该方法的局部变量表里了:)

现在,我知道怎么做了,方法的形参被看作局部变量了,所以你需要去查看该方法的局部变量表,如果该方法有形参的话,则局部变量表中的第一个元素就是第一个形参,第二个元素就是第二个形参,其他类推

你从局部变量表中可以查到这个局部变量的名字是存在哪个常量池表项里的,直接从常量池中获取即可!你没有必要枚举常量池的:)
祝顺利:)
@王跃峰
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-04-07 13:58 | 王跃峰
非常感谢!感觉这回应该很接近目的了.

class文件结构中的method_info里面没有局部变量表呀?

怎样得到它?@独孤求败
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-04-07 14:11 | 王跃峰
每个method_info表都包含了与方法相关的一些信息,包括方法名和描述符(即方法的返回值及参数类型)。如果一个方法既非abstract也非native,那么method_info表则包含方法局部变量所需的栈空间长度、为方法所捕获的异常表、字节码序列以及可选的行号表和局部变量表等信息
_______________________________________________________

上面有些东西难理解,比如method_info表包含的栈空间长度、为方法所捕获的异常表、字节码序列以及可选的行号表和局部变量表等信息.

我们知道method_info的结构是这样的:
method_info{
u2 acces_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attribute_count]
};

"方法所捕获的异常表" 是对应异常属性
"字节码序列" 是对应code属性

但是"栈空间长度"\"可选的行号表"\"局部变量表"没有对应的结构啊.

在  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-04-08 12:43 |

我已经知道了,局部变量表在code属性的属性里,谢谢你啊,有空一定常来看你的博客,非常感谢!

@独孤求败
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-04-09 22:23 | 独孤求败
呵呵...不客气:)
@王
  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-05-07 18:09 | FutureBoy
请教一下: 两个字符串常量"Hello"和"World"存放在什么地方? CLASS文件中哪里有对它们的表述?   回复  更多评论
  
# re: 一个解析Java Class文件的实例 2008-05-14 16:53 | 独孤求败
@FutureBoy
在常量池里啊:)
关于常量池的表述,请参考上一篇博客“Class文件格式解析 ”  回复  更多评论
  
# re: 一个解析Java Class文件的实例[未登录] 2009-04-13 17:32 | 阿飞
太厉害了!  回复  更多评论
  
# re: 一个解析Java Class文件的实例[未登录] 2010-12-21 15:17 | lynn
写得太好了!高手。有机会的话希望能请教你些问题。  回复  更多评论
  
# re: 一个解析Java Class文件的实例[未登录] 2012-05-10 16:21 | wei
写的真好  回复  更多评论
  
# re: 一个解析Java Class文件的实例 2013-02-03 10:07 | dereky
好强大啊  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: