class文件结构浅析(2)

欢迎转载,转载需声明出处

------------------

请先看上一篇:Class类文件结构浅析

上一篇讲的都是理论,下面我们亲自实践一下。

首先编写一个简单的java类:

public class Test
{
    private int m;
    private String str;

    public int func(int m,String str)
    {
        str += "OK";
        m = 10;
        return -1;
    }

    public static void main(String[] arg)
    {
        String str = "test";
        int m = 20;
        new Test().func(m,str);
    }
}

紧接着我们使用javac编译器将其编译为class字节码格式。这篇文章以分析这个类的字节码为主。分析之前先介绍两个工具,一个是winHex,16进制编辑器,可以以16进制的形式查看class源文件。另一个是jdk自带的反编译工具javap。我们可以一边分析16进制数据,一边对照着javap编译后的结果。

首先使用javap -verbose命令查看反编译结果。

C:\Users\Rowand jj\Desktop>javap -verbose Test
Classfile /C:/Users/Rowand jj/Desktop/Test.class
  Last modified 2014-5-2; size 616 bytes
  MD5 checksum cfd6b2e7d9d99dda3b84d1dbe87ce657
  Compiled from "Test.java"
public class Test
  SourceFile: "Test.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#26        //  java/lang/Object."<init>":()V
   #2 = Class              #27            //  java/lang/StringBuilder
   #3 = Methodref          #2.#26         //  java/lang/StringBuilder."<init>":(
)V
   #4 = Methodref          #2.#28         //  java/lang/StringBuilder.append:(Lj
ava/lang/String;)Ljava/lang/StringBuilder;
   #5 = String             #29            //  OK
   #6 = Methodref          #2.#30         //  java/lang/StringBuilder.toString:(
)Ljava/lang/String;
   #7 = String             #31            //  test
   #8 = Class              #32            //  Test
   #9 = Methodref          #8.#26         //  Test."<init>":()V
  #10 = Methodref          #8.#33         //  Test.func:(ILjava/lang/String;)I
  #11 = Class              #34            //  java/lang/Object
  #12 = Utf8               m
  #13 = Utf8               I
  #14 = Utf8               str
  #15 = Utf8               Ljava/lang/String;
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               func
  #21 = Utf8               (ILjava/lang/String;)I
  #22 = Utf8               main
  #23 = Utf8               ([Ljava/lang/String;)V
  #24 = Utf8               SourceFile
  #25 = Utf8               Test.java
  #26 = NameAndType        #16:#17        //  "<init>":()V
  #27 = Utf8               java/lang/StringBuilder
  #28 = NameAndType        #35:#36        //  append:(Ljava/lang/String;)Ljava/l
ang/StringBuilder;
  #29 = Utf8               OK
  #30 = NameAndType        #37:#38        //  toString:()Ljava/lang/String;
  #31 = Utf8               test
  #32 = Utf8               Test
  #33 = NameAndType        #20:#21        //  func:(ILjava/lang/String;)I
  #34 = Utf8               java/lang/Object
  #35 = Utf8               append
  #36 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #37 = Utf8               toString
  #38 = Utf8               ()Ljava/lang/String;
{
  public Test();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public int func(int, java.lang.String);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: new           #2                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #3                  // Method java/lang/StringBuilder.
"<init>":()V
         7: aload_2
         8: invokevirtual #4                  // Method java/lang/StringBuilder.
append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        11: ldc           #5                  // String OK
        13: invokevirtual #4                  // Method java/lang/StringBuilder.
append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        16: invokevirtual #6                  // Method java/lang/StringBuilder.
toString:()Ljava/lang/String;
        19: astore_2
        20: bipush        10
        22: istore_1
        23: iconst_m1
        24: ireturn
      LineNumberTable:
        line 8: 0
        line 9: 20
        line 10: 23

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, args_size=1
         0: ldc           #7                  // String test
         2: astore_1
         3: bipush        20
         5: istore_2
         6: new           #8                  // class Test
         9: dup
        10: invokespecial #9                  // Method "<init>":()V
        13: iload_2
        14: aload_1
        15: invokevirtual #10                 // Method func:(ILjava/lang/String
;)I
        18: pop
        19: return
      LineNumberTable:
        line 15: 0
        line 16: 3
        line 17: 6
        line 18: 19
}

大家可能看的有点晕,不要紧,我们一步一步来分析。下面我们使用winhex打开Test.class文件:

准备工作都做好了,下面开始分析吧。如果你对class文件一无所知,请先阅读上一篇文章。

通过上一篇文章,我们了解了class文件的基本结构,首先来回顾一下,class文件是由一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在一起,中间没有任何分隔符。class文件格式以一种类似c语言结构体的伪结构存储,这种伪结构只有两种数据类型:

无符号数和表,无符号数是基本数据类型,以u1 u2 u4 u8代表1字节 2字节 4字节 8字节,表是由多个无符号数或者其他表构成的复合结构。整个class文件就是一张表,其结构如下:

ClassFile {
    u4 magic;//魔数(0xCAFEBABE)
    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];//属性表集合
}

下面看头四个字节,正好是0xCAFEBABE,即所谓的魔数。紧接着四个字节是0x0000 0033,对应10进制为51,这代表当前class文件的版本号,查表后发现是JDK1.7.0。

接下来就是所谓的常量池了,首先是常量池计数器,值为0x0027,对应10进制为39,说明一共有38项常量(第0项保留),对照javap反编译后的汇编代码也可以证实(#38)。

常量池中的项目类型以一个u1类型的tag标识,比如0x0A(offset为0000 000A),十进制为10,这个tag代表的是CONSTANT_Methodref_info类型(查表可知),表示对类中方法的符号引用,

紧接着两个字节(u2)为类描述符索引,值为0x000B(第11项常量池数据项),再接着的两个字节是名称和类型描述符,值为0x001A(第26项常量池数据项)。这四个字节都引用了常量池其他数据项。反编译的结果如下,可以看到方法的类描述符为java/lang/Object,名称和类型描述符"<init>":<v>.

第一项常量分析完毕,咱们往下看,紧接着一个字节是0x07,该tag对应的表是CONSTANT_Class_info类型:

所以紧随其后的两个字节为指向全限定名的常量项的索引,值为0x001B,指向常量池第27项常量,如下:

可以发现第27项常量的头一个字节为0x01,即CONSTANT_Utf8_info:

所以接下来的两个字节为字符串所占的字节数,0x0017,即十进制的23,所以接下来的23个字节即为字符串值,查看winhex可知,值为:java/lang/StringBuilder:

对照一下汇编代码:

说明我们解读没有任何问题。第二项常量分析完毕,看第三项,tag值为0x0A,又是一个

CONSTANT_Methodref_info,按照上面的分析流程分析....

由于常量池太过庞大,这里仅分析到这,后面的大家可以自己分析,然后对照汇编代码验证你的分析结果。

常量池之后的表项为access_flags,即访问标志,占两个字节(u2)。如何在winhex中找到两个字节呢?这里有个技巧,通过刚才的汇编代码发现常量池最后一项常量为#38,该常量后即为访问标志位:

在winhex中找到这个串:

所以红色圈出来的两个字节即为访问标志,0x0021。这正好是0x0020|0x0001(查表),所以访问标志为ACC_PUBLIC,ACC_SUPER,查看汇编代码:

验证了我们的解析结果,是不是很简单?

接下来分别是this_class和super_class即本类索引和父类索引,

this_class值为0x0008,类索引为8,即常量池的第8项:

super_class值为0x000B,类索引为11,即常量池的第11项:

可以清晰的看出类名为Test,父类为Object!

接下来是接口计数器,值为0x0000,代表这个类没有实现任何接口。

至此,我们分析完了类索引,父类索引,接口索引。

下面到了字段表集合了。首先两个字节为字段表计数器,值为0x0002:

有两个字段,咱只分析第一个字段。头两个字节0x0002为访问标志,对应的是private:

紧接着两个字节为字段简单名称,0x000C,指向常量池的第12项,值为m。然后0x000D为字段描述符,指向常量池的第13项,值为I,代表Int,最后两个字节为属性表,值为0x0000,到这里我们可以推测源文件中有个私有成员变量定义:private int m,与事实相符!接下来第二个字段为private String str。

字段表分析到这,接下来是方法表,其实方法表跟字段表格式是基本一致的,按照上面流程即可,首先是方法表计数器0x0003,代表有三个方法(多的那个方法是<init>方法),看第一个,访问标志位0x0001,对应的是public,然后名称索引位0x0010,指向第16项常量,这是名称索引:

然后是0x0011,为描述符索引:

即为init方法。

好了,方法表就分析到这了。整个class文件也分析的差不多了,相信大家看完之后对class文件结构会有进一步的了解的!

class文件结构浅析(2)

时间: 2024-08-02 07:49:40

class文件结构浅析(2)的相关文章

Class类文件结构浅析

欢迎转载,转载需注明出处! ------------- 前言 class文件时java虚拟机执行引擎的数据入口,也是java技术体系的基础支柱之一,了解class文件的结构对后面进一步了解虚拟机执行引擎有很重要的意义. 概要: class文件是一组以八位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在class文件中,中间没有添加任何分隔符,这使得整个class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙存在.当需要占用8位字节以上的空间数据时,则会按照高位在前的方式分

MXF文件结构浅析

MXF是英文Material eXchange Format(素材交换格式)的缩语.MXF是SMPTE(美国电影与电视工程师学会)组织定义的一种专业音视频媒体文件格式.MXF主要应用于影视行业媒体制作.编辑.发行和存储等环节.SMPTE为其定义的标准包括:SMPTE - 377M.SMPTE - EG41.SMPTE - EG42等,并不断进行更新和完善.它是一个外壳格式 而不是压缩格式, 所以并不能保证每一款MXF文件 都能被任何一种解码器识别. MXF标准并非一成不变,一直在发展.SMPTE

android学习笔记--android启动过程之init.rc文件浅析

1.  init.rc文件结构文件位置:init.c  : /system/core/initinit.rc  : /system/core/rootdir 首先init.rc文件是以模块为单位的,每个模块里的内容都是一起执行的,模块分为3种类型:on.service.import.我们可以看下init.rc文件是怎么写的:1.import import /init.usb.rc import /init.${ro.hardware}.rc import /init.trace.rc 上面的内容

java语言安全机制浅析

java通过所谓的沙箱安全模型保证了其安全性,下面我们就来看看java提供的安全沙箱机制. 组成沙箱的基本组件如下: 1.类装载器结构: 2.class文件检验器: 3.内置于java虚拟机(及语言)的安全特性: 4.安全管理器及java API. 一.类装载器体系结构 1.防止恶意代码去干涉善意的代码. 这是通过为不同类加载器提供不同的命名空间来实现的,在java虚拟机中,在同一个命名空间内的类可以直接进行交互,而不同的命名空间中类甚至不能觉察彼此的存在,除非显式地提供允许它们交互的机制. 2

浅析MSIL中间语言——基础篇

一.开篇 研究MSIL纯属于个人喜好,说在前面MSIL应用于开发的地方很少,但是很大程度上能够帮着我们理解底层的原理,这是我了解MSIL的主要原因.托管代码表示应用程序的方法的功能,它们以微软的中间语言(Microsoft intermediate language,MSIL)或公共语言运行(common intermediate language,CIL)的抽象二进制形式进行编码. MSIL代码由CLR“托管”.CLR托管至少包括三个主要的活动:类型控制,结构化异常和垃圾收集.类型控制设计在执

MySQL redo log及recover过程浅析

写在前面:作者水平有限,欢迎不吝赐教,一切以最新源码为准. InnoDB redo log 首先介绍下Innodb redo log是什么,为什么需要记录redo log,以及redo log的作用都有哪些.这些作为常识,只是为了本文完整. InnoDB有buffer pool(简称bp).bp是数据库页面的缓存,对InnoDB的任何修改操作都会首先在bp的page上进行,然后这样的页面将被标记为dirty并被放到专门的flush list上,后续将由master thread或专门的刷脏线程阶

Java网络编程和NIO详解8:浅析mmap和Direct Buffer

Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO https://blog.csdn.net/column/details/21963.html 部分代码会放在我的的Github:https://github.com/h2pl/ Java网络编程与NIO详解8:浅析mmap和Direct Buffer 之前看到一篇文章说epoll中在维护epo

Python之encode与decode浅析

 Python之encode与decode浅析 在 python 源代码文件中,如果你有用到非ASCII字符,则需要在文件头部进行字符编码的声明,声明如下: # code: UTF-8 因为python 只检查 #.coding 和编码字符串,为了美观等原因可以如下写法: #-*-coding:utf-8-*- 常见编码介绍: GB2312编码:适用于汉字处理.汉字通信等系统之间的信息交换. GBK编码:是汉字编码标准之一,是在 GB2312-80 标准基础上的内码扩展规范,使用了双字节编码.

浅析PHP的开源产品二次开发的基本要求

浅析PHP的开源产品二次开发的基本要求 第一, 基本要求:HTML(必须要非常熟悉),PHP(能看懂代码,能写一些小系统,如:留言板,小型CMS),Mysql(至少会一种数据库),Javascript(能看懂,能改现成的一些代码),Div+Css(能进行界面的调整,明白CSS是怎么使用的) 第二, 熟悉开源产品的使用,比如 Dedecms,你要知道怎么登录,怎么新建栏目,怎么添加文章,模板标签的使用方法,模型的概念和使用方法等等一些功能 第三, 要熟悉这个开源产品的数据库结构,还要理解里面核心文