lparser.py: proper support for locals

This commit is contained in:
CPunch 2022-08-11 23:58:21 -05:00
parent b8bf02f7d0
commit 0f72e71a59
2 changed files with 70 additions and 23 deletions

View File

@ -12,10 +12,10 @@ 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
i = 0 local i, x = 0, 2
while i < 10 do while i < 10 do
print(i) print(i + x)
i = i + 1 i = i + 1
end end
> python main.py example.luac > python main.py example.luac
@ -23,34 +23,32 @@ example.luac
==== [[example.lua's constants]] ==== ==== [[example.lua's constants]] ====
0: [STRING] i 0: [NUMBER] 0.0
1: [NUMBER] 0.0 1: [NUMBER] 2.0
2: [NUMBER] 10.0 2: [NUMBER] 10.0
3: [STRING] print 3: [STRING] print
4: [NUMBER] 1.0 4: [NUMBER] 1.0
==== [[example.lua's dissassembly]] ==== ==== [[example.lua's dissassembly]] ====
[ 0] LOADK : R[0] K[1] ; load 0.0 into R[0] [ 0] LOADK : R[0] K[0] ; load 0.0 into R[0]
[ 1] SETGLOBAL : R[0] K[0] ; [ 1] LOADK : R[1] K[1] ; load 2.0 into R[1]
[ 2] GETGLOBAL : R[0] K[0] ; [ 2] LT : R[0] R[0] K[2] ;
[ 3] LT : R[0] R[0] K[2] ; [ 3] JMP : R[0] 5 ;
[ 4] JMP : R[0] 7 ; [ 4] GETGLOBAL : R[2] K[3] ;
[ 5] GETGLOBAL : R[0] K[3] ; [ 5] ADD : R[3] R[0] R[1] ;
[ 6] GETGLOBAL : R[1] K[0] ; [ 6] CALL : R[2] 2 1 ;
[ 7] CALL : R[0] 2 1 ; [ 7] ADD : R[0] R[0] K[4] ;
[ 8] GETGLOBAL : R[0] K[0] ; [ 8] JMP : R[0] -7 ;
[ 9] ADD : R[0] R[0] K[4] ; [ 9] RETURN : R[0] 1 0 ;
[ 10] SETGLOBAL : R[0] K[0] ;
[ 11] JMP : R[0] -10 ;
[ 12] RETURN : R[0] 1 0 ;
==== [[example.lua's decompiled source]] ==== ==== [[example.lua's decompiled source]] ====
i = 0.0 local i = 0.0
local x = 2.0
while i < 10.0 do while i < 10.0 do
print(i) print((i + x))
i = (i + 1.0) i = (i + 1.0)
end end

View File

@ -8,6 +8,7 @@
from operator import concat from operator import concat
from subprocess import call from subprocess import call
from xmlrpc.client import Boolean
from lundump import Chunk, Constant, Instruction, Opcodes, whichRK, readRKasK from lundump import Chunk, Constant, Instruction, Opcodes, whichRK, readRKasK
class _Scope: class _Scope:
@ -15,6 +16,12 @@ class _Scope:
self.startPC = startPC self.startPC = startPC
self.endPC = endPC self.endPC = endPC
class _Traceback:
def __init__(self):
self.sets = []
self.uses = []
self.isConst = False
class LuaDecomp: class LuaDecomp:
def __init__(self, chunk: Chunk): def __init__(self, chunk: Chunk):
self.chunk = chunk self.chunk = chunk
@ -22,13 +29,16 @@ class LuaDecomp:
self.scope = [] self.scope = []
self.top = {} self.top = {}
self.locals = {} self.locals = {}
self.traceback = {}
self.unknownLocalCount = 0 self.unknownLocalCount = 0
self.src: str = "" self.src: str = ""
# configurations! # configurations!
self.aggressiveLocals = False # should *EVERY* accessed register be considered a local? self.aggressiveLocals = False # should *EVERY* set register be considered a local?
self.indexWidth = 4 # how many spaces for indentions? self.indexWidth = 4 # how many spaces for indentions?
self.__loadLocals()
# parse instructions # parse instructions
while self.pc < len(self.chunk.instructions): while self.pc < len(self.chunk.instructions):
self.parseInstr() self.parseInstr()
@ -54,6 +64,33 @@ class LuaDecomp:
def __getCurrInstr(self) -> Instruction: def __getCurrInstr(self) -> Instruction:
return self.__getInstrAtPC(self.pc) return self.__getInstrAtPC(self.pc)
# when we read from a register, call this
def __addUseTraceback(self, reg: int) -> None:
if not self.pc in self.traceback:
self.traceback[self.pc] = _Traceback()
self.traceback[self.pc].uses.append(reg)
# when we write from a register, call this
def __addSetTraceback(self, reg: int) -> None:
if not self.pc in self.traceback:
self.traceback[self.pc] = _Traceback()
self.traceback[self.pc].sets.append(reg)
# walks traceback, if local wasn't set before, the local needs to be defined
def __needsDefined(self, reg) -> Boolean:
for _, trace in self.traceback.items():
if reg in trace.sets:
return False
# wasn't set in traceback! needs defined!
return True
def __loadLocals(self):
for i in range(len(self.chunk.locals)):
self.locals[i] = self.chunk.locals[i].name
def __addExpr(self, code: str) -> None: def __addExpr(self, code: str) -> None:
self.src += code self.src += code
@ -61,22 +98,34 @@ class LuaDecomp:
self.src += '\n' + (' ' * self.indexWidth * len(self.scope)) self.src += '\n' + (' ' * self.indexWidth * len(self.scope))
def __getReg(self, indx: int) -> str: def __getReg(self, indx: int) -> str:
self.__addUseTraceback(indx)
# if the top indx is a local, get it # if the top indx is a local, get it
return self.locals[indx] if indx in self.locals else self.top[indx] return self.locals[indx] if indx in self.locals else self.top[indx]
def __setReg(self, indx: int, code: str) -> None: def __setReg(self, indx: int, code: str) -> None:
# if the top indx is a local, set it # if the top indx is a local, set it
if indx in self.locals: if indx in self.locals:
if self.__needsDefined(indx):
self.__newLocal(indx, code)
else:
self.__startStatement() self.__startStatement()
self.__addExpr(self.locals[indx] + " = " + code) self.__addExpr(self.locals[indx] + " = " + code)
elif self.aggressiveLocals: # 'every register is a local!!' elif self.aggressiveLocals: # 'every register is a local!!'
self.__newLocal(indx, code) self.__newLocal(indx, code)
self.__addSetTraceback(indx)
self.top[indx] = code self.top[indx] = code
# ========================================[[ Locals ]]========================================= # ========================================[[ Locals ]]=========================================
def __makeLocalIdentifier(self, indx: int) -> str: def __makeLocalIdentifier(self, indx: int) -> str:
# first, check if we have a local name already determined
if indx in self.locals:
return self.locals[indx]
# otherwise, generate a local
self.locals[indx] = "__unknLocal%d" % self.unknownLocalCount self.locals[indx] = "__unknLocal%d" % self.unknownLocalCount
self.unknownLocalCount += 1 self.unknownLocalCount += 1