Lua2.4 儲存位元組碼 dump.c

來源:互聯網
上載者:User

標籤:io   使用   ar   for   檔案   資料   sp   on   cti   

嚴格意義上說,把 dump 這部分叫儲存位元組碼並不準確。
因為除了儲存 TFunc 裡的位元組碼 code 之外,還儲存了其它的內容。比如函數頭,位元組序及位元組碼需要的資料等。所以,準確的說應該叫儲存位元組碼及環境,或者叫做儲存世界,就是位元組碼產生之後的運行時相關資訊也儲存了下來。
可以從儲存下來的這些資訊恢複出位元組碼執行時需要的運行時,預設的儲存檔案就是之前所說的那個 luac.out 的二進位檔案。
咬文嚼字一下,dump 這裡翻譯為儲存意思應該差不多,undump 則是它的相反操作,可以叫做恢複。dump 這個詞在程式員使用時其實不翻譯意思也是比較明確的。比如,常見的就是 coredump 或者 dump 檔案之類的。所以,下面說的過程中在可能不再區別儲存或者 dump 了。
進入正題。
luac.c 裡調了兩個和 dump 相關的函數: DumpHeader 和 DumpFunction。下面分別看一下:

void DumpHeader(FILE* D){ Word w=TEST_WORD; float f=TEST_FLOAT; fputc(ID_CHUNK,D); fputs(SIGNATURE,D); fputc(VERSION,D); fwrite(&w,sizeof(w),1,D); fwrite(&f,sizeof(f),1,D);}

函數是儲存一些全域性的環境資訊 。
儲存 ID_CHUNK,相當於一個標籤,這樣在解譯器直接執行編譯器產生的二進位檔案 luac.out 時,看到檔案的第一個位元組是 ID_CHUNK 它就知道這是一個編譯過的檔案,就不再進行編譯,而是調用相應的 undump 從 luac.out 中獲得所儲存的全部資訊。
接著儲存字串 "Lua" 和版本號碼,版本號碼是一個十六進位數 0x23,這應該用的是和 Lua2.3 一樣的格式。
儲存一個字和一個浮點值,這兩個是為了確定位元組序(在不同的處理器體繫結構上,位元組序可能不同,就是常說的大端序或者小端序,網上有介紹,不深入了。不過一會兒看代碼的話也是能看出來的,因為代碼裡在對位元組序不同時有交換操作)。

在看 DumpFunction 之前,先看下 ThreadCode 。
ThreadCode 主要做了一件事,把符號和字串位置資訊在位元組碼中清空,把相同的符號或字串位置連起來。這可能也是這個函數叫 ThreadCode 的原因。
程式一上來先把符號和字串的下標清 0,就是頭兩個 for 迴圈做的事。
在第三個 for 迴圈裡,簡單解析位元組碼指令,通過 SawVar 和 SawStr 設定符號或字串在哪裡(位元組碼相於位元組碼起始處的位移量)被使用。
舉個小例子,比如 SawVar 第一次被調用時,at 是位元組碼的位移量,c.w 是索引,返回 0。
以同樣的 c.w 第二次調用 SawVar 時,返回的就是上一次的 at。這樣就相當於把相同的符號引用的地方串聯起來了。
這麼做的另一個原因是沒有用到的符號或者字串不會被 dump ,節省了空間。參見 DumpStrings 方法裡儲存之前先判斷相應的索引是否為 0。
這麼說可能有點抽象,稍後看一個小例子就什麼都明白了。而 undump.c 裡的 Unthread 函數就是上面這個串聯過程的逆過程。

void DumpFunction(TFunc* tf, FILE* D){ lastF=tf; ThreadCode(tf->code,tf->code+tf->size); fputc(ID_FUN,D); DumpSize(tf->size,D); DumpWord(tf->lineDefined,D); if (IsMain(tf))  DumpString(tf->fileName,D); else  DumpWord(tf->marked,D); DumpBlock(tf->code,tf->size,D); DumpStrings(D);}

儲存函數相關資訊。
串聯符號和字串,位元組碼裡面做出相應的改動。
dump ID_FUN 標籤,就是字元 ‘F‘。
dump 函數的大小 size。
dump 函數的定義行號 lineDefined。
如果是主函數,dump 檔案名稱。
如果不是主函數,dump 它的標記 marked。
dump 位元組碼。
dump 字串資訊,包括符號和字串。

其它的方法比較直觀,不細看了,下面看例子的時候可以很直觀的看到。
單說下 DumpStrings。

static void DumpStrings(FILE* D){ int i; for (i=0; i<lua_ntable; i++) {  if (VarLoc(i)!=0)  {   fputc(ID_VAR,D);   DumpWord(VarLoc(i),D);   DumpString(VarStr(i),D);  }  VarLoc(i)=i; } for (i=0; i<lua_nconstant; i++) {  if (StrLoc(i)!=0)  {   fputc(ID_STR,D);   DumpWord(StrLoc(i),D);   DumpString(StrStr(i),D);  }  StrLoc(i)=i; }}

看一下上面的那個 for 迴圈,它是用來儲存符號的。
迴圈裡面,先判斷符號的索引是否為 0 ,如果不是 0 ,說明符號在函數中被使用了,就儲存它。
儲存的時候,先 dump 一個 ID_VAR 標籤,也就是 ‘V‘,表明下面儲存的是一個符號。
接著儲存它在位元組碼中最後一次出現的位置,這個位置就是上面 ThreadCode 中調整過的那個位置。
儲存符號的字串。(注意,儲存字串時儲存的有字串的長度和字串本身。)
恢複符號在索引值,以備下一次 DumpFunction 使用。

下面看一個小例子,以加深理解。
---------------------------------

function add(x, y)    return x + yendprint (add(3, 4))

---------------------------------
這個還是之前看到的那個例子。

先看下它列印出來的位元組碼:
---------------------------------
main of "test.lua" (25 bytes at 001720D8)
     0 PUSHFUNCTION 00172168 ; "test.lua":1
     5 STOREGLOBAL 13 ; add
     8 PUSHGLOBAL 7 ; print
    11 PUSHGLOBAL 13 ; add
    14 PUSHBYTE 3
    16 PUSHBYTE 4
    18 CALLFUNC 2 1
    21 CALLFUNC 1 0
    24 RETCODE0
function "test.lua":1 (9 bytes at 00172168); used at main+1
     0 ADJUST 2
     2 PUSHLOCAL0 0 ;
     3 PUSHLOCAL1 1 ;
     4 ADDOP
     5 RETCODE 2
     7 RETCODE 2
---------------------------------

這個主要是為了對比觀察下面的 dump 的 luac.out,看下 dump 出來的 luac.out 的二進位檔案:
---------------------------------
1B 4C 75 61 23 34 12 46 0A BF 17 46 00 00 19 00 00 00 09 00 74 65 73 74 2E 6C 75 61 00 08 68 21 17 00 22 00 00 14 00 00 14 06 00 04 03 04 04 3F 02 01 3F 01 00 40 56 09 00 06 00 70 72 69 6E 74 00 56 0C 00 04 00 61 64 64 00 46 00 00 09 00 01 00 01 00 29 02 09 0A 30 41 02 41 02
---------------------------------
一點點的分析下上面的每個位元組代表的是什麼:
1B 4C 75 61 23 34 12 46 0A BF 17
這部分是 Header,也就是 DumpHeader 儲存下來的內容:
1B : 也就是十進位的 27,就是 ID_CHUNK。(上面的都是十六進位的表示,這裡就不在前面加 0x 了。)
4C 75 61 : 字串 "Lua",SIGNATURE。
23 : 版本號碼 0x23,VERSION。
34 12 :數字 0x1234,TEST_WORD。
46 0A BF 17 :浮點值 0.123456789e-23,TEST_FLOAT。
----------------
46 00 00 19 00 00 00 09 00 74 65 73 74 2E 6C 75 61 00 08 68 21 17 00 22 00 00 14 00 00 14 06 00 04 03 04 04 3F 02 01 3F 01 00 40 56 09 00 06 00 70 72 69 6E 74 00 56 0C 00 04 00 61 64 64 00
這個是主函數 dump 出來的內容。下面分別說下其中每個位元組的意思:
46 : 字元 ‘F‘,函數標籤 ID_FUN。

00 00 19 00:函數的 size,DumpSize 時用了兩個雙位元組,這裡是後面的高位元組代表實際的低位元組,所以大小為 0x19,剛好是 25,也就是上面的 print 列印出來的位元組碼中的 main of "test.lua" (25 bytes at 001720D8) 這裡的 25。

00 00 :主函數的 lineDefined,主函數的話這個值一定是 0 。

09 00 74 65 73 74 2E 6C 75 61 00 : 前面的兩位元組 00 09 是後面的字串長度 9 。後面的字串 "test.lua",後面的 00 是字串的結尾 0 。這是 dump 字串的格式,先 dump 字串的長度,再 dump 字串。

08 68 21 17 00 22 00 00 14 00 00 14 06 00 04 03 04 04 3F 02 01 3F 01 00 40 :
這段是位元組碼,長度為 25 位元組,也就是函數的 size 。對比上面列印出來的位元組碼
==========
08 68 21 17 00 : PUSHFUNCTION 00172168 ; "test.lua":1,08 就是 PUSHFUNCTION。PUSHFUNCTION 在指令 OpCode 裡的枚舉值就是它。後面的就是主函數的記憶體位址。

22 00 00 : STOREGLOBAL 13 ; add;十六進位的 22 就是 34,也就是 STOREGLOBAL。後面的 13 哪去了?這就是前面我們所說的 ThreadCode 的功勞了。這裡被清 0 了。

14 00 00 :PUSHGLOBAL 7 ; print;十六進位的 14 就是 20, 也就是 PUSHGLOBAL,後面的 7 被清 0 。注意,這裡的 print 的位元組碼位移量為 9 ,也就是下面的 dump 字串裡的 9 的含意。

14 06 00 :PUSHGLOBAL 13 ; add;這個地方為啥沒有被清 0 ?06 是什嗎?這也要歸功於 ThreadCode  。看看這裡也是操作 add 這個符號,也就是和前面的 STOREGLOBAL 13 同樣的符號,也就是上一個 13 在位元組碼裡的位移就是 6 。這裡的 6 就是這麼來的,ThreadCode  就是幹這個事兒的,所以它才叫 ThreadCode (串邊代碼)。注意,這裡的 0006 在位元組碼裡的位移量為 12,也就是下面的 dump 字串裡的 000C 的含意。

04 03 :PUSHBYTE 3
04 04 :PUSHBYTE 4
3F 02 01 :CALLFUNC 2 1
3F 01 00 :CALLFUNC 1 0
40 :RETCODE0
==========

56 09 00 06 00 70 72 69 6E 74 00 56 0C 00 04 00 61 64 64 00 :
這部分就是 DumpStrings,下面也分別看一下:
56 09 00 06 00 70 72 69 6E 74 00 :這個就是 dump print 符號的。56 就是字元 ‘V‘,0009 就是它出現在位元組碼裡的位置,0006 是後面的字串的長度,後面的就是字串 "print" 再加上結尾的 0 。
56 0C 00 04 00 61 64 64 00 : 這個就是 dump add 符號的。56 就是字元 ‘V‘,000C 就是它最後一次出現在位元組碼裡的位置,0004 是後面的字串的長度,後面的就是字串 "add" 再加上結尾的 0 。
主函數分析完了。
----------------
再看下 add 函數。
46 00 00 09 00 01 00 01 00 29 02 09 0A 30 41 02 41 02:
46 : 字元 ‘F‘,函數標籤 ID_FUN。

00 00 09 00:函數的 size,DumpSize 時用了兩個雙位元組,這裡是後面的高位元組代表實際的低位元組,所以大小為 0x09,也就是上面的 print 列印出來的位元組碼中的 function "test.lua":1 (9 bytes at 00172168); used at main+1,裡的 9 的。

01 00 : 非主函數,dump 它的定義行號 lineDefined,這裡它為 1 。
01 00 : 非主函數,列印它的 marked,此時為 1 。

29 02 09 0A 30 41 02 41 02:這部分就是 add 函數的位元組碼
29 02 : ADJUST 2
09 :PUSHLOCAL0 0 ; 注意,這裡列印出來的 0 是沒有意義的,只是為了和 PUSHLOCAL 列印結果保持一致。
0A:PUSHLOCAL1 1 ; 同上。
30:ADDOP
41 02 :RETCODE 2
41 02 :RETCODE 2
因為這個函數沒有用到符號和字串,所以 DumpStrings 這裡沒有輸出。
函數 add 分析完了。

----------------------------------------
編譯器分析部分到此結束。
下面看看解譯器是如何工作的。

Lua2.4 儲存位元組碼 dump.c

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.