Lua 5.2 字节码和虚拟机
by Dirk Laurie
Copyright © 2013. Freely available under the terms of the Lua license.
原文:http://files.catwell.info/misc/mirror/lua-5.2-bytecode-vm-dirk-laurie/lua52vm.html 翻译:阳春
序言
我写此文是因为只有如此我才能理解它。此篇适合给非常熟悉Lua5.2参考手册(LRM5.2),在至少一台机器(Knuth’s MIX就行了)上有一点机器指令经验,并且想要对Lua5.2源码做些修修补补的读者阅读。
感谢:我从Elijah Frederickson的网站上借鉴了许多(https://github.com/mlnlover11/LuaAssemblyTools) (LAT) ,上面不仅有Lua5.1和Lua5.2的汇编工具,还有一份超完整的极其有用的其他人早期工作的集合,特别是,Kein-Hong Man的No-Frills Introduction to Lua5.1 VM Instructions(NFI),虽然是为Lua5.1而写但仍具极大的参考价值。当然,早晚你都得阅读Lus5.2源码的简练而权威的注释。
二进制区块
一个区块可以被存储在文件或者宿主程序的字符串中。为了执行一个区块,Lua首先为一台虚拟机将区块预编译成指令,然后它用一个解释器为虚拟机执行编译后的代码。
区块也可以被预编译成二进制形式,细节请参考luac。程序的源码形式和编译后的形式可以互转,Lua自动检测文件类型并自动执行。——§3.3.2
上面是LRM5.2中唯一提到”虚拟机”字眼的段落。三个标准库函数处理二进制区块:load、loadfile和string.dump.这就是用户能从官方获取的全部信息。对闭源代码来说这也就是用户能知道的全部了。
但是Lua是开源的。它的源代码有很好的注释,同时我们也可以做逆向工程。让我们从luac开始。
$ luac
luac: no input files given
usage: luac [options] [filenames]
Available options are:
-l list (use -l -l for full listing)
-o name output to file 'name' (default is "luac.out")
-p parse only
-s strip debug information
-v show version information
-- stop handling options
- stop handling options and process stdin
嗯,我们会给它一个Hello World程序,略微修改一下让它更为有趣。第二行之后,终端输入停止(我按Control-D)然后luac的输出开始。
$ luac -l -l -v -s -
Lua 5.2.1 Copyright (C) 1994-2012 Lua.org, PUC-Rio
local hello = "Hello"
print (hello.." World!")
main <0> (7 instructions at 0x9984970)0>
0+ params, 4 slots, 1 upvalue, 1 local, 3 constants, 0 functions
1 [1] LOADK 0 -1 ; "Hello"
2 [2] GETTABUP 1 0 -2 ; _ENV "print"
3 [2] MOVE 2 0
4 [2] LOADK 3 -3 ; " World!"
5 [2] CONCAT 2 2 3
6 [2] CALL 1 2 1
7 [2] RETURN 0 1
constants (3) for 0x9984970:
1 "Hello"
2 "print"
3 " World!"
locals (1) for 0x9984970:
0 hello 2 8
upvalues (1) for 0x9984970:
0 _ENV 1 0
我们稍后讨论这个输出,在这时候我想要你看到这四个清单:稍微注释过的汇编代码,常数,局部变量,外部局部变量。
现在我们回到Lua本身。load同一区块,dump下来获取其字节码,把它写到文件里。我修改(patched)了我的Lua,用三个空格代替了控制台的>,这样你可以直接剪切粘贴这些代码。
$ lua
Lua 5.2.1 Copyright (C) 1994-2012 Lua.org, PUC-Rio
func = load 'local hello = "Hello" print (hello.." World!")'
chunk = string.dump(func)
io.open("hello.luac","wb"):write(chunk)
luac也可以接受二进制区块。我们可以用它来看看load做了什么。
$ luac -l -l -v hello.luac
Lua 5.2.1 Copyright (C) 1994-2012 Lua.org, PUC-Rio
main (7 instructions at 0x90d0b50)
0+ params, 4 slots, 1 upvalue, 1 local, 3 constants, 0 functions
1 [1] LOADK 0 -1 ; "Hello"
2 [1] GETTABUP 1 0 -2 ; _ENV "print"
3 [1] MOVE 2 0
4 [1] LOADK 3 -3 ; " World!"
5 [1] CONCAT 2 2 3
6 [1] CALL 1 2 1
7 [1] RETURN 0 1
constants (3) for 0x90d0b50:
1 "Hello"
2 "print"