2.4 解析二进制chunk
如前所述,具体的二进制chunk解析工作由reader结构体来完成。请读者在binchunk目录下面创建reader.go文件,在里面定义reader结构体,代码如下所示。
package binchunk import "encoding/binary" import "math" type reader struct { data []byte }
reader结构体只有一个data字段,存放将要被解析的二进制chunk数据。下面我们来看看由reader结构体解析二进制chunk都有哪些方法。
2.4.1 读取基本数据类型
读取基本数据类型的方法一共有7种,其他方法通过调用这7种方法来从二进制chunk里提取数据。最简单的是“readByte()”方法,即从字节流里读取一个字节,代码如下所示。
func (self *reader) readByte() byte { b := self.data[0] self.data = self.data[1:] return b }
readUint32()方法使用小端方式从字节流里读取一个cint存储类型(占4个字节,映射为Go语言uint32类型)的整数,代码如下所示。
func (self *reader) readUint32() uint32 { i := binary.LittleEndian.Uint32(self.data) self.data = self.data[4:] return i }
readUint64()方法使用小端方式从字节流里读取一个size_t存储类型(占8个字节,映射为Go语言uint64类型)的整数,代码如下所示。
func (self *reader) readUint64() uint64 { i := binary.LittleEndian.Uint64(self.data) self.data = self.data[8:] return i }
readLuaInteger()方法借助readUint64()方法从字节流里读取一个Lua整数(占8个字节,映射为Go语言int64类型),代码如下所示。
func (self *reader) readLuaInteger() int64 { return int64(self.readUint64()) }
readLuaNumber()方法借助readUint64()方法从字节流里读取一个Lua浮点数(占8个字节,映射为Go语言float64类型),代码如下所示。
func (self *reader) readLuaNumber() float64 { return math.Float64frombits(self.readUint64()) }
readString()方法从字节流里读取字符串(映射为Go语言string类型),代码如下所示:
func (self *reader) readString() string { size := uint(self.readByte()) // 短字符串? if size == 0 { // NULL字符串 return "" } if size == 0xFF { // 长字符串 size = uint(self.readUint64()) } bytes := self.readBytes(size -1) return string(bytes) }
readBytes()方法从字节流里读取n个字节,代码如下所示。
func (self *reader) readBytes(n uint) []byte { bytes := self.data[:n] self.data = self.data[n:] return bytes }
2.4.2 检查头部
checkHeader()方法从字节流里读取并检查二进制chunk头部的各个字段,如果发现某个字段和期望不符,则调用panic函数终止加载,代码如下所示。
func (self *reader) checkHeader() { if string(self.readBytes(4)) ! = LUA_SIGNATURE { panic("not a precompiled chunk! ") } else if self.readByte() ! = LUAC_VERSION { panic("version mismatch! ") } else if self.readByte() ! = LUAC_FORMAT { panic("format mismatch! ") } else if string(self.readBytes(6)) ! = LUAC_DATA { panic("corrupted! ") } else if self.readByte() ! = CINT_SIZE { panic("int size mismatch! ") } else if self.readByte() ! = CSZIET_SIZE { panic("size_t size mismatch! ") } else if self.readByte() ! = INSTRUCTION_SIZE { panic("instruction size mismatch! ") } else if self.readByte() ! = LUA_INTEGER_SIZE { panic("lua_Integer size mismatch! ") } else if self.readByte() ! = LUA_NUMBER_SIZE { panic("lua_Number size mismatch! ") } else if self.readLuaInteger() ! = LUAC_INT { panic("endianness mismatch! ") } else if self.readLuaNumber() ! = LUAC_NUM { panic("float format mismatch! ") } }
2.4.3 读取函数原型
readProto()方法从字节流里读取函数原型,代码如下所示。
func (self *reader) readProto(parentSource string) *Prototype { source := self.readString() if source == "" { source = parentSource } return &Prototype{ Source: source, LineDefined: self.readUint32(), LastLineDefined: self.readUint32(), NumParams: self.readByte(), IsVararg: self.readByte(), MaxStackSize: self.readByte(), Code: self.readCode(), Constants: self.readConstants(), Upvalues: self.readUpvalues(), Protos: self.readProtos(source), LineInfo: self.readLineInfo(), LocVars: self.readLocVars(), UpvalueNames: self.readUpvalueNames(), } }
读取函数基本信息的部分比较简单,只有Source字段的处理稍微有点麻烦,这是因为Lua编译器只给主函数设置了源文件名以减少冗余数据,所以子函数原型需要从自己的父函数原型那里获取源文件名。下面我们来看一下指令表等列表的读取方法。
readCode()方法从字节流里读取指令表,代码如下所示。
func (self *reader) readCode() []uint32 { code := make([]uint32, self.readUint32()) for i := range code { code[i] = self.readUint32() } return code } readConstants()方法从字节流里读取常量表,代码如下所示。 func (self *reader) readConstants() []interface{} { constants := make([]interface{}, self.readUint32()) for i := range constants { constants[i] = self.readConstant() } return constants }
readConstant()方法从字节流里读取一个常量,代码如下所示。
func (self *reader) readConstant() interface{} { switch self.readByte() { // tag case TAG_NIL: return nil case TAG_BOOLEAN: return self.readByte() ! = 0 case TAG_INTEGER: return self.readLuaInteger() case TAG_NUMBER: return self.readLuaNumber() case TAG_SHORT_STR: return self.readString() case TAG_LONG_STR: return self.readString() default: panic("corrupted! ") } }
readUpvalues()方法从字节流里读取Upvalue表,代码如下所示。
func (self *reader) readUpvalues() []Upvalue { upvalues := make([]Upvalue, self.readUint32()) for i := range upvalues { upvalues[i] = Upvalue{ Instack: self.readByte(), Idx: self.readByte(), } } return upvalues }
由于函数原型本身就是递归数据结构,所以readProto()方法也会递归调用自己去读取子函数原型。
readProtos()方法从字节流里读取子函数原型表,代码如下所示。
func (self *reader) readProtos(parentSource string) []*Prototype { protos := make([]*Prototype, self.readUint32()) for i := range protos { protos[i] = self.readProto(parentSource) } return protos } readLineInfo()方法从字节流里读取行号表,代码如下所示。 func (self *reader) readLineInfo() []uint32 { lineInfo := make([]uint32, self.readUint32()) for i := range lineInfo { lineInfo[i] = self.readUint32() } return lineInfo }
readLocVars()方法从字节流里读取局部变量表,代码如下所示。
func (self *reader) readLocVars() []LocVar { locVars := make([]LocVar, self.readUint32()) for i := range locVars { locVars[i] = LocVar{ VarName: self.readString(), StartPC: self.readUint32(), EndPC: self.readUint32(), } } return locVars }
readUpvalueNames()方法从字节流里读取Upvalue名列表,代码如下所示。
func (self *reader) readUpvalueNames() []string { names := make([]string, self.readUint32()) for i := range names { names[i] = self.readString() } return names }
到此为止,二进制chunk解析代码也都介绍完毕了。在2.5节,我们会实现一个简化版的Lua反编译器。