- 自己動手實現Lua:虛擬機、編譯器和標準庫
- 張秀宏
- 2239字
- 2019-01-03 15:00:08
2.2 luac命令介紹
luac命令主要有兩個用途:第一,作為編譯器,把Lua源文件編譯成二進制chunk文件:第二,作為反編譯器,分析二進制chunk,將信息輸出到控制臺。這里仍然以Java為對照,JDK提供了單獨的命令行工具javap,用來反編譯class文件,而Lua則是將編譯命令和反編譯命令整合在了一起。在命令行里直接執行luac命令(不帶任何參數)可以看到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! ”程序為例討論luac命令的兩種用法。請讀者在$LUAGO/lua/ch02/目錄下創建hello_world.lua文件,并且在里面輸入如下代碼。
print("Hello, World! ")
為了便于討論,我們暫時將當前路徑切換到$LUAGO/lua/ch02/目錄。
$ cd $LUAGO/lua/ch02
2.2.1 編譯Lua源文件
將一個或者多個文件名作為參數調用luac命令就可以編譯指定的Lua源文件,如果編譯成功,在當前目錄下會出現luac.out文件,里面的內容就是對應的二進制chunk。如果不想使用默認的輸出文件,可以使用“-o”選項對輸出文件進行明確指定。編譯生成的二進制chunk默認包含調試信息(行號、變量名等),可以使用“-s”選項告訴luac去掉調試信息。另外,如果僅僅想檢查語法是否正確,不想產生輸出文件,可以使用“-p”選項進行編譯。下面是luac的一些用法示例。
$ luac hello_world.lua # 生成luac.out $ luac -o hw.luac hello_world.lua # 生成hw.luac $ luac -s hello_world.lua # 不包含調試信息 $ luac -p hello_world.lua # 只進行語法檢查
為了方便后面的討論,本節還會簡單介紹一下Lua編譯器的內部工作原理,本書第二部分(第14~17章)會詳細介紹Lua編譯器的實現細節。
Lua編譯器以函數為單位進行編譯,每一個函數都會被Lua編譯器編譯為一個內部結構,這個結構叫作“原型”(Prototype)。原型主要包含6部分內容,分別是:函數基本信息(包括參數數量、局部變量數量等)、字節碼、常量表、Upvalue表、調式信息、子函數原型列表。由此可知,函數原型是一種遞歸結構,并且Lua源碼中函數的嵌套關系會直接反映在編譯后的原型里。
細心的讀者一定會想到這樣一個問題:前面我們寫的“Hello, World! ”程序里面只有一條打印語句,并沒有定義函數,那么Lua編譯器是怎么編譯這個文件的呢?由于Lua是腳本語言,如果我們每執行一段腳本都必須要定義一個函數(就像Java那樣),豈不是很麻煩?所以這個吃力不討好的工作就由Lua編譯器代勞了。
Lua編譯器會自動為我們的腳本添加一個main函數(后文稱其為主函數),并且把整個程序都放進這個函數里,然后再以它為起點進行編譯,那么自然就把整個程序都編譯出來了。這個主函數不僅是編譯的起點,也是未來Lua虛擬機解釋執行程序時的入口。我們寫的“Hello, World! ”程序被Lua編譯器加工之后,就變成了下面這個樣子。
function main(...) print("Hello, World! ") return end
把主函數編譯成函數原型后,Lua編譯器會給它再添加一個頭部(Header,詳見2.3.3節),然后一起dump成luac.out文件,這樣,一份熱乎的二進制chunk文件就新鮮出爐了。綜上所述,函數原型和二進制chunk的內部結構如圖2-3所示。

圖2-3 二進制chunk內部結構
2.2.2 查看二進制chunk
二進制chunk之所以使用二進制格式,是為了方便虛擬機加載,然而對人類卻不夠友好,因為其很難直接閱讀。如前所述,luac命令兼具編譯和反編譯功能,使用“-l”選項可以將luac切換到反編譯模式。正如javap命令是查看class文件的利器,luac命令搭配“-l”選項則是查看二進制chunk的利器。本節的目標是學會閱讀luac的反編譯輸出。在2.3節,我們將深入到二進制chunk的內部來研究其格式。
以前面編譯出來的hello_world.luac文件為例,其反編譯輸出如下。
$ luac -l hello_world.luac main <hello_world.lua:0,0> (4 instructions at 0x7fb4dbc030f0) 0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions 1 [1]GETTABUP 0 0-1 ; _ENV "print" 2 [1]LOADK 1-2 ; "Hello, World! " 3 [1]CALL 0 2 1 4 [1]RETURN 0 1
上面的例子以二進制chunk文件為參數,實際上也可以直接以Lua源文件為參數,luac會先編譯源文件,生成二進制chunk文件,然后再進行反編譯,產生輸出。由于“Hello, World! ”程序只有一條打印語句,所以編譯出來的二進制chunk里也只有一個主函數原型(沒有子函數),因此反編譯輸出里也只有主函數信息。如果我們的Lua程序里有函數定義,那么luac反編譯器會按順序依次輸出這些函數原型的信息,例如如下的Lua程序(請讀者將其保存在$LUAGO/lua/ch02/foo_bar.lua文件中)。
function foo() function bar() end end
反編譯輸出中會依次包含main、foo和bar函數的信息,如下所示。
$ luac -l foo_bar.lua main <foo_bar.lua:0,0> (3 instructions at 0x7fc43fc02b20) 0+ params, 2 slots, 1 upvalue, 0 locals, 1 constant, 1 function 1 [4] CLOSURE 0 0 ; 0x7fc43fc02cc0 2 [1] SETTABUP 0-1 0 ; _ENV "foo" 3 [4] RETURN 0 1 function <foo_bar.lua:1,4> (3 instructions at 0x7fc43fc02cc0) 0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 1 function 1 [3] CLOSURE 0 0 ; 0x7fc43fc02e40 2 [2] SETTABUP 0-1 0 ; _ENV "bar" 3 [4] RETURN 0 1 function <foo_bar.lua:2,3> (1 instruction at 0x7fc43fc02e40) 0 params, 2 slots, 0 upvalues, 0 locals, 0 constants, 0 functions 1 [3] RETURN 0 1
反編譯打印出的函數信息包含兩個部分:前面兩行是函數基本信息,后面是指令列表。
第一行如果以main開頭,說明這是編譯器為我們生成的主函數;以function開頭,說明這是一個普通函數。接著是定義函數的源文件名和函數在文件里的起止行號(對于主函數,起止行號都是0),然后是指令數量和函數地址。
第二行依次給出函數的固定參數數量(如果有+號,表示這是一個vararg函數)、運行函數所必要的寄存器數量、upvalue數量、局部變量數量、常量數量、子函數數量。如果讀者看不懂這些信息也沒有關系,我們在后面的章節中會陸續介紹這些信息。
指令列表里的每一條指令都包含指令序號、對應行號、操作碼和操作數。分號后面是luac根據指令操作數生成的注釋,以便于我們理解指令。第3章會詳細介紹Lua虛擬機指令。
以上看到的是luac反編譯器精簡模式的輸出內容,如果使用兩個“-l”選項,則可以進入詳細模式,這樣,luac會把常量表、局部變量表和upvalue表的信息也打印出來。
$ luac -l -l hello_world.lua main <hello_world.lua:0,0> (4 instructions at 0x7fbcb5401c00) 0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions 1 [1] GETTABUP 0 0-1 ; _ENV "print" 2 [1] LOADK 1-2 ; "Hello, World! " 3 [1] CALL 0 2 1 4 [1] RETURN 0 1 constants (2) for 0x7fbcb5401c00: 1 "print" 2 "Hello, World! " locals (0) for 0x7fbcb5401c00: upvalues (1) for 0x7fbcb5401c00: 0 _ENV 1 0
到這里luac命令反編譯模式的基本用法和閱讀方法就介紹完畢了,如果讀者覺得一頭霧水也不要擔心,暫時只要對二進制chunk有一個粗略的認識就可以了,在2.3節我們會詳細地討論二進制chunk格式。
- Docker技術入門與實戰(第3版)
- 構建移動網站與APP:HTML 5移動開發入門與實戰(跨平臺移動開發叢書)
- 趣學Python算法100例
- Learn Programming in Python with Cody Jackson
- Web Application Development with MEAN
- Practical Game Design
- 深入淺出RxJS
- Python全棧數據工程師養成攻略(視頻講解版)
- Extreme C
- Maker基地嘉年華:玩轉樂動魔盒學Scratch
- MyBatis 3源碼深度解析
- Instant Automapper
- 讀故事學編程:Python王國歷險記
- Linux Networking Cookbook
- Three.js Essentials