Lua 5.3の逆アセンブラを書いたこと
はじめに
Lua 5.3のソースコードを追っていたのですが、コンパイラの部分で詰まってしまいました。 処理自体は特に難しいことはやっていないはずなのですが、Luaのコンパイラは1パスで、構文解析、意味解析、バイトコードの生成処理がグチャグチャに入り組んでおり非常に読み辛いです。
複雑に絡み合ったCコードを読んでいるうちに、ソースコードから処理を把握するよりは、吐き出されるバイトコードから逆に実装を追った方が理解が進むのではないかと次第に思い始めました。 ということでLua 5.3のバイトコードの逆アセンブラを作成しました。
インストール
LuaRocksを入れて以下のコマンドを叩いてください。
git clone https://github.com/tacigar/lhades.git cd lhades luarocks make
簡単!LuaRocks最高!
使い方
luac
が吐くバイトコードのファイルを lhades
に食わせてください。
lhades <filename>
利用例
例として、以下の3回 "Hello! John" と叫ぶだけのLuaプログラム準備し、 test.lua
として保存します。
local name = "John" function hello_world(count) for i = 1, count do print("Hello!, " .. name) end end hello_world(3)
次に、以下のコマンドでこのプログラムをコンパイルしてください。
luac test.lua
すると、以下のバイトコードが書かれたluac.out
というファイルが生成されるはずです。
1b4c 7561 5300 1993 0d0a 1a0a 0408 0408 0878 5600 0000 0000 0000 0000 0000 2877 4001 0a40 7465 7374 2e6c 7561 0000 0000 0000 0000 0002 0307 0000 0001 0000 006c 0000 0008 4080 8046 4040 0081 8000 0064 4000 0126 0080 0003 0000 0004 054a 6f68 6e04 0c68 656c 6c6f 5f77 6f72 6c64 1303 0000 0000 0000 0001 0000 0001 0001 0000 0000 0300 0000 0700 0000 0100 080b 0000 0041 0000 0080 0000 00c1 0000 0068 0001 8046 4140 0081 8100 00c5 0180 009d c101 0364 4100 0167 40fe 7f26 0080 0003 0000 0013 0100 0000 0000 0000 0406 7072 696e 7404 0948 656c 6c6f 212c 2002 0000 0000 0001 0000 0000 000b 0000 0004 0000 0004 0000 0004 0000 0004 0000 0005 0000 0005 0000 0005 0000 0005 0000 0005 0000 0004 0000 0007 0000 0005 0000 0006 636f 756e 7400 0000 000b 0000 000c 2866 6f72 2069 6e64 6578 2903 0000 000a 0000 000c 2866 6f72 206c 696d 6974 2903 0000 000a 0000 000b 2866 6f72 2073 7465 7029 0300 0000 0a00 0000 0269 0400 0000 0900 0000 0200 0000 055f 454e 5605 6e61 6d65 0700 0000 0100 0000 0700 0000 0300 0000 0900 0000 0900 0000 0900 0000 0900 0000 0100 0000 056e 616d 6501 0000 0007 0000 0001 0000 0005 5f45 4e56
このファイルを以下のコマンドでlhades
に食わせてください。
lhades luac.out
アセンブリコードが標準出力されるはずです!やったぜ!
source name "@test.lua": 0 - 0: 0 params, 3 stacks .codes: 7 [1] LOADK 0 0 ; R(0) := Kst(0) [2] CLOSURE 1 0 ; R(1) := closure(KPROTO[0]) [3] SETTABUP 0 257 1 ; UpValue[0][RK(257)] := RK(1) [4] GETTABUP 1 0 257 ; R(1) := UpValue[0][RK(257)] [5] LOADK 2 2 ; R(2) := Kst(2) [6] CALL 1 2 1 ; R(1), ... ,R(1+1-2) := R(1)(R(1+1), ... ,R(1+2-1)) [7] RETURN 0 1 ; return R(0), ... ,R(0+1-2) .consts: 3 [0] "John" [1] "hello_world" [2] 3 .upvalues: 1 [0] inStack: true, index: 0 ; _ENV .locals: 1 [0] "name" (1 - 7) .protos: 1 [0] source name "@test.lua": 3 - 7: 1 params, 8 stacks .codes: 11 [ 1] LOADK 1 0 ; R(1) := Kst(0) [ 2] MOVE 2 0 ; R(2) := R(0) [ 3] LOADK 3 0 ; R(3) := Kst(0) [ 4] FORPREP 1 5 ; R(1)-=R(1+2); pc+=5 [ 5] GETTABUP 5 0 257 ; R(5) := UpValue[0][RK(257)] [ 6] LOADK 6 2 ; R(6) := Kst(2) [ 7] GETUPVAL 7 1 ; R(7) := UpValue[1] [ 8] CONCAT 6 6 7 ; R(6) := R(6).. ... ..R(7) [ 9] CALL 5 2 1 ; R(5), ... ,R(5+1-2) := R(5)(R(5+1), ... ,R(5+2-1)) [10] FORLOOP 1 -6 ; R(1)+=R(1+2); if R(1) <?= R(1+1) then { pc+=-6; R(1+3)=R(1) } [11] RETURN 0 1 ; return R(0), ... ,R(0+1-2) .consts: 3 [0] 1 [1] "print" [2] "Hello!, " .upvalues: 2 [0] inStack: false, index: 0 ; _ENV [1] inStack: true, index: 0 ; name .locals: 5 [0] "count" (0 - 11) [1] "(for index)" (3 - 10) [2] "(for limit)" (3 - 10) [3] "(for step)" (3 - 10) [4] "i" (4 - 9) .protos: 0
なお、Luaのアセンブリコードが読めない人は以下を参考にしてください(責任放棄)。
Lua 5.3 Bytecode Reference — Ravi Programming Language 0.1 documentation