├── .gitignore ├── luavm ├── vmcore.lua ├── decompiler3.lua ├── vm.lua ├── decompiler2.lua ├── decompiler3 │ ├── decoder.lua │ ├── pretty.lua │ ├── decompiler │ │ ├── block_splitter.lua │ │ ├── jump_deopt.lua │ │ ├── instr_utils.lua │ │ ├── expr_manager.lua │ │ ├── block_identifier.lua │ │ ├── block_utils.lua │ │ └── block_metadata.lua │ └── decoder51.lua ├── decompiler │ ├── decoder.lua │ ├── pass │ │ ├── inline.lua │ │ └── table_inline.lua │ ├── formatter.lua │ ├── analyzer.lua │ └── decoder51.lua ├── evaluator.lua ├── compiler.lua ├── vm51.lua ├── bytecode │ ├── lua52.lua │ ├── lua51.lua │ └── lua53.lua └── vm52.lua ├── test ├── life.lua ├── echo.lua ├── hello.lua ├── printf.lua ├── env.lua ├── luac.lua ├── fibfor.lua ├── readonly.lua ├── table.lua ├── cf.lua ├── xd.lua ├── globals.lua ├── bisect.lua ├── fib.lua ├── factorial.lua ├── trace-calls.lua ├── sieve.lua ├── trace-globals.lua ├── README └── sort.lua ├── vm_accuracy_test ├── test_return_multiple.lua ├── test_concat.lua ├── test_return_single.lua ├── test_varargs_count.lua ├── test_table_varargs.lua ├── test_return_varargs.lua ├── test_tailcall.lua ├── test_varargs.lua ├── test_closure_return_varargs.lua ├── test_varargs_0.lua ├── test_for_loops.lua ├── test_table_insert.lua ├── test_closure.lua ├── test_call.lua ├── test_call_self.lua ├── test_for_generic.lua ├── test_table_varargs_functioncall.lua ├── test_tailcall_parameters.lua ├── test_varargs_with_regular_parameters.lua ├── test_call_self_varargs.lua ├── test_call_parameters.lua ├── test_arith.lua ├── test_repeat_until.lua ├── test_while_loops.lua ├── test_closure_upvalues.lua └── test_closure_upvalue_close.lua ├── globalswithspaces.bcasm ├── hello.bcasm ├── compilertest.lua ├── compile.lua ├── vm_accuracy_test_native.lua ├── dump_bytecode.lua ├── vm_accuracy_test_vm.lua ├── five_point_two_test.lua ├── analyze.lua ├── vm_accuracy_test_dynarec.lua ├── hello.lua ├── testrunner.lua ├── patchertest.lua ├── vm_accuracy_test_common.lua ├── decompile.lua ├── LICENSE ├── run_vm.lua ├── test.lua ├── README.md ├── dynarectest.lua ├── patcherexample.lua ├── patcherhook.lua ├── convert_bytecode.lua ├── decompile3.lua └── decompile2.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | *.luac -------------------------------------------------------------------------------- /luavm/vmcore.lua: -------------------------------------------------------------------------------- 1 | this file is a syntax error. lol -------------------------------------------------------------------------------- /test/life.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ds84182/LuaVM/HEAD/test/life.lua -------------------------------------------------------------------------------- /vm_accuracy_test/test_return_multiple.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | return 1, 2, 3 3 | end, {}, {1,2,3} 4 | -------------------------------------------------------------------------------- /test/echo.lua: -------------------------------------------------------------------------------- 1 | -- echo command line arguments 2 | 3 | for i=0,table.getn(arg) do 4 | print(i,arg[i]) 5 | end 6 | -------------------------------------------------------------------------------- /test/hello.lua: -------------------------------------------------------------------------------- 1 | -- the first program in every language 2 | 3 | io.write("Hello world, from ",_VERSION,"!\n") 4 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_concat.lua: -------------------------------------------------------------------------------- 1 | return function(a,b,c) 2 | return a..b..c 3 | end,{"a","b","c"},{"abc"} 4 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_return_single.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | return "returned" 3 | end, {}, {"returned"} 4 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_varargs_count.lua: -------------------------------------------------------------------------------- 1 | return function(...) 2 | return select("#", ...) 3 | end,{1,2,3},{3} 4 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_table_varargs.lua: -------------------------------------------------------------------------------- 1 | return function(...) 2 | return {...} 3 | end,{"a","b","c"},{{"a","b","c"}} 4 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_return_varargs.lua: -------------------------------------------------------------------------------- 1 | return function(...) 2 | return ... 3 | end, {1,2,3,4,5,6,7,8}, {1,2,3,4,5,6,7,8} 4 | -------------------------------------------------------------------------------- /globalswithspaces.bcasm: -------------------------------------------------------------------------------- 1 | --lua asm example: globals with spaces() 2 | .const gws "globals with spaces" 3 | getglobal 0, gws 4 | call 0, 1, 1 5 | -------------------------------------------------------------------------------- /hello.bcasm: -------------------------------------------------------------------------------- 1 | --lua asm example: return "hello" 2 | .const hello "hello" 3 | loadk hello, 0 4 | return 0, 2, 0 --returns the loaded constant 5 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_tailcall.lua: -------------------------------------------------------------------------------- 1 | function foo() 2 | return "bar","baz","foo" 3 | end 4 | 5 | return function() 6 | return foo() 7 | end,{},{"bar","baz","foo"} -------------------------------------------------------------------------------- /vm_accuracy_test/test_varargs.lua: -------------------------------------------------------------------------------- 1 | return function(...) 2 | local function closure(...) 3 | return ... 4 | end 5 | return closure(...) 6 | end,{1,2,3},{1,2,3} 7 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_closure_return_varargs.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local function closure() 3 | return 1, 2, 3 4 | end 5 | return closure() 6 | end,{},{1,2,3} 7 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_varargs_0.lua: -------------------------------------------------------------------------------- 1 | return function(a, b, c, ...) 2 | local function closure(...) 3 | return ... 4 | end 5 | return closure(...) 6 | end,{1,2,3},{} 7 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_for_loops.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local t = {} 3 | for i=1, 10 do 4 | table.insert(t,i) 5 | end 6 | return t 7 | end,{},{{1,2,3,4,5,6,7,8,9,10}} 8 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_table_insert.lua: -------------------------------------------------------------------------------- 1 | return function(tab) 2 | tab[#tab+1] = "a" 3 | tab[#tab+1] = "b" 4 | tab[#tab+1] = "c" 5 | return tab 6 | end,{{}},{{"a","b","c"}} 7 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_closure.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local function closure() 3 | return 1, 2, 3 4 | end 5 | local a,b,c = closure() 6 | return a,b,c 7 | end,{},{1,2,3} 8 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_call.lua: -------------------------------------------------------------------------------- 1 | function foo() 2 | return "bar","baz","foo" 3 | end 4 | 5 | return function() 6 | local b,bz,f = foo() 7 | return b,bz,f 8 | end,{},{"bar","baz","foo"} -------------------------------------------------------------------------------- /vm_accuracy_test/test_call_self.lua: -------------------------------------------------------------------------------- 1 | selfcall = {} 2 | function selfcall:call() 3 | return self 4 | end 5 | 6 | return function() 7 | return selfcall:call() 8 | end,{},{selfcall} 9 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_for_generic.lua: -------------------------------------------------------------------------------- 1 | return function(i) 2 | local t = {} 3 | for i,v in pairs(i) do 4 | t[v] = i 5 | end 6 | return t 7 | end,{{a=1,b=2,c=3,d=4}},{{"a","b","c","d"}} 8 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_table_varargs_functioncall.lua: -------------------------------------------------------------------------------- 1 | function foo() 2 | return "foo","bar","guize" 3 | end 4 | 5 | return function() 6 | return {foo()} 7 | end,{},{{"foo","bar","guize"}} 8 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_tailcall_parameters.lua: -------------------------------------------------------------------------------- 1 | function foo(h) 2 | return h.."bar",h.."baz",h.."foo" 3 | end 4 | 5 | return function() 6 | return foo("foo") 7 | end,{},{"foobar","foobaz","foofoo"} -------------------------------------------------------------------------------- /test/printf.lua: -------------------------------------------------------------------------------- 1 | -- an implementation of printf 2 | 3 | function printf(...) 4 | io.write(string.format(...)) 5 | end 6 | 7 | printf("Hello %s from %s on %s\n",os.getenv"USER" or "there",_VERSION,os.date()) 8 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_varargs_with_regular_parameters.lua: -------------------------------------------------------------------------------- 1 | return function(a, b, c, ...) 2 | local function closure(...) 3 | return ... 4 | end 5 | return closure(...) 6 | end,{1,2,3,4,5,6,7},{4,5,6,7} 7 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_call_self_varargs.lua: -------------------------------------------------------------------------------- 1 | selfcall = {} 2 | function selfcall:call(...) 3 | return self, ... 4 | end 5 | 6 | return function(...) 7 | return selfcall:call(...) 8 | end,{1,2,3},{selfcall,1,2,3} 9 | -------------------------------------------------------------------------------- /test/env.lua: -------------------------------------------------------------------------------- 1 | -- read environment variables as if they were global variables 2 | 3 | local f=function (t,i) return os.getenv(i) end 4 | setmetatable(getfenv(),{__index=f}) 5 | 6 | -- an example 7 | print(a,USER,PATH) 8 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_call_parameters.lua: -------------------------------------------------------------------------------- 1 | function foo(h) 2 | return h.."bar",h.."baz",h.."foo" 3 | end 4 | 5 | return function() 6 | local b,bz,f = foo("foo") 7 | return b,bz,f 8 | end,{},{"foobar","foobaz","foofoo"} -------------------------------------------------------------------------------- /vm_accuracy_test/test_arith.lua: -------------------------------------------------------------------------------- 1 | local a,b,c = math.random(1,10),math.random(1,10),math.random(1,10) 2 | 3 | return function(a,b,c) 4 | return a+b+c, a-b-c, a*b*c, a/b/c, a%b%c, a^b^c 5 | end,{a,b,c},{{a+b+c, a-b-c, a*b*c, a/b/c, a%b%c, a^b^c}} 6 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_repeat_until.lua: -------------------------------------------------------------------------------- 1 | local i = 0 2 | 3 | return function(func) 4 | local t = {} 5 | repeat 6 | table.insert(t,func()) 7 | until t[#t] == 10 8 | return t 9 | end,{function() i = i+1 return i end},{{1,2,3,4,5,6,7,8,9,10}} 10 | -------------------------------------------------------------------------------- /test/luac.lua: -------------------------------------------------------------------------------- 1 | -- bare-bones luac in Lua 2 | -- usage: lua luac.lua file.lua 3 | 4 | assert(arg[1]~=nil and arg[2]==nil,"usage: lua luac.lua file.lua") 5 | f=assert(io.open("luac.out","wb")) 6 | assert(f:write(string.dump(assert(loadfile(arg[1]))))) 7 | assert(f:close()) 8 | -------------------------------------------------------------------------------- /luavm/decompiler3.lua: -------------------------------------------------------------------------------- 1 | -- Third times the charm. 2 | local decompiler = {} 3 | 4 | decompiler.decoder = require "luavm.decompiler3.decoder" 5 | decompiler.core = require "luavm.decompiler3.decompiler" 6 | require "luavm.decompiler3.pretty"(decompiler) 7 | 8 | return decompiler 9 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_while_loops.lua: -------------------------------------------------------------------------------- 1 | local i = 0 2 | 3 | return function(func) 4 | local t = {} 5 | local n = func() 6 | while n ~= 10 do 7 | table.insert(t,n) 8 | n = func() 9 | end 10 | return t 11 | end,{function() i = i+1 return i end},{{1,2,3,4,5,6,7,8,9}} 12 | -------------------------------------------------------------------------------- /compilertest.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | require "luavm.compiler" 3 | require "luavm.vm51" 4 | 5 | local hello = io.open("hello.bcasm","r"):read("*a") 6 | hello = compiler.compile(hello) 7 | print(assert(loadstring(bytecode.save(hello)))()) 8 | print(vm.lua51.run(hello)) 9 | -------------------------------------------------------------------------------- /compile.lua: -------------------------------------------------------------------------------- 1 | local args = {...} 2 | if #args < 2 then print("Usage: compile [bcasm] [luac]") return end 3 | 4 | local inf = io.open(args[1],"r"):read"*a" 5 | require "luavm.compiler" 6 | local bytecode = require "luavm.bytecode" 7 | io.open(args[2],"wb"):write(bytecode.save(compiler.compile(inf))) 8 | -------------------------------------------------------------------------------- /vm_accuracy_test_native.lua: -------------------------------------------------------------------------------- 1 | require "vm_accuracy_test_common" 2 | 3 | for test in iterateTests() do 4 | if test:sub(-4,-1) == ".lua" then 5 | local f,a,r = getTest(test) 6 | local cr = {f(unpack(a))} 7 | assert(match(cr,r), test.." failed") 8 | print(test.." suceeded") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_closure_upvalues.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local a,b,c 3 | local function set() 4 | a,b,c = 1,2,3 5 | end 6 | local function increment() 7 | a = a+c 8 | b = b+a 9 | c = c+b 10 | end 11 | set() 12 | increment() 13 | increment() 14 | return a,b,c 15 | end,{},{13,19,28} 16 | -------------------------------------------------------------------------------- /test/fibfor.lua: -------------------------------------------------------------------------------- 1 | -- example of for with generator functions 2 | 3 | function generatefib (n) 4 | return coroutine.wrap(function () 5 | local a,b = 1, 1 6 | while a <= n do 7 | coroutine.yield(a) 8 | a, b = b, a+b 9 | end 10 | end) 11 | end 12 | 13 | for i in generatefib(1000) do print(i) end 14 | -------------------------------------------------------------------------------- /test/readonly.lua: -------------------------------------------------------------------------------- 1 | -- make global variables readonly 2 | 3 | local f=function (t,i) error("cannot redefine global variable `"..i.."'",2) end 4 | local g={} 5 | local G=getfenv() 6 | setmetatable(g,{__index=G,__newindex=f}) 7 | setfenv(1,g) 8 | 9 | -- an example 10 | rawset(g,"x",3) 11 | x=2 12 | y=1 -- cannot redefine `y' 13 | -------------------------------------------------------------------------------- /dump_bytecode.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | print "required bytecode" 3 | 4 | local args = {...} 5 | print("Got args: ",args[1]) 6 | local file = table.remove(args,1) 7 | print("File: ",file) 8 | local bc = bytecode.load(string.dump(loadfile(file))) 9 | print("Loaded bytecode") 10 | 11 | bytecode.dump(bc) 12 | -------------------------------------------------------------------------------- /test/table.lua: -------------------------------------------------------------------------------- 1 | -- make table, grouping all data for the same item 2 | -- input is 2 columns (item, data) 3 | 4 | local A 5 | while 1 do 6 | local l=io.read() 7 | if l==nil then break end 8 | local _,_,a,b=string.find(l,'"?([_%w]+)"?%s*(.*)$') 9 | if a~=A then A=a io.write("\n",a,":") end 10 | io.write(" ",b) 11 | end 12 | io.write("\n") 13 | -------------------------------------------------------------------------------- /luavm/vm.lua: -------------------------------------------------------------------------------- 1 | local vm = {} 2 | vm.debug = false 3 | vm.typechecking = false 4 | 5 | local pkgname = ... 6 | 7 | function vm.get(version) 8 | return require(pkgname..version) 9 | end 10 | 11 | function vm.native() 12 | local major, minor = _VERSION:match("Lua (%d)%.(%d)") 13 | 14 | return vm.get(major..minor) 15 | end 16 | 17 | return vm 18 | -------------------------------------------------------------------------------- /test/cf.lua: -------------------------------------------------------------------------------- 1 | -- temperature conversion table (celsius to farenheit) 2 | 3 | for c0=-20,50-1,10 do 4 | io.write("C ") 5 | for c=c0,c0+10-1 do 6 | io.write(string.format("%3.0f ",c)) 7 | end 8 | io.write("\n") 9 | 10 | io.write("F ") 11 | for c=c0,c0+10-1 do 12 | f=(9/5)*c+32 13 | io.write(string.format("%3.0f ",f)) 14 | end 15 | io.write("\n\n") 16 | end 17 | -------------------------------------------------------------------------------- /vm_accuracy_test_vm.lua: -------------------------------------------------------------------------------- 1 | require "vm_accuracy_test_common" 2 | local bytecode = require "luavm.bytecode" 3 | local lua = require "luavm.vm".native() 4 | 5 | for test in iterateTests() do 6 | if test:sub(-4,-1) == ".lua" then 7 | local f,a,r = getTest(test) 8 | local cr = {lua.run(bytecode.load(string.dump(f)),a)} 9 | assert(match(cr,r), test.." failed") 10 | print(test.." suceeded") 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/xd.lua: -------------------------------------------------------------------------------- 1 | -- hex dump 2 | -- usage: lua xd.lua < file 3 | 4 | local offset=0 5 | while true do 6 | local s=io.read(16) 7 | if s==nil then return end 8 | io.write(string.format("%08X ",offset)) 9 | string.gsub(s,"(.)", 10 | function (c) io.write(string.format("%02X ",string.byte(c))) end) 11 | io.write(string.rep(" ",3*(16-string.len(s)))) 12 | io.write(" ",string.gsub(s,"%c","."),"\n") 13 | offset=offset+16 14 | end 15 | -------------------------------------------------------------------------------- /five_point_two_test.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | require "luavm.vm51" 3 | 4 | local testbc = string.dump(function() print "Hello" end) 5 | io.open("testbc.luac","wb"):write(testbc) 6 | local testbcl = bytecode.load(testbc) 7 | --local testbco = bytecode.save(testbcl) 8 | --assert(testbc == testbco,"Bytecode save test failed, INCONSISTENT!") 9 | print(vm.lua51.run(testbcl)) 10 | --print(loadstring(testbc)()) 11 | --print(loadstring(testbco)()) 12 | -------------------------------------------------------------------------------- /test/globals.lua: -------------------------------------------------------------------------------- 1 | -- reads luac listings and reports global variable usage 2 | -- lines where a global is written to are marked with "*" 3 | -- typical usage: luac -p -l file.lua | lua globals.lua | sort | lua table.lua 4 | 5 | while 1 do 6 | local s=io.read() 7 | if s==nil then break end 8 | local ok,_,l,op,g=string.find(s,"%[%-?(%d*)%]%s*([GS])ETGLOBAL.-;%s+(.*)$") 9 | if ok then 10 | if op=="S" then op="*" else op="" end 11 | io.write(g,"\t",l,op,"\n") 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /luavm/decompiler2.lua: -------------------------------------------------------------------------------- 1 | local decompiler = {} 2 | 3 | decompiler.decoder = require "luavm.decompiler.decoder" 4 | decompiler.analyzer = require "luavm.decompiler.analyzer" 5 | decompiler.formatter = require "luavm.decompiler.formatter" 6 | 7 | -- Do the table inline pass before general inline so tables can be inlined 8 | decompiler.pass = { 9 | require "luavm.decompiler.pass.inline"(decompiler), 10 | require "luavm.decompiler.pass.table_inline"(decompiler), 11 | } 12 | 13 | return decompiler 14 | -------------------------------------------------------------------------------- /vm_accuracy_test/test_closure_upvalue_close.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local getA, getB, getC 3 | -- Simple Close 4 | do 5 | local a = 32 6 | function getA() return a end 7 | end 8 | -- Loop Close 9 | do 10 | for i=1, 128 do 11 | if i == 65 then 12 | function getB() return i end 13 | end 14 | end 15 | end 16 | -- Implicit Function Close 17 | getC = (function(c) return function() return c end end)(123) 18 | 19 | return getA(), getB(), getC() 20 | end,{},{32, 65, 123} 21 | -------------------------------------------------------------------------------- /analyze.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | 3 | local code = "local x = 0 repeat print(x) x = x+1 until x > 100" 4 | local lines = {} 5 | for line in code:gmatch("[^\n]*") do 6 | lines[#lines+1] = line 7 | end 8 | 9 | local d = string.dump((loadstring or load)(code)) 10 | local bc = bytecode.load(d) 11 | 12 | bytecode.dump(bc) 13 | 14 | local nd = bytecode.save(bc) 15 | print(nd == d) 16 | print(#nd, #d) 17 | 18 | --io.open("hai.luac","wb"):write(d) 19 | io.open("hai.new.luac","wb"):write(nd) 20 | -------------------------------------------------------------------------------- /vm_accuracy_test_dynarec.lua: -------------------------------------------------------------------------------- 1 | require "vm_accuracy_test_common" 2 | require "luavm.bytecode" 3 | require "luavm.dynarec" 4 | 5 | for test in iterateTests() do 6 | if test:sub(-4,-1) == ".lua" then 7 | local f,a,r = getTest(test) 8 | print("running "..test) 9 | local dyncode = table.concat( 10 | dynarec.compile(bytecode.load(string.dump(f))) 11 | ,"\n") 12 | print(dyncode) 13 | local cr = {assert(loadstring(dyncode))(unpack(a))} 14 | assert(match(cr,r), test.." failed") 15 | print(test.." suceeded") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /luavm/decompiler3/decoder.lua: -------------------------------------------------------------------------------- 1 | -- Decoder: Takes Lua bytecode and returns a version independent immediate representation -- 2 | 3 | -- For example: 4 | -- LOADK R1, K0 ["hello"] -> 5 | -- {op = "set", src = {{"constant", 0}}, dest = {{"register", 1}}} 6 | 7 | local pkgname = ... 8 | 9 | local decoder = {} 10 | 11 | function decoder.get(version) 12 | return require(pkgname..version)(decoder) 13 | end 14 | 15 | function decoder.native() 16 | local major, minor = _VERSION:match("Lua (%d)%.(%d)") 17 | 18 | return decoder.get(major..minor) 19 | end 20 | 21 | return decoder 22 | -------------------------------------------------------------------------------- /hello.lua: -------------------------------------------------------------------------------- 1 | --[[print("Hello, World!"); 2 | print((4*5.5) < 5) 3 | 4 | local nastyAssUpvalue = "LOL" 5 | local function closureTest() 6 | print(nastyAssUpvalue,nastyAssUpvalue == "LOL") 7 | nastyAssUpvalue = nastyAssUpvalue == "LOL" and "LAWL" or "LOL" 8 | end 9 | 10 | closureTest() 11 | closureTest() 12 | closureTest()]] 13 | 14 | local a,b = "\1",3 15 | local c = a:byte(1) == b and "five" or "four" 16 | print(c) 17 | 18 | local a = os.clock() 19 | local c = 0 20 | local clk = os.clock 21 | while true do 22 | if clk()-a > 1 then break end 23 | c = c+1 24 | end 25 | print("Loops per second: "..c) 26 | -------------------------------------------------------------------------------- /luavm/decompiler/decoder.lua: -------------------------------------------------------------------------------- 1 | -- Decoder: Takes Lua bytecode and returns a version independent immediate representation -- 2 | 3 | -- For example: 4 | -- LOADK R1, K0 ["hello"] -> 5 | -- {op = "set", src = {{"constant", 0}}, dest = {{"register", 1}}} 6 | 7 | -- Decoders have a context. A decoder context will keep track of things related to loops. 8 | 9 | local pkgname = ... 10 | 11 | local decoder = {} 12 | 13 | function decoder.get(version) 14 | return require(pkgname..version)(decoder) 15 | end 16 | 17 | function decoder.native() 18 | local major, minor = _VERSION:match("Lua (%d)%.(%d)") 19 | 20 | return decoder.get(major..minor) 21 | end 22 | 23 | return decoder 24 | -------------------------------------------------------------------------------- /testrunner.lua: -------------------------------------------------------------------------------- 1 | local tests = { 2 | "bisect", 3 | "cf", 4 | "echo", 5 | "env", 6 | "factorial", 7 | "fib", 8 | "fibfor", 9 | "hello", 10 | "life", 11 | "printf", 12 | "readonly", 13 | "sieve", 14 | "sort" 15 | } 16 | 17 | local native = (...) ~= nil 18 | 19 | if native then 20 | for i, v in pairs(tests) do 21 | arg = {} 22 | local s, e = pcall(loadfile("test/"..v..".lua")) 23 | if not s then print(e) end 24 | end 25 | else 26 | local bytecode = require "luavm.bytecode" 27 | require "luavm.vm51" 28 | for i, v in pairs(tests) do 29 | arg = {} 30 | local s, e = pcall(vm.lua51.run,bytecode.load(string.dump(loadfile("test/"..v..".lua")))) 31 | if not s then print(e) end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/bisect.lua: -------------------------------------------------------------------------------- 1 | -- bisection method for solving non-linear equations 2 | 3 | delta=1e-6 -- tolerance 4 | 5 | function bisect(f,a,b,fa,fb) 6 | local c=(a+b)/2 7 | io.write(n," c=",c," a=",a," b=",b,"\n") 8 | if c==a or c==b or math.abs(a-b)>> ",string.rep(" ",level)) 9 | if t~=nil and t.currentline>=0 then io.write(t.short_src,":",t.currentline," ") end 10 | t=debug.getinfo(2) 11 | if event=="call" then 12 | level=level+1 13 | else 14 | level=level-1 if level<0 then level=0 end 15 | end 16 | if t.what=="main" then 17 | if event=="call" then 18 | io.write("begin ",t.short_src) 19 | else 20 | io.write("end ",t.short_src) 21 | end 22 | elseif t.what=="Lua" then 23 | -- table.foreach(t,print) 24 | io.write(event," ",t.name or "(Lua)"," <",t.linedefined,":",t.short_src,">") 25 | else 26 | io.write(event," ",t.name or "(C)"," [",t.what,"] ") 27 | end 28 | io.write("\n") 29 | end 30 | 31 | debug.sethook(hook,"cr") 32 | level=0 33 | -------------------------------------------------------------------------------- /test/sieve.lua: -------------------------------------------------------------------------------- 1 | -- the sieve of of Eratosthenes programmed with coroutines 2 | -- typical usage: lua -e N=1000 sieve.lua | column 3 | 4 | -- generate all the numbers from 2 to n 5 | function gen (n) 6 | return coroutine.wrap(function () 7 | for i=2,n do coroutine.yield(i) end 8 | end) 9 | end 10 | 11 | -- filter the numbers generated by `g', removing multiples of `p' 12 | function filter (p, g) 13 | return coroutine.wrap(function () 14 | while 1 do 15 | local n = g() 16 | if n == nil then return end 17 | if math.mod(n, p) ~= 0 then coroutine.yield(n) end 18 | end 19 | end) 20 | end 21 | 22 | N=N or 1000 -- from command line 23 | x = gen(N) -- generate primes up to N 24 | while 1 do 25 | local n = x() -- pick a number until done 26 | if n == nil then break end 27 | print(n) -- must be a prime number 28 | x = filter(n, x) -- now remove its multiples 29 | end 30 | -------------------------------------------------------------------------------- /test/trace-globals.lua: -------------------------------------------------------------------------------- 1 | -- trace assigments to global variables 2 | 3 | do 4 | -- a tostring that quotes strings. note the use of the original tostring. 5 | local _tostring=tostring 6 | local tostring=function(a) 7 | if type(a)=="string" then 8 | return string.format("%q",a) 9 | else 10 | return _tostring(a) 11 | end 12 | end 13 | 14 | local log=function (name,old,new) 15 | local t=debug.getinfo(3,"Sl") 16 | local line=t.currentline 17 | io.write(t.short_src) 18 | if line>=0 then io.write(":",line) end 19 | io.write(": ",name," is now ",tostring(new)," (was ",tostring(old),")","\n") 20 | end 21 | 22 | local g={} 23 | local set=function (t,name,value) 24 | log(name,g[name],value) 25 | g[name]=value 26 | end 27 | setmetatable(getfenv(),{__index=g,__newindex=set}) 28 | end 29 | 30 | -- an example 31 | 32 | a=1 33 | b=2 34 | a=10 35 | b=20 36 | b=nil 37 | b=200 38 | print(a,b,c) 39 | -------------------------------------------------------------------------------- /decompile.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | require "luavm.decompiler" 3 | 4 | local function func() 5 | --print("Hello!") 6 | --local five = getfive() 7 | --local isfive = five == 5 8 | --[[if isfive and five == 5 then 9 | print("It equals five!") 10 | end]] 11 | 12 | --[[local a = 6 13 | if a == 6 then 14 | print("a == 6") 15 | elseif a == 5 then 16 | print("a == 5") 17 | else 18 | print("a ~= 6") 19 | end 20 | 21 | while a > 0 do 22 | print(a) 23 | a = a-1 24 | if a == 3 then 25 | break 26 | end 27 | end]] 28 | 29 | --[[for i=1, 10 do 30 | print(i*8) 31 | end]] 32 | 33 | --io.write("test\n") 34 | 35 | --multiplication tables 36 | for i=1, 12 do 37 | for j=1, 12 do 38 | io.write((i*j).." ") 39 | end 40 | io.write("\n") 41 | end 42 | 43 | --[[for i, v in pairs(_G) do 44 | print(i, v) 45 | end]] 46 | end 47 | 48 | local bc = bytecode.load(string.dump(func)) 49 | bytecode.dump(bc) 50 | local syntaxrep = decompiler.decompile(bc) 51 | 52 | local source = decompiler.constructSyntax(syntaxrep,"pretty") 53 | 54 | print(source) 55 | 56 | --func() 57 | --loadstring(source)() 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dwayne Slater 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | These are simple tests for Lua. Some of them contain useful code. 2 | They are meant to be run to make sure Lua is built correctly and also 3 | to be read, to see how Lua programs look. 4 | 5 | Here is a one-line summary of each program: 6 | 7 | bisect.lua bisection method for solving non-linear equations 8 | cf.lua temperature conversion table (celsius to farenheit) 9 | echo.lua echo command line arguments 10 | env.lua environment variables as automatic global variables 11 | factorial.lua factorial without recursion 12 | fib.lua fibonacci function with cache 13 | fibfor.lua fibonacci numbers with coroutines and generators 14 | globals.lua report global variable usage 15 | hello.lua the first program in every language 16 | life.lua Conway's Game of Life 17 | luac.lua bare-bones luac 18 | printf.lua an implementation of printf 19 | readonly.lua make global variables readonly 20 | sieve.lua the sieve of of Eratosthenes programmed with coroutines 21 | sort.lua two implementations of a sort function 22 | table.lua make table, grouping all data for the same item 23 | trace-calls.lua trace calls 24 | trace-globals.lua trace assigments to global variables 25 | xd.lua hex dump 26 | 27 | -------------------------------------------------------------------------------- /run_vm.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | local lua = require "luavm.vm".native() 3 | 4 | local gbpkgs = {} 5 | 6 | local globals 7 | globals = setmetatable({ 8 | require = function(name) 9 | if gbpkgs[name] then return gbpkgs[name] end 10 | 11 | local filename = name:gsub("%.","/")..".lua" 12 | local file = io.open(filename) 13 | if file then 14 | file:close() 15 | local bc = bytecode.load(string.dump(loadfile(filename))) 16 | gbpkgs[name] = lua.run(bc,{name},nil,globals) 17 | return gbpkgs[name] 18 | else 19 | return require(name) 20 | end 21 | end, 22 | loadstring = function(str,name) 23 | local bc = bytecode.load(string.dump(loadstring(str, name))) 24 | return function(...) return lua.run(bc,{...},nil,globals) end 25 | end, 26 | --[[loadfile = function(file) 27 | local bc = bytecode.load(string.dump(loadfile(file))) 28 | return function(...) return lua.run(bc,{...},nil,globals) end 29 | end]] 30 | },{__index=_G}) 31 | print("Created globals") 32 | 33 | local args = {...} 34 | print("Got args: ",args[1]) 35 | local file = table.remove(args,1) 36 | print("File: ",file) 37 | local bc = bytecode.load(string.dump(loadfile(file))) 38 | --local bc = bytecode.load(io.open(file,"rb"):read("*a")) 39 | print("Loaded bytecode") 40 | 41 | lua.run(bc,args,nil,globals) 42 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | local vm = require "luavm.vm".native() 3 | 4 | function pass(...) 5 | return ... 6 | end 7 | 8 | local dump = string.dump(function(...) 9 | print(...) 10 | local r,e = 4,4 11 | local a, b, c = pass(1,2,3) 12 | print(a,b,c) 13 | print(unpack({4,6,2,4})) 14 | local function t() 15 | r = 5 16 | e = 6 17 | end 18 | t() 19 | print(r,e) 20 | local i = 1 21 | while true do 22 | local m = math.random(1,100) 23 | print("iter "..i,m) 24 | if m < 15 then 25 | break 26 | end 27 | i = i+1 28 | end 29 | return false and 1 or 0 30 | end) 31 | 32 | --print(vm.lua51.run(bytecode.load(dump),{"h","i",3,4,5})) 33 | 34 | local testbc = string.dump(function() return "Hello" end) 35 | local testbcl = bytecode.load(testbc) 36 | local testbco = bytecode.save(testbcl) 37 | assert(testbc == testbco,"Bytecode save test failed, INCONSISTENT!") 38 | print(vm.lua51.run(testbcl)) 39 | print(loadstring(testbc)()) 40 | print(loadstring(testbco)()) 41 | 42 | loadfile("hello.lua")() 43 | vm.run(bytecode.load(string.dump(loadfile("hello.lua")))) 44 | 45 | local opscalled = 0 46 | vm.run( 47 | bytecode.load(string.dump(function() while true do end end)), 48 | nil, 49 | nil, 50 | nil, 51 | function() opscalled = opscalled+1 if opscalled > 480000 then error("Timeout.",0) end end) 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LuaVM 2 | ===== 3 | 4 | Lua Virtual Machine for Lua so you can Lua while you Lua 5 | 6 | Usage 7 | ----- 8 | 9 | To use LuaVM, first you must meet all the requirements: 10 | 11 | * Lua 5.1 and 5.2 are the only versions supported. Unoffical versions of Lua (like LuaJIT, LuaJ, etc.) are not fully supported. 12 | * A way to generate Lua 5.1 valid bytecode (an ASM suite is in the works, a Lua to bytecode compiler is coming) 13 | 14 | Then, you must require the modules you need (sorry for the global namespace injections): 15 | 16 | ``` 17 | require("luavm.bytecode") 18 | require("luavm.vm[your version here, 51 or 52]") 19 | ``` 20 | 21 | After that, simply call ```vm.lua[your version here, like 51 or 52].run(bytecode.load([bytecode here]))``` 22 | 23 | Documentation 24 | ------------- 25 | 26 | ### bytecode.load(bytecode) 27 | Loads valid Lua 5.1 or 5.2 bytecode (as a string). Returns a table. 28 | 29 | ### bytecode.save(bytecode) 30 | Redumps bytecode tables created from ```bytecode.new``` or ```bytecode.load```. Returns a string. 31 | 32 | ### vm.run(bytecode, arguments, upvalues, globals, hook) 33 | Starts running bytecode. Arguments, Upvalues, Globals, and Hooks are optional. Returns whatever the emulated bytecode returns. Arguments, upvalues, and globals must be a table. Hook must be a function, it is called every single instruction. 34 | -------------------------------------------------------------------------------- /dynarectest.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | require "luavm.dynarec" 3 | require "luavm.vm51" 4 | 5 | function ret_true() 6 | return true 7 | end 8 | 9 | local sbc = string.dump(function(...) 10 | --local a = b and 5 or 6 11 | --[[local a 12 | if b then 13 | a = 5 14 | b = 8 15 | elseif not a then 16 | a = 6 17 | elseif c then 18 | a = 3 19 | else 20 | a = 9 21 | end 22 | print(a,b)]] 23 | --[[local a 24 | if b then 25 | a = 5 26 | if not a then 27 | a = 6 28 | end 29 | end]] 30 | --[[local a 31 | while one do 32 | a = 6 33 | end]] 34 | --[[local a = 6 35 | local b = 4 36 | local c = 2 37 | local m = (a+b+c)/3 38 | print(m) 39 | while not m do 40 | 41 | end]] 42 | --[[local i = 1 43 | while i < 8 do 44 | print(i) 45 | i = i+1 46 | end]] 47 | --print("hai") 48 | --[[while ret_true() do 49 | --print("RET TRUE!") 50 | while true do 51 | local i = 1 52 | while i < 8 do 53 | if i%2 == 0 then 54 | print(i, "is even") 55 | else 56 | print(i, "is odd") 57 | end 58 | i = i+1 59 | end 60 | end 61 | end]] 62 | --[[for i=1, 10 do 63 | print(i) 64 | end]] 65 | for i, v in pairs(table) do 66 | print(i,v) 67 | end 68 | end) 69 | local bc = bytecode.load(sbc) 70 | 71 | bytecode.dump(bc) 72 | print("dynamic recomp below") 73 | 74 | local dyncode = table.concat( 75 | dynarec.compile(bc) 76 | ,"\n") 77 | 78 | print(dyncode) 79 | --loadstring(dyncode)() 80 | --loadstring(sbc)() 81 | --vm.run(bc) 82 | -------------------------------------------------------------------------------- /patcherexample.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | 3 | function stalkUpvalues(func) 4 | local bc = bytecode.load(string.dump(func)) 5 | 6 | local globals = setmetatable({},{__index=function(_,i) 7 | if type(i) == "string" and i:sub(1,10) == "__UPVALUE_" then 8 | return select(2,debug.getupvalue(func,i:sub(11))) 9 | else 10 | return _G[i] 11 | end 12 | end,__newindex=function(_,i,v) 13 | if i:sub(1,10) == "__UPVALUE_" then 14 | debug.setupvalue(func,i:sub(11),v) 15 | else 16 | _G[i] = v 17 | end 18 | end}) 19 | 20 | --patch all GETUPVAL to GETGLOBAL-- 21 | local ngg = 0 22 | while ngg do 23 | ngg = bytecode.lua51.patcher.find(bc, ngg, bytecode.lua51.instructions.GETUPVAL) 24 | if ngg then 25 | local _,a,b,c = bytecode.lua51.decode(bc.instructions[ngg]) 26 | local const = bytecode.lua51.patcher.addConstant(bc, "__UPVALUE_"..(b+1)) 27 | bytecode.lua51.patcher.replace(bc, ngg, bytecode.lua51.encode(bytecode.lua51.instructions.GETGLOBAL, a, const, 0)) 28 | end 29 | end 30 | 31 | --patch all SETUPVAL to SETGLOBAL-- 32 | local ngg = 0 33 | while ngg do 34 | ngg = bytecode.lua51.patcher.find(bc, ngg, bytecode.lua51.instructions.SETUPVAL) 35 | if ngg then 36 | local _,a,b,c = bytecode.lua51.decode(bc.instructions[ngg]) 37 | local const = bytecode.lua51.patcher.addConstant(bc, "__UPVALUE_"..(b+1)) 38 | bytecode.lua51.patcher.replace(bc, ngg, bytecode.lua51.encode(bytecode.lua51.instructions.SETGLOBAL, a, const, 0)) 39 | end 40 | end 41 | 42 | bytecode.dump(bc) 43 | return setfenv(assert(loadstring(bytecode.save(bc), "=changed", "bt")), globals) 44 | end 45 | 46 | local a,b,c = 1,2,3 47 | stalkUpvalues(function() 48 | print(a,b,c) 49 | a,b,c = 5,6,7 50 | end)() 51 | 52 | print(a,b,c) 53 | -------------------------------------------------------------------------------- /patcherhook.lua: -------------------------------------------------------------------------------- 1 | local bytecode = require "luavm.bytecode" 2 | 3 | function _HOOK() 4 | print("Hook") 5 | end 6 | 7 | function addHook(func) 8 | local bc = bytecode.load(string.dump(func)) 9 | bytecode.dump(bc) 10 | 11 | local tempReg = bc.maxStack 12 | bc.maxStack = bc.maxStack+1 13 | local hookConstant = bytecode.lua51.patcher.addConstant(bc, "_HOOK") 14 | 15 | local get = bytecode.lua51.encode(bytecode.lua51.instructions.GETGLOBAL, tempReg, hookConstant) 16 | local call = bytecode.lua51.encode(bytecode.lua51.instructions.CALL, tempReg, 1, 1) 17 | 18 | local function tryAddHookAt(pc) 19 | bytecode.lua51.patcher.insert(bc,pc,get) 20 | bytecode.lua51.patcher.insert(bc,pc+1,call) 21 | end 22 | 23 | local pc = 0 24 | local rpc = 0 25 | local ninst = #bc.instructions 26 | while rpc < ninst do 27 | local s,e = pcall(tryAddHookAt,pc) 28 | pc = pc+3 29 | rpc = rpc+1 30 | end 31 | 32 | --patch all JMP 0 -1-- 33 | --this fixes a problem where "while true do end" could not be patched-- 34 | local ngg = 0 35 | while ngg do 36 | ngg = bytecode.lua51.patcher.find(bc, ngg, bytecode.lua51.instructions.JMP) 37 | if ngg then 38 | local _,a,b,c = bytecode.lua51.decode(bc.instructions[ngg]) 39 | if b == -1 then 40 | bytecode.lua51.patcher.insert(bc,ngg,get) 41 | bytecode.lua51.patcher.insert(bc,ngg+1,call) 42 | bytecode.lua51.patcher.replace(bc, ngg+2, bytecode.lua51.encode(bytecode.lua51.instructions.JMP, 0, -2, 0)) 43 | ngg = ngg+2 44 | end 45 | end 46 | end 47 | 48 | bytecode.dump(bc) 49 | return assert(loadstring(bytecode.save(bc), "=changed", "bt")) 50 | end 51 | 52 | addHook(function() 53 | local a,b,c = 1,2,3 54 | print(a,b,c) 55 | while true do 56 | print("HI") 57 | break 58 | end 59 | end)() 60 | -------------------------------------------------------------------------------- /test/sort.lua: -------------------------------------------------------------------------------- 1 | -- two implementations of a sort function 2 | -- this is an example only. Lua has now a built-in function "sort" 3 | 4 | -- extracted from Programming Pearls, page 110 5 | function qsort(x,l,u,f) 6 | if ly end) 58 | show("after reverse selection sort",x) 59 | qsort(x,1,n,function (x,y) return x 3 then 8 | print("Q!") 9 | end 10 | end 11 | 12 | Compiles into (not real Lua bytecode): 13 | 14 | loop: 15 | branch j > 3, [onFalse, onTrue] 16 | onFalse: goto exit 17 | onTrue: print("Q!") 18 | 19 | exit: 20 | goto loop 21 | 22 | Which gets optimized into: 23 | 24 | loop: 25 | branch j > 3, [onFalse, onTrue] 26 | onFalse: goto loop 27 | onTrue: print("Q!") 28 | 29 | exit: 30 | goto loop 31 | 32 | This is because Lua notices a jump to another jump. 33 | When the bytecode compiler notices this, it removes the intermediate jump. 34 | 35 | The jump deoptimizer handles this by infering what the original jump was, 36 | based on knowledge known about the construct used. 37 | 38 | The "onFalse" block of conditional expression _usually_ jumps to where the 39 | "onTrue" block falls through, for example. 40 | ]] 41 | 42 | return function(decompiler) 43 | 44 | decompiler.jumpDeopt = {} 45 | function decompiler.jumpDeopt.tryDeoptConditional(context, conditional) 46 | -- blockIndex points to conditional block 47 | -- usually the next block is an alias block 48 | -- but in the event its a loop block, we need to do jump deopt to fix it 49 | local blockA = conditional.next 50 | local blockB = blockA.next 51 | if blockA.type == "loop" and blockB then 52 | -- deoptimize, find the last loop block with this jump target 53 | local lastLoopBlock 54 | for testBlock in context:reverseBlocksUntil(blockB) do 55 | if testBlock.type == "loop" and testBlock.loop.target == blockA.loop.target then 56 | lastLoopBlock = testBlock 57 | break 58 | end 59 | end 60 | 61 | if lastLoopBlock then 62 | assert(blockA.length == 1) 63 | 64 | if lastLoopBlock.length > 1 then 65 | -- Split the last loop block 66 | local jump = lastLoopBlock.decoded[lastLoopBlock.length-1] 67 | 68 | local singleJumpBlock = decompiler.block { 69 | start = lastLoopBlock.start+lastLoopBlock.length-1, 70 | length = 1, 71 | decoded = {jump} 72 | } 73 | 74 | context:insertBlockAfter(lastLoopBlock, singleJumpBlock) 75 | 76 | lastLoopBlock.decoded[lastLoopBlock.length-1] = nil 77 | lastLoopBlock.length = lastLoopBlock.length-1 78 | context:clearBlockIdentification(lastLoopBlock) 79 | 80 | context:identifyBlock(lastLoopBlock) 81 | context:identifyBlock(singleJumpBlock) 82 | 83 | lastLoopBlock = singleJumpBlock 84 | end 85 | 86 | -- mutate the jump 87 | blockA.decoded[0] = {"jump", to = lastLoopBlock.start} 88 | context:clearBlockIdentification(blockA) 89 | context:identifyBlock(blockA) 90 | end 91 | end 92 | end 93 | 94 | end 95 | -------------------------------------------------------------------------------- /luavm/decompiler3/decompiler/instr_utils.lua: -------------------------------------------------------------------------------- 1 | return function(decompiler) 2 | local instr = {} 3 | 4 | decompiler.instr = instr 5 | 6 | local regDetailCache = setmetatable({}, {__mode = "k"}) 7 | 8 | function instr.getRegisterDetails(instr) 9 | if not instr then return nil end 10 | 11 | local details = regDetailCache[instr] 12 | if not details then 13 | details = { 14 | write = {}, 15 | read = {} 16 | } 17 | 18 | local typ = instr[1] 19 | 20 | local function write(reg) 21 | details.write[reg] = true 22 | end 23 | 24 | local function read(reg) 25 | details.read[reg] = true 26 | end 27 | 28 | local function readVA(base) 29 | details.read[-1] = base 30 | end 31 | 32 | local function writeVA(base) 33 | details.write[-1] = base 34 | end 35 | 36 | if instr.dest then 37 | write(instr.dest) 38 | end 39 | 40 | if instr.src then 41 | read(instr.src) 42 | end 43 | 44 | if instr.lhs and instr.lhs < 256 then 45 | read(instr.lhs) 46 | end 47 | 48 | if instr.rhs and instr.rhs < 256 then 49 | read(instr.rhs) 50 | end 51 | 52 | if typ == "call" or typ == "tailcall" then 53 | read(instr.base) -- function 54 | 55 | if instr.narg < 0 then 56 | readVA(instr.base+1) -- VA arguments 57 | else 58 | for i=instr.base+1, instr.base+instr.narg do 59 | read(i) -- argument 60 | end 61 | end 62 | 63 | if instr.nret < 0 then 64 | writeVA(instr.base) -- VA results 65 | else 66 | for i=instr.base, instr.base+instr.nret-1 do 67 | write(i) -- result 68 | end 69 | end 70 | elseif typ == "gettable" or typ == "settable" then 71 | read(instr.table) 72 | read(instr.index) 73 | elseif typ == "self" then 74 | read(instr.object) 75 | write(instr.base) 76 | write(instr.base+1) 77 | elseif typ == "concat" then 78 | for i=instr.from, instr.to do 79 | read(i) 80 | end 81 | elseif typ == "loadnil" then 82 | for i=instr.from, instr.to do 83 | write(i) 84 | end 85 | elseif typ == "condop" and (instr[2] == "test" or instr[2] == "testset") then 86 | read(instr.target) 87 | elseif typ == "return" then 88 | if instr.count < 0 then 89 | readVA(instr.base) 90 | else 91 | for i=instr.base, instr.base+instr.count-1 do 92 | read(i) 93 | end 94 | end 95 | elseif typ == "forprep" then 96 | read(instr.base) 97 | read(instr.base+2) 98 | write(instr.base) 99 | elseif typ == "forloop" then 100 | read(instr.base) -- index 101 | read(instr.base+1) -- limit 102 | read(instr.base+2) -- step 103 | write(instr.base) 104 | write(instr.base+3) 105 | elseif typ == "tforloop" then 106 | read(instr.base) 107 | read(instr.base+1) 108 | read(instr.base+2) 109 | for i=instr.base+3, instr.base+3+instr.count-1 do 110 | write(i) 111 | end 112 | write(instr.base+2) 113 | elseif typ == "setlist" then 114 | if instr.count < 0 then 115 | readVA(instr.base+1) 116 | else 117 | for i=0, instr.count-1 do 118 | read(instr.base+1+i) 119 | end 120 | end 121 | elseif typ == "vararg" then 122 | if instr.count < 0 then 123 | writeVA(instr.base) 124 | else 125 | for i=0, instr.count-1 do 126 | write(instr.base+1+i) 127 | end 128 | end 129 | end 130 | end 131 | return details 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /luavm/evaluator.lua: -------------------------------------------------------------------------------- 1 | --evaluates and compiles any lua bytecode (as long as the instruction set is supported-- 2 | evaluator = {} 3 | 4 | evaluator.lua = { 5 | keywords = {"do","end","function","while","repeat","until","if","then","elseif","then","else","for","in","local","return","break", 6 | "nil","false","true", 7 | "%.%.%.","==","~=","%.%.","<=",">=","and","or","not", 8 | "[-+;.:+*/,=%[%]%(%)%<%>%^%%#{}]"}, 9 | ast = {}, 10 | comment = "%-%-[^\n]*\n", 11 | blockCommentStart = "%-%-%[%[", 12 | blockCommentEnd = "%]%]", 13 | } 14 | 15 | function evaluator.eval(code, name, language, disallowCustomMatchers) 16 | name = name or "[string]" 17 | language = language or evaluator.lua 18 | local tokens = {} 19 | do 20 | local pos = 1 21 | 22 | local line = 1 23 | local char = 1 24 | 25 | local lasttok 26 | local function accept(regex) 27 | --print(regex, " ", pos) 28 | --yield() 29 | local r = code:match("^"..regex, pos) 30 | if r == nil then return false end 31 | lasttok = r 32 | pos = pos + #lasttok 33 | 34 | for k=1,#r do 35 | if r:sub(k,k) == "\n" then 36 | line = line + 1 37 | char = 1 38 | else 39 | char = char + 1 40 | end 41 | end 42 | 43 | return true 44 | end 45 | 46 | local function getContext() 47 | return {prefix=locprefix, line=line, char=char} 48 | --return c:sub(pos, pos+100) 49 | end 50 | 51 | local keywords = language.keywords 52 | local function tokenise1() 53 | accept("[ \r\n\t]+") 54 | if accept(language.blockCommentStart) then 55 | while not accept(language.blockCommentEnd) do 56 | if not accept("[^%]]+") then accept(".") end 57 | end 58 | return tokenise1() 59 | end 60 | if accept(language.comment) then return tokenise1() end 61 | if accept("[a-zA-Z_][a-zA-Z_0-9]*") then 62 | for k,v in ipairs(keywords) do 63 | if lasttok == v then return v end 64 | end 65 | return "id" 66 | end 67 | for k,v in ipairs(keywords) do if accept(v) then return lasttok end end 68 | if accept("[0-9]+%.[0-9]*") or accept("[0-9]+") then return "num" end 69 | if accept("\"") or accept("%[%[") then 70 | local s = "" 71 | local long = lasttok == "[[" 72 | local _end = long and "%]%]" or "\"" 73 | while not accept(_end) do 74 | if accept("\\") then 75 | if accept("a") then s=s.."\a" 76 | elseif accept("b") then s=s.."\b" 77 | elseif accept("f") then s=s.."\f" 78 | elseif accept("n") then s=s.."\n" 79 | elseif accept("r") then s=s.."\r" 80 | elseif accept("t") then s=s.."\t" 81 | elseif accept("v") then s=s.."\v" 82 | elseif accept("\\") then s=s.."\\" 83 | elseif accept("\"") then s=s.."\"" 84 | elseif accept("'") then s=s.."\'" 85 | elseif accept("%[") then s=s.."[" 86 | elseif accept("%]") then s=s.."]" 87 | elseif accept("[0-9][0-9][0-9]") or accept("[0-9][0-9]") or accept("[0-9]") then s=s..string.char(tonumber(lasttok)) 88 | end 89 | elseif accept(long and "[^%]\\]+" or "[^\n\"\\]+") then s=s..lasttok 90 | else error("unfinished string") 91 | end 92 | end 93 | lasttok = s 94 | return "str" 95 | end 96 | if pos > #c then lasttok="" return "" end 97 | error("Unknown token near "..c:sub(pos-50,pos+100)) 98 | return nil 99 | end 100 | 101 | while pos <= #c do 102 | local t = tokenise1() 103 | if t == nil then --[[print(c:sub(pos,pos+100))]] break end 104 | table.insert(tokens, {t, lasttok, getContext()}) 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /luavm/decompiler/pass/inline.lua: -------------------------------------------------------------------------------- 1 | -- Inline Pass: Takes immediate representation and inlines -- 2 | 3 | local function dumpValue(value, indent, skipTables) 4 | indent = indent or "" 5 | local typ = type(value) 6 | 7 | if typ == "table" and (not skipTables or not skipTables[value]) then 8 | if not skipTables then skipTables = {} end 9 | skipTables[value] = true 10 | local buffer = {"{"} 11 | local currentNumber = 1 12 | 13 | for i, v in pairs(value) do 14 | if i == currentNumber then 15 | currentNumber = currentNumber+1 16 | buffer[#buffer+1] = indent.."\t"..dumpValue(v, indent.."\t", skipTables) 17 | else 18 | buffer[#buffer+1] = indent.."\t"..dumpValue(i, indent.."\t", skipTables)..": "..dumpValue(v, indent.."\t", skipTables) 19 | end 20 | end 21 | 22 | return table.concat(buffer, "\n").."\n"..indent.."}" 23 | end 24 | 25 | return tostring(value) 26 | end 27 | 28 | return function(decompiler) 29 | local analyzer = decompiler.analyzer 30 | return function(irBlock) 31 | local liveRanges = irBlock.liveRanges 32 | for pass=1, 3 do 33 | print("Starting inline pass "..pass) 34 | local ir 35 | local actuallyDidSomething = false -- If a pass does nothing then the loop is exit early 36 | 37 | local function handleSource(reg, t, i) 38 | local possible, inlineIR = analyzer.isInlinePossible(liveRanges, reg, ir.pc, t.pc) 39 | if possible then 40 | -- To inline, we take the first source explet of the IR and put it into where the register used to be 41 | -- The entire IR expr is then disabled so it doesn't show up in final output 42 | -- This is so the block doesn't really have to be modified 43 | inlineIR.disabled = "inline_source" 44 | t[i] = inlineIR.src[1] 45 | 46 | actuallyDidSomething = true 47 | end 48 | end 49 | 50 | -- Recursively inline explets in blocks 51 | local function inlineBlock(irBlock) 52 | for i=1, #irBlock do 53 | ir = irBlock[i] 54 | if ir.src then analyzer.forEachRegisterInEachExplet(ir.src, handleSource) end 55 | if ir.dest and #ir.dest == 1 and ir.dest[1][1] ~= "register" and 56 | #ir.src == 1 and ir.src[1][1] == "register" then 57 | -- Attempt destination inline 58 | --[[ 59 | r0, r1 = func() 60 | b = r1 61 | a = r0 62 | 63 | TO 64 | 65 | a, b = func() 66 | ]] 67 | -- Index the current block backwards until we hit an instruction that has our single source reg as a dest reg 68 | -- Or we could just reuse analyzer usage data :P 69 | -- TODO: Disable destination inline when global state is modified by dest and used by source! 70 | -- This means we need more analyzer stuff! 71 | local dest = ir.dest[1] 72 | local src = ir.src[1] 73 | local range = analyzer.findRange(liveRanges[src[2]], ir.pc) 74 | if range then 75 | local setir = range.set 76 | for i=1, #setir.dest do 77 | local sdest = setir.dest[i] 78 | if sdest[1] == "register" and sdest[2] == src[2] then 79 | setir.dest[i] = dest 80 | ir.disabled = "inline_dest" 81 | break 82 | end 83 | end 84 | end 85 | end 86 | if ir.block then 87 | inlineBlock(ir.block) 88 | end 89 | end 90 | end 91 | 92 | --TODO: Because of a bug, things need to be rewriten 93 | -- Register range compatibility needs to be checked in the registers an explet uses before it inlines 94 | 95 | inlineBlock(irBlock) 96 | 97 | if not actuallyDidSomething then 98 | print("Nothing done in inline pass "..pass) 99 | break 100 | else 101 | irBlock.liveRanges = analyzer.computeLiveRanges(irBlock) 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /luavm/decompiler3/decompiler/expr_manager.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Expression Manager: Manages expressions. 3 | ]] 4 | 5 | return function(decompiler) 6 | 7 | local exprmgr = {} 8 | 9 | decompiler.exprmgr = exprmgr 10 | 11 | function exprmgr.findWriteBefore(block, reg, pc) 12 | for i=pc-1, 0, -1 do 13 | local instr = block.decoded[i] 14 | local details = decompiler.instr.getRegisterDetails(instr) 15 | if details.write[reg] then 16 | -- TODO: Inline checks, like multiple usage and etc 17 | return i 18 | end 19 | end 20 | return nil 21 | end 22 | 23 | function exprmgr.tryVisitReg(block, reg, pc) 24 | local wpc = exprmgr.findWriteBefore(block, reg, pc) 25 | 26 | if wpc then 27 | return exprmgr.visit(block, wpc) 28 | else 29 | return {"local", reg = reg} 30 | end 31 | end 32 | 33 | -- TODO: This might be the wrong way to do this... we should have a different system for doing this. 34 | 35 | --[[ 36 | The main problem with this system was that we iteratively generate expressions based off what we see at an earlier expression. 37 | 38 | This is stupid. 39 | 40 | Instead, we should generate each expression part in a linked list structure 41 | with parent child relations. Then just collapse expressions into other expressions. 42 | 43 | Also the structure would look like the structure of the resulting source code. 44 | ]] 45 | 46 | function exprmgr.visit(block, pc) 47 | -- Visit the expression at pc in the given block (block relative pc) 48 | local instr = block.decoded[pc] 49 | local typ = instr[1] 50 | 51 | if typ == "condop" then 52 | local subtyp = instr[2] 53 | 54 | if subtyp == "test" then 55 | return { 56 | "condop", "test", 57 | invert = instr.invert, 58 | target = exprmgr.tryVisitReg(block, instr.target, pc) 59 | } 60 | else 61 | return { 62 | "condop", subtyp, 63 | invert = instr.invert, 64 | lhs = exprmgr.tryVisitReg(block, instr.lhs, pc), 65 | rhs = exprmgr.tryVisitReg(block, instr.rhs, pc) 66 | } 67 | end 68 | elseif typ == "binop" then 69 | return { 70 | "binop", instr[2], 71 | lhs = exprmgr.tryVisitReg(block, instr.lhs, pc), 72 | rhs = exprmgr.tryVisitReg(block, instr.rhs, pc) 73 | } 74 | elseif typ == "unop" then 75 | return { 76 | "unop", instr[2], 77 | rhs = exprmgr.tryVisitReg(block, instr.rhs, pc) 78 | } 79 | elseif typ == "getglobal" then 80 | return {"getglobal", index = {"constant", kst = instr.index}} 81 | elseif typ == "loadk" then 82 | return {"constant", kst = instr.kst} 83 | elseif typ == "move" then 84 | return {"local", reg = instr.src} 85 | elseif typ == "gettable" then 86 | return { 87 | "gettable", 88 | table = exprmgr.tryVisitReg(block, instr.table, pc), 89 | index = exprmgr.tryVisitReg(block, instr.index, pc) 90 | } 91 | elseif typ == "call" or typ == "tailcall" then 92 | local args = {} 93 | local self = false 94 | 95 | for i=instr.base, instr.base+instr.narg do 96 | local expr = exprmgr.tryVisitReg(block, i, pc) 97 | if expr[1] == "self" then 98 | self = true 99 | end 100 | args[#args+1] = expr 101 | end 102 | 103 | local func = table.remove(args, 1) 104 | 105 | if self then 106 | -- Remove the first argument from the function 107 | -- Otherwise the rendered code is invalid 108 | table.remove(args, 1) 109 | end 110 | 111 | return {typ, func = func, args = args} 112 | elseif typ == "self" then 113 | return { 114 | "self", 115 | object = exprmgr.tryVisitReg(block, instr.object, pc), 116 | method = exprmgr.tryVisitReg(block, instr.method, pc), 117 | } 118 | elseif typ == "closure" then 119 | return { 120 | "closure", 121 | proto = instr.proto, 122 | upvalues = instr.upvalues 123 | } 124 | elseif typ == "return" then 125 | local rets = {} 126 | for i=instr.base, instr.base+instr.count-1 do 127 | rets[#rets+1] = exprmgr.tryVisitReg(block, i, pc) 128 | end 129 | return {"return", rets = rets} 130 | end 131 | end 132 | 133 | end 134 | -------------------------------------------------------------------------------- /luavm/decompiler/pass/table_inline.lua: -------------------------------------------------------------------------------- 1 | -- Table Inline Pass: Inlines table sets into the table declaration -- 2 | -- This works by indexing blocks backwards 3 | -- If we find a set instruction on a table destination and the src does not use the declared table 4 | -- or the parent tables then we can inline 5 | 6 | --[[ 7 | Example: 8 | 9 | local tab = {} 10 | tab[1] = {} 11 | tab[1][2] = "hi" 12 | tab.a = 5 13 | tab.b = tab 14 | tab.c = tab.a 15 | tab.d = tab[1][2] 16 | 17 | to 18 | 19 | local tab = {{[2] = "hi"}, a = 5} 20 | tab.b = tab 21 | tab.c = tab.a 22 | tab.d = tab[1][2] 23 | 24 | ]] 25 | 26 | return function(decompiler) 27 | local analyzer = decompiler.analyzer 28 | return function(irBlock) 29 | local liveRanges = irBlock.liveRanges 30 | print("Starting table inline") 31 | 32 | -- Pass 1: Find table declarations and their live ranges 33 | print("Finding table decls") 34 | local decls = { 35 | --[[ 36 | [0] = { 37 | {ir pos, ir block, live range, table} 38 | } 39 | ]] 40 | } 41 | do 42 | local function scanBlock(irBlock) 43 | for i=1, #irBlock do 44 | local ir = irBlock[i] 45 | if ir.src and #ir.src == 1 and ir.src[1][1] == "value" and type(ir.src[1][2]) == "table" then 46 | -- If the ir part has a single source as a value that holds a table 47 | print("Found a table") 48 | if ir.dest and #ir.dest == 1 and ir.dest[1][1] == "register" then 49 | -- If it has a single dest and it is a register 50 | local reg = ir.dest[1][2] 51 | local regdecls = decls[reg] 52 | if not regdecls then 53 | regdecls = {} 54 | decls[reg] = regdecls 55 | end 56 | regdecls[#regdecls+1] = {i, irBlock, analyzer.findRange(liveRanges[reg], i), ir.src[1][2]} 57 | end 58 | end 59 | 60 | if ir.block then 61 | scanBlock(ir.block) 62 | end 63 | end 64 | end 65 | 66 | scanBlock(irBlock) 67 | end 68 | 69 | print("Collecting table sets") 70 | do 71 | local function scanBlock(irBlock) 72 | for i=1, #irBlock do 73 | local ir = irBlock[i] 74 | if ir.dest and #ir.dest == 1 and ir.dest[1][1] == "index" and ir.dest[1][2][1] == "register" then 75 | local sets = decls[ir.dest[1][2][2]] 76 | for j=1, #sets do 77 | local tabdecl = sets[j] 78 | if tabdecl[2] == irBlock and i >= tabdecl[3][1] and i <= tabdecl[3][2] then 79 | print("Can inline into table at "..tabdecl[1]) 80 | tabdecl[4][ir.dest[1][3]] = ir.src[1] 81 | ir.disabled = "table_inline" 82 | end 83 | end 84 | end 85 | 86 | if ir.block then 87 | scanBlock(ir.block) 88 | end 89 | end 90 | end 91 | 92 | scanBlock(irBlock) 93 | end 94 | 95 | --[=[for pass=1, 3 do 96 | print("Starting inline pass "..pass) 97 | local ir 98 | local actuallyDidSomething = false -- If a pass does nothing then the loop is exit early 99 | 100 | local function handleSource(reg, t, i) 101 | local possible, inlineIR = analyzer.isInlinePossible(liveRanges, reg, ir.pc) 102 | if possible then 103 | -- To inline, we take the first source explet of the IR and put it into where the register used to be 104 | -- The entire IR expr is then disabled so it doesn't show up in final output 105 | -- This is so the block doesn't really have to be modified 106 | inlineIR.disabled = "inline_source" 107 | t[i] = inlineIR.src[1] 108 | 109 | actuallyDidSomething = true 110 | end 111 | end 112 | 113 | -- Recursively inline explets in blocks 114 | local function inlineBlock(irBlock) 115 | for i=1, #irBlock do 116 | ir = irBlock[i] 117 | if ir.src then analyzer.forEachRegisterInEachExplet(ir.src, handleSource) end 118 | if ir.dest and #ir.dest == 1 and ir.dest[1][1] ~= "register" and 119 | #ir.src == 1 and ir.src[1][1] == "register" then 120 | -- Attempt destination inline 121 | --[[ 122 | r0, r1 = func() 123 | b = r1 124 | a = r0 125 | 126 | TO 127 | 128 | a, b = func() 129 | ]] 130 | -- Index the current block backwards until we hit an instruction that has our single source reg as a dest reg 131 | -- Or we could just reuse analyzer usage data :P 132 | local dest = ir.dest[1] 133 | local src = ir.src[1] 134 | local range = analyzer.findRange(liveRanges[src[2]], ir.pc) 135 | if range then 136 | local setir = range.set 137 | for i=1, #setir.dest do 138 | local sdest = setir.dest[i] 139 | if sdest[1] == "register" and sdest[2] == src[2] then 140 | setir.dest[i] = dest 141 | ir.disabled = "inline_dest" 142 | break 143 | end 144 | end 145 | end 146 | end 147 | if ir.block then 148 | inlineBlock(ir.block) 149 | end 150 | end 151 | end 152 | 153 | inlineBlock(irBlock) 154 | 155 | if not actuallyDidSomething then 156 | print("Nothing done in inline pass "..pass) 157 | break 158 | else 159 | irBlock.liveRanges = analyzer.computeLiveRanges(irBlock) 160 | end 161 | end]=] 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /luavm/decompiler3/decompiler/block_identifier.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Block identifier: Identifies what blocks are based on their structure. 3 | 4 | Block types: 5 | 6 | Alias: A block that only jumps to another block after itself. 7 | Loop: A block that exits to a previous block or itself. 8 | Simple: A block that falls through to the next block. 9 | Conditional: A block that executes one block or the other based on the 10 | results from an expression. 11 | Normal: A block that jumps forward to another block, but has other 12 | instructions. (DEPRECATED) 13 | Exit: A block that exits the function through a return. 14 | Complex: A block that always jumps forward to another block. 15 | 16 | Identifying blocks: 17 | 18 | Alias: 19 | Only one instruction, a jump. 20 | The jump's target is after the current block. 21 | Loop: 22 | The final instruction is a jump. 23 | The jump's target is before or the current block. 24 | Simple: 25 | The final instruction is NOT a block terminator. 26 | Conditional: 27 | The final instruction is a conditional instruction. 28 | Normal: 29 | Everything that doesn't fit in with the rest. 30 | ]] 31 | 32 | return function(decompiler) 33 | 34 | local function getJumpTarget(instr, pc) 35 | if instr then 36 | if instr[1] == "jump" then 37 | return instr.to 38 | elseif instr[1] == "forprep" or instr[1] == "forloop" then 39 | return instr.target 40 | elseif instr[1] == "loadbool" and instr.skipNext then 41 | return pc+2 42 | end 43 | end 44 | end 45 | 46 | local function handleBlockAlias(block) 47 | if block.length ~= 1 then return false end 48 | local instr = block.decoded[0] 49 | if not instr or instr[1] ~= "jump" then return false end 50 | if instr.to <= block.start then return false end 51 | 52 | block.type = "alias" 53 | block.alias = { 54 | target = instr.to 55 | } 56 | 57 | return true 58 | end 59 | 60 | local function handleBlockLoop(block) 61 | local instr = block.decoded[block.length-1] 62 | local jt = getJumpTarget(instr, block.start+block.length-1) 63 | if not jt then return false end 64 | if jt > block.start then return false end 65 | 66 | block.type = "loop" 67 | block.loop = { 68 | target = jt 69 | } 70 | 71 | return true 72 | end 73 | 74 | local function isBlockSimple(block) 75 | local instr = block.decoded[block.length-1] 76 | if decompiler.isTerminator(instr) then 77 | -- last instruction is a block terminator 78 | return false 79 | end 80 | return true 81 | end 82 | 83 | local function isBlockConditional(block) 84 | local instr = block.decoded[block.length-1] 85 | if not instr or instr[1] ~= "condop" then return false end 86 | return true 87 | end 88 | 89 | local function handleBlockExit(block) 90 | local instr = block.decoded[block.length-1] 91 | if not instr or instr[1] ~= "return" then return false end 92 | 93 | block.type = "exit" 94 | block.exit = {} 95 | 96 | return true 97 | end 98 | 99 | local function handleBlockComplex(block) 100 | local instr = block.decoded[block.length-1] 101 | local jt = getJumpTarget(instr, block.start+block.length-1) 102 | if not jt then return false end 103 | if jt <= block.start then return false end 104 | 105 | block.type = "complex" 106 | block.complex = { 107 | target = jt 108 | } 109 | 110 | return true 111 | end 112 | 113 | function decompiler.clearBlockIdentification(context, block) 114 | if block.type then 115 | block[block.type] = nil 116 | block.type = nil 117 | end 118 | end 119 | 120 | function decompiler.identifyBlock(context, block) 121 | if not block.type then 122 | if handleBlockAlias(block) then 123 | elseif handleBlockLoop(block) then 124 | elseif handleBlockExit(block) then 125 | elseif handleBlockComplex(block) then 126 | elseif isBlockSimple(block) then 127 | block.type = "simple" 128 | block.simple = {} 129 | elseif isBlockConditional(block) then 130 | block.type = "conditional" 131 | block.conditional = {} 132 | else 133 | block.type = "normal" 134 | block.normal = {} 135 | end 136 | end 137 | end 138 | 139 | -- TODO: Should this be merged with identifyBlock? 140 | function decompiler.identifyBlockPost(context, block) 141 | -- determine if this normal block is a break block 142 | -- a break block is a block that jumps to the block AFTER a loop block 143 | if block.type == "complex" then 144 | local instr = block.decoded[block.length-1] 145 | if instr and instr[1] == "jump" then 146 | -- Find block it exits to 147 | local exitBlock 148 | for testBlock in context:blocks(block) do 149 | if testBlock.start == instr.to then 150 | exitBlock = testBlock 151 | end 152 | end 153 | if exitBlock.prev.type == "loop" then 154 | -- If the block before the exit block is a loop... 155 | block.metadata["break"] = true 156 | end 157 | end 158 | end 159 | end 160 | 161 | function decompiler.identifyBlocks(context) 162 | for block in context:blocks() do 163 | context:identifyBlock(block) 164 | end 165 | 166 | context:identifyBlocksPost() 167 | end 168 | 169 | function decompiler.identifyBlocksPost(context) 170 | for block in context:blocks() do 171 | context:identifyBlockPost(block) 172 | end 173 | end 174 | 175 | end 176 | -------------------------------------------------------------------------------- /decompile3.lua: -------------------------------------------------------------------------------- 1 | local bc = require "luavm.bytecode" 2 | local decompiler = require "luavm.decompiler3" 3 | serpent = require "serpent" 4 | 5 | local function testFunc() 6 | print("Hello, World!", x+4) 7 | if y then 8 | print("y is set!") 9 | end 10 | local i = 0 11 | while i < 10 and w do 12 | if i == 5 or i == 5 then 13 | print('FIVE!') 14 | end 15 | if math.deg(math.pi) == 180 then 16 | print("PI == 180") 17 | end 18 | i = i+1 19 | print("i is", i) 20 | end 21 | if i > 4 and j or n == 4 then 22 | return 0 23 | end 24 | if q > p or r < n and f > 4 then 25 | return 1 26 | end 27 | for i=1, 10 do 28 | print(i) 29 | end 30 | return 5 31 | end 32 | 33 | -- not on and widgetPowerLevel > 0 34 | --[[ 35 | 0 {"getglobal", dest = 0, index = 0} 36 | 1 {"condop", "test", invert = true, target = 0} 37 | 2 {"jump", to = 8} 38 | 3 {"getglobal", dest = 0, index = 1} 39 | 4 {"condop", "<", invert = false, lhs = 258, rhs = 0} 40 | 5 {"jump", to = 8} 41 | 6 {"loadk", dest = 0, kst = 3} 42 | 7 {"return", base = 0, count = 1} 43 | ]] 44 | -- not on and widgetPowerLevel > 0 45 | 46 | -- not (on and widgetPowerLevel > 0) 47 | --[[ 48 | 0 {"getglobal", dest = 0, index = 0} 49 | 1 {"condop", "test", invert = false, target = 0} 50 | 2 {"jump", to = 6} 51 | 3 {"getglobal", dest = 0, index = 1} 52 | 4 {"condop", "<", invert = true, lhs = 258, rhs = 0} 53 | 5 {"jump", to = 8} 54 | 6 {"loadk", dest = 0, kst = 3} 55 | 7 {"return", base = 0, count = 1} 56 | ]] 57 | -- on or widgetPowerLevel > 0 58 | 59 | -- a or b > 0 60 | --[[ 61 | 0 {"getglobal", dest = 0, index = 0} 62 | 1 {"condop", "test", invert = false, target = 0} 63 | 2 {"jump", to = 6} 64 | 3 {"getglobal", dest = 0, index = 1} 65 | 4 {"condop", "<", invert = true, lhs = 258, rhs = 0} 66 | 5 {"jump", to = 8} 67 | 6 {"loadk", dest = 0, kst = 3} 68 | 7 {"return", base = 0, count = 1} 69 | ]] 70 | 71 | -- a or b 72 | --[[ 73 | 8 {"getglobal", dest = 0, index = 4} 74 | 9 {"condop", "test", invert = true, target = 0} 75 | 10 {"jump", to = 14} 76 | 11 {"getglobal", dest = 0, index = 5} 77 | 12 {"condop", "test", invert = false, target = 0} 78 | 13 {"jump", to = 19} 79 | ]] 80 | -- 81 | 82 | local function testFunc() 83 | if not (on and widgetPowerLevel > 0) then 84 | return not (on and widgetPowerLevel > 0) 85 | end 86 | 87 | if a or b then 88 | return a or b 89 | end 90 | if a and (b and c or d) or e then 91 | print("!") 92 | end 93 | if i > 4 and j or n == 4 then 94 | print("!!!") 95 | end 96 | if q > p or r < n and f > 4 then 97 | print("!!") 98 | end 99 | return a > a and (b > b and c or d) or e 100 | end 101 | 102 | local function testFunc1() 103 | while true do 104 | print("Hey") 105 | if j > 2 then 106 | print("Bye") 107 | break 108 | end 109 | end 110 | print("!") 111 | return a > 0 and b > 0 112 | end 113 | 114 | local function testFunc1() 115 | for i, v in pairs(_G) do 116 | print(i, v) 117 | end 118 | 119 | for i=1, 10 do 120 | print(i) 121 | end 122 | end 123 | 124 | local function testFunc1() 125 | while true do 126 | while true do 127 | print("!") 128 | break 129 | end 130 | print("!!") 131 | end 132 | end 133 | 134 | local function testFunc1() 135 | if j then 136 | print("J!") 137 | return j 138 | elseif q then 139 | print("Q!") 140 | if m then 141 | print("BUT M!") 142 | end 143 | return q 144 | else 145 | print("?!") 146 | return false 147 | end 148 | print("!") 149 | return true 150 | end 151 | 152 | local function testFunc1() 153 | if g > 3 then 154 | print("!") 155 | else 156 | g = 3 157 | end 158 | 159 | repeat 160 | print("jk") 161 | until not kidding 162 | end 163 | 164 | local function testFunc1() 165 | local g, s = 3, 0 166 | while g > 0 do 167 | local n = g * g 168 | s = s + n 169 | g = g - 1 170 | end 171 | if s > 4 then 172 | local q = s * s 173 | s = q * 0.5 174 | end 175 | return s, s * s, s * s * s 176 | end 177 | 178 | local function testFunc1() 179 | local q = 3 180 | do 181 | local g = 3 182 | local function x() 183 | g = 4 184 | end 185 | q = 4 186 | end 187 | return q 188 | end 189 | 190 | local function testFunc() 191 | local x = {} 192 | x.x = 5 193 | x.y = 3 194 | return x 195 | end 196 | 197 | local dump = string.dump(testFunc) 198 | --local dump = io.open(..., "rb"):read("*a") 199 | local chunk = bc.load(dump) --.functionPrototypes[1] 200 | local decoded = decompiler.decoder.native().decodeChunk(chunk) 201 | 202 | for i=0, decoded.last do 203 | print(i, serpent.line(decoded[i])) 204 | end 205 | 206 | bc.dump(chunk) 207 | 208 | local source = decompiler.core.decompile(decoded, chunk, { 209 | asFunction = true 210 | }) 211 | 212 | print() 213 | for i=1, #source do 214 | print(source[i]) 215 | end 216 | 217 | --[[x = 3 218 | w = true 219 | testFunc(); 220 | (function() 221 | local L_0, L_1 222 | print("Hello, World!", x+4) 223 | L_0 = y 224 | if L_0 then 225 | print("y is set!") 226 | end 227 | L_0 = 0 228 | while L_0<10 and w do 229 | if L_0==5 or L_0==5 then 230 | print("FIVE!") 231 | end 232 | L_1 = math.deg(math.pi) 233 | if L_1==180 then 234 | print("PI == 180") 235 | end 236 | L_0 = L_0+1 237 | print("i is", L_0) 238 | end 239 | if 4 #bcasm then 104 | error("End of string not found at"..sidx,0) 105 | end 106 | c = bcasm:sub(idx,idx) 107 | end 108 | idx = idx+1 109 | return table.concat(str) 110 | end 111 | end 112 | 113 | local function matchNumber() 114 | local s, e = bcasm:find("^[-+]?%d+",idx) 115 | idx = e and e+1 or idx 116 | return s and tonumber(bcasm:sub(s,e)) or nil 117 | end 118 | 119 | local function matchValue() 120 | return matchNumber() or matchString() 121 | end 122 | 123 | local function matchConst() 124 | if bcasm:sub(idx,idx+5) == ".const" then 125 | local sidx = idx 126 | idx = idx+6 127 | matchSpace() 128 | local id = matchIdentifier() 129 | if not id then error("Failed to identify constant at "..sidx) end 130 | matchSpace() 131 | local val = matchValue() 132 | if not val then error("Failed to identify constant at "..sidx) end 133 | constants[nconst] = {name=id, value=val, idx=nconst} 134 | constants[id] = constants[nconst] 135 | nconst = nconst+1 136 | end 137 | end 138 | 139 | local function matchInstruction() 140 | local s,e = bcasm:find("^([^ \n\t]+)",idx) 141 | if not s then return end 142 | local iname = bcasm:sub(s,e):upper() 143 | local i = ins[iname] 144 | if not i then error("Invalid instruction name \""..iname.."\" at "..idx) end 145 | idx = e+1 146 | debug("Found instruction "..iname) 147 | local fmt = instructionFormats[i] 148 | local args = {} 149 | while #args < 2 or bcasm:sub(idx,idx) == "," do 150 | matchSpace() 151 | if bcasm:sub(idx,idx) == "," then idx = idx+1 matchSpace() end 152 | local id = matchIdentifier() 153 | if id then 154 | args[#args+1] = constants[id].idx 155 | else 156 | local num = matchNumber() 157 | if not num then 158 | error("Failed to identify constant at "..idx) 159 | end 160 | args[#args+1] = num 161 | end 162 | end 163 | 164 | instructions[ninstr] = encode(i,args[1] or 0,args[2] or 0,args[3] or 0) 165 | ninstr = ninstr+1 166 | end 167 | 168 | local lastidx = idx 169 | while idx < #bcasm do 170 | matchSpace() 171 | matchComment() 172 | matchSpace() 173 | matchConst() 174 | matchInstruction() 175 | if idx == lastidx then error("Failed to identify at "..idx) end 176 | lastidx = idx 177 | end 178 | 179 | local bc = bytecode.new() 180 | for i=0, nconst-1 do 181 | bc.constants[i] = constants[i].value 182 | end 183 | for i=0, ninstr-1 do 184 | bc.instructions[i] = instructions[i] 185 | end 186 | bc.instructions[ninstr] = bytecode.defaultReturn 187 | bc.maxStack = 127 --TODO: Max stack! 188 | return bc 189 | end 190 | -------------------------------------------------------------------------------- /decompile2.lua: -------------------------------------------------------------------------------- 1 | local decompiler = require "luavm.decompiler2" 2 | local bytecode = require "luavm.bytecode" 3 | 4 | local lfs = require "lfs" 5 | 6 | lfs.mkdir "decompiler2temp" 7 | 8 | local function dumpValue(value, indent, skipTables) 9 | indent = indent or "" 10 | local typ = type(value) 11 | 12 | if typ == "table" and (not skipTables or not skipTables[value]) then 13 | if not skipTables then skipTables = {} end 14 | skipTables[value] = true 15 | local buffer = {"{"} 16 | local currentNumber = 1 17 | 18 | for i, v in pairs(value) do 19 | if i == currentNumber then 20 | currentNumber = currentNumber+1 21 | buffer[#buffer+1] = indent.."\t"..dumpValue(v, indent.."\t", skipTables) 22 | else 23 | buffer[#buffer+1] = indent.."\t"..dumpValue(i, indent.."\t", skipTables)..": "..dumpValue(v, indent.."\t", skipTables) 24 | end 25 | end 26 | 27 | return table.concat(buffer, "\n").."\n"..indent.."}" 28 | end 29 | 30 | return tostring(value) 31 | end 32 | 33 | --[[local function testFunc(a, b) 34 | return a+b+3 35 | end]] 36 | 37 | --[[local function testFunc(a, b) 38 | local c = a * b + 3 39 | if c < 0 then 40 | c = -c 41 | else 42 | c = c+c 43 | end 44 | return c 45 | end]] 46 | 47 | --[[local function testFunc() 48 | local a = 0 49 | for i=1, 100 do 50 | a = a+i 51 | end 52 | return a 53 | end]] 54 | 55 | --[[local function testFunc() 56 | for i, v in next, _G do 57 | print(i, v) 58 | end 59 | end]] 60 | 61 | --[[local function testFunc() 62 | local i = 100 63 | local v = 0 64 | while i > 0 do 65 | v = v+i 66 | i = i-1 67 | if v % 2 == 0 then 68 | break 69 | end 70 | end 71 | return v 72 | end]] 73 | 74 | --[[local function testFunc() 75 | while true do end 76 | end]] 77 | 78 | --[[local function testFunc() 79 | print("Hello, World!", 1, 2, 2+3) 80 | a,b = func() 81 | -- register swap 82 | local j,k = 1,2 83 | j,k=k,j 84 | -- global swap 85 | a,b,c = b,a,k 86 | end]] 87 | 88 | --[[local function testFunc() 89 | local testTable = {} 90 | testTable.a = 4 91 | return testTable.a 92 | end]] 93 | 94 | --[[local function testFunc() 95 | return { 96 | a = 5, 97 | b = 6, 98 | [4] = 3 99 | } 100 | end]] 101 | 102 | --[[local function testFunc() 103 | if (not x) and y then 104 | return 3 105 | end 106 | end]] 107 | 108 | local function testFunc() 109 | local x, y, z = 5, 6 110 | while x == 5 and y == 6 do 111 | if not z then 112 | z = {} 113 | end 114 | print(z) 115 | end 116 | end 117 | 118 | print(dumpValue(bc)) 119 | bytecode.dump(bc) 120 | local decoder = decompiler.decoder.native() 121 | 122 | local formatExpressionlets 123 | 124 | local function formatExpressionlet(explet) 125 | if explet[1] == "register" then 126 | return "r"..explet[2] 127 | elseif explet[1] == "binaryop" then 128 | return formatExpressionlet(explet[2]).." "..explet[3].." "..formatExpressionlet(explet[4]) 129 | elseif explet[1] == "unaryop" then 130 | return explet[2]..formatExpressionlet(explet[3]) 131 | elseif explet[1] == "constant" then 132 | return bc.constants[explet[2]] 133 | elseif explet[1] == "global" then 134 | return bc.constants[explet[2]] 135 | elseif explet[1] == "value" then 136 | if type(explet[2]) == "table" then 137 | return "{}" -- TODO: Render table contents for table inlining 138 | end 139 | return tostring(explet[2]) 140 | elseif explet[1] == "call" then 141 | return formatExpressionlet(explet[2]).."("..formatExpressionlets(explet[3])..")" 142 | elseif explet[1] == "index" then 143 | return formatExpressionlet(explet[2]).."["..formatExpressionlet(explet[3]).."]" 144 | else 145 | error("Unhandle explet "..tostring(explet[1])) 146 | end 147 | end 148 | 149 | function formatExpressionlets(explets) 150 | if #explets == 1 then 151 | return formatExpressionlet(explets[1]) 152 | else 153 | local buffer = {} 154 | for i=1, #explets do 155 | buffer[i] = formatExpressionlet(explets[i]) 156 | end 157 | return table.concat(buffer, ", ") 158 | end 159 | end 160 | 161 | --local formatBlock 162 | 163 | local function formatDecoded(dec) 164 | if dec.disabled then return "" end 165 | if dec.op == "set" then 166 | if #dec.dest == 0 then 167 | return formatExpressionlets(dec.src) 168 | else 169 | return formatExpressionlets(dec.dest).." = "..formatExpressionlets(dec.src) 170 | end 171 | elseif dec.op == "return" then 172 | return "return "..formatExpressionlets(dec.src) 173 | elseif dec.op == "if" then 174 | return "if "..formatExpressionlets(dec.src).." then\n"..formatBlock(dec.block).."\nend" 175 | elseif dec.op == "else" then 176 | return "else\n"..formatBlock(dec.block) 177 | elseif dec.op == "for" then 178 | return "for "..formatExpressionlets(dec.dest).." = "..formatExpressionlets(dec.src).." do\n"..formatBlock(dec.block).."\nend" 179 | elseif dec.op == "gfor" then 180 | return "for "..formatExpressionlets(dec.dest).." in "..formatExpressionlets(dec.src).." do\n"..formatBlock(dec.block).."\nend" 181 | elseif dec.op == "while" then 182 | return "while "..formatExpressionlets(dec.src).." do\n"..formatBlock(dec.block).."\nend" 183 | elseif dec.op == "break" then 184 | return "break" 185 | end 186 | return dumpValue(dec) 187 | end 188 | 189 | function formatBlock(block) 190 | local decoded = {} 191 | for i=1, #block do 192 | decoded[i] = formatDecoded(block[i]) 193 | end 194 | return table.concat(decoded, "\n") 195 | end 196 | 197 | -- Steps: 198 | -- Decode 199 | -- Analyze 200 | -- Inline Pass 201 | -- Analyze 202 | -- Some other passes... 203 | -- Output 204 | 205 | local block = {} 206 | 207 | local function printValue(value) 208 | --print(formatDecoded(value)) 209 | block[#block+1] = value 210 | end 211 | 212 | decoder.decode(bc, nil, nil, {}, printValue) 213 | 214 | for i=1, #block do 215 | print(formatDecoded(block[i])) 216 | end 217 | 218 | block.liveRanges = decompiler.analyzer.computeLiveRanges(block) 219 | 220 | decompiler.pass[2](block) 221 | decompiler.pass[1](block) 222 | 223 | for i=1, #block do 224 | print(formatDecoded(block[i])) 225 | end 226 | 227 | print(decompiler.formatter.formatFunction(bc, block, "testFunc")) 228 | -------------------------------------------------------------------------------- /luavm/decompiler/formatter.lua: -------------------------------------------------------------------------------- 1 | -- Formatter: Takes IR Expressions and converts them into a list of strings 2 | 3 | local formatter = {} 4 | 5 | -- TODO: Use this 6 | local _DEFAULTCONFIG = { 7 | binopSpacing = true, -- whether to emit spaces between binary operators (note: this is force turned on for word based binops) 8 | } 9 | 10 | -- TODO: Possibly make a function named formatter.new(bc) that wraps everything so bc doesn't have to be passed around 11 | 12 | -- Formats a value into a parsable string representation 13 | function formatter.formatValue(bc, value) 14 | local typ = type(value) 15 | if typ == "string" then 16 | return ("%q"):format(value):gsub("\\\n", "\\n") 17 | elseif typ == "table" then 18 | if not next(value) then 19 | return "{}" 20 | else 21 | local integerValues = {} 22 | local normalValues = {} 23 | local currentInteger = 1 24 | for k, v in pairs(value) do 25 | -- keys and values are explets 26 | local ktyp = k[1] 27 | local keystr, integer = nil, false 28 | 29 | if ktyp == "constant" then 30 | local kconst = bc.constants[k[2]] 31 | local kconsttyp = type(kconst) 32 | if kconsttyp == "string" and kconst:find("^[a-zA-Z_][a-zA-Z0-9_]*$") then 33 | keystr = kconst 34 | elseif kconsttyp == "number" and kconst == currentInteger then 35 | keystr = nil 36 | integerValues[kconst] = formatter.formatExplet(bc, v) 37 | currentInteger = kconst+1 38 | integer = true 39 | end 40 | end 41 | 42 | if not keystr then 43 | keystr = "["..formatter.formatExplet(bc, k).."]" 44 | end 45 | 46 | if not integer then 47 | normalValues[#normalValues+1] = keystr.." = "..formatter.formatExplet(bc, v) 48 | end 49 | end 50 | return "{".. 51 | (integerValues[1] and table.concat(integerValues, ",\n")..",\n" or "").. 52 | (normalValues[1] and table.concat(normalValues, ",\n")..",\n" or "") 53 | .."}" 54 | end 55 | else 56 | return tostring(value) 57 | end 58 | end 59 | 60 | -- Formats an explet into a parsable string representation 61 | function formatter.formatExplet(bc, explet) 62 | -- TODO: Handle config 63 | local typ = explet[1] 64 | 65 | if typ == "register" then 66 | return "r"..explet[2] 67 | elseif typ == "binaryop" then 68 | return formatter.formatExplet(bc, explet[2]).." "..explet[3].." "..formatter.formatExplet(bc, explet[4]) 69 | elseif typ == "unaryop" then 70 | return explet[2].." "..formatter.formatExplet(bc, explet[3]) 71 | elseif typ == "constant" then 72 | return formatter.formatValue(bc, bc.constants[explet[2]]) 73 | elseif typ == "global" then 74 | return bc.constants[explet[2]] 75 | elseif typ == "value" then 76 | --[[if type(explet[2]) == "table" then 77 | return "{}" -- TODO: Render table contents for table inlining 78 | end]] 79 | return formatter.formatValue(bc, explet[2]) 80 | elseif typ == "call" then 81 | return formatter.formatExplet(bc, explet[2]).."("..formatter.formatExplets(bc, explet[3])..")" 82 | elseif typ == "index" then 83 | return formatter.formatExplet(bc, explet[2]).."["..formatter.formatExplet(bc, explet[3]).."]" 84 | else 85 | error("Unhandle explet "..tostring(typ)) 86 | end 87 | end 88 | 89 | function formatter.formatExplets(bc, explets) 90 | if #explets == 1 then 91 | return formatter.formatExplet(bc, explets[1]) 92 | else 93 | local buffer = {} 94 | for i=1, #explets do 95 | buffer[i] = formatter.formatExplet(bc, explets[i]) 96 | end 97 | return table.concat(buffer, ", ") 98 | end 99 | end 100 | 101 | function formatter.formatExpression(bc, irexp) 102 | if irexp.disabled then return nil end 103 | if irexp.op == "set" then 104 | if #irexp.dest == 0 then 105 | return formatter.formatExplets(bc, irexp.src) 106 | else 107 | return formatter.formatExplets(bc, irexp.dest).." = "..formatter.formatExplets(bc, irexp.src) 108 | end 109 | elseif irexp.op == "return" then 110 | return "return "..formatter.formatExplets(bc, irexp.src) 111 | elseif irexp.op == "if" then 112 | return "if "..formatter.formatExplets(bc, irexp.src).." then\n"..formatter.formatBlock(bc, irexp.block).."\nend" 113 | elseif irexp.op == "else" then 114 | return "else\n"..formatter.formatBlock(bc, irexp.block) 115 | elseif irexp.op == "for" then 116 | return "for "..formatter.formatExplets(bc, irexp.dest).." = "..formatter.formatExplets(bc, irexp.src).." do\n"..formatter.formatBlock(bc, irexp.block).."\nend" 117 | elseif irexp.op == "gfor" then 118 | return "for "..formatter.formatExplets(bc, irexp.dest).." in "..formatter.formatExplets(bc, irexp.src).." do\n"..formatter.formatBlock(bc, irexp.block).."\nend" 119 | elseif irexp.op == "while" then 120 | return "while "..formatter.formatExplets(bc, irexp.src).." do\n"..formatter.formatBlock(bc, irexp.block).."\nend" 121 | elseif irexp.op == "break" then 122 | return "break" 123 | else 124 | error("Unsupported ir exp "..tostring(irexp.op)) 125 | end 126 | --return dumpValue(irexp) 127 | end 128 | 129 | function formatter.formatBlock(bc, irblock) 130 | local buffer = {} 131 | for i=1, #irblock do 132 | buffer[#buffer+1] = formatter.formatExpression(bc, irblock[i]) 133 | end 134 | return table.concat(buffer, "\n") 135 | end 136 | 137 | function formatter.formatFunction(bc, irblock, name) 138 | local buffer = {"function", name and " "..name or "", "("} 139 | -- function prelude 140 | for i=0, bc.nparam-1 do 141 | buffer[#buffer+1] = "r"..i 142 | if i+1 ~= bc.nparam then buffer[#buffer+1] = ", " end 143 | end 144 | if bc.isvararg ~= 0 then 145 | buffer[#buffer+1] = "..." 146 | end 147 | buffer[#buffer+1] = ")\n" 148 | 149 | if bc.maxStack-bc.nparam > 0 then 150 | -- TODO: Most registers are unused after inlining happens 151 | -- TODO: Some registers are used to track for loop state 152 | -- TODO: Make block level random variable names and variable definitions 153 | buffer[#buffer+1] = "local " 154 | for i=bc.nparam, bc.maxStack-1 do 155 | buffer[#buffer+1] = "r"..i 156 | if i+1 ~= bc.maxStack then buffer[#buffer+1] = ", " end 157 | end 158 | buffer[#buffer+1] = "\n" 159 | end 160 | 161 | buffer[#buffer+1] = formatter.formatBlock(bc, irblock) 162 | 163 | buffer[#buffer+1] = "\nend" 164 | 165 | return table.concat(buffer) 166 | end 167 | 168 | return formatter 169 | -------------------------------------------------------------------------------- /luavm/decompiler3/decompiler/block_utils.lua: -------------------------------------------------------------------------------- 1 | return function(decompiler) 2 | function decompiler.block(b) 3 | setmetatable(b, {__index = decompiler}) 4 | 5 | if not b.decoded then 6 | b.decoded = {} 7 | end 8 | 9 | if not b.metadata then 10 | b.metadata = {} 11 | end 12 | 13 | return b 14 | end 15 | 16 | function decompiler.blocks(context, startBlock) 17 | return function(_, block) 18 | if not block then 19 | return startBlock or context.firstBlock 20 | end 21 | return block.next 22 | end 23 | end 24 | 25 | function decompiler.blocksUntil(context, stopBlock, startBlock) 26 | local iter = decompiler.blocks(context, startBlock) 27 | return function(_, block) 28 | local nextBlock = iter(_, block) 29 | if nextBlock == stopBlock then return nil end 30 | return nextBlock 31 | end 32 | end 33 | 34 | function decompiler.reverseBlocks(context, startBlock) 35 | return function(_, block) 36 | if not block then 37 | return startBlock or context.lastBlock 38 | end 39 | return block.prev 40 | end 41 | end 42 | 43 | function decompiler.reverseBlocksUntil(context, stopBlock, startBlock) 44 | local iter = decompiler.reverseBlocks(context, startBlock) 45 | return function(_, block) 46 | local nextBlock = iter(_, block) 47 | if nextBlock == stopBlock then return nil end 48 | return nextBlock 49 | end 50 | end 51 | 52 | function decompiler.blocksRecursive(context, startBlock) 53 | return function(_, block) 54 | if not block then 55 | return startBlock or context.firstBlock 56 | end 57 | 58 | local n = block.firstBlock 59 | if not n then 60 | n = block.next 61 | if not n then 62 | n = block.parent.next 63 | if n == context then 64 | n = nil 65 | end 66 | end 67 | end 68 | 69 | return n 70 | end 71 | end 72 | 73 | function decompiler.insertBlock(context, block) 74 | decompiler.insertBlockAfter(context, context.lastBlock, block) 75 | end 76 | 77 | function decompiler.insertBlockAfter(context, block, nextBlock) 78 | print("Insert", nextBlock, "after", block, "in", context) 79 | 80 | local oldNext 81 | 82 | if block then 83 | oldNext = block.next 84 | block.next = nextBlock 85 | else 86 | oldNext = context.firstBlock 87 | context.firstBlock = nextBlock 88 | end 89 | 90 | nextBlock.prev = block 91 | 92 | nextBlock.next = oldNext 93 | 94 | if oldNext then 95 | oldNext.prev = nextBlock 96 | else 97 | context.lastBlock = nextBlock 98 | end 99 | 100 | nextBlock.parent = context 101 | end 102 | 103 | function decompiler.insertBlockBefore(context, block, prevBlock) 104 | local oldPrev = block.prev 105 | 106 | if not oldPrev then 107 | context.firstBlock = prevBlock 108 | else 109 | oldPrev.next = prevBlock 110 | end 111 | 112 | prevBlock.prev = oldPrev 113 | 114 | prevBlock.next = block 115 | 116 | if not block then 117 | context.lastBlock = prevBlock 118 | else 119 | block.prev = prevBlock 120 | end 121 | 122 | prevBlock.parent = context 123 | end 124 | 125 | function decompiler.removeBlock(context, block) 126 | local prev = block.prev 127 | local next = block.next 128 | 129 | if prev then 130 | prev.next = next 131 | else 132 | context.firstBlock = next 133 | end 134 | 135 | if next then 136 | next.prev = prev 137 | else 138 | context.lastBlock = prev 139 | end 140 | 141 | block.next = nil 142 | block.prev = nil 143 | block.parent = nil 144 | end 145 | 146 | function decompiler.removeFromParent(context) 147 | context.parent:removeBlock(context) 148 | end 149 | 150 | local lbi = {} 151 | lbi.__index = lbi 152 | 153 | function lbi:addPending(block, root) 154 | assert(root) 155 | self.pending[#self.pending+1] = {block, root} 156 | end 157 | 158 | function lbi:addRoot(root) 159 | local typ = root.type 160 | 161 | self:addPending(root, root) 162 | 163 | if typ == "exit" then 164 | -- Nothing to do. 165 | elseif typ == "alias" or typ == "complex" then 166 | local target = root[typ].target 167 | for block in self.context:blocks(root) do 168 | if block.start == target then 169 | self:addPending(block, root) 170 | break 171 | end 172 | end 173 | elseif typ == "loop" then 174 | for block in self.context:reverseBlocks(root.prev) do 175 | if block.start == root.loop.target then 176 | self:addPending(block, root) 177 | break 178 | end 179 | end 180 | 181 | if root.metadata.nfor or root.metadata.gfor then 182 | self:addPending(root.next, root) 183 | end 184 | elseif typ == "conditional" then 185 | self:addPending(root.next, root) 186 | self:addPending(root.next.next, root) 187 | elseif typ == "simple" or typ == "normal" then 188 | self:addPending(root.next, root) 189 | end 190 | 191 | self:addPending(nil, root) 192 | end 193 | 194 | function lbi:iter() 195 | return function() 196 | local p = table.remove(self.pending, 1) 197 | if p then 198 | return p[1] ~= nil, p[1], p[2] 199 | end 200 | end 201 | end 202 | 203 | function decompiler.logicalBlockIter(context, block) 204 | local o = setmetatable({ 205 | context = context, 206 | pending = {} 207 | }, lbi) 208 | o:addRoot(block) 209 | return o 210 | end 211 | 212 | local noComments = {comment = false} 213 | 214 | function decompiler.dumpBlock(context, block) 215 | local parts = {} 216 | 217 | parts[#parts+1] = block.type 218 | 219 | if block.type == "meta" then 220 | parts[#parts+1] = "("..block.metatype..")" 221 | end 222 | 223 | parts[#parts+1] = " @ "..block.start.." # "..block.length 224 | 225 | if block.type == "alias" then 226 | parts[#parts+1] = " -> "..block.alias.target 227 | elseif block.type == "complex" then 228 | parts[#parts+1] = " -> "..block.complex.target 229 | elseif block.type == "loop" then 230 | parts[#parts+1] = " -> "..block.loop.target 231 | elseif block.type == "normal" then 232 | parts[#parts+1] = " -> "..serpent.line(block.decoded[block.length-1], noComments) 233 | end 234 | 235 | if next(block.metadata) then 236 | parts[#parts+1] = " [" 237 | local first = true 238 | for type, metadata in pairs(block.metadata) do 239 | if first then 240 | first = false 241 | else 242 | parts[#parts+1] = "; " 243 | end 244 | parts[#parts+1] = type..": "..serpent.line(metadata, noComments) 245 | end 246 | parts[#parts+1] = "]" 247 | end 248 | 249 | return table.concat(parts) 250 | end 251 | end 252 | -------------------------------------------------------------------------------- /luavm/decompiler3/decoder51.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Decodes Lua 5.1 bytecode into a more generic form. 3 | 4 | This does not aim to reconstruct loops or any other high level operations. 5 | Just instruction decoding. 6 | 7 | MOVE a, b -> {"move", dest = a, src = b} 8 | LOADK a, b -> {"loadk", dest = a, kst = b} 9 | LOADBOOL a, b -> {"loadbool", dest = a, bool = b ~= 0} 10 | LOADNIL a, b -> {"loadnil", from = a, to = b} 11 | GETUPVAL a, b -> {"getupval", dest = a, upvalue = b} 12 | GETGLOBAL a, b -> {"getglobal", dest = a, index = b} 13 | GETTABLE a, b, c -> {"gettable", dest = a, table = b, index = c} 14 | SETGLOBAL a, b -> {"setglobal", src = a, index = b} 15 | SETUPVAL a, b -> {"setupval", src = a, upvalue = b} 16 | SETTABLE a, b, c -> {"settable", src = c, table = a, index = b} 17 | NEWTABLE a, b, c -> {"newtable", dest = a, arraycnt = b, hashcnt = c} 18 | SELF a, b, c -> {"self", dest = a, object = b, method = c} 19 | a, b, c -> {"binop", "", dest = a, lhs = b, rhs = c} 20 | a, b -> {"unop", "", dest = a, rhs = b} 21 | CONCAT a, b, c -> {"concat", dest = a, from = b, to = c} 22 | JMP sBx -> {"jump", to = pc+1+sBx} 23 | EQ; LT; LE a, b, c -> {"condop", "", lhs = b, rhs = c, invert = a ~= 0} 24 | TEST a, c -> {"condop", "test", target = a, invert = c ~= 0} 25 | TESTSET a, b, c -> {"testset", dest = a, target = b, invert = c ~= 0} 26 | CALL a, b, c -> {"call", base = a, nret = c-2, narg = b-2} 27 | TAILCALL a, b, c -> {"tailcall", base = a, nret = c-2, narg = b-2} 28 | RETURN a, b -> {"return", base = a, count = b-2} 29 | FORLOOP a, sBx -> {"forloop", base = a, target = pc+1+sBx} 30 | FORPREP a, sBx -> {"forprep", base = a, target = pc+1+sBx} 31 | TFORLOOP a, c -> {"tforloop", base = a, count = c} 32 | SETLIST a, b, c -> {"setlist", base = a, count = b, set = c} 33 | CLOSE a -> {"close", base = a} 34 | CLOSURE a, Bx -> {"closure", dest = a, proto = Bx} 35 | VARARG a, b -> {"vararg", base = a, count = b} 36 | ]] 37 | 38 | local bytecode = require "luavm.bytecode" 39 | local version = bytecode.version.lua51 40 | 41 | local MOVE = 0 42 | local LOADK = 1 43 | local LOADBOOL = 2 44 | local LOADNIL = 3 45 | local GETUPVAL = 4 46 | local GETGLOBAL = 5 47 | local GETTABLE = 6 48 | local SETGLOBAL = 7 49 | local SETUPVAL = 8 50 | local SETTABLE = 9 51 | local NEWTABLE = 10 52 | local SELF = 11 53 | local ADD = 12 54 | local SUB = 13 55 | local MUL = 14 56 | local DIV = 15 57 | local MOD = 16 58 | local POW = 17 59 | local UNM = 18 60 | local NOT = 19 61 | local LEN = 20 62 | local CONCAT = 21 63 | local JMP = 22 64 | local EQ = 23 65 | local LT = 24 66 | local LE = 25 67 | local TEST = 26 68 | local TESTSET = 27 69 | local CALL = 28 70 | local TAILCALL = 29 71 | local RETURN = 30 72 | local FORLOOP = 31 73 | local FORPREP = 32 74 | local TFORLOOP = 33 75 | local SETLIST = 34 76 | local CLOSE = 35 77 | local CLOSURE = 36 78 | local VARARG = 37 79 | 80 | local binaryOps = { 81 | [ADD] = "+", 82 | [SUB] = "-", 83 | [MUL] = "*", 84 | [DIV] = "/", 85 | [MOD] = "%", 86 | [POW] = "^", 87 | } 88 | 89 | local unaryOps = { 90 | [UNM] = "-", 91 | [NOT] = "not", 92 | [LEN] = "#", 93 | } 94 | 95 | local conditionalOps = { 96 | [EQ] = "==", 97 | [LT] = "<", 98 | [LE] = "<=", 99 | } 100 | 101 | return function(decoder) 102 | local target = {} 103 | 104 | local function decodeInstruction(chunk, i) 105 | local op, a, b, c = version.decode(chunk.instructions[i]) 106 | 107 | if op == MOVE then 108 | return {"move", dest = a, src = b} 109 | elseif op == LOADK then 110 | return {"loadk", dest = a, kst = b} 111 | elseif op == LOADBOOL then 112 | return {"loadbool", dest = a, value = b ~= 0, skipNext = c ~= 0} 113 | elseif op == LOADNIL then 114 | return {"loadnil", from = a, to = b} 115 | elseif op == GETUPVAL then 116 | return {"getupval", dest = a, upvalue = b} 117 | elseif op == GETGLOBAL then 118 | return {"getglobal", dest = a, index = b} 119 | elseif op == GETTABLE then 120 | return {"gettable", dest = a, table = b, index = c} 121 | elseif op == SETGLOBAL then 122 | return {"setglobal", src = a, index = b} 123 | elseif op == SETUPVAL then 124 | return {"setupval", src = a, upvalue = b} 125 | elseif op == SETTABLE then 126 | return {"settable", src = c, table = a, index = b} 127 | elseif op == NEWTABLE then 128 | return {"newtable", dest = a, arraycnt = b, hashcnt = c} 129 | elseif op == SELF then 130 | return {"self", base = a, object = b, method = c} 131 | elseif binaryOps[op] then 132 | return {"binop", binaryOps[op], dest = a, lhs = b, rhs = c} 133 | elseif unaryOps[op] then 134 | return {"unop", unaryOps[op], dest = a, rhs = b} 135 | elseif op == CONCAT then 136 | return {"concat", dest = a, from = b, to = c} 137 | elseif op == JMP then 138 | return {"jump", to = i+1+b} 139 | elseif conditionalOps[op] then 140 | return {"condop", conditionalOps[op], lhs = b, rhs = c, invert = a ~= 0} 141 | elseif op == TEST then 142 | return {"condop", "test", target = a, invert = c ~= 0} 143 | elseif op == TESTSET then 144 | return {"testset", dest = a, target = b, invert = c ~= 0} 145 | elseif op == CALL then 146 | return {"call", base = a, nret = c-1, narg = b-1} 147 | elseif op == TAILCALL then 148 | return {"tailcall", base = a, nret = c-1, narg = b-1} 149 | elseif op == RETURN then 150 | return {"return", base = a, count = b-1} 151 | elseif op == FORLOOP then 152 | return {"forloop", base = a, target = i+1+b} 153 | elseif op == FORPREP then 154 | return {"forprep", base = a, target = i+1+b} 155 | elseif op == TFORLOOP then 156 | return {"tforloop", base = a, count = c} 157 | elseif op == SETLIST then 158 | local set, ni = c-1, i+1 159 | if set < 0 then 160 | set, ni = chunk.instructions[i+1], i+2 161 | end 162 | return {"setlist", base = a, count = b, set = set}, ni 163 | elseif op == CLOSE then 164 | return {"close", base = a} 165 | elseif op == CLOSURE then 166 | local upvalues = {} 167 | 168 | local nupvals = chunk.functionPrototypes[b].nupval 169 | 170 | for j=1, nupvals do 171 | upvalues[j] = decodeInstruction(chunk, i+j) 172 | end 173 | 174 | return {"closure", dest = a, proto = b, upvalues = upvalues}, i+1+nupvals 175 | elseif op == VARARG then 176 | return {"vararg", base = a, count = b-1} 177 | end 178 | end 179 | 180 | function target.decodeChunk(chunk) 181 | -- We subtract an extra instruction to skip the default return instruction 182 | local decoded = {last = -1} 183 | local i, j = 0, chunk.instructions.count-1 184 | while i <= j do 185 | local s, ni = decodeInstruction(chunk, i) 186 | if not s then 187 | return nil, "failed to decode instruction at "..i 188 | end 189 | decoded[i] = s 190 | decoded.last = i 191 | i = ni or (i+1) 192 | end 193 | decoded.protos = {} 194 | for i=0, chunk.functionPrototypes.count-1 do 195 | local dec, e = target.decodeChunk(chunk.functionPrototypes[i]) 196 | if not dec then return nil, e end 197 | decoded.protos[i] = dec 198 | end 199 | return decoded 200 | end 201 | 202 | return target 203 | end 204 | -------------------------------------------------------------------------------- /luavm/decompiler/analyzer.lua: -------------------------------------------------------------------------------- 1 | -- Analyzer: Analyzes decoded bytecode to create data structures that are integral to the decompilation process -- 2 | 3 | -- Sample Destination Output: 4 | -- ADD R2, R0, R1 5 | -- ADD R2, R2, K0 [3] 6 | -- RETURN R2 7 | --[[ Output: { 8 | liveRanges = { -- Per register live ranges 9 | -- A live range is a set, followed by several gets, before another set 10 | -- The first PC is always the one that set the instruction 11 | -- For argument registers, they start at PC -1 (because they are set before the first instruction runs) 12 | [0] = { 13 | {-1, 0} -- Start PC, End PC 14 | }, 15 | { 16 | {-1, 0} -- Start PC, End PC 17 | }, 18 | { 19 | {0, 1}, -- Start PC, End PC 20 | {1, 2} 21 | } 22 | -- With live ranges, you can easily compute things like inlining. 23 | } 24 | }]] 25 | 26 | -- The analyzer is also responsible for computing exact register usage positions when asked -- 27 | 28 | local analyzer = {} 29 | 30 | -- Helper functions for iterating through registers used in an explet 31 | local function forEachRegister(explet, func, parent, parentIndex) 32 | if explet[1] == "register" then 33 | func(explet[2], parent, parentIndex, false) 34 | elseif explet[1] == "constant" then --ignore 35 | elseif explet[1] == "global" then --ignore 36 | elseif explet[1] == "value" then --ignore 37 | elseif explet[1] == "binaryop" then 38 | forEachRegister(explet[2], func, explet, 2) 39 | forEachRegister(explet[4], func, explet, 4) 40 | elseif explet[1] == "unaryop" then 41 | forEachRegister(explet[3], func, explet, 3) 42 | elseif explet[1] == "call" then 43 | forEachRegister(explet[2], func, explet, 2) 44 | local args = explet[3] 45 | for i=1, #args do 46 | forEachRegister(args[i], func, args, i) 47 | end 48 | elseif explet[1] == "index" then 49 | forEachRegister(explet[2], func, explet, 2) 50 | forEachRegister(explet[3], func, explet, 3) 51 | else 52 | error("Unsupported explet type in analyzer: "..explet[1]) 53 | end 54 | end 55 | 56 | -- Helper functions for iterating through registers used multiple explets 57 | local function forEachRegisterInEachExplet(explets, func, toplevelOnly) 58 | if toplevel then 59 | for i=1, #explets do 60 | local explet = explets[i] 61 | if explet[1] == "register" then 62 | func(explet[2], explets, i, true) 63 | end 64 | end 65 | else 66 | for i=1, #explets do 67 | local explet = explets[i] 68 | if explet[1] == "register" then 69 | func(explet[2], explets, i, true) 70 | else 71 | forEachRegister(explet, func, explets, i) 72 | end 73 | end 74 | end 75 | end 76 | 77 | analyzer.forEachRegister = forEachRegister 78 | analyzer.forEachRegisterInEachExplet = forEachRegisterInEachExplet 79 | 80 | function analyzer.computeLiveRanges(irBlock) 81 | -- Recursively flatten sources and create a list of used registers in the block (for per register state) 82 | local registers = {} 83 | 84 | local currentBlock = irBlock 85 | local ir 86 | 87 | local function makeRange(s, e, set) 88 | return {s, e, set = set, gets = 0, block = currentBlock} 89 | end 90 | 91 | local function handleSource(reg) 92 | if not registers[reg] then 93 | registers[reg] = {makeRange(-1, 0), argument = true} 94 | end 95 | local r = registers[reg] 96 | local rrange = r[#r] 97 | rrange[2] = ir.pc 98 | rrange.gets = rrange.gets+1 99 | if rrange.block ~= currentBlock then 100 | rrange.crossBlock = true 101 | end 102 | end 103 | 104 | local function handleDest(reg, _, _, toplevel) 105 | if toplevel then 106 | if not registers[reg] then 107 | registers[reg] = {makeRange(ir.pc, ir.pc, ir)} 108 | else 109 | local r = registers[reg] 110 | r[#r+1] = makeRange(ir.pc, ir.pc, ir) 111 | end 112 | else 113 | -- A non top level destination is actually just a source, since the destination gets set indirectly 114 | handleSource(reg) 115 | end 116 | end 117 | 118 | local function doBlock(irBlock) 119 | currentBlock = irBlock 120 | for i=1, #irBlock do 121 | ir = irBlock[i] 122 | if not ir.disabled then 123 | if ir.src then forEachRegisterInEachExplet(ir.src, handleSource) end 124 | if ir.dest then forEachRegisterInEachExplet(ir.dest, handleDest) end 125 | if ir.block then 126 | local wasSubBlock = inSubBlock 127 | inSubBlock = true 128 | doBlock(ir.block) 129 | currentBlock = irBlock 130 | inSubBlock = wasSubBlock 131 | -- Sub register ranges are marked inside the current register range 132 | -- When a register range has sub ranges it cannot be used for inlines 133 | -- Sub ranges are added when the register is used inside the sub range 134 | -- Sub ranges include regular blocks and closures 135 | end 136 | end 137 | end 138 | end 139 | 140 | doBlock(irBlock) 141 | 142 | return registers 143 | end 144 | 145 | function analyzer.computeGlobalStateChanges(irBlock) 146 | local gsc = {} 147 | end 148 | 149 | -- Helper method to find the range that pc falls in in a list of register ranges 150 | local function findRange(ranges, pc) 151 | for i=1, #ranges do 152 | local range = ranges[i] 153 | if pc >= range[1] and pc <= range[2] then 154 | return range 155 | end 156 | end 157 | end 158 | 159 | analyzer.findRange = findRange 160 | 161 | -- Computes whether an inline is possible for a register 162 | function analyzer.isInlinePossible(liveRanges, reg, pc) 163 | -- Arguments cannot be inlined because they are arguments 164 | if liveRanges[reg].argument then 165 | print("Cannot inline register "..reg..": Is argument") 166 | return false 167 | end 168 | 169 | local range = findRange(liveRanges[reg], pc) 170 | -- Registers without live ranges cannot be inlined (not enough data) 171 | if not range then 172 | print("Cannot inline register "..reg..": Range not found") 173 | return false 174 | end 175 | 176 | -- Register cannot have multiple gets 177 | if range.gets > 1 then 178 | print("Cannot inline register "..reg..": More than one register get") 179 | return false 180 | end 181 | 182 | -- Registers with multiple uses cannot be inlined 183 | if range[2] > pc then 184 | print("Cannot inline register "..reg..": Usage range is greater than current pc") 185 | return false 186 | end 187 | 188 | -- Registers that are used in multiple blocks cannot be inlined 189 | if range.crossBlock then 190 | print("Cannot inline register "..reg..": Cross block usage") 191 | return false 192 | end 193 | 194 | --[[local targetrange = findRange(liveRanges[reg], targetpc) 195 | -- Registers cannot be optimized across ranges 196 | if range ~= targetrange then 197 | print("Cannot inline register "..reg..": Source and target ranges do not match") 198 | return false 199 | end]] 200 | 201 | -- The following checks go off of the instruction that started the register range 202 | -- If the instruction is disabled, disallow inline 203 | if range.set.disabled then 204 | print("Cannot inline register "..reg..": Instruction disabled because "..range.set.disabled) 205 | end 206 | 207 | -- If the instruction with the register dest has multiple dests, inline is canceled 208 | if #range.set.dest ~= 1 then 209 | print("Cannot inline register "..reg..": Multiple dests in instruction") 210 | return false 211 | end 212 | 213 | -- If the instruction has multiple sources, inline is canceled (this shouldn't happen though) 214 | if #range.set.src ~= 1 then 215 | print("Cannot inline register "..reg..": Multiple sources in instruction") 216 | return false 217 | end 218 | 219 | -- If the set instruction has a block, disallow inline (else it would try to inling if statements) 220 | if range.set.block then 221 | print("Cannot inline register "..reg..": Instruction has block") 222 | return false 223 | end 224 | 225 | -- meh, I guess it can be inlined 226 | return true, range.set 227 | end 228 | 229 | return analyzer 230 | -------------------------------------------------------------------------------- /luavm/decompiler3/decompiler/block_metadata.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Block metadata: Fills out metadata about blocks, like relations. 3 | 4 | For example, the two blocks following a conditional block are children of 5 | the conditional block. 6 | ]] 7 | 8 | return function(decompiler) 9 | 10 | function decompiler.computeBlockMetadata(context) 11 | -- Pass 1: Compute block relationships 12 | for block in context:blocks() do 13 | if block.type == "conditional" then 14 | local nextBlock = block.next 15 | 16 | decompiler.jumpDeopt.tryDeoptConditional(context, block) 17 | 18 | if nextBlock.type == "alias" then 19 | 20 | local meta = decompiler.id { 21 | mainBlockRange = { 22 | nextBlock.next.start, 23 | nextBlock.alias.target 24 | } 25 | } 26 | 27 | local lastInnerBlock 28 | for testBlock in context:blocks(nextBlock.next) do 29 | if testBlock.start == nextBlock.alias.target then 30 | lastInnerBlock = testBlock.prev 31 | break 32 | end 33 | end 34 | assert(lastInnerBlock) 35 | 36 | local libTarget = lastInnerBlock[lastInnerBlock.type].target 37 | 38 | if libTarget and nextBlock.alias.target ~= libTarget and not (lastInnerBlock.metadata["break"]) then 39 | meta.subBlockRange = { 40 | nextBlock.alias.target, 41 | libTarget 42 | } 43 | end 44 | 45 | block.metadata["if"] = meta 46 | 47 | -- if else chain 48 | --[[local metaBlock = decompiler.block { 49 | type = "meta", 50 | metatype = "ifelse", 51 | start = block.start, 52 | length = 0, 53 | decoded = {}, 54 | } 55 | 56 | block.parent:insertBlockBefore(block, metaBlock) 57 | 58 | local nextNextBlock = nextBlock.next 59 | 60 | block:removeFromParent() 61 | metaBlock:insertBlock(block) 62 | 63 | nextBlock:removeFromParent() 64 | metaBlock:insertBlock(nextBlock) 65 | 66 | local lastInnerBlock 67 | for testBlock in context:blocks(nextNextBlock) do 68 | if testBlock.start == nextBlock.alias.target then 69 | lastInnerBlock = testBlock.prev 70 | break 71 | else 72 | testBlock:removeFromParent() 73 | metaBlock:insertBlock(testBlock) 74 | end 75 | end]] 76 | end 77 | elseif block.type == "loop" then 78 | -- Insert a "metablock" above the loop start 79 | local loopTarget = block.loop.target 80 | local loopBlock 81 | for testBlock in context:blocksUntil(block) do 82 | if testBlock.start == loopTarget then 83 | loopBlock = testBlock 84 | break 85 | end 86 | end 87 | 88 | local prevBlock = block.prev 89 | 90 | local meta = decompiler.id {} 91 | 92 | if prevBlock and prevBlock.type == "conditional" then 93 | block.metadata["until"] = meta 94 | elseif block.decoded[0][1] == "tforloop" then 95 | block.metadata["gfor"] = meta 96 | elseif block.decoded[block.length-1][1] == "forloop" then 97 | block.metadata["nfor"] = meta 98 | else 99 | block.metadata["while"] = meta 100 | end 101 | 102 | --[[assert(loopBlock) 103 | local metaBlock = decompiler.block { 104 | type = "meta", 105 | metatype = "loopstart", 106 | start = loopBlock.start, 107 | length = 0, 108 | decoded = {}, 109 | } 110 | -- If the loop's parent is a conditional block, we are a 111 | -- repeat until loop! 112 | context:insertBlockBefore(loopBlock, metaBlock) 113 | -- link parents and children 114 | local lastLinkBlock = nil 115 | for linkBlock in context:blocksUntil(block.next, loopBlock) do 116 | if not relationships[linkBlock] then 117 | relationships[linkBlock] = metaBlock 118 | afterRelationships[linkBlock] = lastLinkBlock 119 | relationships[#relationships+1] = linkBlock 120 | 121 | lastLinkBlock = linkBlock 122 | end 123 | end]] 124 | elseif block.type == "normal" then 125 | --[[local instr = block.decoded[block.length-1] 126 | if instr and instr[1] == "loadbool" and instr.skipNext then 127 | relationships[block.next] = block 128 | relationships[#relationships+1] = block.next 129 | end]] 130 | end 131 | 132 | decompiler.computeRegisterDependenciesForBlock(block) 133 | end 134 | 135 | for block in context:blocks() do 136 | context:deduceBlockScope(block) 137 | end 138 | end 139 | 140 | function decompiler.computeRegisterDependenciesForBlock(block) 141 | local written = {} 142 | local writtenList = {} 143 | local read = {} 144 | local readList = {} 145 | 146 | local function onRead(reg) 147 | if not written[reg] and not read[reg] then 148 | read[reg] = true 149 | readList[#readList+1] = reg 150 | end 151 | end 152 | 153 | local function onWrite(reg) 154 | if not written[reg] then 155 | written[reg] = true 156 | writtenList[#writtenList+1] = reg 157 | end 158 | end 159 | 160 | for i=0, block.length-1 do 161 | local instr = block.decoded[i] 162 | if instr then 163 | local regDetails = decompiler.instr.getRegisterDetails(instr) 164 | 165 | for reg in pairs(regDetails.read) do 166 | onRead(reg) 167 | end 168 | 169 | for reg in pairs(regDetails.write) do 170 | onWrite(reg) 171 | end 172 | end 173 | end 174 | 175 | if block.type == "exit" then 176 | -- We can't actually export anything from an exit block... 177 | -- writtenList = {} 178 | end 179 | 180 | block.metadata.regs = { 181 | importSet = read, 182 | imports = readList, 183 | exportSet = written, 184 | exports = writtenList 185 | } 186 | end 187 | 188 | function decompiler.deduceBlockScope(context, block) 189 | -- Find out which exported registers can be block local variables 190 | local blockLocalRejections = {} 191 | local blockRegData = block.metadata.regs 192 | 193 | -- Simple case: The register is both imported and exported 194 | for reg in pairs(blockRegData.exportSet) do 195 | if blockRegData.importSet[reg] then 196 | blockLocalRejections[reg] = "register is imported from another block" 197 | end 198 | end 199 | 200 | -- Complex case: The register is imported in pending control flow 201 | local visited = {[block] = true} -- TODO: visited might have to be per root? 202 | local followed = {[block] = true} 203 | local ignoreStack = {} 204 | local ignore = {} 205 | local pendingIgnores = {} 206 | 207 | local function addIgnore(reg) 208 | ignore[reg] = true 209 | end 210 | 211 | local function pushIgnore() 212 | ignoreStack[#ignoreStack+1] = ignore 213 | ignore = {} 214 | end 215 | 216 | local function popIgnore() 217 | ignore = ignoreStack[#ignoreStack] 218 | ignoreStack[#ignoreStack] = nil 219 | end 220 | 221 | local function isIgnored(reg, i) 222 | local ig 223 | if not i then 224 | ig = ignore 225 | i = #ignoreStack+1 226 | else 227 | ig = ignoreStack[i] 228 | end 229 | 230 | if i <= 0 then return false end 231 | if ig[reg] then 232 | return true 233 | end 234 | 235 | return isIgnored(reg, i-1) 236 | end 237 | 238 | local lbi = context:logicalBlockIter(block) 239 | for hasBlock, block, root in lbi:iter() do 240 | if hasBlock then 241 | if block == root then 242 | print("start root "..root.start) 243 | end 244 | print("do "..block.start.." root "..root.start) 245 | else 246 | print("exit root "..root.start) 247 | end 248 | 249 | if hasBlock and block == root then 250 | pushIgnore() 251 | ignore = pendingIgnores[block.start] or ignore 252 | end 253 | 254 | if hasBlock and not visited[block] then 255 | -- visited[block] = true 256 | 257 | local follow = false 258 | local pendingIgnore = {} 259 | for reg in pairs(blockRegData.exportSet) do 260 | if not blockLocalRejections[reg] and not isIgnored(reg) then 261 | if block.metadata.regs.importSet[reg] then 262 | blockLocalRejections[reg] = "register imported in block @ "..block.start 263 | else 264 | if not block.metadata.regs.exportSet[reg] then 265 | -- follow block if possible 266 | follow = true 267 | else 268 | -- ignore register from root 269 | print("Ignoring "..reg.." in root @ "..block.start) 270 | pendingIgnore[reg] = true 271 | end 272 | end 273 | end 274 | end 275 | 276 | if follow and not followed[block] then 277 | print("follow "..block.start) 278 | 279 | pendingIgnores[block.start] = pendingIgnore 280 | 281 | lbi:addRoot(block) 282 | followed[block] = true 283 | end 284 | elseif block == nil then 285 | popIgnore() 286 | end 287 | end 288 | 289 | print("BLR for "..block.start..":") 290 | print(serpent.block(blockLocalRejections)) 291 | 292 | local blrList = {set={}} 293 | for reg in pairs(blockRegData.exportSet) do 294 | if not blockLocalRejections[reg] then 295 | blrList[#blrList+1] = reg 296 | blrList.set[reg] = true 297 | end 298 | end 299 | print("BLROK: ", serpent.line(blrList)) 300 | 301 | block.metadata.locals = blrList 302 | end 303 | 304 | end 305 | -------------------------------------------------------------------------------- /luavm/vm51.lua: -------------------------------------------------------------------------------- 1 | --executes lua 5.1 bytecode-- 2 | local bit = bit32 or require "bit" 3 | if not bit.blshift then 4 | bit.blshift = bit.lshift 5 | bit.brshift = bit.rshift 6 | end 7 | 8 | local band, brshift = bit.band, bit.brshift 9 | local tostring, unpack = tostring, unpack or table.unpack 10 | local pack = table.pack or function(...) return {n=select("#",...),...} end 11 | 12 | local vm = require "luavm.vm" 13 | vm.lua51 = {} 14 | 15 | local function debug(...) 16 | if vm.debug then 17 | print(...) 18 | end 19 | end 20 | 21 | local function attemptCall(v) 22 | if vm.typechecking then 23 | local t = type(v) 24 | if not (t == "function" or (t == "table" and getmetatable(v) and type(getmetatable(v).__call) == "function")) then 25 | error("attempt to call a "..t.." value") 26 | end 27 | end 28 | return v 29 | end 30 | 31 | local function attemptMetatable(v,n,typ,meta) 32 | if vm.typechecking then 33 | local t = type(v) 34 | if not (t == typ or (t == "table" and getmetatable(v) and type(getmetatable(v)[meta]) == "function")) then 35 | error("attempt to "..n.." a "..t.." value") 36 | end 37 | end 38 | return v 39 | end 40 | 41 | local function attempt(v,to,...) 42 | if vm.typechecking then 43 | local t = type(v) 44 | for i=1, select("#",...) do 45 | if t == select(i,...) then return v end 46 | end 47 | error("attempt to "..to.." a "..t.." value") 48 | end 49 | return v 50 | end 51 | 52 | do 53 | local instructionNames = { 54 | [0]="MOVE","LOADK","LOADBOOL","LOADNIL", 55 | "GETUPVAL","GETGLOBAL","GETTABLE", 56 | "SETGLOBAL","SETUPVAL","SETTABLE","NEWTABLE", 57 | "SELF","ADD","SUB","MUL","DIV","MOD","POW","UNM","NOT","LEN","CONCAT", 58 | "JMP","EQ","LT","LE","TEST","TESTSET","CALL","TAILCALL","RETURN", 59 | "FORLOOP","FORPREP","TFORLOOP","SETLIST","CLOSE","CLOSURE","VARARG" 60 | } 61 | 62 | local iABC = 0 63 | local iABx = 1 64 | local iAsBx = 2 65 | 66 | local instructionFormats = { 67 | [0]=iABC,iABx,iABC,iABC, 68 | iABC,iABx,iABC, 69 | iABx,iABC,iABC,iABC, 70 | iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC, 71 | iAsBx,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC, 72 | iAsBx,iAsBx,iABC,iABC,iABC,iABx,iABC 73 | } 74 | 75 | function vm.lua51.run(chunk, args, upvals, globals, hook) 76 | if chunk.header.version ~= 0x51 then error(string.format("attempt to run %x bytecode in 51",chunk.header.version)) end 77 | local R = {} 78 | local top = 0 79 | local pc = 0 80 | local code = chunk.instructions 81 | local constants = chunk.constants 82 | args = args or {} 83 | globals = globals or _G 84 | upvals = upvals or {} 85 | local openUpvalues = {} 86 | for i=1,chunk.nparam do R[i-1] = args[i] top = i-1 end 87 | 88 | local function decodeInstruction(inst) 89 | local opcode = band(inst,0x3F) 90 | local format = instructionFormats[opcode] 91 | if format == iABC then 92 | return opcode, band(brshift(inst,6),0xFF), band(brshift(inst,23),0x1FF), band(brshift(inst,14),0x1FF) 93 | elseif format == iABx then 94 | return opcode, band(brshift(inst,6),0xFF), band(brshift(inst,14),0x3FFFF) 95 | elseif format == iAsBx then 96 | local sBx = band(brshift(inst,14),0x3FFFF)-131071 97 | return opcode, band(brshift(inst,6),0xFF), sBx 98 | else 99 | error(opcode.." "..tostring(instructionNames[opcode])) 100 | end 101 | end 102 | 103 | local function getsBx(inst) 104 | local sBx = band(brshift(inst,14),0x3FFFF)-131071 105 | return sBx 106 | end 107 | 108 | local function RK(n) 109 | if n >= 256 then 110 | return constants[n-256] 111 | else 112 | return R[n] 113 | end 114 | end 115 | 116 | --instruction constants-- 117 | local MOVE = 0 118 | local LOADK = 1 119 | local LOADBOOL = 2 120 | local LOADNIL = 3 121 | local GETUPVAL = 4 122 | local GETGLOBAL = 5 123 | local GETTABLE = 6 124 | local SETGLOBAL = 7 125 | local SETUPVAL = 8 126 | local SETTABLE = 9 127 | local NEWTABLE = 10 128 | local SELF = 11 129 | local ADD = 12 130 | local SUB = 13 131 | local MUL = 14 132 | local DIV = 15 133 | local MOD = 16 134 | local POW = 17 135 | local UNM = 18 136 | local NOT = 19 137 | local LEN = 20 138 | local CONCAT = 21 139 | local JMP = 22 140 | local EQ = 23 141 | local LT = 24 142 | local LE = 25 143 | local TEST = 26 144 | local TESTSET = 27 145 | local CALL = 28 146 | local TAILCALL = 29 147 | local RETURN = 30 148 | local FORLOOP = 31 149 | local FORPREP = 32 150 | local TFORLOOP = 33 151 | local SETLIST = 34 152 | local CLOSE = 35 153 | local CLOSURE = 36 154 | local VARARG = 37 155 | 156 | local ret = pack(pcall(function() 157 | while true do 158 | local o,a,b,c = decodeInstruction(code[pc]) 159 | if vm.debug then debug(pc,instructionNames[o],a,b,c) end 160 | pc = pc+1 161 | if hook then hook(o,a,b,c,pc-1,instructionNames[o]) end 162 | 163 | if o == MOVE then 164 | R[a] = R[b] 165 | elseif o == LOADNIL then 166 | for i=a, c do 167 | R[i] = nil 168 | end 169 | elseif o == LOADK then 170 | R[a] = constants[b] 171 | elseif o == LOADBOOL then 172 | R[a] = b ~= 0 173 | if c ~= 0 then 174 | pc = pc+1 175 | end 176 | elseif o == GETGLOBAL then 177 | R[a] = globals[constants[b]] 178 | elseif o == SETGLOBAL then 179 | globals[constants[b]] = R[a] 180 | elseif o == GETUPVAL then 181 | R[a] = upvals[b] 182 | elseif o == SETUPVAL then 183 | upvals[b] = R[a] 184 | elseif o == GETTABLE then 185 | R[a] = R[b][RK(c)] 186 | elseif o == SETTABLE then 187 | R[a][RK(b)] = RK(c) 188 | elseif o == ADD then 189 | R[a] = RK(b)+RK(c) 190 | elseif o == SUB then 191 | R[a] = RK(b)-RK(c) 192 | elseif o == MUL then 193 | R[a] = RK(b)*RK(c) 194 | elseif o == DIV then 195 | R[a] = RK(b)/RK(c) 196 | elseif o == MOD then 197 | R[a] = RK(b)%RK(c) 198 | elseif o == POW then 199 | R[a] = RK(b)^RK(c) 200 | elseif o == UNM then 201 | R[a] = -R[b] 202 | elseif o == NOT then 203 | R[a] = not R[b] 204 | elseif o == LEN then 205 | R[a] = #R[b] 206 | elseif o == CONCAT then 207 | local sct = {} 208 | for i=b, c do sct[#sct+1] = tostring(R[i]) end 209 | R[a] = table.concat(sct) 210 | elseif o == JMP then 211 | pc = (pc+b) 212 | elseif o == CALL then 213 | attemptCall(R[a]) 214 | local ret 215 | if b == 1 then 216 | if c == 1 then 217 | R[a]() 218 | elseif c == 2 then 219 | R[a] = R[a]() 220 | else 221 | ret = pack(R[a]()) 222 | 223 | if c == 0 then 224 | for i=1, ret.n do R[a+i-1] = ret[i] end 225 | top = a+ret.n-1 226 | else 227 | local g = 1 228 | for i=a, a+c-1 do R[i] = ret[g] g=g+1 end 229 | end 230 | end 231 | else 232 | --local cargs = {} 233 | local s,e 234 | if b == 0 then 235 | s,e=a+1,top 236 | --for i=a+2, chunk.maxStack-2 do cargs[#cargs+1] = R[i] end 237 | else 238 | s,e=a+1,a+b-1 239 | --for i=a+1, a+b-1 do cargs[#cargs+1] = R[i] end 240 | end 241 | if c == 1 then 242 | R[a](unpack(R,s,e)) 243 | elseif c == 2 then 244 | R[a] = R[a](unpack(R,s,e)) 245 | else 246 | ret = pack(R[a](unpack(R,s,e))) 247 | 248 | if c == 0 then 249 | for i=1, ret.n do R[a+i-1] = ret[i] end 250 | top = a+ret.n-1 251 | else 252 | local g = 1 253 | for i=a, a+c-2 do R[i] = ret[g] g=g+1 end 254 | end 255 | end 256 | end 257 | elseif o == RETURN then 258 | local ret = {} 259 | local rti = 1 260 | if b == 0 then 261 | for i=a, top do ret[rti] = R[i] rti = rti+1 end 262 | else 263 | for i=a, a+b-2 do ret[rti] = R[i] rti = rti+1 end 264 | end 265 | return unpack(ret,1,rti-1) 266 | elseif o == TAILCALL then 267 | local cargs = {} 268 | local ai = 1 269 | if b == 0 then 270 | for i=a+1, top do cargs[ai] = R[i] ai = ai+1 end 271 | else 272 | for i=a+1, a+b-1 do cargs[ai] = R[i] ai = ai+1 end 273 | end 274 | return attemptCall(R[a])(unpack(cargs,1,ai-1)) 275 | elseif o == VARARG then 276 | if b > 0 then 277 | local i = 1 278 | for n=a, a+b-1 do 279 | R[n] = args[i] 280 | i = i+1 281 | end 282 | else 283 | local base = chunk.nparam+1 284 | local nargs = #args 285 | for i=base, nargs do 286 | R[a+i-base] = args[i] 287 | end 288 | top = a+nargs-base 289 | end 290 | elseif o == SELF then 291 | R[a+1] = R[b] 292 | R[a] = R[b][RK(c)] 293 | elseif o == EQ then 294 | if (RK(b) == RK(c)) == (a ~= 0) then 295 | pc = pc+getsBx(code[pc])+1 296 | else 297 | pc = pc+1 298 | end 299 | elseif o == LT then 300 | if (RK(b) < RK(c)) == (a ~= 0) then 301 | pc = pc+getsBx(code[pc])+1 302 | else 303 | pc = pc+1 304 | end 305 | elseif o == LE then 306 | if (RK(b) <= RK(c)) == (a ~= 0) then 307 | pc = pc+getsBx(code[pc])+1 308 | else 309 | pc = pc+1 310 | end 311 | elseif o == TEST then 312 | if (not R[a]) ~= (c ~= 0) then 313 | pc = pc+getsBx(code[pc])+1 314 | else 315 | pc = pc+1 316 | end 317 | elseif o == TESTSET then 318 | if (not R[b]) ~= (c ~= 0) then 319 | R[a] = R[b] 320 | pc = pc+getsBx(code[pc])+1 321 | else 322 | pc = pc+1 323 | end 324 | elseif o == FORPREP then 325 | R[a] = R[a]-R[a+2] 326 | pc = pc+b 327 | elseif o == FORLOOP then 328 | local step = R[a+2] 329 | R[a] = R[a]+step 330 | local idx = R[a] 331 | local limit = R[a+1] 332 | local can 333 | 334 | if step < 0 then 335 | can = limit <= idx 336 | else 337 | can = limit >= idx 338 | end 339 | 340 | if can then 341 | pc = pc+b 342 | R[a+3] = R[a] 343 | end 344 | elseif o == TFORLOOP then 345 | local ret = {R[a](R[a+1],R[a+2])} 346 | local i = 1 347 | for n=a+3, a+3+c do R[n] = ret[i] i=i+1 end 348 | if R[a+3] ~= nil then 349 | R[a+2] = R[a+3] 350 | else 351 | pc = pc+1 352 | end 353 | elseif o == NEWTABLE then 354 | R[a] = {} 355 | elseif o == SETLIST then 356 | if b > 0 then 357 | for i=1, b do 358 | R[a][((c-1)*50)+i] = R[a+i] 359 | end 360 | else 361 | for i=1, top-a do 362 | R[a][((c-1)*50)+i] = R[a+i] 363 | end 364 | end 365 | elseif o == CLOSURE then 366 | local proto = chunk.functionPrototypes[b] 367 | local upvaldef = {} 368 | local upvalues = setmetatable({},{__index=function(_,i) 369 | if not upvaldef[i] then error("unknown upvalue") end 370 | local uvd = upvaldef[i] 371 | if uvd.type == 0 then --local upvalue 372 | return R[uvd.reg] 373 | elseif uvd.type == 1 then --upvalue upvalue 374 | return upvals[uvd.reg] 375 | else -- closed upvalue 376 | return uvd.storage 377 | end 378 | end,__newindex=function(_,i,v) 379 | if not upvaldef[i] then error("unknown upvalue") end 380 | local uvd = upvaldef[i] 381 | if uvd.type == 0 then --local upvalue 382 | R[uvd.reg] = v 383 | elseif uvd.type == 1 then --upvalue upvalue 384 | upvals[uvd.reg] = v 385 | else --closed upvalue 386 | uvd.storage = v 387 | end 388 | end}) 389 | R[a] = function(...) 390 | return vm.lua51.run(proto, {...}, upvalues, globals, hook) 391 | end 392 | for i=1, proto.nupval do 393 | local o,a,b,c = decodeInstruction(code[pc+i-1]) 394 | debug(pc+i,"PSD",instructionNames[o],a,b,c) 395 | if o == MOVE then 396 | upvaldef[i-1] = openUpvalues[b] or {type=0,reg=b} 397 | openUpvalues[b] = upvaldef[i-1] 398 | elseif o == GETUPVAL then 399 | upvaldef[i-1] = {type=1,reg=b} 400 | else 401 | error("unknown upvalue psuedop") 402 | end 403 | end 404 | pc = pc+proto.nupval 405 | elseif o == CLOSE then 406 | for i=a, chunk.maxStack do 407 | if openUpvalues[i] then 408 | local ouv = openUpvalues[i] 409 | ouv.type = 2 --closed 410 | ouv.storage = R[ouv.reg] 411 | openUpvalues[i] = nil 412 | end 413 | end 414 | else 415 | error("Unknown opcode!") 416 | end 417 | end 418 | end)) 419 | -- Implicit Upvalue Close 420 | for i=0, chunk.maxStack do 421 | if openUpvalues[i] then 422 | local ouv = openUpvalues[i] 423 | ouv.type = 2 --closed 424 | ouv.storage = R[ouv.reg] 425 | openUpvalues[i] = nil 426 | end 427 | end 428 | if not ret[1] then 429 | error(tostring(ret[2]).."\n"..tostring(chunk.name).." at pc "..(pc-1).." line "..tostring(chunk.sourceLines[pc-1]),0) 430 | else 431 | return unpack(ret,2,ret.n) 432 | end 433 | end 434 | end 435 | 436 | return vm.lua51 437 | -------------------------------------------------------------------------------- /luavm/bytecode/lua52.lua: -------------------------------------------------------------------------------- 1 | return function(bytecode) 2 | local impl = {} 3 | 4 | local debug = bytecode.printDebug 5 | local bit = bytecode.bit 6 | 7 | -- instruction definitions 8 | 9 | local instructionNames = { 10 | [0]="MOVE","LOADK","LOADKX","LOADBOOL","LOADNIL", 11 | "GETUPVAL","GETTABUP","GETTABLE", 12 | "SETTABUP","SETUPVAL","SETTABLE","NEWTABLE", 13 | "SELF","ADD","SUB","MUL","DIV","MOD","POW","UNM","NOT","LEN","CONCAT", 14 | "JMP","EQ","LT","LE","TEST","TESTSET","CALL","TAILCALL","RETURN", 15 | "FORLOOP","FORPREP","TFORCALL","TFORLOOP","SETLIST","CLOSURE","VARARG","EXTRAARG" 16 | } 17 | 18 | local iABC = 0 19 | local iABx = 1 20 | local iAsBx = 2 21 | local iAx = 4 22 | 23 | local instructionFormats = { 24 | [0]=iABC,iABx,iABC,iABC,iABC, 25 | iABC,iABC,iABC, 26 | iABC,iABC,iABC,iABC, 27 | iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC, 28 | iAsBx,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC, 29 | iAsBx,iAsBx,iABC,iAsBx,iABC,iABx,iABC,iAx 30 | } 31 | 32 | local ins = {} 33 | for i, v in pairs(instructionNames) do ins[v] = i end 34 | 35 | impl.instructionNames = instructionNames 36 | impl.instructions = ins 37 | impl.defaultReturn = 8388638 38 | 39 | -- instruction constants 40 | 41 | local MOVE = 0 42 | local LOADK = 1 43 | local LOADKX = 2 44 | local LOADBOOL = 3 45 | local LOADNIL = 4 46 | local GETUPVAL = 5 47 | local GETTABUP = 6 48 | local GETTABLE = 7 49 | local SETTABUP = 8 50 | local SETUPVAL = 9 51 | local SETTABLE = 10 52 | local NEWTABLE = 11 53 | local SELF = 12 54 | local ADD = 13 55 | local SUB = 14 56 | local MUL = 15 57 | local DIV = 16 58 | local MOD = 17 59 | local POW = 18 60 | local UNM = 19 61 | local NOT = 20 62 | local LEN = 21 63 | local CONCAT = 22 64 | local JMP = 23 65 | local EQ = 24 66 | local LT = 25 67 | local LE = 26 68 | local TEST = 27 69 | local TESTSET = 28 70 | local CALL = 29 71 | local TAILCALL = 30 72 | local RETURN = 31 73 | local FORLOOP = 32 74 | local FORPREP = 33 75 | local TFORCALL = 34 76 | local TFORLOOP = 35 77 | local SETLIST = 36 78 | local CLOSURE = 37 79 | local VARARG = 38 80 | local EXTRAARG = 39 81 | 82 | -- instruction encoding and decoding 83 | 84 | function impl.encode(inst,a,b,c) 85 | inst = type(inst) == "string" and ins[inst] or inst 86 | local format = instructionFormats[inst] 87 | 88 | if format == iABC then 89 | return bit.bor(inst,bit.blshift(bit.band(a,0xFF),6),bit.blshift(bit.band(b,0x1FF),23), bit.blshift(bit.band(c,0x1FF),14)) 90 | elseif format == iABx then 91 | return bit.bor(inst,bit.blshift(bit.band(a,0xFF),6),bit.blshift(bit.band(b,0x3FFFF),14)) 92 | elseif format == iAsBx then 93 | return bit.bor(inst,bit.blshift(bit.band(a,0xFF),6),bit.blshift(bit.band(b+131071,0x3FFFF),14)) 94 | elseif format == iAx then 95 | return bit.bor(inst,bit.blshift(a,6)) 96 | else 97 | error("unknown opcode "..inst) 98 | end 99 | end 100 | 101 | function impl.decode(inst) 102 | local opcode = bit.band(inst,0x3F) 103 | local format = instructionFormats[opcode] 104 | if format == iABC then 105 | return opcode, bit.band(bit.brshift(inst,6),0xFF), bit.band(bit.brshift(inst,23),0x1FF), bit.band(bit.brshift(inst,14),0x1FF) 106 | elseif format == iABx then 107 | return opcode, bit.band(bit.brshift(inst,6),0xFF), bit.band(bit.brshift(inst,14),0x3FFFF) 108 | elseif format == iAsBx then 109 | local sBx = bit.band(bit.brshift(inst,14),0x3FFFF)-131071 110 | return opcode, bit.band(bit.brshift(inst,6),0xFF), sBx 111 | elseif format == iAx then 112 | return opcode, bit.brshift(inst,6) 113 | else 114 | error("unknown opcode "..opcode) 115 | end 116 | end 117 | 118 | -- bytecode loading 119 | 120 | function impl.loadHeader(bc) 121 | local header = {version = 0x52} 122 | 123 | local fmtver = bc:byte(6) 124 | header.fmtver = fmtver 125 | debug("Format Version: %02X", fmtver) 126 | 127 | local types = bc:sub(7, 12) 128 | debug("Types: "..types:gsub(".", function(c) return string.format("%02X ", c:byte()) end)) 129 | 130 | local bigEndian = types:byte(1) ~= 1 131 | header.bigEndian = bigEndian 132 | debug("Big Endian: %s", tostring(bigEndian)) 133 | 134 | local integer = types:byte(2) 135 | header.integer = integer 136 | debug("Integer Size: %d bytes", integer) 137 | 138 | local size_t = types:byte(3) 139 | header.size_t = size_t 140 | debug("Size_T Size: %d bytes", size_t) 141 | 142 | local instruction = types:byte(4) 143 | header.instruction = instruction 144 | debug("Instruction Size: %d bytes", instruction) 145 | 146 | --integral or numerical number stuff 147 | do 148 | local integralNumbers = types:byte(6) ~= 0 149 | local size = types:byte(5) 150 | header.number_integral = integralNumbers 151 | header.number = size 152 | debug("Numerical Format: %d bytes <%s>", size, integralNumbers and "integral" or "floating") 153 | end 154 | 155 | return header 156 | end 157 | 158 | function impl.load(bc) 159 | debug("Lua 5.2 Bytecode Loader") 160 | 161 | local idx = 13 162 | local integer, size_t, number 163 | local bigEndian 164 | local binarytypes = bytecode.binarytypes 165 | 166 | local function u1() 167 | idx = idx+1 168 | return binarytypes.decode.u1(bc, idx-1, bigEndian) 169 | end 170 | 171 | local function u2() 172 | idx = idx+2 173 | return binarytypes.decode.u2(bc, idx-2, bigEndian) 174 | end 175 | 176 | local function u4() 177 | idx = idx+4 178 | return binarytypes.decode.u4(bc, idx-4, bigEndian) 179 | end 180 | 181 | local function u8() 182 | idx = idx+8 183 | return binarytypes.decode.u8(bc, idx-8, bigEndian) 184 | end 185 | 186 | local function float() 187 | idx = idx+4 188 | return binarytypes.decode.float(bc, idx-4, bigEndian) 189 | end 190 | 191 | local function double() 192 | idx = idx+8 193 | return binarytypes.decode.double(bc, idx-8, bigEndian) 194 | end 195 | 196 | local function ub(n) 197 | idx = idx+n 198 | return bc:sub(idx-n,idx-1) 199 | end 200 | 201 | local function us() 202 | local size = size_t() 203 | --print(size) 204 | return ub(size):sub(1,-2) 205 | end 206 | 207 | local integralSizes = { 208 | [1] = u1, 209 | [2] = u2, 210 | [4] = u4, 211 | [8] = u8 212 | } 213 | 214 | local numericSizes = { 215 | [4] = float, 216 | [8] = double 217 | } 218 | 219 | local header = impl.loadHeader(bc) 220 | 221 | assert(header.fmtver == 0 or header.fmtver == 255, "unknown format version: "..header.fmtver) 222 | bigEndian = header.bigEndian 223 | integer = assert(integralSizes[header.integer], "unsupported integer size: "..header.integer) 224 | size_t = assert(integralSizes[header.size_t], "unsupported size_t size: "..header.size_t) 225 | assert(header.instruction == 4, "unsupported instruction size: "..header.instruction) 226 | 227 | --integral or numerical number stuff 228 | do 229 | local integralNumbers = header.number_integral 230 | local size = header.number 231 | number = assert(integralNumbers and integralSizes[size] or numericSizes[size], "unsupported number size: "..(integralNumbers and "integral" or "floating").." "..size) 232 | end 233 | 234 | assert(ub(6) == "\25\147\r\n\26\n", "header has invalid tail") 235 | 236 | local function chunk() 237 | local function instructionList() 238 | local instructions = {} 239 | local count = integer() 240 | instructions.count = count 241 | for i=1, count do 242 | instructions[i-1] = u4() 243 | end 244 | return instructions 245 | end 246 | 247 | local function constantList() 248 | local constants = {} 249 | local c = integer() 250 | constants.count = c 251 | for i=1, c do 252 | local type = u1() 253 | if type == 0 then 254 | constants[i-1] = nil 255 | elseif type == 1 then 256 | constants[i-1] = u1() > 0 257 | elseif type == 3 then 258 | constants[i-1] = number() 259 | elseif type == 4 then 260 | constants[i-1] = us() 261 | else 262 | error("Type: "..type) 263 | end 264 | debug("Constant %d: %s %s", i-1, tostring(constants[i-1]), type) 265 | end 266 | return constants 267 | end 268 | 269 | local function functionPrototypeList() 270 | local functionPrototypes = {} 271 | local count = integer() 272 | functionPrototypes.count = count 273 | for i=1, count do 274 | functionPrototypes[i-1] = chunk() 275 | end 276 | return functionPrototypes 277 | end 278 | 279 | local function sourceLineList() 280 | local sourceLines = {} 281 | local count = integer() 282 | sourceLines.count = count 283 | for i=1, count do 284 | sourceLines[i-1] = integer() 285 | end 286 | return sourceLines 287 | end 288 | 289 | local function localList() 290 | local locals = {} 291 | local count = integer() 292 | locals.count = count 293 | for i=1, count do 294 | locals[i-1] = { 295 | name = us(), 296 | startpc = integer(), 297 | endpc = integer() 298 | } 299 | end 300 | return locals 301 | end 302 | 303 | local function upvalueList() 304 | local upvalues = {} 305 | local count = integer() 306 | upvalues.count = count 307 | for i=1, count do 308 | upvalues[i-1] = us() 309 | end 310 | return upvalues 311 | end 312 | 313 | local function upvalueDefinitionList() 314 | local upvalues = {} 315 | local count = integer() 316 | upvalues.count = count 317 | for i=1, count do 318 | upvalues[i-1] = {instack=u1(),idx=u1()} 319 | debug("upvalue %d instack=%d idx=%d", i-1, upvalues[i-1].instack, upvalues[i-1].idx) 320 | end 321 | return upvalues 322 | end 323 | 324 | --extract an lua chunk into a table-- 325 | local c = {header = header} 326 | 327 | c.lineDefined = integer() 328 | c.lastLineDefined = integer() 329 | c.nparam = u1() 330 | c.isvararg = u1() 331 | c.maxStack = u1() 332 | c.instructions = instructionList() 333 | c.constants = constantList() 334 | c.functionPrototypes = functionPrototypeList() 335 | c.upvalues = upvalueDefinitionList() 336 | c.name = us() 337 | c.sourceLines = sourceLineList() 338 | c.locals = localList() 339 | c.upvaluesDebug = upvalueList() 340 | return c 341 | end 342 | 343 | return chunk() 344 | end 345 | 346 | function impl.save(chunk) 347 | local header = chunk.header 348 | local bc = {"\27Lua", string.char(header.version, 0)} 349 | 350 | bc[#bc+1] = string.char( 351 | header.bigEndian and 0 or 1, 352 | header.integer, 353 | header.size_t, 354 | header.instruction, 355 | header.number, 356 | header.number_integral and 1 or 0 357 | ) 358 | 359 | bc[#bc+1] = "\25\147\r\n\26\n" 360 | 361 | local integer, size_t, number 362 | local bigEndian = header.bigEndian 363 | local binarytypes = bytecode.binarytypes 364 | 365 | local function u1(value) 366 | bc[#bc+1] = string.char(value) 367 | end 368 | 369 | local function u2(value) 370 | bc[#bc+1] = binarytypes.encode.u2(value, bigEndian) 371 | end 372 | 373 | local function u4(value) 374 | bc[#bc+1] = binarytypes.encode.u4(value, bigEndian) 375 | end 376 | 377 | local function u8(value) 378 | bc[#bc+1] = binarytypes.encode.u8(value, bigEndian) 379 | end 380 | 381 | local function float(value) 382 | bc[#bc+1] = binarytypes.encode.float(value, bigEndian) 383 | end 384 | 385 | local function double(value) 386 | bc[#bc+1] = binarytypes.encode.double(value, bigEndian) 387 | end 388 | 389 | local function us(str) 390 | size_t(#str+1) 391 | bc[#bc+1] = str 392 | bc[#bc+1] = string.char(0) 393 | end 394 | 395 | local function len(t) 396 | local n = 0 397 | for i, v in pairs(t) do n = n+1 end 398 | return n 399 | end 400 | 401 | local integralSizes = { 402 | [1] = u1, 403 | [2] = u2, 404 | [4] = u4, 405 | [8] = u8 406 | } 407 | 408 | local numericSizes = { 409 | [4] = float, 410 | [8] = double 411 | } 412 | 413 | assert(header.fmtver == 0 or header.fmtver == 255, "unknown format version: "..header.fmtver) 414 | bigEndian = header.bigEndian 415 | integer = assert(integralSizes[header.integer], "unsupported integer size: "..header.integer) 416 | size_t = assert(integralSizes[header.size_t], "unsupported size_t size: "..header.size_t) 417 | assert(header.instruction == 4, "unsupported instruction size: "..header.instruction) 418 | 419 | --integral or numerical number stuff 420 | do 421 | local integralNumbers = header.number_integral 422 | local size = header.number 423 | number = assert(integralNumbers and integralSizes[size] or numericSizes[size], "unsupported number size: "..(integralNumbers and "integral" or "floating").." "..size) 424 | end 425 | 426 | local function dumpChunk(chunk) 427 | integer(chunk.lineDefined) 428 | integer(chunk.lastLineDefined) 429 | u1(chunk.nparam) 430 | u1(chunk.isvararg) 431 | u1(chunk.maxStack) 432 | 433 | local lenInstructions = len(chunk.instructions) 434 | integer(lenInstructions) 435 | for i=0, lenInstructions-1 do 436 | u4(chunk.instructions[i]) 437 | end 438 | 439 | local lenConstants = len(chunk.constants) 440 | integer(lenConstants) 441 | for i=0, lenConstants-1 do 442 | local v = chunk.constants[i] 443 | local t = type(v) 444 | u1(t == "nil" and 0 or t == "boolean" and 1 or t == "number" and 3 or t == "string" and 4 or error("Unknown constant type.")) 445 | if t == "boolean" then 446 | u1(v and 1 or 0) 447 | elseif t == "number" then 448 | double(v) 449 | elseif t == "string" then 450 | us(v) 451 | end 452 | end 453 | 454 | local lenFunctionPrototypes = len(chunk.functionPrototypes) 455 | integer(lenFunctionPrototypes) 456 | for i=0, lenFunctionPrototypes-1 do 457 | writeChunk(chunk.functionPrototypes[i]) 458 | end 459 | 460 | local lenUpvalueDefs = len(chunk.upvalues) 461 | integer(lenUpvalueDefs) 462 | for i=0, lenUpvalueDefs-1 do 463 | u1(chunk.upvalues[i].instack and 1 or 0) 464 | u1(chunk.upvalues[i].idx) 465 | end 466 | 467 | us(chunk.name) 468 | 469 | local lenSourceLines = len(chunk.sourceLines) 470 | integer(lenSourceLines) 471 | for i=0, lenSourceLines-1 do 472 | integer(chunk.sourceLines[i]) 473 | end 474 | 475 | local lenLocals = len(chunk.locals) 476 | integer(lenLocals) 477 | for i=0, lenLocals-1 do 478 | local l = chunk.locals[i] 479 | us(l.name) 480 | integer(l.startpc) 481 | integer(l.endpc) 482 | end 483 | 484 | local lenUpvaluesDebug = len(chunk.upvaluesDebug) 485 | integer(lenUpvaluesDebug) 486 | for i=0, lenUpvaluesDebug-1 do 487 | us(chunk.upvaluesDebug[i]) 488 | end 489 | end 490 | 491 | dumpChunk(chunk) 492 | return table.concat(bc) 493 | end 494 | 495 | --TODO: impl.new 496 | 497 | return impl 498 | end -------------------------------------------------------------------------------- /luavm/vm52.lua: -------------------------------------------------------------------------------- 1 | --executes lua 5.2 bytecode-- 2 | local bit = bit32 or require "bit" 3 | if not bit.blshift then 4 | bit.blshift = bit.lshift 5 | bit.brshift = bit.rshift 6 | end 7 | 8 | local band, brshift = bit.band, bit.brshift 9 | local tostring, unpack = tostring, unpack or table.unpack 10 | local pack = table.pack or function(...) return {n=select("#",...),...} end 11 | 12 | local vm = require "luavm.vm" 13 | vm.lua52 = {} 14 | 15 | local function debug(...) 16 | if vm.debug then 17 | print(...) 18 | end 19 | end 20 | 21 | local function attemptCall(v) 22 | if vm.typechecking then 23 | local t = type(v) 24 | if not (t == "function" or (t == "table" and getmetatable(v) and type(getmetatable(v).__call) == "function")) then 25 | error("attempt to call a "..t.." value") 26 | end 27 | end 28 | return v 29 | end 30 | 31 | local function attemptMetatable(v,n,typ,meta) 32 | if vm.typechecking then 33 | local t = type(v) 34 | if not (t == typ or (t == "table" and getmetatable(v) and type(getmetatable(v)[meta]) == "function")) then 35 | error("attempt to "..n.." a "..t.." value") 36 | end 37 | end 38 | return v 39 | end 40 | 41 | local function attempt(v,to,...) 42 | if vm.typechecking then 43 | local t = type(v) 44 | for i=1, select("#",...) do 45 | if t == select(i,...) then return v end 46 | end 47 | error("attempt to "..to.." a "..t.." value") 48 | end 49 | return v 50 | end 51 | 52 | do 53 | local instructionNames = { 54 | [0]="MOVE","LOADK","LOADKX","LOADBOOL","LOADNIL", 55 | "GETUPVAL","GETTABUP","GETTABLE", 56 | "SETTABUP","SETUPVAL","SETTABLE","NEWTABLE", 57 | "SELF","ADD","SUB","MUL","DIV","MOD","POW","UNM","NOT","LEN","CONCAT", 58 | "JMP","EQ","LT","LE","TEST","TESTSET","CALL","TAILCALL","RETURN", 59 | "FORLOOP","FORPREP","TFORCALL","TFORLOOP","SETLIST","CLOSURE","VARARG","EXTRAARG" 60 | } 61 | 62 | local iABC = 0 63 | local iABx = 1 64 | local iAsBx = 2 65 | local iA = 3 66 | local iAx = 4 67 | 68 | local instructionFormats = { 69 | [0]=iABC,iABx,iA,iABC,iABC, 70 | iABC,iABC,iABC, 71 | iABC,iABC,iABC,iABC, 72 | iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC, 73 | iAsBx,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC, 74 | iAsBx,iAsBx,iABC,iAsBx,iABC,iABx,iABC,iAx 75 | } 76 | 77 | function vm.lua52.run(chunk, args, upvals, globals, hook) 78 | if chunk.header.version ~= 0x52 then error(string.format("attempt to run %x bytecode in 52",chunk.header.version)) end 79 | local R = {} 80 | local top = 0 81 | local pc = 0 82 | local code = chunk.instructions 83 | local constants = chunk.constants 84 | local openUpvalues = {} 85 | args = args or {} 86 | globals = globals or _G 87 | upvals = upvals or {[0]=globals} 88 | 89 | for i=1,chunk.nparam do R[i-1] = args[i] top = i-1 end 90 | 91 | local function decodeInstruction(inst) 92 | local opcode = band(inst,0x3F) 93 | local format = instructionFormats[opcode] 94 | if format == iABC then 95 | return opcode, band(brshift(inst,6),0xFF), band(brshift(inst,23),0x1FF), band(brshift(inst,14),0x1FF) 96 | elseif format == iABx then 97 | return opcode, band(brshift(inst,6),0xFF), band(brshift(inst,14),0x3FFFF) 98 | elseif format == iAsBx then 99 | local sBx = band(brshift(inst,14),0x3FFFF)-131071 100 | return opcode, band(brshift(inst,6),0xFF), sBx 101 | elseif format == iA then 102 | return opcode, band(brshift(inst,6),0xFF) 103 | elseif format == iAx then 104 | return opcode, brshift(inst,6) 105 | else 106 | error(opcode.." "..tostring(instructionNames[opcode])) 107 | end 108 | end 109 | 110 | local function getsBx(inst) 111 | local sBx = band(brshift(inst,14),0x3FFFF)-131071 112 | return sBx 113 | end 114 | 115 | local function getAx(inst) 116 | return brshift(inst,6) 117 | end 118 | 119 | local function RK(n) 120 | if n >= 256 then 121 | return constants[n-256] 122 | else 123 | return R[n] 124 | end 125 | end 126 | 127 | --instruction constants-- 128 | local MOVE = 0 129 | local LOADK = 1 130 | local LOADKX = 2 131 | local LOADBOOL = 3 132 | local LOADNIL = 4 133 | local GETUPVAL = 5 134 | local GETTABUP = 6 135 | local GETTABLE = 7 136 | local SETTABUP = 8 137 | local SETUPVAL = 9 138 | local SETTABLE = 10 139 | local NEWTABLE = 11 140 | local SELF = 12 141 | local ADD = 13 142 | local SUB = 14 143 | local MUL = 15 144 | local DIV = 16 145 | local MOD = 17 146 | local POW = 18 147 | local UNM = 19 148 | local NOT = 20 149 | local LEN = 21 150 | local CONCAT = 22 151 | local JMP = 23 152 | local EQ = 24 153 | local LT = 25 154 | local LE = 26 155 | local TEST = 27 156 | local TESTSET = 28 157 | local CALL = 29 158 | local TAILCALL = 30 159 | local RETURN = 31 160 | local FORLOOP = 32 161 | local FORPREP = 33 162 | local TFORCALL = 34 163 | local TFORLOOP = 35 164 | local SETLIST = 36 165 | local CLOSURE = 37 166 | local VARARG = 38 167 | local EXTRAARG = 39 168 | 169 | local ret = pack(pcall(function() 170 | while true do 171 | local o,a,b,c = decodeInstruction(code[pc]) 172 | if vm.debug then debug(chunk.name,tostring(chunk.sourceLines[pc]),pc,instructionNames[o],a,b,c) end 173 | pc = pc+1 174 | if hook then hook(o,a,b,c,pc-1,instructionNames[o]) end 175 | 176 | if o == MOVE then 177 | R[a] = R[b] 178 | debug("Ra =",R[b]) 179 | elseif o == LOADNIL then 180 | for i=a, a+b do 181 | R[i] = nil 182 | debug("R"..i,"= nil") 183 | end 184 | elseif o == LOADK then 185 | R[a] = constants[b] 186 | debug("Ra =",R[a]) 187 | if R[a] == "START_DEBUGGING" then 188 | vm.debug = true 189 | end 190 | elseif o == LOADKX then 191 | R[a] = constants[getAx(code[pc])] 192 | pc = pc+1 193 | debug("Ra =",R[a]) 194 | elseif o == LOADBOOL then 195 | R[a] = b ~= 0 196 | debug("Ra =",R[a]) 197 | if c ~= 0 then 198 | pc = pc+1 199 | end 200 | elseif o == GETTABUP then 201 | R[a] = attempt(upvals[b], "index", "table", "string")[RK(c)] 202 | debug("Ra =",upvals[b],"[",RK(c),"]") 203 | elseif o == SETTABUP then 204 | attempt(upvals[a], "index", "table", "string")[RK(b)] = RK(c) 205 | debug(upvals[a],"[",RK(b),"] =",RK(c)) 206 | elseif o == GETUPVAL then 207 | R[a] = upvals[b] 208 | debug("Ra =",upvals[b]) 209 | elseif o == SETUPVAL then 210 | upvals[b] = R[a] 211 | debug("UVb =",R[a]) 212 | elseif o == GETTABLE then 213 | R[a] = attempt(R[b], "index", "table", "string")[RK(c)] 214 | debug("Ra =",R[b],"[",RK(c),"]") 215 | elseif o == SETTABLE then 216 | attempt(R[a], "index", "table", "string")[RK(b)] = RK(c) 217 | debug(R[a],"[",RK(b),"] =",RK(c)) 218 | elseif o == ADD then 219 | R[a] = attemptMetatable(RK(b), "perform arithmetic on", "number", "__add")+attemptMetatable(RK(c), "perform arithmetic on", "number", "__add") 220 | elseif o == SUB then 221 | R[a] = attemptMetatable(RK(b), "perform arithmetic on", "number", "__sub")-attemptMetatable(RK(c), "perform arithmetic on", "number", "__sub") 222 | elseif o == MUL then 223 | R[a] = attemptMetatable(RK(b), "perform arithmetic on", "number", "__mul")*attemptMetatable(RK(c), "perform arithmetic on", "number", "__mul") 224 | elseif o == DIV then 225 | R[a] = attemptMetatable(RK(b), "perform arithmetic on", "number", "__div")/attemptMetatable(RK(c), "perform arithmetic on", "number", "__div") 226 | elseif o == MOD then 227 | R[a] = attemptMetatable(RK(b), "perform arithmetic on", "number", "__mod")%attemptMetatable(RK(c), "perform arithmetic on", "number", "__mod") 228 | elseif o == POW then 229 | R[a] = attemptMetatable(RK(b), "perform arithmetic on", "number", "__pow")^attemptMetatable(RK(c), "perform arithmetic on", "number", "__pow") 230 | elseif o == UNM then 231 | R[a] = -attemptMetatable(R[b], "perform arithmetic on", "number", "__unm") 232 | elseif o == NOT then 233 | R[a] = not R[b] 234 | elseif o == LEN then 235 | R[a] = #attempt(R[b], "get length of", "string", "table") 236 | elseif o == CONCAT then 237 | local sct = {} 238 | for i=b, c do sct[#sct+1] = tostring(R[i]) end 239 | R[a] = table.concat(sct) 240 | elseif o == JMP then 241 | pc = (pc+b) 242 | -- Lua 5.2 JMP closes upvalues 243 | if a > 0 then 244 | for i=a-1, chunk.maxStack do 245 | if openUpvalues[i] then 246 | local ouv = openUpvalues[i] 247 | ouv.type = 2 --closed 248 | ouv.storage = R[ouv.reg] 249 | openUpvalues[i] = nil 250 | end 251 | end 252 | end 253 | elseif o == CALL then 254 | attemptCall(R[a]) 255 | local ret 256 | if b == 1 then 257 | if c == 1 then 258 | R[a]() 259 | elseif c == 2 then 260 | R[a] = R[a]() 261 | else 262 | ret = {R[a]()} 263 | debug(ret[1], ret[2], ret[3]) 264 | 265 | if c == 0 then 266 | for i=a, a+#ret-1 do R[i] = ret[i-a+1] end 267 | top = a+#ret-1 268 | else 269 | local g = 1 270 | for i=a, a+c-1 do R[i] = ret[g] g=g+1 end 271 | end 272 | end 273 | else 274 | --local cargs = {} 275 | local s,e 276 | if b == 0 then 277 | s,e=a+1,top 278 | --for i=a+2, chunk.maxStack-2 do cargs[#cargs+1] = R[i] end 279 | else 280 | s,e=a+1,a+b-1 281 | --for i=a+1, a+b-1 do cargs[#cargs+1] = R[i] end 282 | end 283 | if c == 1 then 284 | R[a](unpack(R,s,e)) 285 | elseif c == 2 then 286 | R[a] = R[a](unpack(R,s,e)) 287 | debug("RETURN VALUE",R[a]) 288 | else 289 | ret = pack(R[a](unpack(R,s,e))) 290 | debug(ret[1], ret[2], ret[3], ret.n) 291 | 292 | if c == 0 then 293 | for i=1, #ret do R[a+i-1] = ret[i] end 294 | debug("NRET",unpack(ret)) 295 | top = a+#ret-1 296 | else 297 | local g = 1 298 | for i=a, a+c-2 do R[i] = ret[g] g=g+1 end 299 | end 300 | end 301 | end 302 | elseif o == RETURN then 303 | local ret = {} 304 | local rti = 1 305 | if b == 0 then 306 | for i=a, top do ret[rti] = R[i] rti = rti+1 end 307 | else 308 | for i=a, a+b-2 do ret[rti] = R[i] rti = rti+1 end 309 | end 310 | return unpack(ret,1,rti-1) 311 | elseif o == TAILCALL then 312 | local cargs = {} 313 | local ai = 1 314 | if b == 0 then 315 | for i=a+1, top do cargs[ai] = R[i] ai = ai+1 end 316 | else 317 | for i=a+1, a+b-1 do cargs[ai] = R[i] ai = ai+1 end 318 | end 319 | return attemptCall(R[a])(unpack(cargs,1,ai-1)) 320 | elseif o == VARARG then 321 | if b > 0 then 322 | local i = 1 323 | for n=a, a+b-2 do 324 | R[n] = args[i] 325 | i = i+1 326 | end 327 | else 328 | local idx = a 329 | for i=chunk.nparam+1, #args do 330 | R[idx] = args[i] 331 | idx = idx+1 332 | end 333 | top = idx-1 334 | end 335 | elseif o == SELF then 336 | debug("SELF",R[b]) 337 | R[a+1] = R[b] 338 | R[a] = attempt(R[b], "index", "table", "string")[RK(c)] 339 | elseif o == EQ then 340 | if (RK(b) == RK(c)) == (a ~= 0) then 341 | pc = pc+getsBx(code[pc])+1 342 | else 343 | pc = pc+1 344 | end 345 | elseif o == LT then 346 | if (RK(b) < RK(c)) == (a ~= 0) then 347 | pc = pc+getsBx(code[pc])+1 348 | else 349 | pc = pc+1 350 | end 351 | elseif o == LE then 352 | if (RK(b) <= RK(c)) == (a ~= 0) then 353 | pc = pc+getsBx(code[pc])+1 354 | else 355 | pc = pc+1 356 | end 357 | elseif o == TEST then 358 | if (not R[a]) == (c ~= 0) then 359 | pc = pc+1 360 | else 361 | pc = pc+getsBx(code[pc])+1 362 | end 363 | elseif o == TESTSET then 364 | if (not R[b]) == (c ~= 0) then 365 | pc = pc+1 366 | else 367 | R[a] = R[b] 368 | pc = pc+getsBx(code[pc])+1 369 | end 370 | elseif o == FORPREP then 371 | R[a] = R[a]-R[a+2] 372 | pc = pc+b 373 | elseif o == FORLOOP then 374 | local step = R[a+2] 375 | R[a] = R[a]+step 376 | local idx = R[a] 377 | local limit = R[a+1] 378 | 379 | if (step > 0 and idx <= limit) or (step < 0 and limit <= idx) then 380 | pc = pc+b 381 | R[a+3] = R[a] 382 | end 383 | elseif o == TFORCALL then 384 | local ret = {R[a](R[a+1],R[a+2])} 385 | local i = 1 386 | for n=a+3, a+2+c do R[n] = ret[i] i=i+1 end 387 | 388 | o, a, b = decodeInstruction(code[pc]) 389 | pc = pc+1 390 | if R[a+1] ~= nil then 391 | R[a] = R[a+1] 392 | pc = pc+b 393 | end 394 | elseif o == TFORLOOP then 395 | if R[a+1] ~= nil then 396 | R[a] = R[a+1] 397 | pc = pc+b 398 | end 399 | elseif o == NEWTABLE then 400 | R[a] = {} 401 | elseif o == SETLIST then 402 | if b > 0 then 403 | for i=1, b do 404 | R[a][((c-1)*50)+i] = R[a+i] 405 | end 406 | else 407 | for i=1, top-a do 408 | R[a][((c-1)*50)+i] = R[a+i] 409 | end 410 | end 411 | elseif o == CLOSURE then 412 | local proto = chunk.functionPrototypes[b] 413 | local upvaldef = {} 414 | local upvalues = setmetatable({},{__index=function(_,i) 415 | if not upvaldef[i] then error("unknown upvalue") end 416 | local uvd = upvaldef[i] 417 | if uvd.type == 0 then -- local upvalue 418 | return R[uvd.reg] 419 | elseif uvd.type == 1 then -- upvalue upvalue 420 | return upvals[uvd.reg] 421 | else -- closed upvalue 422 | return uvd.storage 423 | end 424 | end,__newindex=function(_,i,v) 425 | if not upvaldef[i] then error("unknown upvalue") end 426 | local uvd = upvaldef[i] 427 | if uvd.type == 0 then -- local upvalue 428 | R[uvd.reg] = v 429 | elseif uvd.type == 1 then -- upvalue upvalue 430 | upvals[uvd.reg] = v 431 | else -- closed upvalue 432 | uvd.storage = v 433 | end 434 | end}) 435 | R[a] = function(...) 436 | return vm.lua52.run(proto, pack(...), upvalues, globals, hook) 437 | end 438 | for i=0, proto.upvalues.count-1 do 439 | local uv = proto.upvalues[i] 440 | debug("UPVALUE",proto.upvaluesDebug[i],i,uv.idx,uv.instack) 441 | if uv.instack > 0 then 442 | upvaldef[i] = {type=0,reg=uv.idx} 443 | openUpvalues[uv.idx] = upvaldef[i] 444 | else 445 | upvaldef[i] = {type=1,reg=uv.idx} 446 | end 447 | end 448 | elseif o == EXTRAARG then 449 | error("Not supposed to hit EXTRAARG") 450 | else 451 | error("Unknown opcode!") 452 | end 453 | end 454 | end)) 455 | -- Implicit Upvalue Close 456 | for i=0, chunk.maxStack do 457 | if openUpvalues[i] then 458 | local ouv = openUpvalues[i] 459 | ouv.type = 2 --closed 460 | ouv.storage = R[ouv.reg] 461 | openUpvalues[i] = nil 462 | end 463 | end 464 | if not ret[1] then 465 | error(tostring(ret[2]).."\n"..tostring(chunk.name).." at pc "..(pc-1).." line "..tostring(chunk.sourceLines[pc-1]),0) 466 | else 467 | return unpack(ret,2,ret.n) 468 | end 469 | end 470 | end 471 | 472 | return vm.lua52 473 | -------------------------------------------------------------------------------- /luavm/bytecode/lua51.lua: -------------------------------------------------------------------------------- 1 | return function(bytecode) 2 | local impl = {} 3 | 4 | local debug = bytecode.printDebug 5 | local bit = bytecode.bit 6 | 7 | -- instruction definitions 8 | 9 | local instructionNames = { 10 | [0]="MOVE","LOADK","LOADBOOL","LOADNIL", 11 | "GETUPVAL","GETGLOBAL","GETTABLE", 12 | "SETGLOBAL","SETUPVAL","SETTABLE","NEWTABLE", 13 | "SELF","ADD","SUB","MUL","DIV","MOD","POW","UNM","NOT","LEN","CONCAT", 14 | "JMP","EQ","LT","LE","TEST","TESTSET","CALL","TAILCALL","RETURN", 15 | "FORLOOP","FORPREP","TFORLOOP","SETLIST","CLOSE","CLOSURE","VARARG" 16 | } 17 | 18 | local iABC = 0 19 | local iABx = 1 20 | local iAsBx = 2 21 | 22 | local instructionFormats = { 23 | [0]=iABC,iABx,iABC,iABC, 24 | iABC,iABx,iABC, 25 | iABx,iABC,iABC,iABC, 26 | iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC, 27 | iAsBx,iABC,iABC,iABC,iABC,iABC,iABC,iABC,iABC, 28 | iAsBx,iAsBx,iABC,iABC,iABC,iABx,iABC 29 | } 30 | 31 | local ins = {} 32 | for i, v in pairs(instructionNames) do ins[v] = i end 33 | 34 | impl.instructionNames = instructionNames 35 | impl.instructions = ins 36 | impl.defaultReturn = 8388638 --Default return instruction, this is the extra return found at the end of instruction streams 37 | 38 | -- instruction constants 39 | 40 | local MOVE = 0 41 | local LOADK = 1 42 | local LOADBOOL = 2 43 | local LOADNIL = 3 44 | local GETUPVAL = 4 45 | local GETGLOBAL = 5 46 | local GETTABLE = 6 47 | local SETGLOBAL = 7 48 | local SETUPVAL = 8 49 | local SETTABLE = 9 50 | local NEWTABLE = 10 51 | local SELF = 11 52 | local ADD = 12 53 | local SUB = 13 54 | local MUL = 14 55 | local DIV = 15 56 | local MOD = 16 57 | local POW = 17 58 | local UNM = 18 59 | local NOT = 19 60 | local LEN = 20 61 | local CONCAT = 21 62 | local JMP = 22 63 | local EQ = 23 64 | local LT = 24 65 | local LE = 25 66 | local TEST = 26 67 | local TESTSET = 27 68 | local CALL = 28 69 | local TAILCALL = 29 70 | local RETURN = 30 71 | local FORLOOP = 31 72 | local FORPREP = 32 73 | local TFORLOOP = 33 74 | local SETLIST = 34 75 | local CLOSE = 35 76 | local CLOSURE = 36 77 | local VARARG = 37 78 | 79 | -- instruction encoding and decoding 80 | 81 | function impl.encode(inst,a,b,c) 82 | inst = type(inst) == "string" and ins[inst] or inst 83 | local format = instructionFormats[inst] 84 | return 85 | format == iABC and 86 | bit.bor(inst,bit.blshift(bit.band(a,0xFF),6),bit.blshift(bit.band(b,0x1FF),23), bit.blshift(bit.band(c,0x1FF),14)) or 87 | format == iABx and 88 | bit.bor(inst,bit.blshift(bit.band(a,0xFF),6),bit.blshift(bit.band(b,0x3FFFF),14)) or 89 | bit.bor(inst,bit.blshift(bit.band(a,0xFF),6),bit.blshift(bit.band(b+131071,0x3FFFF),14)) 90 | end 91 | 92 | function impl.decode(inst) 93 | local opcode = bit.band(inst,0x3F) 94 | local format = instructionFormats[opcode] 95 | if format == iABC then 96 | return opcode, bit.band(bit.brshift(inst,6),0xFF), bit.band(bit.brshift(inst,23),0x1FF), bit.band(bit.brshift(inst,14),0x1FF) 97 | elseif format == iABx then 98 | return opcode, bit.band(bit.brshift(inst,6),0xFF), bit.band(bit.brshift(inst,14),0x3FFFF) 99 | elseif format == iAsBx then 100 | local sBx = bit.band(bit.brshift(inst,14),0x3FFFF)-131071 101 | return opcode, bit.band(bit.brshift(inst,6),0xFF), sBx 102 | else 103 | error(opcode.." "..format) 104 | end 105 | end 106 | 107 | -- bytecode patching extras 108 | 109 | impl.patcher = {} 110 | 111 | local function patchJumpsAndAdd(bc, pc, op) 112 | for i=0, #bc.instructions do 113 | local o,a,b,c = impl.decode(bc.instructions[i]) 114 | if o == LOADBOOL then 115 | if c ~= 0 then 116 | if i+1 == pc then 117 | error("TODO: Patch LOADBOOL") 118 | end 119 | end 120 | elseif o == JMP then 121 | if (i < pc and i+b+1 > pc) then 122 | b = b+1 --since this gets shifted forward... 123 | elseif (i > pc and i+b+1 <= pc) then 124 | b = b-1 --since this gets shifted backward... 125 | end 126 | elseif o == TEST then 127 | if i+1 == pc then 128 | error("TODO: Patch TEST") 129 | end 130 | elseif o == TESTSET then 131 | if i+1 == pc then 132 | error("TODO: Patch TESTSET") 133 | end 134 | elseif o == FORLOOP then 135 | if (i < pc and i+b+1 > pc) then 136 | b = b+1 --since this gets shifted forward... 137 | elseif (i > pc and i+b+1 <= pc) then 138 | b = b-1 --since this gets shifted backward... 139 | end 140 | elseif o == FORPREP then 141 | if (i < pc and i+b+1 > pc) then 142 | b = b+1 --since this gets shifted forward... 143 | elseif (i > pc and i+b+1 <= pc) then 144 | b = b-1 --since this gets shifted backward... 145 | end 146 | elseif o == TFORPREP then 147 | if i+1 == pc then 148 | error("TODO: Patch TFORPREP") 149 | end 150 | end 151 | bc.instructions[i] = impl.encode(o,a,b,c) 152 | print(i,bc.instructions[i]) 153 | end 154 | 155 | for i=#bc.instructions, pc, -1 do 156 | bc.instructions[i+1] = bc.instructions[i] 157 | bc.sourceLines[i+1] = bc.sourceLines[i] 158 | end 159 | bc.instructions[pc] = op 160 | end 161 | 162 | -- Insert a single instruction at a specific program counter index 163 | function impl.patcher.insert(bc, pc, inst) 164 | --insert commands, fix jump targets-- 165 | patchJumpsAndAdd(bc, pc, inst) 166 | end 167 | 168 | -- Replaces an instruction at a program counter index with another instruction 169 | function impl.patcher.replace(bc, pc, inst) 170 | bc.instructions[pc] = inst 171 | end 172 | 173 | -- Attempts to find an instruction after a specific program counter index 174 | function impl.patcher.find(bc, pc, o) 175 | if type(o) == "string" then 176 | o = ins[o] 177 | end 178 | 179 | for i=pc+1, #bc.instructions do 180 | local no = impl.decode(bc.instructions[i]) 181 | if no == o then 182 | return i 183 | end 184 | end 185 | end 186 | 187 | function impl.patcher.addConstant(bc, const) 188 | -- If the constant already exists, just return the id of that constant 189 | for i, v in pairs(bc.constants) do 190 | if v == const then 191 | return i 192 | end 193 | end 194 | 195 | bc.constants[#bc.constants+1] = const 196 | return #bc.constants 197 | end 198 | 199 | -- bytecode loading 200 | 201 | function impl.loadHeader(bc) 202 | local header = {version = 0x51} 203 | 204 | local fmtver = bc:byte(6) 205 | header.fmtver = fmtver 206 | debug("Format Version: %02X", fmtver) 207 | 208 | local types = bc:sub(7, 12) 209 | debug("Types: "..types:gsub(".", function(c) return string.format("%02X ", c:byte()) end)) 210 | 211 | local bigEndian = types:byte(1) ~= 1 212 | header.bigEndian = bigEndian 213 | debug("Big Endian: %s", tostring(bigEndian)) 214 | 215 | local integer = types:byte(2) 216 | header.integer = integer 217 | debug("Integer Size: %d bytes", integer) 218 | 219 | local size_t = types:byte(3) 220 | header.size_t = size_t 221 | debug("Size_T Size: %d bytes", size_t) 222 | 223 | local instruction = types:byte(4) 224 | header.instruction = instruction 225 | debug("Instruction Size: %d bytes", instruction) 226 | 227 | --integral or numerical number stuff 228 | do 229 | local integralNumbers = types:byte(6) ~= 0 230 | local size = types:byte(5) 231 | header.number_integral = integralNumbers 232 | header.number = size 233 | debug("Numerical Format: %d bytes <%s>", size, integralNumbers and "integral" or "floating") 234 | end 235 | 236 | return header 237 | end 238 | 239 | function impl.load(bc) 240 | debug("Lua 5.1 Bytecode Loader") 241 | 242 | local idx = 13 243 | local integer, size_t, number 244 | local bigEndian 245 | local binarytypes = bytecode.binarytypes 246 | 247 | local function u1() 248 | idx = idx+1 249 | return binarytypes.decode.u1(bc, idx-1, bigEndian) 250 | end 251 | 252 | local function u2() 253 | idx = idx+2 254 | return binarytypes.decode.u2(bc, idx-2, bigEndian) 255 | end 256 | 257 | local function u4() 258 | idx = idx+4 259 | return binarytypes.decode.u4(bc, idx-4, bigEndian) 260 | end 261 | 262 | local function u8() 263 | idx = idx+8 264 | return binarytypes.decode.u8(bc, idx-8, bigEndian) 265 | end 266 | 267 | local function float() 268 | idx = idx+4 269 | return binarytypes.decode.float(bc, idx-4, bigEndian) 270 | end 271 | 272 | local function double() 273 | idx = idx+8 274 | return binarytypes.decode.double(bc, idx-8, bigEndian) 275 | end 276 | 277 | local function ub(n) 278 | idx = idx+n 279 | return bc:sub(idx-n,idx-1) 280 | end 281 | 282 | local function us() 283 | local size = size_t() 284 | --print(size) 285 | return ub(size):sub(1,-2) 286 | end 287 | 288 | local integralSizes = { 289 | [1] = u1, 290 | [2] = u2, 291 | [4] = u4, 292 | [8] = u8 293 | } 294 | 295 | local numericSizes = { 296 | [4] = float, 297 | [8] = double 298 | } 299 | 300 | local header = impl.loadHeader(bc) 301 | 302 | assert(header.fmtver == 0 or header.fmtver == 255, "unknown format version: "..header.fmtver) 303 | bigEndian = header.bigEndian 304 | integer = assert(integralSizes[header.integer], "unsupported integer size: "..header.integer) 305 | size_t = assert(integralSizes[header.size_t], "unsupported size_t size: "..header.size_t) 306 | assert(header.instruction == 4, "unsupported instruction size: "..header.instruction) 307 | 308 | --integral or numerical number stuff 309 | do 310 | local integralNumbers = header.number_integral 311 | local size = header.number 312 | number = assert(integralNumbers and integralSizes[size] or numericSizes[size], "unsupported number size: "..(integralNumbers and "integral" or "floating").." "..size) 313 | end 314 | 315 | local function chunk() 316 | local function instructionList() 317 | local instructions = {} 318 | local count = integer() 319 | for i=1, count do 320 | instructions[i-1] = u4() 321 | end 322 | instructions.count = count 323 | return instructions 324 | end 325 | 326 | local function constantList() 327 | local constants = {} 328 | local count = integer() 329 | for i=1, count do 330 | local type = u1() 331 | if type == 0 then 332 | constants[i-1] = nil 333 | elseif type == 1 then 334 | constants[i-1] = u1() > 0 335 | elseif type == 3 then 336 | constants[i-1] = number() 337 | elseif type == 4 then 338 | constants[i-1] = us() 339 | else 340 | error("Type: "..type) 341 | end 342 | debug("Constant %d: %s %s", i-1, tostring(constants[i-1]), type) 343 | end 344 | constants.count = count 345 | return constants 346 | end 347 | 348 | local function functionPrototypeList() 349 | local functionPrototypes = {} 350 | local count = integer() 351 | for i=1, count do 352 | functionPrototypes[i-1] = chunk() 353 | end 354 | functionPrototypes.count = count 355 | return functionPrototypes 356 | end 357 | 358 | local function sourceLineList() 359 | local sourceLines = {} 360 | local count = integer() 361 | for i=1, count do 362 | sourceLines[i-1] = integer() 363 | end 364 | sourceLines.count = count 365 | return sourceLines 366 | end 367 | 368 | local function localList() 369 | local locals = {} 370 | local count = integer() 371 | for i=1, count do 372 | locals[i-1] = { 373 | name = us(), 374 | startpc = integer(), 375 | endpc = integer() 376 | } 377 | end 378 | locals.count = count 379 | return locals 380 | end 381 | 382 | local function upvalueList() 383 | local upvalues = {} 384 | local count = integer() 385 | for i=1, count do 386 | upvalues[i-1] = us() 387 | end 388 | upvalues.count = count 389 | return upvalues 390 | end 391 | 392 | --extract an lua chunk into a table-- 393 | local c = {header = header} 394 | c.name = us() 395 | c.lineDefined = integer() 396 | c.lastLineDefined = integer() 397 | c.nupval = u1() 398 | c.nparam = u1() 399 | c.isvararg = u1() 400 | c.maxStack = u1() 401 | c.instructions = instructionList() 402 | c.constants = constantList() 403 | c.functionPrototypes = functionPrototypeList() 404 | c.sourceLines = sourceLineList() 405 | c.locals = localList() 406 | c.upvalues = upvalueList() 407 | return c 408 | end 409 | 410 | return chunk() 411 | end 412 | 413 | function impl.save(chunk) 414 | local header = chunk.header 415 | local bc = {"\27Lua", string.char(header.version, 0)} 416 | 417 | bc[#bc+1] = string.char( 418 | header.bigEndian and 0 or 1, 419 | header.integer, 420 | header.size_t, 421 | header.instruction, 422 | header.number, 423 | header.number_integral and 1 or 0 424 | ) 425 | 426 | local integer, size_t, number 427 | local bigEndian = header.bigEndian 428 | local binarytypes = bytecode.binarytypes 429 | 430 | local function u1(value) 431 | bc[#bc+1] = string.char(value) 432 | end 433 | 434 | local function u2(value) 435 | bc[#bc+1] = binarytypes.encode.u2(value, bigEndian) 436 | end 437 | 438 | local function u4(value) 439 | bc[#bc+1] = binarytypes.encode.u4(value, bigEndian) 440 | end 441 | 442 | local function u8(value) 443 | bc[#bc+1] = binarytypes.encode.u8(value, bigEndian) 444 | end 445 | 446 | local function float(value) 447 | bc[#bc+1] = binarytypes.encode.float(value, bigEndian) 448 | end 449 | 450 | local function double(value) 451 | bc[#bc+1] = binarytypes.encode.double(value, bigEndian) 452 | end 453 | 454 | local function us(str) 455 | size_t(#str+1) 456 | bc[#bc+1] = str 457 | bc[#bc+1] = string.char(0) 458 | end 459 | 460 | local function len(t) 461 | local n = 0 462 | for i, v in pairs(t) do n = n+1 end 463 | return n 464 | end 465 | 466 | local integralSizes = { 467 | [1] = u1, 468 | [2] = u2, 469 | [4] = u4, 470 | [8] = u8 471 | } 472 | 473 | local numericSizes = { 474 | [4] = float, 475 | [8] = double 476 | } 477 | 478 | assert(header.fmtver == 0 or header.fmtver == 255, "unknown format version: "..header.fmtver) 479 | bigEndian = header.bigEndian 480 | integer = assert(integralSizes[header.integer], "unsupported integer size: "..header.integer) 481 | size_t = assert(integralSizes[header.size_t], "unsupported size_t size: "..header.size_t) 482 | assert(header.instruction == 4, "unsupported instruction size: "..header.instruction) 483 | 484 | --integral or numerical number stuff 485 | do 486 | local integralNumbers = header.number_integral 487 | local size = header.number 488 | number = assert(integralNumbers and integralSizes[size] or numericSizes[size], "unsupported number size: "..(integralNumbers and "integral" or "floating").." "..size) 489 | end 490 | 491 | local function dumpChunk(chunk) 492 | us(chunk.name) 493 | integer(chunk.lineDefined) 494 | integer(chunk.lastLineDefined) 495 | u1(chunk.nupval) 496 | u1(chunk.nparam) 497 | u1(chunk.isvararg) 498 | u1(chunk.maxStack) 499 | 500 | local lenInstructions = chunk.instructions.count or len(chunk.instructions) 501 | integer(lenInstructions) 502 | for i=0, lenInstructions-1 do 503 | u4(chunk.instructions[i]) 504 | end 505 | 506 | local lenConstants = chunk.constants.count or len(chunk.constants) 507 | integer(lenConstants) 508 | for i=0, lenConstants-1 do 509 | local v = chunk.constants[i] 510 | local t = type(v) 511 | u1(t == "nil" and 0 or t == "boolean" and 1 or t == "number" and 3 or t == "string" and 4 or error("Unknown constant type.")) 512 | if t == "boolean" then 513 | u1(v and 1 or 0) 514 | elseif t == "number" then 515 | number(v) 516 | elseif t == "string" then 517 | us(v) 518 | end 519 | end 520 | 521 | local lenFunctionPrototypes = chunk.functionPrototypes.count or len(chunk.functionPrototypes) 522 | integer(lenFunctionPrototypes) 523 | for i=0, lenFunctionPrototypes-1 do 524 | dumpChunk(chunk.functionPrototypes[i]) 525 | end 526 | 527 | local lenSourceLines = chunk.sourceLines.count or len(chunk.sourceLines) 528 | integer(lenSourceLines) 529 | for i=0, lenSourceLines-1 do 530 | integer(chunk.sourceLines[i]) 531 | end 532 | 533 | local lenLocals = chunk.locals.count or len(chunk.locals) 534 | integer(lenLocals) 535 | for i=0, lenLocals-1 do 536 | local l = chunk.locals[i] 537 | us(l.name) 538 | integer(l.startpc) 539 | integer(l.endpc) 540 | end 541 | 542 | local lenUpvalues = chunk.upvalues.count or len(chunk.upvalues) 543 | integer(lenUpvalues) 544 | for i=0, lenUpvalues-1 do 545 | us(chunk.upvalues[i]) 546 | end 547 | end 548 | 549 | dumpChunk(chunk) 550 | return table.concat(bc) 551 | end 552 | 553 | function impl.new(header) 554 | return { 555 | header = header, 556 | lineDefined = 0, 557 | isvararg = 2, 558 | sourceLines = {count=0}, 559 | nparam = 0, 560 | lastLineDefined = 0, 561 | maxStack = 2, 562 | upvalues = {count=0}, 563 | instructions = {count=1, [0]=impl.defaultReturn}, 564 | locals = {count=0}, 565 | functionPrototypes = {count=0}, 566 | nupval = 0, 567 | name = "", 568 | constants = {count=0} 569 | } 570 | end 571 | 572 | return impl 573 | end 574 | -------------------------------------------------------------------------------- /luavm/decompiler/decoder51.lua: -------------------------------------------------------------------------------- 1 | -- Decodes Lua 5.1 Bytecode into the Immediate Representation -- 2 | 3 | local bytecode = require "luavm.bytecode" 4 | local version = bytecode.version.lua51 5 | 6 | -- instruction constants copied from luavm.bytecode.lua51 7 | 8 | local MOVE = 0 9 | local LOADK = 1 10 | local LOADBOOL = 2 11 | local LOADNIL = 3 12 | local GETUPVAL = 4 13 | local GETGLOBAL = 5 14 | local GETTABLE = 6 15 | local SETGLOBAL = 7 16 | local SETUPVAL = 8 17 | local SETTABLE = 9 18 | local NEWTABLE = 10 19 | local SELF = 11 20 | local ADD = 12 21 | local SUB = 13 22 | local MUL = 14 23 | local DIV = 15 24 | local MOD = 16 25 | local POW = 17 26 | local UNM = 18 27 | local NOT = 19 28 | local LEN = 20 29 | local CONCAT = 21 30 | local JMP = 22 31 | local EQ = 23 32 | local LT = 24 33 | local LE = 25 34 | local TEST = 26 35 | local TESTSET = 27 36 | local CALL = 28 37 | local TAILCALL = 29 38 | local RETURN = 30 39 | local FORLOOP = 31 40 | local FORPREP = 32 41 | local TFORLOOP = 33 42 | local SETLIST = 34 43 | local CLOSE = 35 44 | local CLOSURE = 36 45 | local VARARG = 37 46 | 47 | -- These functions define common explets 48 | 49 | local function R(c) 50 | return {"register", c} 51 | end 52 | 53 | local function K(c) 54 | return {"constant", c} 55 | end 56 | 57 | local function RK(c) 58 | if c > 255 then 59 | return K(c-256) 60 | else 61 | return R(c) 62 | end 63 | end 64 | 65 | local function V(v) 66 | return {"value", v} 67 | end 68 | 69 | local function U(p) 70 | return {"upvalue", p} 71 | end 72 | 73 | local function G(i) 74 | return {"global", i} 75 | end 76 | 77 | local function I(t, i) 78 | return {"index", t, i} 79 | end 80 | 81 | local function BIN(a, op, b) 82 | return {"binaryop", a, op, b} 83 | end 84 | 85 | local function UN(op, a) 86 | return {"unaryop", op, a} 87 | end 88 | 89 | -- Operator data 90 | 91 | local binaryOps = { 92 | [ADD] = "+", 93 | [SUB] = "-", 94 | [MUL] = "*", 95 | [DIV] = "/", 96 | [MOD] = "%", 97 | [POW] = "^", 98 | } 99 | 100 | local unaryOps = { 101 | [UNM] = "-", 102 | [NOT] = "not", 103 | [LEN] = "#", 104 | } 105 | 106 | local conditionalOps = { 107 | [EQ] = "==", 108 | [LT] = "<", 109 | [LE] = "<=", 110 | [TEST] = true, -- special handling 111 | } 112 | 113 | return function(decoder) 114 | local target = {} 115 | 116 | -- The decoder is organized into 3 parts: 117 | -- Block decoder 118 | -- Expression decoder 119 | -- Conditional expression decoder 120 | 121 | local function decodeExpression(chunk, i, context, collect) 122 | local op, a, b, c = version.decode(chunk.instructions[i]) 123 | 124 | if op == MOVE then 125 | collect {op = "set", src = {R(b)}, dest = {R(a)}, pc = i} 126 | elseif op == LOADK then 127 | collect {op = "set", src = {K(b)}, dest = {R(a)}, pc = i} 128 | elseif op == LOADBOOL and c == 0 then 129 | collect {op = "set", src = {V(b ~= 0)}, dest = {R(a)}, pc = i} 130 | elseif op == LOADNIL then 131 | for i=a, b do 132 | collect {op = "set", src = {V(nil)}, dest = {R(i)}, pc = i} 133 | end 134 | elseif op == GETUPVAL then 135 | collect {op = "set", src = {U(b)}, dest = {R(a)}, pc = i} 136 | elseif op == GETGLOBAL then 137 | collect {op = "set", src = {G(b)}, dest = {R(a)}, pc = i} 138 | elseif op == GETTABLE then 139 | collect {op = "set", src = {I(R(b),RK(c))}, dest = {R(a)}, pc = i} 140 | elseif op == SETGLOBAL then 141 | collect {op = "set", src = {R(a)}, dest = {G(b)}, pc = i} 142 | elseif op == SETUPVAL then 143 | collect {op = "set", src = {R(a)}, dest = {U(b)}, pc = i} 144 | elseif op == SETTABLE then 145 | collect {op = "set", src = {RK(c)}, dest = {I(R(a), RK(b))}, pc = i} 146 | elseif op == NEWTABLE then 147 | collect {op = "set", src = {V{}}, dest = {R(a)}, pc = i} 148 | elseif op == SELF then 149 | --error "TODO: Do extra processing here to deduce to a function call" 150 | 151 | -- To find the ending CALL for the self expression we iterate through until we see a CALL 152 | -- that shares our same dest register as a source 153 | --if i == 54 then error '!' end 154 | local callidx 155 | for j=i, chunk.instructions.count-1 do 156 | local cop, ca, cb, cc = version.decode(chunk.instructions[j]) 157 | if (cop == CALL or cop == TAILCALL) and ca == a then 158 | callidx = j 159 | break 160 | end 161 | end 162 | 163 | if not callidx then 164 | error 'TODO: Support handwritten bytecode exploiting SELF' 165 | end 166 | 167 | -- TODO: Some way to tag the call... 168 | collect {op = "set", src = {R(b), I(R(b), RK(c))}, dest = {R(a+1), R(a)}, pc = i} 169 | 170 | -- self needs to be it's own psuedo-op 171 | -- for simplicity's sake 172 | --collect {op = "self", src = {R(b), RK(c)}, dest = {R(a), R(a+1)}, pc = i} 173 | elseif binaryOps[op] then 174 | collect {op = "set", src = {BIN(RK(b), binaryOps[op], RK(c))}, dest = {R(a)}, pc = i} 175 | elseif unaryOps[op] then 176 | collect {op = "set", src = {UN(unaryOps[op], R(b))}, dest = {R(a)}, pc = i} 177 | elseif op == CONCAT then 178 | local src = {"concat"} 179 | 180 | for i=b, c do 181 | src[#src+1] = R(i) 182 | end 183 | 184 | collect {op = "set", src = {src}, dest = {R(a)}, pc = i} 185 | elseif op == CALL then 186 | local args = {} 187 | local dests = {} 188 | 189 | -- TODO: TOP support! 190 | 191 | for i=a, a+c-2 do 192 | dests[#dests+1] = R(i) 193 | end 194 | 195 | for i=a+1, a+b-1 do 196 | args[#args+1] = R(i) 197 | end 198 | 199 | collect {op = "set", src = {{"call", R(a), args}}, dest = dests, pc = i} 200 | elseif op == CLOSURE then 201 | print('start proto '..b) 202 | local block = {} 203 | target.decode(chunk.functionPrototypes[b], nil, nil, context, function(v) 204 | block[#block+1] = v 205 | end) 206 | collect {op = "set", src = {{"closure", block}}, dest = {R(a)}, pc = i} 207 | else 208 | return false 209 | end 210 | 211 | return true, i+1 212 | end 213 | 214 | -- TODO: Figure out exactly what the hell this function is doing and make examples! 215 | -- TODO: Fix all of this 216 | --[[ 217 | Conditional jumps have 2 ways they can go. 218 | They can jump inside of the block or outside of the block. 219 | They ONLY have 2 targets. 220 | The inside block jump ALWAYS has a smaller PC than the outside 221 | block jump. 222 | When two conditionals are jumping OUT (1 after another) it forms 223 | an `and` statement. 224 | When the first conditional jumps IN and the second conditional 225 | jumps OUT it forms an `or` statement. 226 | 227 | Conditionals ALWAYS fall in at the bottom. The bottom of the 228 | conditional block IS ALWAYS THE JUMP IN POINT. 229 | 230 | [OUT, OUT] == and statement 231 | [IN, OUT] == or statement 232 | [OUT, IN] == ill formed 233 | [IN, IN] == ill formed 234 | 235 | Example and statement: 236 | 237 | if x == 5 and y == 6 then 238 | 239 | end 240 | 241 | 242 | 0| EQ r0, 5 [JUMPS OUT ON FAIL] 243 | 1| JMP +3 244 | 2| EQ r1, 6 [JUMPS OUT ON FAIL] 245 | 3| JMP +1 246 | 4| 247 | 5| 248 | 249 | Example or statement: 250 | 251 | if x == 5 or y == 6 then 252 | 253 | end 254 | 255 | 256 | 0| NEQ r0, 5 [JUMPS IN ON "FAIL"] [NOTICE THE CONDITION IS INVERT] 257 | 1| JMP +2 258 | 2| EQ r1, 6 [JUMPS OUT ON FAIL] 259 | 3| JMP +1 260 | 4| 261 | 5| 262 | 263 | How do expressions play in this: 264 | 265 | Lua will generate expressions inside of our conditional block. 266 | To work with that, while we aren't a conditional op, we parse 267 | expressions but we do manual collection and inlining. 268 | ]] 269 | local function decodeConditionalJumps(chunk, i) 270 | local logic = {} 271 | 272 | -- First, get the jump bounds (AKA places where conditional jumps can go) 273 | local ci = i 274 | while true do 275 | local op, a, b, c = version.decode(chunk.instructions[ci]) 276 | if op == EQ or op == LT or op == LE or op == TEST then 277 | local invert = op == TEST and (c ~= 0) or (a ~= 0) 278 | local nextop = version.decode(chunk.instructions[ci+1]) 279 | local l = {} 280 | logic[ci] = l 281 | 282 | -- I have no idea how this code works anymore 283 | -- But it does 284 | if nextop ~= EQ and nextop ~= LT and nextop ~= LE and nextop ~= TEST then 285 | if invert then 286 | l.exitOnTrue = true 287 | else 288 | l.exitOnFalse = true 289 | end 290 | ci = ci+1 291 | -- Visit the very next, resolve as true 292 | nextop = version.decode(chunk.instructions[ci+1]) 293 | if nextop ~= EQ and nextop ~= LT and nextop ~= LE and nextop ~= TEST then 294 | if not invert then 295 | l.exitOnTrue = true 296 | else 297 | l.exitOnFalse = true 298 | end 299 | break 300 | end 301 | end 302 | ci = ci+1 303 | else 304 | break 305 | end 306 | end 307 | 308 | en = ci 309 | 310 | --[[ 311 | Example: 312 | 313 | NEQ R0 6 (false continues, true exits) 314 | JMP 2 315 | EQ R0 8 (false exits, true exits) 316 | JMP 4 317 | 318 | So, an Lua or statement always executes the second one if the first one fails 319 | So if it is "false continues" then it's an or statement 320 | Else if it's "true continues" then it's an and statement 321 | 322 | ]] 323 | 324 | local root = {} 325 | local exp = root 326 | ci = i 327 | while ci <= en do -- Now, try to resolve boolean logic 328 | local op, a, b, c = version.decode(chunk.instructions[ci]) 329 | if op == EQ or op == LT or op == LE or op == TEST then 330 | local l = logic[ci] 331 | 332 | local opdest, shouldBreak = nil, false 333 | if (not l.exitOnFalse) and l.exitOnTrue then 334 | opdest = {} 335 | exp[1] = "binaryop" 336 | exp[2] = opdest 337 | exp[3] = "or" 338 | exp[4] = {} 339 | exp = exp[4] 340 | elseif (not l.exitOnTrue) and l.exitOnFalse then 341 | opdest = {} 342 | exp[1] = "binaryop" 343 | exp[2] = opdest 344 | exp[3] = "and" 345 | exp[4] = {} 346 | exp = exp[4] 347 | else 348 | opdest = exp 349 | shouldBreak = true 350 | end 351 | 352 | print(l.exitOnTrue, l.exitOnFalse) 353 | 354 | local invert = op == TEST and (c ~= 0) or (a ~= 0) 355 | 356 | if invert then 357 | opdest[1] = "unaryop" 358 | opdest[2] = "not" 359 | opdest[3] = {} 360 | opdest = opdest[3] 361 | end 362 | 363 | if op == TEST then 364 | opdest[1] = "register" 365 | opdest[2] = a 366 | else 367 | opdest[1] = "binaryop" 368 | opdest[2] = RK(b) 369 | opdest[3] = conditionalOps[op] 370 | opdest[4] = RK(c) 371 | end 372 | 373 | if shouldBreak then 374 | break 375 | end 376 | end 377 | ci = ci+1 378 | end 379 | 380 | return root, en 381 | end 382 | 383 | -- "Block" instructions 384 | local function decodeInstruction(chunk, i, context, collect) 385 | local op, a, b, c = version.decode(chunk.instructions[i]) 386 | 387 | --print(version.instructionNames[op], a, b, c) 388 | 389 | if op == RETURN then 390 | local srcs = {} 391 | 392 | if b == 0 then 393 | srcs[1] = {"top", a} -- TODO: Remember that top needs to be forced inlined! 394 | else 395 | for i=a, a+b-2 do 396 | srcs[#srcs+1] = R(i) 397 | end 398 | end 399 | 400 | collect {op = "return", src = srcs, pc = i} 401 | elseif op == JMP then 402 | local dest = i+b+1 -- Lua does pre increment 403 | local jop, ja, jb, jc = version.decode(chunk.instructions[dest]) 404 | 405 | if jop == TFORLOOP then 406 | -- generic for loop 407 | local gfor = { 408 | op = "gfor", 409 | src={R(ja), R(ja+1), R(ja+2)}, 410 | dest = {}, 411 | loop = {entry = i, exit = dest}, 412 | block = {}, 413 | pc = i, 414 | } 415 | 416 | for i=ja+3,ja+2+jc do 417 | gfor.dest[#gfor.dest+1] = R(i) 418 | end 419 | 420 | target.decode(chunk, i+1, dest-1, gfor, function(v) 421 | gfor.block[#gfor.block+1] = v 422 | end) 423 | 424 | collect(gfor) 425 | i = dest+1 -- skip jump 426 | elseif jb == -1 then -- tight loop with no statements (hang) 427 | collect { 428 | op = "while", 429 | src = {V(true)}, 430 | block = {}, 431 | pc = i 432 | } 433 | elseif context and context.loop and dest == context.loop.exit then 434 | collect {op = "break", pc = i} 435 | else 436 | --return false, "invalid jump" 437 | error("Unhandled jump!") 438 | end 439 | elseif op == FORPREP then 440 | -- for loop 441 | local base, dest = a, i+b+1 442 | 443 | local forloop = { 444 | op = "for", 445 | src = {R(base), R(base+1), R(base+2)}, 446 | dest = {R(base+3)}, 447 | loop = {entry = i, exit = dest}, 448 | block = {}, 449 | pc = i 450 | } 451 | 452 | target.decode(chunk, i+1, dest-1, forloop, function(v) 453 | forloop.block[#forloop.block+1] = v 454 | end) 455 | 456 | collect(forloop) 457 | i = dest 458 | elseif conditionalOps[op] then 459 | local condI = i 460 | local cond, ni = decodeConditionalJumps(chunk, i) 461 | i = ni 462 | 463 | op, a, b, c = version.decode(chunk.instructions[i]) 464 | 465 | print("hit conditional at "..condI) 466 | print("target "..i) 467 | 468 | if op == JMP then 469 | -- If the very next statement after a conditional expression is a jump, it is an if statement or a while loop 470 | 471 | -- For IF Statements: 472 | -- This jump goes to the else part of the if statement 473 | -- If the op before the else part of the if statement is a jump that goes past the else statement, an else 474 | -- statement is present 475 | --[[ 476 | Example: 477 | if a < 0 then 478 | a = -a 479 | end 480 | 481 | Possible output (assuming `a` is a local variable): 482 | LT r0, 256 (0 [constant 0]) 483 | JMP 1 [skip next] <-- this is jumping out of the if statement (or to the else part, if any) 484 | UNM r0, r0 485 | 486 | ~~~~~~~ 487 | 488 | Example: 489 | 490 | if a < 0 then 491 | a = a*a 492 | else 493 | a = a/a 494 | end 495 | 496 | Possible output (assuming `a` is a local variable): 497 | LT r0, k0 [0] 498 | JMP 2 [skip next 2] <-- Jump to the start of the else statement 499 | MUL r0, r0, r0 500 | JMP 1 [skip next] <-- Jump to the end of the else statement 501 | DIV r0, r0, r0 502 | ]] 503 | 504 | -- For WHILE Statements: 505 | -- The jump goes to the instruction after the backwards jump that targets the instruction after the current jump 506 | --[[ 507 | Example: 508 | 509 | local a = 100 510 | while a < 0 do 511 | a = a-1 512 | end 513 | 514 | Possible output: 515 | LOADK r0, k0 [100] 516 | LT r0, k1 [0] 517 | JMP 2 <-- Jump after the backwards jump 518 | SUB r0, r0, k2 [1] 519 | JMP -4 <-- Jump to the condition check (LT) 520 | ]] 521 | 522 | -- An IF and WHILE statement are different because of the backwards jump 523 | 524 | local dest = i+b 525 | 526 | -- If the jump continues the current loop from the top, this is an if statement at the end of a loop block 527 | if context and context.loop and context.loop.entry == dest+1 then 528 | local ifstat = { 529 | op = "if", 530 | src = {cond}, 531 | loop = context.loop, 532 | block = {}, 533 | pc = i, 534 | assertLast = true -- This NEEDS to be the last one in a block 535 | } 536 | 537 | target.decode(chunk, i+1, i+1, ifstat, function(v) 538 | ifstat.block[#ifstat.block+1] = v 539 | end) 540 | 541 | i = i+1 542 | 543 | collect(ifstat) 544 | else 545 | local destOp, _, destB = version.decode(chunk.instructions[dest]) 546 | 547 | if destOp == JMP and destB < -1 then -- -1 jumps back onto the instruction itself 548 | local whileloop = { 549 | op = "while", 550 | src = {cond}, 551 | loop = {entry = condI, exit = dest+1}, 552 | block = {}, 553 | pc = i 554 | } 555 | 556 | print("generating while ["..condI.." -> "..(dest+1).."]") 557 | 558 | target.decode(chunk, i+1, dest-1, whileloop, function(v) -- don't hit that end jump 559 | whileloop.block[#whileloop.block+1] = v 560 | end) 561 | 562 | i = dest 563 | collect(whileloop) 564 | else 565 | local ifstat = { 566 | op = "if", 567 | src = {cond}, 568 | loop = context and context.loop or nil, 569 | block = {}, 570 | pc = i 571 | } 572 | 573 | local decodeElse = false 574 | local decodeDest = dest 575 | if destOp == JMP and destB > 0 then 576 | -- TODO: Decide if this is a break or a jump 577 | decodeDest = dest-1 578 | decodeElse = true 579 | end 580 | 581 | target.decode(chunk, i+1, decodeDest, ifstat, function(v) 582 | ifstat.block[#ifstat.block+1] = v 583 | end) 584 | 585 | if decodeElse then 586 | local elsestat = { 587 | op = "else", 588 | loop = context and context.loop or nil, 589 | block = {}, 590 | pc = dest 591 | } 592 | 593 | local elseDest = dest+destB 594 | target.decode(chunk, dest+1, elseDest, elsestat, function(v) 595 | elsestat.block[#elsestat.block+1] = v 596 | end) 597 | 598 | ifstat.block[#ifstat.block+1] = elsestat 599 | i = elseDest 600 | else 601 | i = dest 602 | end 603 | 604 | collect(ifstat) 605 | end 606 | end 607 | else 608 | error("Invalid conditional operation magic!") 609 | end 610 | else 611 | return decodeExpression(chunk, i, context, collect) 612 | end 613 | 614 | return true, i+1 615 | end 616 | 617 | function target.decode(chunk, i, j, context, collect) 618 | i = i or 0 619 | j = j or chunk.instructions.count-2 -- Skip default return 620 | while i <= j do 621 | local s, ni = decodeInstruction(chunk, i, context, collect) 622 | if not s then return false, ni end 623 | i = ni 624 | end 625 | return true, i 626 | end 627 | 628 | return target 629 | end 630 | -------------------------------------------------------------------------------- /luavm/bytecode/lua53.lua: -------------------------------------------------------------------------------- 1 | return function(bytecode) 2 | local impl = {} 3 | 4 | local debug = bytecode.printDebug 5 | 6 | --of all the Lua versions, 5.3 has to set up the most. 7 | bytecode.bit = { 8 | bor = function(a, b) return a|b end, 9 | band = function(a, b) return a&b end, 10 | bnot = function(a) return ~a end, 11 | blshift = function(a, b) return a << b end, 12 | brshift = function(a, b) return a >> b end, 13 | } 14 | 15 | bytecode.binarytypes = { 16 | encode = { 17 | u1 = function(value, bigEndian) 18 | return string.char(value) 19 | end, 20 | u2 = function(value, bigEndian) 21 | return string.pack(bigEndian and ">I2" or "I4" or "I8" or "f" or "d" or "I2" or "I4" or "I8" or "f" or "d" or "> 6)&0xFF, (inst >> 23)&0x1FF, (inst >> 14)&0x1FF 169 | elseif format == iABx then 170 | return opcode, (inst >> 6)&0xFF, (inst >> 14)&0x3FFFF 171 | elseif format == iAsBx then 172 | local sBx = ((inst >> 14)&0x3FFFF)-131071 173 | return opcode, (inst >> 6)&0xFF, sBx 174 | elseif format == iAx then 175 | return opcode, inst >> 6 176 | else 177 | error("unknown opcode "..opcode) 178 | end 179 | end 180 | 181 | function impl.new(header) 182 | return { 183 | header = header, 184 | name = "", 185 | lineDefined = 0, 186 | lastLineDefined = 0, 187 | nparam = 0, 188 | isvararg = 2, 189 | maxStack = 2, 190 | instructions = {count=1, [0]=impl.defaultReturn}, 191 | constants = {count=0}, 192 | upvalues = {count=0}, 193 | functionPrototypes = {count=0}, 194 | sourceLines = {count=0}, 195 | locals = {count=0}, 196 | upvaluesDebug = {count=0} 197 | } 198 | end 199 | 200 | -- bytecode loading 201 | 202 | function impl.loadHeader(bc) 203 | local header = {version = 0x53} 204 | 205 | local fmtver = bc:byte(6) 206 | header.fmtver = fmtver 207 | debug("Format Version: %02X", fmtver) 208 | 209 | local types = bc:sub(13, 17) 210 | debug("Types: "..types:gsub(".", function(c) return string.format("%02X ", c:byte()) end)) 211 | 212 | local bigEndian = bc:byte(18) == 0 213 | header.bigEndian = bigEndian 214 | debug("Big Endian: %s", tostring(bigEndian)) 215 | 216 | local integer = types:byte(1) 217 | header.integer = integer 218 | debug("Integer Size: %d bytes", integer) 219 | 220 | local size_t = types:byte(2) 221 | header.size_t = size_t 222 | debug("Size_T Size: %d bytes", size_t) 223 | 224 | local instruction = types:byte(3) 225 | header.instruction = instruction 226 | debug("Instruction Size: %d bytes", instruction) 227 | 228 | local luaint = types:byte(4) 229 | header.luaint = luaint 230 | debug("lua_Integer Size: %d bytes", luaint) 231 | 232 | debug(bc:sub(1,32):gsub(".", function(c) return string.format("%02X ", c:byte()) end)) 233 | 234 | --integral or numerical number stuff 235 | do 236 | local size = types:byte(5) 237 | local integralNumbers = bc:byte(bigEndian and (18+luaint) or (18+luaint+size-1)) == 0x72 238 | header.number_integral = integralNumbers 239 | header.number = size 240 | debug("Numerical Format: %d bytes <%s>", size, integralNumbers and "integral" or "floating") 241 | end 242 | 243 | return header 244 | end 245 | 246 | function impl.load(bc) 247 | debug("Lua 5.3 Bytecode Loader") 248 | 249 | local idx = 13 250 | local integer, size_t, number, luaint 251 | local bigEndian 252 | local binarytypes = bytecode.binarytypes 253 | 254 | local function u1() 255 | idx = idx+1 256 | return binarytypes.decode.u1(bc, idx-1, bigEndian) 257 | end 258 | 259 | local function u2() 260 | idx = idx+2 261 | return binarytypes.decode.u2(bc, idx-2, bigEndian) 262 | end 263 | 264 | local function u4() 265 | idx = idx+4 266 | return binarytypes.decode.u4(bc, idx-4, bigEndian) 267 | end 268 | 269 | local function u8() 270 | idx = idx+8 271 | return binarytypes.decode.u8(bc, idx-8, bigEndian) 272 | end 273 | 274 | local function float() 275 | idx = idx+4 276 | return binarytypes.decode.float(bc, idx-4, bigEndian) 277 | end 278 | 279 | local function double() 280 | idx = idx+8 281 | return binarytypes.decode.double(bc, idx-8, bigEndian) 282 | end 283 | 284 | local function ub(n) 285 | idx = idx+n 286 | return bc:sub(idx-n,idx-1) 287 | end 288 | 289 | local function us() 290 | local size 291 | local bytesize = u1() 292 | if bytesize < 0xFF then 293 | size = bytesize 294 | else 295 | size = size_t() 296 | end 297 | return ub(size-1) 298 | end 299 | 300 | local integralSizes = { 301 | [1] = u1, 302 | [2] = u2, 303 | [4] = u4, 304 | [8] = u8 305 | } 306 | 307 | local numericSizes = { 308 | [4] = float, 309 | [8] = double 310 | } 311 | 312 | local header = impl.loadHeader(bc) 313 | 314 | assert(header.fmtver == 0 or header.fmtver == 255, "unknown format version: "..header.fmtver) 315 | bigEndian = header.bigEndian 316 | integer = assert(integralSizes[header.integer], "unsupported integer size: "..header.integer) 317 | size_t = assert(integralSizes[header.size_t], "unsupported size_t size: "..header.size_t) 318 | luaint = assert(integralSizes[header.luaint], "unsupported luaint size: "..header.luaint) 319 | assert(header.instruction == 4, "unsupported instruction size: "..header.instruction) 320 | 321 | --integral or numerical number stuff 322 | do 323 | local integralNumbers = header.number_integral 324 | local size = header.number 325 | number = assert(integralNumbers and integralSizes[size] or numericSizes[size], "unsupported number size: "..(integralNumbers and "integral" or "floating").." "..size) 326 | end 327 | 328 | assert(bc:sub(7, 12) == "\25\147\r\n\26\n", "header has invalid encoding") 329 | 330 | idx = 18+header.luaint+header.number 331 | 332 | local sizeupvalues = u1() 333 | 334 | debug("Size Upvalues: "..sizeupvalues) 335 | 336 | local function chunk() 337 | local function instructionList() 338 | local instructions = {} 339 | local count = integer() 340 | instructions.count = count 341 | for i=1, count do 342 | instructions[i-1] = u4() 343 | end 344 | return instructions 345 | end 346 | 347 | local function constantList() 348 | local constants = {} 349 | local c = integer() 350 | constants.count = c 351 | for i=1, c do 352 | local type = u1() 353 | local subtype = type>>4 354 | type = type&0xF 355 | if type == 0 then 356 | constants[i-1] = nil 357 | elseif type == 1 then 358 | constants[i-1] = u1() > 0 359 | elseif type == 3 and subtype == 0 then 360 | constants[i-1] = number() 361 | elseif type == 3 and subtype == 1 then 362 | constants[i-1] = luaint() 363 | elseif type == 4 then 364 | constants[i-1] = us() 365 | else 366 | error("Type: "..type) 367 | end 368 | debug("Constant %d: %s %s %s", i-1, tostring(constants[i-1]), type, subtype) 369 | end 370 | return constants 371 | end 372 | 373 | local function functionPrototypeList() 374 | local functionPrototypes = {} 375 | local c = integer() 376 | functionPrototypes.count = c 377 | for i=1, c do 378 | functionPrototypes[i-1] = chunk() 379 | end 380 | return functionPrototypes 381 | end 382 | 383 | local function sourceLineList() 384 | local sourceLines = {} 385 | local c = integer() 386 | sourceLines.count = c 387 | for i=1, c do 388 | sourceLines[i-1] = integer() 389 | end 390 | return sourceLines 391 | end 392 | 393 | local function localList() 394 | local locals = {} 395 | local c = integer() 396 | locals.count = c 397 | for i=1, c do 398 | locals[i-1] = { 399 | name = us(), 400 | startpc = integer(), 401 | endpc = integer() 402 | } 403 | end 404 | return locals 405 | end 406 | 407 | local function upvalueList() 408 | local upvalues = {} 409 | local c = integer() 410 | upvalues.count = c 411 | for i=1, c do 412 | upvalues[i-1] = us() 413 | end 414 | return upvalues 415 | end 416 | 417 | local function upvalueDefinitionList() 418 | local upvalues = {} 419 | local c = integer() 420 | upvalues.count = c 421 | for i=1, c do 422 | upvalues[i-1] = {instack=u1(),idx=u1()} 423 | debug("upvalue %d instack=%d idx=%d", i-1, upvalues[i-1].instack, upvalues[i-1].idx) 424 | end 425 | return upvalues 426 | end 427 | 428 | --extract an lua chunk into a table-- 429 | local c = {header = header} 430 | 431 | c.name = us() 432 | c.lineDefined = integer() 433 | c.lastLineDefined = integer() 434 | c.nparam = u1() 435 | c.isvararg = u1() 436 | c.maxStack = u1() 437 | c.instructions = instructionList() 438 | c.constants = constantList() 439 | c.upvalues = upvalueDefinitionList() 440 | c.functionPrototypes = functionPrototypeList() 441 | c.sourceLines = sourceLineList() 442 | c.locals = localList() 443 | c.upvaluesDebug = upvalueList() 444 | return c 445 | end 446 | 447 | return chunk() 448 | end 449 | 450 | function impl.save(chunk) 451 | local header = chunk.header 452 | local bc = {"\27Lua", string.char(header.version, 0), 453 | "\25\147\r\n\26\n"} 454 | 455 | bc[#bc+1] = string.char( 456 | header.integer, 457 | header.size_t, 458 | header.instruction, 459 | header.luaint, 460 | header.number 461 | ) 462 | 463 | local integer, size_t, luaint, number 464 | local bigEndian = header.bigEndian 465 | local binarytypes = bytecode.binarytypes 466 | 467 | local function u1(value) 468 | bc[#bc+1] = string.char(value) 469 | end 470 | 471 | local function u2(value) 472 | bc[#bc+1] = binarytypes.encode.u2(value, bigEndian) 473 | end 474 | 475 | local function u4(value) 476 | bc[#bc+1] = binarytypes.encode.u4(value, bigEndian) 477 | end 478 | 479 | local function u8(value) 480 | bc[#bc+1] = binarytypes.encode.u8(value, bigEndian) 481 | end 482 | 483 | local function float(value) 484 | bc[#bc+1] = binarytypes.encode.float(value, bigEndian) 485 | end 486 | 487 | local function double(value) 488 | bc[#bc+1] = binarytypes.encode.double(value, bigEndian) 489 | end 490 | 491 | local function us(str) 492 | local siz = #str+1 493 | if siz < 0xFF then 494 | u1(siz) 495 | else 496 | u1(0xFF) 497 | size_t(siz) 498 | end 499 | bc[#bc+1] = str 500 | end 501 | 502 | local function len(t) 503 | local n = 0 504 | for i, v in pairs(t) do n = n+1 end 505 | return n 506 | end 507 | 508 | local integralSizes = { 509 | [1] = u1, 510 | [2] = u2, 511 | [4] = u4, 512 | [8] = u8 513 | } 514 | 515 | local numericSizes = { 516 | [4] = float, 517 | [8] = double 518 | } 519 | 520 | assert(header.fmtver == 0 or header.fmtver == 255, "unknown format version: "..header.fmtver) 521 | bigEndian = header.bigEndian 522 | integer = assert(integralSizes[header.integer], "unsupported integer size: "..header.integer) 523 | size_t = assert(integralSizes[header.size_t], "unsupported size_t size: "..header.size_t) 524 | luaint = assert(integralSizes[header.luaint], "unsupported luaint size: "..header.luaint) 525 | assert(header.instruction == 4, "unsupported instruction size: "..header.instruction) 526 | 527 | --integral or numerical number stuff 528 | do 529 | local integralNumbers = header.number_integral 530 | local size = header.number 531 | number = assert(integralNumbers and integralSizes[size] or numericSizes[size], "unsupported number size: "..(integralNumbers and "integral" or "floating").." "..size) 532 | end 533 | 534 | luaint(0x5678) 535 | number(370.5) 536 | u1(chunk.upvalues.count or len(chunk.upvalues)) 537 | 538 | local function dumpChunk(chunk) 539 | us(chunk.name) 540 | integer(chunk.lineDefined) 541 | integer(chunk.lastLineDefined) 542 | u1(chunk.nparam) 543 | u1(chunk.isvararg) 544 | u1(chunk.maxStack) 545 | 546 | local lenInstructions = chunk.instructions.count or len(chunk.instructions) 547 | integer(lenInstructions) 548 | for i=0, lenInstructions-1 do 549 | u4(chunk.instructions[i]) 550 | end 551 | 552 | local lenConstants = chunk.constants.count or len(chunk.constants) 553 | integer(lenConstants) 554 | for i=0, lenConstants-1 do 555 | local v = chunk.constants[i] 556 | local t = type(v) 557 | local tt 558 | if t == "nil" then 559 | tt = 0 560 | elseif t == "boolean" then 561 | tt = 1 562 | elseif t == "number" then 563 | tt = 3 | (math.type(v) == "integer" and (1<<4) or 0) 564 | elseif t == "string" then 565 | tt = 4 566 | end 567 | u1(tt) 568 | if t == "boolean" then 569 | u1(v and 1 or 0) 570 | elseif t == "number" then 571 | if tt > 3 then 572 | luaint(v) 573 | else 574 | number(v) 575 | end 576 | elseif t == "string" then 577 | us(v) 578 | end 579 | end 580 | 581 | local lenUpvalueDefs = chunk.upvalues.count or len(chunk.upvalues) 582 | integer(lenUpvalueDefs) 583 | for i=0, lenUpvalueDefs-1 do 584 | local uv = chunk.upvalues[i] 585 | u1(uv.instack) 586 | u1(uv.idx) 587 | end 588 | 589 | local lenFunctionPrototypes = chunk.functionPrototypes.count or len(chunk.functionPrototypes) 590 | integer(lenFunctionPrototypes) 591 | for i=0, lenFunctionPrototypes-1 do 592 | dumpChunk(chunk.functionPrototypes[i]) 593 | end 594 | 595 | local lenSourceLines = chunk.sourceLines.count or len(chunk.sourceLines) 596 | integer(lenSourceLines) 597 | for i=0, lenSourceLines-1 do 598 | integer(chunk.sourceLines[i]) 599 | end 600 | 601 | local lenLocals = chunk.locals.count or len(chunk.locals) 602 | integer(lenLocals) 603 | for i=0, lenLocals-1 do 604 | local l = chunk.locals[i] 605 | us(l.name) 606 | integer(l.startpc) 607 | integer(l.endpc) 608 | end 609 | 610 | local lenUpvalues = chunk.upvaluesDebug.count or len(chunk.upvaluesDebug) 611 | integer(lenUpvalues) 612 | for i=0, lenUpvalues-1 do 613 | us(chunk.upvalues[i]) 614 | end 615 | end 616 | 617 | dumpChunk(chunk) 618 | return table.concat(bc) 619 | end 620 | 621 | return impl 622 | end 623 | --------------------------------------------------------------------------------