-- See A No-Frills Introduction to Lua 5.1 VM Instructions -- and http://www.lua.org/source/5.2/lundump.c.html#luaU_undump for changes local opcode = require "luainlua.bytecode.opcode" local reader = require "luainlua.bytecode.reader" local ir = require 'luainlua.bytecode.ir' local undump = {} --chunk.sizeof_int = 4 --chunk.sizeof_sizet = 4 --chunk.sizeof_instruction = 4 undump.sizeof_number = 8 local function generic_list(ctx, parser, size) local n = ctx:int(size) local ret = {} for i = 1, n do table.insert(ret, parser(ctx)) end return ret end local function constant(ctx) local type = ctx:byte() if type == 0 then return {} -- nil elseif type == 1 then -- boolean return ctx:byte() ~= 0 elseif type == 3 then return ctx:double() elseif type == 4 then return ctx:string(undump.sizeof_sizet) else error "Cannot parse constant" end end function undump.load_header(ctx) assert(ctx:int() == 0x61754c1b, "Make sure you're running vanilla Lua 5.2") -- ESC. Lua assert(ctx:byte() == 0x52, "Make sure you're running vanilla Lua 5.2") -- version assert(ctx:byte() == 0, "Not expecting extensions.") -- format version assert(ctx:byte() == 1, "Not expecting big-endianess.") -- little endian if not undump.sizeof_int then undump.sizeof_int = assert(ctx:byte()) -- sizeof(int) undump.sizeof_sizet = assert(ctx:byte()) -- sizeof(size_t) undump.sizeof_instruction = assert(ctx:byte()) -- sizeof(Instruction) else assert(ctx:byte() == undump.sizeof_int) -- sizeof(int) assert(ctx:byte() == undump.sizeof_sizet) -- sizeof(size_t) assert(ctx:byte() == undump.sizeof_instruction) -- sizeof(Instruction) end assert(ctx:byte() == undump.sizeof_number) -- sizeof(number) assert(ctx:byte() == 0) -- is integer assert(ctx:int() == 0x0a0d9319) -- TAIL assert(ctx:short() == 0x0a1a) -- MORE TAIL return end function undump.load_code(ctx) local ir = ctx:get_ir() local n = ctx:int() local instructions = {} for i = 1, n do local int = ctx:int(undump.sizeof_instruction) local instruction = opcode.instruction(ir, int, i) local serialized = opcode.serialize(instruction) assert(serialized == int, serialized .. ' versus ' .. int) table.insert(instructions, instruction) end return instructions end function undump.load_constants(ctx) local constants = generic_list(ctx, constant) constants.functions = generic_list(ctx, undump.load_function) return constants end function undump.load_upvalues(ctx) return generic_list( ctx, function(ctx) return {instack = ctx:byte(), index = ctx:byte()} end ) end function undump.load_debug(ctx) local source = ctx:string(undump.sizeof_sizet) local lineinfo = generic_list(ctx, function(ctx) return ctx:int() end) local locals = generic_list( ctx, function(ctx) return {name = ctx:string(undump.sizeof_sizet), first_pc = ctx:int(), last_pc = ctx:int()} end ) local upvalues = generic_list(ctx, function(ctx) return ctx:string(undump.sizeof_sizet) end) return { source = source, lineinfo = lineinfo, locals = locals, upvalues = upvalues, } end function undump.load_function(ctx) table.insert(ctx.ir_stack, ir()) local first_line = ctx:int() local last_line = ctx:int() local nparams = ctx:byte() -- 27 local is_vararg = ctx:byte() local stack_size = ctx:byte() local code = undump.load_code(ctx) local constants = undump.load_constants(ctx) local upvalues = undump.load_upvalues(ctx) local debug = undump.load_debug(ctx) local ir_context = table.remove(ctx.ir_stack) local func = { first_line = first_line, last_line = last_line, nparams = nparams, is_vararg = is_vararg, stack_size = stack_size, code = code, constants = constants, upvalues = upvalues, debug = debug, ir_context = ir_context, } ir_context:configure(func) return func end -- Configure the chunk reader for the first time undump.load_header(reader.new_reader(string.dump(loadstring ''))) function undump.undump(str_or_function) local str = str_or_function if type(str_or_function) == 'function' then str = string.dump(str_or_function) end assert(type(str) == 'string', "You can only undump functions or bytecode") local ctx = reader.new_reader(str) ctx:configure(undump.sizeof_int) ctx.ir_stack = {} function ctx:get_ir() return self.ir_stack[#self.ir_stack] end undump.load_header(ctx) -- verify local func = undump.load_function(ctx) assert(ctx[2] > #ctx[1], "There is some extra data left inside the bytecode.") -- TODO: Verify bytecode return func end return undump