文档章节

JVM-Class文件结构

项籍20130121
 项籍20130121
发布于 2013/06/28 01:25
字数 2167
阅读 3741
收藏 108

这周的分享准备时间太短了,总结比较潦草,还望见谅!

学习jvm的class文件结构基础的就不说了,参照书上的内容简单了解下,感觉写个例子更能加深对class文件结构的了解,于是写了两个例子:

1、替换方法的调用,和书上的那个例子差不多,自己改了场景实践了一下,主要实现思路就是修改常量池中原class的字面值,比较简单。

2、BeanCopy的实现,自己实现的工具类,相当的容易读懂(你懂的),只是为了实践而已,鲁棒性自然很差了(很多地方写死了,比如说是有int类型的字段的Bean才可以复制,Bean的字段名字只能是一个英文字母,都是为了方便哈~~谅解)。主要实现思路是增加常量池常量,修改常量池常量数,修改code区域的jvm指令,修改code的length,最后生成class。

一、方法替换

【工具类】

package jvm;


public class ByteUtil {

    public static byte[] int2Bytes(int value, int len) {
        byte[] b = new byte[len];
        for (int i = 0; i < len; i++) {
            b[len - i - 1] = (byte) ((value >> 8 * i) & 0xff);
        }
        return b;
    }

    public static int byte2Int(byte[] b, int start, int len) {
        int sum = 0;
        int end = start + len;
        for (int i = start; i < end; i++) {
            int n = ((int) b[i]) & 0xff;
            n <<= (--len) * 8;
            sum = n + sum;
        }
        return sum;
    }

    public static String byte2String(byte[] b, int start, int len) {
        return new String(b, start, len);
    }

    public static byte[] string2Bytes(String str) {
        return str.getBytes();
    }

    public static byte[] byteReplace(byte[] originalBytes, int offset, int len, byte[] replaceBytes) {
        byte[] newBytes = new byte[originalBytes.length + replaceBytes.length - len];
        System.arraycopy(originalBytes, 0, newBytes, 0, offset);
        System.arraycopy(replaceBytes, 0, newBytes, offset, replaceBytes.length);
        System.arraycopy(originalBytes, offset + len, newBytes, offset + replaceBytes.length, originalBytes.length
                                                                                              - offset - len);
        return newBytes;
    }

    public static void main(String[] args) {
        byte[] res = int2Bytes(29, 2);
        System.out.println(res);
    }

}
【TestA】
package jvm;


public class TestA {

    public static int fuc() {
        return 1;
    }

}
【TestB】
package jvm;


public class TestB {

    public static int fuc() {
        return 2;
    }

}
【具体的字节码解析和替换的实现类】
package jvm;


public class ClassModifier {

    private static final int CONSTANT_POOL_COUNT_INDEX = 8;
    private static final int   CONSTANT_UTF8_INFO        = 1;
    private static final int[] CONSTANT_ITEM_LENGTH      = { -1, -1, -1, 5, 5, 9, 9, 3, 3, 5, 5, 5, 5 };
    private static final int   u1                        = 1;
    private static final int   u2                        = 2;
    private byte[]             classByte;

    public ClassModifier(byte[] classByte){
        this.classByte = classByte;
    }

    public int getConstantPoolCount() {
        return ByteUtil.byte2Int(classByte, CONSTANT_POOL_COUNT_INDEX, u2);
    }

    public byte[] modify(String oldStr, String newStr) {
        int cpc = getConstantPoolCount();
        int offset = CONSTANT_POOL_COUNT_INDEX + u2;
        for (int i = 0; i < cpc; i++) {
            int tag = ByteUtil.byte2Int(classByte, offset, u1);
            if (tag == CONSTANT_UTF8_INFO) {
                int len = ByteUtil.byte2Int(classByte, offset + u1, u2);
                offset += (u1 + u2);
                String str = ByteUtil.byte2String(classByte, offset, len);
                if (str.equalsIgnoreCase(oldStr)) {
                    byte[] strBytes = ByteUtil.string2Bytes(newStr);
                    byte[] strLen = ByteUtil.int2Bytes(newStr.length(), u2);
                    classByte = ByteUtil.byteReplace(classByte, offset - u2, u2, strLen);
                    classByte = ByteUtil.byteReplace(classByte, offset, len, strBytes);
                    return classByte;
                } else {
                    offset += len;
                }
            } else {
                offset += CONSTANT_ITEM_LENGTH[tag];
            }
        }
        return classByte;
    }
}
【Class的生成】
package jvm;


public class TestClassLoader extends ClassLoader {

    public TestClassLoader(){
        super(TestClassLoader.class.getClassLoader());
    }

    public Class loadByte(byte[] classByte) {
        return defineClass(null, classByte, 0, classByte.length);
    }

}
【Client实现】
package jvm;

import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Date;

public class TestClass {

    private static final int m = staticInc();

    private Date             curr = new Date();

    public int inc() {
        return m + 1;
    }

    public static int staticInc() {
        return TestA.fuc();
    }

    public static void main(String[] args) throws Exception {

        InputStream is = new FileInputStream("/Users/apple/workspace/CommonTest/bin/jvm/TestClass.class");
        byte[] b = new byte[is.available()];
        is.read(b);
        is.close();
        ClassModifier cm = new ClassModifier(b);
        byte[] newBytes = cm.modify("jvm/TestA", "jvm/TestB");
        TestClassLoader loader = new TestClassLoader();
        Class clazz = loader.loadByte(newBytes);
        Method method = clazz.getMethod("staticInc");
        System.out.println((Integer) method.invoke(null));

    }

}

二、BeanCopy的实现

【测试Bean,其实可有可无】

package jvm;


public class Bean {

    int    i;

    int    j;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }

    public int getJ() {
        return j;
    }

    public void setJ(int j) {
        this.j = j;
    }

}
【模型类,不可能自己从零开始吧,所以基于这玩意改的字节码】
package jvm;

public class BeanCopierModel {

    public static Object copy(Object bean) {
        return null;
    }
}

【实现类,比较臭,但是可读性还是可以,那些偏移量不需要关注,主要是流程】 

package jvm;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BeanCopyClient {

    private static final int CODE_LENGTH   = 15;
    private static final int CODE_PER_STEP = 8;
    private static final int CONSTANT_COUNT_OFF          = 8;
    private static final int CONSTANT_NAME_AND_TYPE_INIT = 9;
    private static final int CONSTANT_COUNT              = 19;
    private static final int CODE_BEGIN                  = 358;
    private static final int CODE_LEN                    = 10;
    private static final int CONSTANT_END                = 270;
    private static final int METHOD_LEN                  = 14;
    private static final int ATTRIBUTE_OFF               = 371;
    private static final int ATTRIBUTE_LEN               = 31;
    private static final int U1                          = 1;
    private static final int U2                          = 2;
    private static final int U4                          = 4;
    String                   className;
    List<String>             fields                      = new ArrayList<String>();
    Map<String, Integer>     constantIndex               = new HashMap<String, Integer>();
    
    private void init(){
        //准备测试数据
     // 测试数据
        fields.add("I");
        fields.add("J");
        className = "jvm/Bean";
    }

    private byte[] modifyConstantCount(byte[] b) {
        return ByteUtil.byteReplace(b, CONSTANT_COUNT_OFF, 2, ByteUtil.int2Bytes(constantNum(), U2));
    }

    private byte[] modifyCodeLen(byte[] b) {
        int len = codeLen();
        return ByteUtil.byteReplace(b, CODE_BEGIN - 4, 4, ByteUtil.int2Bytes(len, U4));
    }

    private byte[] generateConstant() {

        int len = coutLen();
        int index = CONSTANT_COUNT;
        byte[] b = new byte[len];
        int offset = 0;
        // class 的utf8-info
        byte[] tag = ByteUtil.int2Bytes(1, U1);
        byte[] length = ByteUtil.int2Bytes(className.length(), U2);
        byte[] bytes = ByteUtil.string2Bytes(className);
        System.arraycopy(tag, 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(length, 0, b, offset, U2);
        offset = offset + U2;
        System.arraycopy(bytes, 0, b, offset, className.length());
        offset = offset + className.length();
        constantIndex.put("utf8_class", ++index);
        // class info
        System.arraycopy(ByteUtil.int2Bytes(7, U1), 0, b, offset, U1);
        offset = offset + U1;
        int class_index = constantIndex.get("utf8_class");
        System.arraycopy(ByteUtil.int2Bytes(class_index, U2), 0, b, offset, U2);
        offset = offset + U2;
        constantIndex.put("class_info", ++index);
        // get set 的utf8-info
        for (String field : fields) {
            String set = "set" + field;
            String get = "get" + field;
            byte[] tag_ = ByteUtil.int2Bytes(1, U1);
            byte[] length_ = ByteUtil.int2Bytes(field.length() + 3, U2);
            byte[] bytes_ = ByteUtil.string2Bytes(set);
            byte[] bytes__ = ByteUtil.string2Bytes(get);
            System.arraycopy(tag_, 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(length_, 0, b, offset, U2);
            offset = offset + U2;
            System.arraycopy(bytes_, 0, b, offset, field.length() + 3);
            offset = offset + field.length() + 3;
            constantIndex.put("utf8_set" + field, ++index);
            System.arraycopy(tag_, 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(length_, 0, b, offset, U2);
            offset = offset + U2;
            System.arraycopy(bytes__, 0, b, offset, field.length() + 3);
            offset = offset + field.length() + 3;
            constantIndex.put("utf8_get" + field, ++index);
        }
        // get set的描述字段
        byte[] tag_setDesc = ByteUtil.int2Bytes(1, U1);
        byte[] length_setDesc = ByteUtil.int2Bytes(4, U2);
        byte[] bytes_setDesc = ByteUtil.string2Bytes("(I)V");
        System.arraycopy(tag_setDesc, 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(length_setDesc, 0, b, offset, U2);
        offset = offset + U2;
        System.arraycopy(bytes_setDesc, 0, b, offset, 4);
        offset = offset + 4;
        constantIndex.put("utf8_setDesc", ++index);
        byte[] tag_getDesc = ByteUtil.int2Bytes(1, U1);
        byte[] length_getDesc = ByteUtil.int2Bytes(3, U2);
        byte[] bytes_getDesc = ByteUtil.string2Bytes("()I");
        System.arraycopy(tag_getDesc, 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(length_getDesc, 0, b, offset, U2);
        offset = offset + U2;
        System.arraycopy(bytes_getDesc, 0, b, offset, 3);
        offset = offset + 3;
        constantIndex.put("utf8_getDesc", ++index);
        // get set 的NameType
        for (String field : fields) {
            int field_name_index = constantIndex.get("utf8_set"+field);
            int field_desc_index = constantIndex.get("utf8_setDesc");
            System.arraycopy(ByteUtil.int2Bytes(12, U1), 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(ByteUtil.int2Bytes(field_name_index, U2), 0, b, offset, U2);
            offset = offset + U2;
            System.arraycopy(ByteUtil.int2Bytes(field_desc_index, U2), 0, b, offset, U2);
            offset = offset + U2;
            constantIndex.put("name_type_set" + field, ++index);
            int field_name_index_ = constantIndex.get("utf8_get" + field);
            int field_desc_index_ = constantIndex.get("utf8_getDesc");
            System.arraycopy(ByteUtil.int2Bytes(12, U1), 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(ByteUtil.int2Bytes(field_name_index_, U2), 0, b, offset, U2);
            offset = offset + U2;
            System.arraycopy(ByteUtil.int2Bytes(field_desc_index_, U2), 0, b, offset, U2);
            offset = offset + U2;
            constantIndex.put("name_type_get" + field, ++index);
        }
        // get set method
        int class_info_index = constantIndex.get("class_info");
        byte[] tag_method = ByteUtil.int2Bytes(10, U1);
        byte[] class_ref = ByteUtil.int2Bytes(class_info_index, U2);
        for (String field : fields) {
            int name_type_set = constantIndex.get("name_type_set" + field);
            System.arraycopy(tag_method, 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(class_ref, 0, b, offset, U2);
            offset = offset + U2;
            System.arraycopy(ByteUtil.int2Bytes(name_type_set, U2), 0, b, offset, U2);
            offset = offset + U2;
            constantIndex.put("method_set" + field, ++index);
            int name_type_get = constantIndex.get("name_type_get" + field);
            System.arraycopy(tag_method, 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(class_ref, 0, b, offset, U2);
            offset = offset + U2;
            System.arraycopy(ByteUtil.int2Bytes(name_type_get, U2), 0, b, offset, U2);
            offset = offset + U2;
            constantIndex.put("method_get" + field, ++index);
        }
        // init method
        System.arraycopy(tag_method, 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(class_ref, 0, b, offset, U2);
        offset = offset + U2;
        System.arraycopy(ByteUtil.int2Bytes(CONSTANT_NAME_AND_TYPE_INIT, U2), 0, b, offset, U2);
        offset = offset + U2;
        constantIndex.put("method_init", ++index);
        return b;
    }

    private byte[] generateCode() {
        int len = 8 + CODE_LENGTH + CODE_PER_STEP * fields.size();
        int offset = 0;
        byte[] b = new byte[len];
        System.arraycopy(ByteUtil.int2Bytes(2, U2), 0, b, offset, U2);
        offset = offset + U2;
        System.arraycopy(ByteUtil.int2Bytes(3, U2), 0, b, offset, U2);
        offset = offset + U2;
        System.arraycopy(ByteUtil.int2Bytes(CODE_LENGTH + CODE_PER_STEP * fields.size(), U4), 0, b, offset, U4);
        offset = offset + U4;
        // 代码体
        int class_info_index = constantIndex.get("class_info");
        int init_index = constantIndex.get("method_init");
        System.arraycopy(ByteUtil.int2Bytes(0x2A, U1), 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(ByteUtil.int2Bytes(0xC0, U1), 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(ByteUtil.int2Bytes(class_info_index, U2), 0, b, offset, U2);
        offset = offset + U2;
        System.arraycopy(ByteUtil.int2Bytes(0x4C, U1), 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(ByteUtil.int2Bytes(0xBB, U1), 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(ByteUtil.int2Bytes(class_info_index, U2), 0, b, offset, U2);
        offset = offset + U2;
        System.arraycopy(ByteUtil.int2Bytes(0x59, U1), 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(ByteUtil.int2Bytes(0xB7, U1), 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(ByteUtil.int2Bytes(init_index, U2), 0, b, offset, U2);
        offset = offset + U2;
        System.arraycopy(ByteUtil.int2Bytes(0x4D, U1), 0, b, offset, U1);
        offset = offset + U1;
        for (String field : fields) {
            int get_index = constantIndex.get("method_get" + field);
            int set_index = constantIndex.get("method_set" + field);
            System.arraycopy(ByteUtil.int2Bytes(0x2C, U1), 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(ByteUtil.int2Bytes(0x2B, U1), 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(ByteUtil.int2Bytes(0xB6, U1), 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(ByteUtil.int2Bytes(get_index, U2), 0, b, offset, U2);
            offset = offset + U2;
            System.arraycopy(ByteUtil.int2Bytes(0xB6, U1), 0, b, offset, U1);
            offset = offset + U1;
            System.arraycopy(ByteUtil.int2Bytes(set_index, U2), 0, b, offset, U2);
            offset = offset + U2;
        }
        System.arraycopy(ByteUtil.int2Bytes(0x2C, U1), 0, b, offset, U1);
        offset = offset + U1;
        System.arraycopy(ByteUtil.int2Bytes(0xB0, U1), 0, b, offset, U1);
        offset = offset + U1;
        return b;
    }

    private byte[] replace(byte[] o, byte[] constant, byte[] code) {
        byte[] step1 = ByteUtil.byteReplace(o, CODE_BEGIN, CODE_LEN, code);
        byte[] step2 = ByteUtil.byteReplace(step1, CONSTANT_END + 1, 0, constant);
        return step2;
    }

    private int coutLen() {
        // class 的utf8-info
        int utf_class = className.length() + 3;
        // get set 的utf8-info
        int utf_getSet = fields.size() * 2 * 7;
        // get set的描述字段
        int utf_getSetDesc = 6 + 7;
        // get set 的NameType
        int name_type = fields.size() * 2 * 5;
        // class info
        int class_info = 3;
        // init method
        int method_init = 5;
        // get set method
        int getSet_method = fields.size() * 2 * 5;
        return utf_class + utf_getSet + utf_getSetDesc + name_type + class_info + method_init + getSet_method;
    }

    private int constantNum() {
        return fields.size() * 6 + 5 + CONSTANT_COUNT + 1;
    }

    private int codeLen() {
        return CODE_LENGTH + fields.size() * CODE_PER_STEP - 2 + METHOD_LEN;
    }

    private byte[] delAttribute(byte[] b) {
        return ByteUtil.byteReplace(b, ATTRIBUTE_OFF, ATTRIBUTE_LEN, new byte[] { 0 });
    }

    private byte[] byteModify(byte[] b) {
        byte[] midRes = modifyCodeLen(b);
        byte[] finalRes = modifyConstantCount(midRes);
        return finalRes;
    }

    public static void main(String[] args) throws Exception {
        String name = "/Users/apple/workspace/CommonTest/bin/jvm/BeanCopierModel.class";
        BeanCopyClient bc = new BeanCopyClient();
        // 准备些测试数据
        bc.init();
        InputStream is = new FileInputStream(name);
        byte[] a = new byte[is.available()];
        is.read(a);
        is.close();
        // 删掉Code区块里面没用的属性,只是为了学习,可以不删
        byte[] b = bc.delAttribute(a);
        // 修改一些count值
        byte[] bm = bc.byteModify(b);
        // 开始做替换
        byte[] constant = bc.generateConstant();
        byte[] code = bc.generateCode();
        byte[] res = bc.replace(bm, constant, code);
        // 打印出来反编译看看而已
        FileOutputStream out = new FileOutputStream("/Users/apple/Downloads/BeanCopierModel.class");
        out.write(res);
        out.close();
        TestClassLoader loader = new TestClassLoader();
        Class clazz = loader.loadByte(res);
        Method method = clazz.getMethod("copy", Object.class);
        Bean bean = new Bean();
        bean.setI(2);
        bean.setJ(3);
        Bean newBean = (Bean) method.invoke(null, bean);
        System.out.println(newBean != bean && newBean.getI() == 2);

    }

}
【最后补充下生成的字节码的长相】
package jvm;


public class BeanCopier {

    public static Object copy(Object bean) {
        Bean plah = (Bean) bean;
        Bean newBean = new Bean();
        newBean.setI(plah.getI());
        newBean.setJ(plah.getJ());
        return newBean;
    }
}

© 著作权归作者所有

上一篇: JVM-JMM
项籍20130121
粉丝 96
博文 36
码字总数 69265
作品 0
杭州
程序员
私信 提问
加载中

评论(5)

项籍20130121
项籍20130121 博主

引用来自“都忠刚”的评论

请问一下 有没有 这本书的电子档

挺畅销的,应该有的
都忠刚
都忠刚
请问一下 有没有 这本书的电子档
优雅先生
优雅先生

引用来自“项籍20130121”的评论

引用来自“jxqlovedn”的评论

楼主,你看的是哪本书呢?《深入Java虚拟机》?

是的,周志明的那本书

Thank you very much.
项籍20130121
项籍20130121 博主

引用来自“jxqlovedn”的评论

楼主,你看的是哪本书呢?《深入Java虚拟机》?

是的,周志明的那本书
优雅先生
优雅先生
楼主,你看的是哪本书呢?《深入Java虚拟机》?
java安全沙箱(二)之.class文件检验器

java是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是: 类加载体系 .class文件检验器 内置于Java虚拟机(及语言)的安全特性 安全管理器...

xionghuiCoder
2015/09/04
817
0
1-Java基础语法-Java初识

欢迎大家来到java世界 带领大家领略编程的奥秘。 人与人沟通需要语言。计算机世界的沟通语言。 Java语法结构,使用循环和选择流控制结构,了解方法和数组的使用。 Java简介 Java是一门编程语...

天涯明月笙
2018/07/29
0
0
JVM规范系列第4章:Class文件格式

这一章节讲的是字节码的整个组成格式,读懂了这一章,就读懂了字节码文件。对于这一章的学习,我更推荐作为工具书去查找。最好是找一个最简单的Hello World例子,一个字节一个字节去分析其含...

陈树义
2018/12/19
0
0
程序员从宏观、微观角度浅析JVM虚拟机!

1.问题 1、JAVA文本文件如何被翻译成CLASS二进制文件? 2、如何理解CLASS文件的组成结构? 3、虚拟机如何加载使用类文件的生命周期? 4、虚拟机系列诊断工具如何使用? 5、虚拟机内存淘汰机制?...

我最喜欢三大框架
03/18
25
0
JVM规范系列开篇:为什么要读JVM规范?

许多人知道类加载机制、JVM内存模型,但他们可能不知道什么是《Java虚拟机规范》。对于Java开发来说,《Java虚拟机规范》才是最为官方、准确的一个文档,了解这个规范可以让我们更深入地理解...

陈树义
2018/12/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Android面试常客之Handler全解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/fnhfire_7030/article/details/79518819 前言:又到了一年...

shzwork
23分钟前
4
0
position sticky 定位

本文转载于:专业的前端网站➫position sticky 定位 1、兼容性 https://caniuse.com/#search=sticky chrome、ios和firefox兼容性良好。 2、使用场景 sticky:粘性。粘性布局。 在屏幕范围内时...

前端老手
30分钟前
4
0
CentOS 7 yum 安装 PHP7.3 教程

参考:https://www.mf8.biz/centos-rhel-install-php7-3/ 1、首先安装 EPEL 源: yum install epel-release 安装 REMI 源: yum install http://rpms.remirepo.net/enterprise/remi-release......

dragon_tech
45分钟前
4
0
Linux物理网卡聚合及桥接

Linux内部实现的bridge可以把一台机器上的多张网卡桥接起来,从而把自己作为一台交换机。同时,LInux bridge还支持虚拟端口,即桥接的不一定都是物理网卡接口,还可以是虚拟接口。目前主要表...

xiangyunyan
45分钟前
4
0
一起来学Java8(一)——函数式编程

在这篇文章中,我们将了解到在Java8下如何进行函数式编程。 函数式编程 所谓的函数式编程就是把函数名字当做值进行传递,然后接收方拿到这个函数名进行调用。 首先来看下JavaScript如何进行函...

猿敲月下码
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部