上QQ阅读APP看书,第一时间看更新
2.5 测试本章代码
前面我们详细讨论了二进制chunk格式,定义了函数原型相关的结构体,并且完成了解析代码。本节我们将通过实现一个简化版的Lua反编译器来进一步加深读者对二进制chunk格式的认识。请读者打开$LUAGO/go/ch02/src/luago/main.go文件(这个文件是从第1章复制过来的),把里面的代码改成如下代码。
package main import "fmt" import "io/ioutil" import "os" import "luago/binchunk" func main() { if len(os.Args) > 1 { data, err := ioutil.ReadFile(os.Args[1]) if err ! = nil { panic(err) } proto := binchunk.Undump(data) list(proto) } }
我们通过命令行参数把需要反编译的二进制chunk文件传递给main()函数,然后用main()函数读取该文件数据,调用Undump()函数把它解析为函数原型,最后通过list()函数把函数原型的信息打印到控制台。下面是list()函数的代码。
func list(f *binchunk.Prototype) { printHeader(f) printCode(f) printDetail(f) for _, p := range f.Protos { list(p) } }
list()函数先打印出函数的基本信息,然后打印指令表和其他详细信息,最后递归调用自己把子函数信息打印出来。printHeader()函数的代码如下所示。
func printHeader(f *binchunk.Prototype) { funcType := "main" if f.LineDefined > 0 { funcType = "function" } varargFlag := "" if f.IsVararg > 0 { varargFlag = "+" } fmt.Printf("\n%s <%s:%d, %d> (%d instructions)\n", funcType, f.Source, f.LineDefined, f.LastLineDefined, len(f.Code)) fmt.Printf("%d%s params, %d slots, %d upvalues, ", f.NumParams, varargFlag, f.MaxStackSize, len(f.Upvalues)) fmt.Printf("%d locals, %d constants, %d functions\n", len(f.LocVars), len(f.Constants), len(f.Protos)) }
我们在第3章会详细讨论Lua虚拟机指令格式,这里printCode()函数只打印出指令的序号、行号和十六进制表示,代码如下所示。
func printCode(f *binchunk.Prototype) { for pc, c := range f.Code { line := "-" if len(f.LineInfo) > 0 { line = fmt.Sprintf("%d", f.LineInfo[pc]) } fmt.Printf("\t%d\t[%s]\t0x%08X\n", pc+1, line, c) } }
printDetail()函数打印常量表、局部变量表和Upvalue表,代码如下所示。
func printDetail(f *binchunk.Prototype) { fmt.Printf("constants (%d):\n", len(f.Constants)) for i, k := range f.Constants { fmt.Printf("\t%d\t%s\n", i+1, constantToString(k)) } fmt.Printf("locals (%d):\n", len(f.LocVars)) for i, locVar := range f.LocVars { fmt.Printf("\t%d\t%s\t%d\t%d\n", i, locVar.VarName, locVar.StartPC+1, locVar.EndPC+1) } fmt.Printf("upvalues (%d):\n", len(f.Upvalues)) for i, upval := range f.Upvalues { fmt.Printf("\t%d\t%s\t%d\t%d\n", i, upvalName(f, i), upval.Instack, upval.Idx) } }
constantToString()函数把常量表里的常量转换成字符串,代码如下所示。
func constantToString(k interface{}) string { switch k.(type) { case nil: return "nil" case bool: return fmt.Sprintf("%t", k) case float64: return fmt.Sprintf("%g", k) case int64: return fmt.Sprintf("%d", k) case string: return fmt.Sprintf("%q", k) default: return "? " } }
upvalName()函数根据Upvalue索引从调试信息里找出Upvalue的名字,代码如下所示。
func upvalName(f *binchunk.Prototype, idx int) string { if len(f.UpvalueNames) > 0 { return f.UpvalueNames[idx] } return "-" }
现在一切准备就绪,请读者在命令行里执行如下命令,编译本章测试代码。
$ cd $LUAGO/go/ $ export GOPATH=$PWD/ch02 $ go install luago
如果没有任何输出,那就表示编译成功了,在ch02/bin目录下会出现可执行文件luago。再编译一下“Hello, World! ”程序,把输出文件当作参数传递给luago,反编译输出如下。
$ luac ../lua/ch02/hello_world.lua $ ./ch02/bin/luago luac.out main <@../lua/ch02/hello_world.lua:0,0> (4 instructions) 0+ params, 2 slots, 1 upvalues, 0 locals, 2 constants, 0 functions 1 [1] 0x00400006 2 [1] 0x00004041 3 [1] 0x01004024 4 [1] 0x00800026 constants (2): 1 "print" 2 "Hello, World! " locals (0): upvalues (1): 0 _ENV 1 0
这样就算完成了,看起来还挺像模像样的。