正确解析class文件是万里长征第一步。本篇我们会全程使用golang完成class文件的解析工作。
数据类型
JVM的class文件完全是二进制文件,最小单位是字节,也有数据类型,但都是字节的整数倍(废话)。规范中class文件一共有两类数据,一种是无符号整数,一种是表。无符号整数一共有u1,u2, u4, u8四种类型,分别表示8bit, 16bit, 32bit, 64bit的无符号整数。表则是无符号整数的集合,class文件中在出现表之前都会先跟着一个u2类型的长度数据,表名后面表的总长度,这样才能正确解析表。
另外还要注意字节序的问题,JVM规范规定class文件统一采用Big Endian
字节序,也就是低地址存储高位,高地址存放低位。如果是用C/C++语言写JVM,则程序使用的字节序是跟CPU绑定的,比如intel的x86平台使用Little Endian
,PowerPC则是Big Endian
。不过幸好我们的主角是Go, Go统一采用大端,这样就不需要操心平台了。假设我们用一个二元素的[]byte
数组来存储从class文件中按顺序读到的u16类型数据,那么byte[0]
就是u16的高8位,byte[1]
就是低8位,组合起来就是:
uint16(b[1]) | uint16(b[0]) << 8
即将高位左移8位,然后跟低位做按位或操作即可还原。
Go读取二进制数据常用函数
我们使用标准库的io.Reader
接口从文件中读取字节,然后从字节数组中还原原本的数据类型,例如读取u16类型的数据可以这么写:
func ReadInt16(bufReader io.Reader) (uint16, error) {
numBuf := make([]byte, 2, 2)
_, err := bufReader.Read(numBuf)
if nil != err {
return 0, err
}
var num uint16
err = binary.Read(bytes