官术网_书友最值得收藏!

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格式。

主站蜘蛛池模板: 乡城县| 扬州市| 望谟县| 互助| 永年县| 和田县| 铜川市| 原阳县| 福清市| 虎林市| 贵阳市| 固原市| 同江市| 察隅县| 沂源县| 政和县| 台南县| 驻马店市| 广灵县| 天柱县| 江口县| 新竹市| 马边| 青海省| 泌阳县| 阳曲县| 察雅县| 长岛县| 噶尔县| 广平县| 贞丰县| 密山市| 滨海县| 油尖旺区| 新密市| 临泽县| 张北县| 沛县| 镇雄县| 琼中| 铜山县|