自己动手实现Lua:虚拟机、编译器和标准库
上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

这样就算完成了,看起来还挺像模像样的。