lp: support OP_CLOSURE, boilerplate function/proto support

This commit is contained in:
CPunch 2022-08-17 22:14:45 -05:00
parent b28edcba1d
commit 3be45f156a
4 changed files with 103 additions and 51 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
example.* example.*
__pycache__ __pycache__
NOTES.md

111
README.md
View File

@ -12,70 +12,95 @@ Lua has a relatively small instruction set (only 38 different opcodes!). This ma
```sh ```sh
> cat example.lua && luac5.1 -o example.luac example.lua > cat example.lua && luac5.1 -o example.luac example.lua
local tbl = {"He", "llo", " ", "Wo", "rld", "!"} local printMsg = function(append)
local str = "" local tbl = {"He", "llo", " ", "Wo"}
local str = ""
for i = 1, #tbl do for i = 1, #tbl do
str = str .. tbl[i] str = str .. tbl[i]
end
print(str .. append)
end end
print(str) printMsg("rld!")
> python main.py example.luac > python main.py example.luac
example.luac example.luac
==== [[example.lua's constants]] ==== ==== [[example.lua's constants]] ====
0: [STRING] rld!
==== [[example.lua's locals]] ====
R[0]: printMsg
==== [[example.lua's dissassembly]] ====
[ 0] CLOSURE : R[0] 0 ;
[ 1] MOVE : 1 0 0 ; move R[0] into R[1]
[ 2] LOADK : R[2] K[0] ; load "rld!" into R[2]
[ 3] CALL : 1 2 1 ;
[ 4] RETURN : 0 1 0 ;
==== [[example.lua's protos]] ====
==== [['s constants]] ====
0: [STRING] He 0: [STRING] He
1: [STRING] llo 1: [STRING] llo
2: [STRING] 2: [STRING]
3: [STRING] Wo 3: [STRING] Wo
4: [STRING] rld 4: [STRING]
5: [STRING] ! 5: [NUMBER] 1.0
6: [STRING] 6: [STRING] print
7: [NUMBER] 1.0
8: [STRING] print
==== [[example.lua's locals]] ==== ==== [['s locals]] ====
R[0]: tbl R[0]: append
R[1]: str R[1]: tbl
R[2]: (for index) R[2]: str
R[3]: (for limit) R[3]: (for index)
R[4]: (for step) R[4]: (for limit)
R[5]: i R[5]: (for step)
R[6]: i
==== [[example.lua's dissassembly]] ==== ==== [['s dissassembly]] ====
[ 0] NEWTABLE : 0 6 0 ; [ 0] NEWTABLE : 1 4 0 ;
[ 1] LOADK : R[1] K[0] ; load "He" into R[1] [ 1] LOADK : R[2] K[0] ; load "He" into R[2]
[ 2] LOADK : R[2] K[1] ; load "llo" into R[2] [ 2] LOADK : R[3] K[1] ; load "llo" into R[3]
[ 3] LOADK : R[3] K[2] ; load " " into R[3] [ 3] LOADK : R[4] K[2] ; load " " into R[4]
[ 4] LOADK : R[4] K[3] ; load "Wo" into R[4] [ 4] LOADK : R[5] K[3] ; load "Wo" into R[5]
[ 5] LOADK : R[5] K[4] ; load "rld" into R[5] [ 5] SETLIST : 1 4 1 ;
[ 6] LOADK : R[6] K[5] ; load "!" into R[6] [ 6] LOADK : R[2] K[4] ; load "" into R[2]
[ 7] SETLIST : 0 6 1 ; [ 7] LOADK : R[3] K[5] ; load 1 into R[3]
[ 8] LOADK : R[1] K[6] ; load "" into R[1] [ 8] LEN : 4 1 0 ;
[ 9] LOADK : R[2] K[7] ; load 1 into R[2] [ 9] LOADK : R[5] K[5] ; load 1 into R[5]
[ 10] LEN : 3 0 0 ; [ 10] FORPREP : R[3] 3 ;
[ 11] LOADK : R[4] K[7] ; load 1 into R[4] [ 11] MOVE : 7 2 0 ; move R[2] into R[7]
[ 12] FORPREP : R[2] 3 ; [ 12] GETTABLE : R[8] 1 R[6] ;
[ 13] MOVE : 6 1 0 ; move R[1] into R[6] [ 13] CONCAT : 2 7 8 ; concat 2 values from R[7] to R[8], store into R[2]
[ 14] GETTABLE : R[7] 0 R[5] ; [ 14] FORLOOP : R[3] -4 ;
[ 15] CONCAT : 1 6 7 ; concat 2 values from R[6] to R[7], store into R[1] [ 15] GETGLOBAL : R[3] K[6] ; move _G["print"] into R[3]
[ 16] FORLOOP : R[2] -4 ; [ 16] MOVE : 4 2 0 ; move R[2] into R[4]
[ 17] GETGLOBAL : R[2] K[8] ; move _G["print"] into R[2] [ 17] MOVE : 5 0 0 ; move R[0] into R[5]
[ 18] MOVE : 3 1 0 ; move R[1] into R[3] [ 18] CONCAT : 4 4 5 ; concat 2 values from R[4] to R[5], store into R[4]
[ 19] CALL : 2 2 1 ; [ 19] CALL : 3 2 1 ;
[ 20] RETURN : 0 1 0 ; [ 20] RETURN : 0 1 0 ;
==== [[example.lua's pseudo-code]] ==== ==== [[example.lua's pseudo-code]] ====
local tbl = {"He", "llo", " ", "Wo", "rld", "!", } local printMsg = function(append)
local str = "" local tbl = {"He", "llo", " ", "Wo", }
for i = 1, #tbl, 1 do local str = ""
str = str .. tbl[i] for i = 1, #tbl, 1 do
str = str .. tbl[i]
end
print(str .. append)
end end
print(str)
printMsg("rld!")
``` ```

View File

@ -6,8 +6,6 @@
An experimental bytecode decompiler. An experimental bytecode decompiler.
''' '''
from operator import concat
from subprocess import call
from lundump import Chunk, Constant, Instruction, Opcodes, whichRK, readRKasK from lundump import Chunk, Constant, Instruction, Opcodes, whichRK, readRKasK
class _Scope: class _Scope:
@ -41,7 +39,7 @@ def isValidLocal(ident: str) -> bool:
return True return True
class LuaDecomp: class LuaDecomp:
def __init__(self, chunk: Chunk): def __init__(self, chunk: Chunk, headChunk: bool = True, scopeOffset: int = 0):
self.chunk = chunk self.chunk = chunk
self.pc = 0 self.pc = 0
self.scope: list[_Scope] = [] self.scope: list[_Scope] = []
@ -50,6 +48,8 @@ class LuaDecomp:
self.locals = {} self.locals = {}
self.traceback = {} self.traceback = {}
self.unknownLocalCount = 0 self.unknownLocalCount = 0
self.headChunk = headChunk
self.scopeOffset = scopeOffset # number of scopes this chunk/proto is in
self.src: str = "" self.src: str = ""
# configurations! # configurations!
@ -59,6 +59,20 @@ class LuaDecomp:
self.__loadLocals() self.__loadLocals()
if not self.headChunk:
functionProto = "function("
# define params
for i in range(self.chunk.numParams):
# add param to function prototype (also make a local in the register if it doesn't exist)
functionProto += ("%s, " if i+1 < self.chunk.numParams else "%s") % self.__makeLocalIdentifier(i)
# mark local as defined
self.__addSetTraceback(i)
functionProto += ")"
self.__startScope(functionProto, 0, len(self.chunk.instructions))
# parse instructions # parse instructions
while self.pc < len(self.chunk.instructions): while self.pc < len(self.chunk.instructions):
self.parseInstr() self.parseInstr()
@ -67,12 +81,18 @@ class LuaDecomp:
# end the scope (if we're supposed too) # end the scope (if we're supposed too)
self.__checkScope() self.__checkScope()
print("\n==== [[" + str(self.chunk.name) + "'s pseudo-code]] ====\n") if not self.headChunk:
self.__endScope()
def getPseudoCode(self) -> str:
fullSrc = ""
for line in self.lines: for line in self.lines:
if self.annotateLines: if self.annotateLines:
print("-- PC: %d to PC: %d" % (line.startPC, line.endPC)) fullSrc += "-- PC: %d to PC: %d\n" % (line.startPC, line.endPC)
print(((' ' * self.indexWidth) * line.scope) + line.src) fullSrc += ((' ' * self.indexWidth) * (line.scope + self.scopeOffset)) + line.src + "\n"
return fullSrc
# =======================================[[ Helpers ]]========================================= # =======================================[[ Helpers ]]=========================================
@ -179,7 +199,6 @@ class LuaDecomp:
return self.locals[indx] return self.locals[indx]
def __newLocal(self, indx: int, expr: str) -> None: def __newLocal(self, indx: int, expr: str) -> None:
# TODO: grab identifier from chunk(?)
self.__makeLocalIdentifier(indx) self.__makeLocalIdentifier(indx)
self.__addExpr("local " + self.locals[indx] + " = " + expr) self.__addExpr("local " + self.locals[indx] + " = " + expr)
@ -408,5 +427,8 @@ class LuaDecomp:
for i in range(numElems): for i in range(numElems):
self.__addExpr("%s[%d] = %s" % (ident, (startAt + i + 1), self.__getReg(instr.A + i + 1))) self.__addExpr("%s[%d] = %s" % (ident, (startAt + i + 1), self.__getReg(instr.A + i + 1)))
self.__endStatement() self.__endStatement()
elif instr.opcode == Opcodes.CLOSURE:
proto = LuaDecomp(self.chunk.protos[instr.B], headChunk=False, scopeOffset=len(self.scope))
self.__setReg(instr.A, proto.getPseudoCode())
else: else:
raise Exception("unsupported instruction: %s" % instr.toString()) raise Exception("unsupported instruction: %s" % instr.toString())

View File

@ -7,4 +7,8 @@ print(sys.argv[1])
chunk = lc.loadFile(sys.argv[1]) chunk = lc.loadFile(sys.argv[1])
lc.print_dissassembly() lc.print_dissassembly()
lp = lparser.LuaDecomp(chunk)
lp = lparser.LuaDecomp(chunk)
print("\n==== [[" + str(chunk.name) + "'s pseudo-code]] ====\n")
print(lp.getPseudoCode())