java实现字节码解析1(常量池的解析)
大体的思路就是按照之前写的那篇字节码解析里面所描述的方式。
https://blog.csdn.net/qq_43147121/article/details/108035464
这里面有几个要我们用到位运算来处理基本类型的地方,可以看我前面那篇Java基本类型的博客。
https://blog.csdn.net/qq_43147121/article/details/108145123
下面直接贴代码:
1.这是一个工具类,主要就是将byte数组转换为各种基本类型的方法。
package util;
public class BytesToOtherUtil {
public static int bytesToInt(byte[] bytes){
int sum = 0;
for (byte aByte : bytes) {
sum+=aByte&0xff;
}
return sum;
}
public static float bytesToFloat(byte[] bytes){
// int accum = 0;
// accum = accum|(bytes[0] & 0xff) << 0;
// accum = accum|(bytes[1] & 0xff) << 8;
// accum = accum|(bytes[2] & 0xff) << 16;
// accum = accum|(bytes[3] & 0xff) << 24;
return Float.intBitsToFloat(bytesToInt(bytes));
}
public static long bytesToLong(byte[] bytes){
// int doubleSize = 8;
long l = 0L;
for (int i = 0; i < bytes.length; i++) {
l+=bytes[i]&0xffL;
}
return l;
}
public static double bytesToDouble(byte[] bytes){
return Double.longBitsToDouble(bytesToLong(bytes));
}
}
2.这个也是一个工具类,主要是从文件io流中读取jvm内部的u1,u2,u4,u8类型的byte数组
package classFileReader;
import java.io.FileInputStream;
import java.io.IOException;
public class ClassFileReader {
// public static byte[] bytes = {-54,-2,-70,-66};
public static int readU1(FileInputStream inputStream){
try {
int read = inputStream.read();
return read;
} catch (IOException e) {
System.err.println("read ClassFile U1 Field!");
e.printStackTrace();
}
return -1;
}
public static byte[] readU2(FileInputStream inputStream){
byte[] bytes = new byte[2];
try {
int read = inputStream.read(bytes);
} catch (IOException e) {
System.err.println("read ClassFile U2 Field!");
e.printStackTrace();
}
return bytes;
}
public static byte[] readU4(FileInputStream inputStream){
byte[] bytes = new byte[4];
try {
int read = inputStream.read(bytes);
} catch (IOException e) {
System.err.println("read ClassFile U4 Field!");
e.printStackTrace();
}
return bytes;
}
public static byte[] readU8(FileInputStream inputStream){
byte[] bytes = new byte[8];
try {
int read = inputStream.read(bytes);
} catch (IOException e) {
System.err.println("read ClassFile U8 Field!");
e.printStackTrace();
}
return bytes;
}
public static boolean checkMOSHU(byte[] bytes){
boolean flag = true;
if (bytes[0]!=-54||bytes[1]!=-2||bytes[2]!=-70||bytes[3]!=-66){
flag=false;
}
return flag;
}
public static boolean checkVersion(byte[] bytes){
System.out.println("jdk version is jdk1."+((bytes[3]&0xff)-44));
return true;//这里暂时不做处理
}
}
3.这个类是根据我们读取出的常量池元素的tag来做对应的解析的类,对应着jvm内常量池的11种元素
package classFileReader.ConstantPoolInfo;
import classFileReader.ClassFileReader;
import util.BytesToOtherUtil;
import java.io.FileInputStream;
import java.io.IOException;
public class ConstantPoolInfoAnalysis {
public static void analysisByTag(int tag , FileInputStream inputStream,int index) throws IOException {
byte[] bytes;
switch (tag){
case 1:{
bytes = ClassFileReader.readU2(inputStream);
int length = BytesToOtherUtil.bytesToInt(bytes);
bytes = new byte[length];
inputStream.read(bytes);
System.out.println("#"+index+" =utf8 "+new String(bytes));
bytes=null;
break;
}
case 3:{
bytes = ClassFileReader.readU4(inputStream);
int i = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =integer "+i);
bytes=null;
break;
}
case 4:{
bytes = ClassFileReader.readU4(inputStream);
float i = BytesToOtherUtil.bytesToFloat(bytes);
System.out.println("#"+index+" =float "+i);
bytes=null;
break;
}
case 5:{
bytes = ClassFileReader.readU8(inputStream);
long i = BytesToOtherUtil.bytesToLong(bytes);
System.out.println("#"+index+" =long "+i);
bytes=null;
break;
}
case 6:{
bytes = ClassFileReader.readU8(inputStream);
double i = BytesToOtherUtil.bytesToDouble(bytes);
System.out.println("#"+index+" =double "+i);
bytes=null;
break;
}
case 7:{
bytes = ClassFileReader.readU2(inputStream);
System.out.println("#"+index+" =Class #"+BytesToOtherUtil.bytesToInt(bytes));
bytes = null;
break;
}
case 8:{
bytes = ClassFileReader.readU2(inputStream);
System.out.println("#"+index+" =String #"+BytesToOtherUtil.bytesToInt(bytes));
bytes = null;
break;
}
case 9:{
bytes = ClassFileReader.readU2(inputStream);
int index1 = BytesToOtherUtil.bytesToInt(bytes);
bytes = ClassFileReader.readU2(inputStream);
int index2 = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =Fieldref #"+index1+",#"+index2);
bytes = null;
break;
}
case 10:{
bytes = ClassFileReader.readU2(inputStream);
int index1 = BytesToOtherUtil.bytesToInt(bytes);
bytes = ClassFileReader.readU2(inputStream);
int index2 = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =Methodref #"+index1+",#"+index2);
bytes = null;
break;
}
case 11:{
bytes = ClassFileReader.readU2(inputStream);
int index1 = BytesToOtherUtil.bytesToInt(bytes);
bytes = ClassFileReader.readU2(inputStream);
int index2 = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =InterfaceMethodref #"+index1+",#"+index2);
bytes = null;
break;
}
case 12:{
bytes = ClassFileReader.readU2(inputStream);
int index1 = BytesToOtherUtil.bytesToInt(bytes);
bytes = ClassFileReader.readU2(inputStream);
int index2 = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =NameAndType #"+index1+",#"+index2);
bytes = null;
break;
}
}
}
}
4.这个是main类,里面就是解析常量池的主要逻辑。
package classFileReader;
import classFileReader.ConstantPoolInfo.ConstantPoolInfoAnalysis;
import util.BytesToOtherUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class ClassFileTest {
public static void main(String[] args) throws IOException {
File file = new File("/Users/caohao/Downloads/spring-framework/ch-jvm/build/classes/java/main/util/BytesToOtherUtil.class");
FileInputStream inputStream = new FileInputStream(file);
boolean flag = true;
//读取魔术
byte[] moshu = ClassFileReader.readU4(inputStream);
flag = ClassFileReader.checkMOSHU(moshu);
if (!flag){
System.out.println("魔数不是CAFEBABY!");
return;
}
System.out.println("moshu is ok!");
//读取version
byte[] version = ClassFileReader.readU4(inputStream);
ClassFileReader.checkVersion(version);
//读取常量池的元素count
byte[] constantPoolCount = ClassFileReader.readU2(inputStream);
int constantPoolCountInt = BytesToOtherUtil.bytesToInt(constantPoolCount);
System.out.println("this classFile's constantPoolCount is "+constantPoolCountInt);
//读取常量池元素
for (int i=0;i<constantPoolCountInt;i++){
int tag = ClassFileReader.readU1(inputStream);
ConstantPoolInfoAnalysis.analysisByTag(tag,inputStream,i+1);
}
inputStream.close();
}
}
之后随便找了个class文件做了一下测试结果如下
moshu is ok!
jdk version is jdk1.8
this classFile’s constantPoolCount is 53
#1 =Methodref #8,#39
#2 =Methodref #40,#41
#3 =long 255
#4 =Methodref #7,#42
#5 =Methodref #43,#44
#6 =Class #45
#7 =Class #46
#8 =utf8
#9 =utf8 ()V
#10 =utf8 Code
#11 =utf8 LineNumberTable
#12 =utf8 LocalVariableTable
#13 =utf8 this
#14 =utf8 Lutil/BytesToOtherUtil;
#15 =utf8 bytesToInt
#16 =utf8 ([B)I
#17 =utf8 aByte
#18 =utf8 B
#19 =utf8 bytes
#20 =utf8 [B
#21 =utf8 sum
#22 =utf8 I
#23 =utf8 StackMapTable
#24 =Class #21
#25 =utf8 bytesToFloat
#26 =utf8 ([B)F
#27 =utf8 accum
#28 =utf8 bytesToLong
#29 =utf8 ([B)J
#30 =utf8 i
#31 =utf8 doubleSize
#32 =utf8 l
#33 =utf8 J
#34 =utf8 bytesToDouble
#35 =utf8 ([B)D
#36 =utf8 SourceFile
#37 =utf8 BytesToOtherUtil.java
#38 =NameAndType #9,#10
#39 =Class #47
#40 =NameAndType #48,#49
#41 =NameAndType #29,#30
#42 =Class #50
#43 =NameAndType #51,#52
#44 =utf8 util/BytesToOtherUtil
#45 =utf8 java/lang/Object
#46 =utf8 java/lang/Float
#47 =utf8 intBitsToFloat
#48 =utf8 (I)F
#49 =utf8 java/lang/Double
#50 =utf8 longBitsToDouble
#51 =utf8 (J)D
目前只到这里,我们已经可以将常量池的部分读取出来了,下面模拟jvm来对这些数据进行内存分配和存储
对于oop-class这方面的知识可以看我前面的博客
首先模拟一个元数据空间,根据hotspot的代码他是将常量池存放在matedata中,所以我们这里也是将它存储在一个用静态list来模拟的matedata中,之所以用的list而不是array数组是因为我们这里所模拟的不打算预先开辟一个过大的空间来存放数据所以使用的是linkedlist这个扩容效率高一点的容器。
下面贴代码
首先就是我们的matedata
public class MateData {
public static List<OopDesc> mateData = new LinkedList<>();
}
其次则是一些OOPDesc的类(没有全部写完,后面继续实现其他JVM功能时一一完善)
public class OopDesc {
OopDesc markOop;
OopDesc mateData;
}
public class ConstantPoolOopDesc extends OopDesc {
Object[] elements;
public ConstantPoolOopDesc(int length) {
this.elements = new Object[length];
}
public Object[] getElements() {
return elements;
}
}
再就是对应JVM为常量池所描述的6个存储索引的对象一一以类的方式进行构建,下面贴一个实例,其他的5个都是差不多的
public class ConstantMethodrefInfo {
public Object ClassIndex;
public Object NameAndTypeIndex;
public ConstantMethodrefInfo(Object classIndex, Object nameAndTypeIndex) {
ClassIndex = classIndex;
NameAndTypeIndex = nameAndTypeIndex;
}
}
然后是对之前constantpoolinfoanalysis类的修改,贴出修改部分的代码
public static ConstantPoolOopDesc allocateOneNewConstantPool(int length){
return new ConstantPoolOopDesc(length);
}
public static void analysisByTag(int tag , FileInputStream inputStream,int index,ConstantPoolOopDesc constantPoolOopDesc) throws IOException {
Object[] constantPool = constantPoolOopDesc.getElements();
byte[] bytes;
switch (tag){
case 1:{
bytes = ClassFileReader.readU2(inputStream);
int length = BytesToOtherUtil.bytesToInt(bytes);
bytes = new byte[length];
inputStream.read(bytes);
String s = new String(bytes);
System.out.println("#"+index+" =utf8 "+s);
constantPool[index] = s;
bytes=null;
break;
}
case 3:{
bytes = ClassFileReader.readU4(inputStream);
int i = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =integer "+i);
constantPool[index] = i;
bytes=null;
break;
}
case 4:{
bytes = ClassFileReader.readU4(inputStream);
float i = BytesToOtherUtil.bytesToFloat(bytes);
System.out.println("#"+index+" =float "+i);
constantPool[index] = i;
bytes=null;
break;
}
case 5:{
bytes = ClassFileReader.readU8(inputStream);
long i = BytesToOtherUtil.bytesToLong(bytes);
System.out.println("#"+index+" =long "+i+"l");
constantPool[index] = i;
bytes=null;
break;
}
case 6:{
bytes = ClassFileReader.readU8(inputStream);
double i = BytesToOtherUtil.bytesToDouble(bytes);
System.out.println("#"+index+" =double "+i);
constantPool[index] = i;
bytes=null;
break;
}
//从这里开始脱离基本类型的解析
case 7:{
bytes = ClassFileReader.readU2(inputStream);
int i = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =Class #"+i);
constantPool[index] = new ConstantClassInfo(i);
bytes = null;
break;
}
case 8:{
bytes = ClassFileReader.readU2(inputStream);
int i = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =String #"+i);
constantPool[index] = new ConstantStringInfo(i);
bytes = null;
break;
}
//从这里开始都是双索引的元素解析
case 9:{
bytes = ClassFileReader.readU2(inputStream);
int index1 = BytesToOtherUtil.bytesToInt(bytes);
bytes = ClassFileReader.readU2(inputStream);
int index2 = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =Fieldref #"+index1+",#"+index2);
constantPool[index] = new ConstantFieldrefInfo(index1,index2);
bytes = null;
break;
}
case 10:{
bytes = ClassFileReader.readU2(inputStream);
int index1 = BytesToOtherUtil.bytesToInt(bytes);
bytes = ClassFileReader.readU2(inputStream);
int index2 = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =Methodref #"+index1+",#"+index2);
constantPool[index] = new ConstantMethodrefInfo(index1,index2);
bytes = null;
break;
}
case 11:{
bytes = ClassFileReader.readU2(inputStream);
int index1 = BytesToOtherUtil.bytesToInt(bytes);
bytes = ClassFileReader.readU2(inputStream);
int index2 = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =InterfaceMethodref #"+index1+",#"+index2);
constantPool[index] = new ConstantInterfaceMethodrefInfo(index1,index2);
bytes = null;
break;
}
case 12:{
bytes = ClassFileReader.readU2(inputStream);
int index1 = BytesToOtherUtil.bytesToInt(bytes);
bytes = ClassFileReader.readU2(inputStream);
int index2 = BytesToOtherUtil.bytesToInt(bytes);
System.out.println("#"+index+" =NameAndType #"+index1+",#"+index2);
constantPool[index] = new ConstantNameAndTypeInfo(index1,index2);
bytes = null;
break;
}
}
}
最后是对test主类的修改
就是在之前的基础上加上如下代码
//将解析好的常量池指针存放到内存中,并保持根引用来防止被GC
MateData.mateData.add(constantPoolOopDesc);
//测试存储的数据是否正确
Object[] elements = constantPoolOopDesc.getElements();
for (int i = 0; i < elements.length; i++) {
if (elements[i]!=null){
if (elements[i] instanceof ConstantClassInfo){
ConstantClassInfo info = (ConstantClassInfo)elements[i];
System.out.println("index="+i+"----values="+info.index);
}else if (elements[i] instanceof ConstantStringInfo){
ConstantStringInfo info = (ConstantStringInfo) elements[i];
System.out.println("index="+i+"----values="+info.index);
}else if (elements[i] instanceof ConstantMethodrefInfo){
ConstantMethodrefInfo info = (ConstantMethodrefInfo) elements[i];
System.out.println("index="+i+"----values="+info.ClassIndex+","+info.NameAndTypeIndex);
}else if (elements[i] instanceof ConstantFieldrefInfo){
ConstantFieldrefInfo info = (ConstantFieldrefInfo) elements[i];
System.out.println("index="+i+"----values="+info.ClassIndex+","+info.NameAndTypeIndex);
}else if (elements[i] instanceof ConstantInterfaceMethodrefInfo){
ConstantInterfaceMethodrefInfo info = (ConstantInterfaceMethodrefInfo) elements[i];
System.out.println("index="+i+"----values="+info.ClassIndex+","+info.NameAndTypeIndex);
}else if (elements[i] instanceof ConstantNameAndTypeInfo){
ConstantNameAndTypeInfo info = (ConstantNameAndTypeInfo) elements[i];
System.out.println("index="+i+"----values="+info.index1+","+info.index2);
}else {
System.out.println("index="+i+"----values="+elements[i]);
}
}
}
测试是通过的,说明我们的逻辑没问题,后面一篇文章会对字节码后面的解析进行模拟。