这周的分享准备时间太短了,总结比较潦草,还望见谅!
学习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【最后补充下生成的字节码的长相】fields = new ArrayList (); Map constantIndex = new HashMap (); 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; }}