forked from GitHub/lad
1017 lines
30 KiB
Lua
1017 lines
30 KiB
Lua
--[[
|
|
Lua Disassembler
|
|
Version 0.1
|
|
Author: Andre Murbach Maidl
|
|
]]
|
|
|
|
-- usage message
|
|
USAGE = [[
|
|
usage: %s [options] [filename]
|
|
Available options are:
|
|
-h print this help
|
|
-l list on luac style
|
|
-o name output to file name (default is %s)
|
|
]]
|
|
|
|
-- global settings
|
|
config = {}
|
|
config.SIGNATURE = "\27Lua"
|
|
config.VERSION = 81
|
|
config.FORMAT = 0
|
|
config.ENDIANNESS = 1
|
|
config.INT = 4
|
|
config.SIZE_T = 8
|
|
config.INSTRUCTION = 4
|
|
config.LUA_NUMBER = 8
|
|
config.INTEGRAL = 0
|
|
config.BLOCK = 4
|
|
config.HEADERBLOCK = 12
|
|
config.SIZE_C = 9
|
|
config.SIZE_B = 9
|
|
config.SIZE_Bx = config.SIZE_C + config.SIZE_B
|
|
config.SIZE_A = 8
|
|
config.SIZE_OP = 6
|
|
config.POS_OP = 0
|
|
config.POS_A = config.POS_OP + config.SIZE_OP
|
|
config.POS_C = config.POS_A + config.SIZE_A
|
|
config.POS_B = config.POS_C + config.SIZE_C
|
|
config.POS_Bx = config.POS_C
|
|
|
|
-- global types
|
|
types = {}
|
|
types.LUA_TNIL = 0
|
|
types.LUA_TBOOLEAN = 1
|
|
types.LUA_TNUMBER = 3
|
|
types.LUA_TSTRING = 4
|
|
|
|
-- opcodes
|
|
opcode = {}
|
|
opcode = { "MOVE", "LOADK", "LOADBOOL", "LOADNIL",
|
|
"GETUPVAL", "GETGLOBAL", "GETTABLE",
|
|
"SETGLOBAL", "SETUPVAL", "SETTABLE",
|
|
"NEWTABLE", "SELF", "ADD", "SUB", "MUL", "DIV",
|
|
"MOD", "POW", "UNM", "NOT", "LEN", "CONCAT",
|
|
"JMP", "EQ", "LT", "LE", "TEST", "TESTSET",
|
|
"CALL", "TAILCALL", "RETURN", "FORLOOP",
|
|
"FORPREP", "TFORLOOP", "SETLIST", "CLOSE",
|
|
"CLOSURE", "VARARG" }
|
|
|
|
-- opcode modes
|
|
opmode = {}
|
|
opmode.iABC = 0
|
|
opmode.iABx = 1
|
|
opmode.iAsBx = 2
|
|
|
|
-- opcode masks
|
|
opargmask = {}
|
|
opargmask.OpArgN = 0
|
|
opargmask.OpArgU = 1
|
|
opargmask.OpArgR = 2
|
|
opargmask.OpArgK = 3
|
|
|
|
-- global options
|
|
options = {}
|
|
options.INPUT = nil
|
|
options.OUTPUT = "d.asm"
|
|
options.LISTING = false
|
|
options.TMP = "luac_tmp.out"
|
|
|
|
-- table to store parsed data
|
|
parsed = {}
|
|
|
|
-- prints usage message
|
|
function usage(msg)
|
|
if msg ~= nil then
|
|
io.write(string.format("%s: %s\n", arg[0], msg))
|
|
end
|
|
io.write(string.format(USAGE, arg[0], options.OUTPUT))
|
|
os.exit(1)
|
|
end
|
|
|
|
-- parse arguments
|
|
function doargs()
|
|
local i = 1
|
|
while i <= #arg do
|
|
if string.find(arg[i], "^-") == nil then
|
|
options.INPUT = arg[i]
|
|
break
|
|
elseif arg[i] == "-h" then usage()
|
|
elseif arg[i] == "-l" then
|
|
options.LISTING = true
|
|
elseif arg[i] == "-o" then
|
|
i = i + 1
|
|
if arg[i] == nil then usage("'-o' needs argument") else options.OUTPUT = arg[i] end
|
|
else usage(string.format("'%s' unkown option", arg[i]))
|
|
end
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
-- print a table - debug only
|
|
function print_table(table)
|
|
for k,p in pairs(table) do print (k,p) end
|
|
end
|
|
|
|
-- print a chunk - debug only
|
|
function print_chunk(binary_chunk)
|
|
for b in string.gfind(binary_chunk, ".") do
|
|
io.write(string.format("%d ", string.byte(b)))
|
|
end
|
|
io.write(string.rep(" ", config.BLOCK - string.len(binary_chunk) + 1))
|
|
io.write(string.gsub(binary_chunk, "%c", "."), "\n")
|
|
end
|
|
|
|
-- print what still needs to be parsed - debug only
|
|
function print_rest(file)
|
|
while true do
|
|
binary_chunk = file:read(config.BLOCK)
|
|
if not binary_chunk then break else print_chunk(binary_chunk) end
|
|
end
|
|
end
|
|
|
|
-- get a bit from an hex string
|
|
function get_bit(x,a,b)
|
|
return (math.floor((x / 2^a) % 2^b))
|
|
end
|
|
|
|
-- generate an hex string
|
|
function get_hex_string(x)
|
|
str = "0x"
|
|
if config.ENDIANNESS == 1 then
|
|
for i=config.BLOCK,1,-1 do
|
|
str = str .. string.format("%02x", string.byte(x,i))
|
|
end
|
|
else
|
|
for i=1,config.BLOCK do
|
|
str = str .. string.format("%02x", string.byte(x,i))
|
|
end
|
|
end
|
|
|
|
return str
|
|
end
|
|
|
|
-- get an int from a chunk
|
|
function get_int(binary_chunk)
|
|
i = 0
|
|
if config.ENDIANNESS == 1 then
|
|
for p=config.INT,2,-1 do
|
|
i = i + (256 * string.byte(binary_chunk,p))
|
|
end
|
|
i = i + string.byte(binary_chunk,1)
|
|
else
|
|
for p=1,config.INT-1 do
|
|
i = i + (256 * string.byte(binary_chunk,p))
|
|
end
|
|
i = i + string.byte(binary_chunk,config.INT)
|
|
end
|
|
return i
|
|
end
|
|
|
|
-- revert chunk
|
|
function revert(x)
|
|
reverted = ""
|
|
for k=config.LUA_NUMBER,1,-1 do
|
|
reverted = reverted .. string.format("%c", string.byte(x,k))
|
|
end
|
|
return reverted
|
|
end
|
|
|
|
-- convert little endian number to float
|
|
function convert_little_endian(x)
|
|
sign = 1
|
|
mantissa = string.byte(x, 7) % 16
|
|
for i=6,1,-1 do mantissa = mantissa * 256 + string.byte(x,i) end
|
|
if string.byte(x,8) > 127 then sign = -1 end
|
|
exponent = (string.byte(x,8) % 128) * 16 + math.floor(string.byte(x,7) / 16)
|
|
if exponent == 0 then return 0 end
|
|
mantissa = (math.ldexp(mantissa, -52) + 1) * sign
|
|
return math.ldexp(mantissa, exponent - 1023)
|
|
end
|
|
|
|
-- convert big endian number to float
|
|
function convert_big_endian(x)
|
|
sign = 1
|
|
mantissa = string.byte(x, 2) % 16
|
|
for i=3,config.LUA_NUMBER do mantissa = mantissa * 256 + string.byte(x,i) end
|
|
if string.byte(x,1) > 127 then sign = -1 end
|
|
exponent = (string.byte(x,1) % 128) * 16 + math.floor(string.byte(x,2) / 16)
|
|
if exponent == 0 then return 0 end
|
|
mantissa = (math.ldexp(mantissa, -52) + 1) * sign
|
|
return math.ldexp(mantissa, exponent - 1023)
|
|
end
|
|
|
|
-- convert a number
|
|
function convert_number(x)
|
|
if config.ENDIANNESS == 1 then
|
|
return convert_little_endian(x)
|
|
end
|
|
|
|
return convert_big_endian(x)
|
|
end
|
|
|
|
-- get string length
|
|
function get_str_len(binary_chunk)
|
|
if config.ENDIANNESS == 1 then
|
|
i = string.byte(binary_chunk,1)
|
|
for p=2,config.LUA_NUMBER do
|
|
i = i + (256 * string.byte(binary_chunk,p))
|
|
end
|
|
else
|
|
i = string.byte(binary_chunk,config.INT)
|
|
for p=config.INT-1,1,-1 do
|
|
i = i + (256 * string.byte(binary_chunk,p))
|
|
end
|
|
end
|
|
return i
|
|
end
|
|
|
|
-- get boolean value
|
|
function get_boolean(binary_chunk)
|
|
if string.byte(binary_chunk,1) == 0 then return "false" end
|
|
return "true"
|
|
end
|
|
|
|
-- check if is a control char
|
|
function iscntrl(x)
|
|
if (x >= 0 and x <= 31) or (x == 127) then return true end
|
|
return false
|
|
end
|
|
|
|
-- check if is a printable char
|
|
function isprint(x)
|
|
return not iscntrl(x)
|
|
end
|
|
|
|
-- load header from file
|
|
function load_header(file)
|
|
return file:read(config.HEADERBLOCK)
|
|
end
|
|
|
|
-- check Lua signature
|
|
function check_signature(header_chunk)
|
|
if (string.sub(header_chunk, 1, string.len(config.SIGNATURE)) == config.SIGNATURE) then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- check Lua version for 5.1
|
|
function check_version(header_chunk)
|
|
if (string.byte(header_chunk,5) == config.VERSION) then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- set flags detected from file
|
|
function set_auto_flags(header_chunk)
|
|
config.ENDIANNESS = string.byte(header_chunk,7)
|
|
config.INT = string.byte(header_chunk,8)
|
|
config.SIZE_T = string.byte(header_chunk,9)
|
|
config.INSTRUCTION = string.byte(header_chunk,10)
|
|
config.LUA_NUMBER = string.byte(header_chunk,11)
|
|
config.INGETRAL = string.byte(header_chunk,12)
|
|
end
|
|
|
|
-- get a string based on its length
|
|
function get_string(file, str_len)
|
|
binary_chunk = file:read(str_len)
|
|
local str = ""
|
|
for i=1,str_len do
|
|
local byte = string.byte(binary_chunk, i)
|
|
if byte ~= 0 then
|
|
str = str .. string.format("%c", string.byte(binary_chunk,i))
|
|
else
|
|
str = str .. '\0'
|
|
end
|
|
end
|
|
|
|
return str
|
|
end
|
|
|
|
-- get source name from file
|
|
function get_source_name(file)
|
|
binary_chunk = file:read(config.SIZE_T)
|
|
str_len = get_str_len(binary_chunk)
|
|
return get_string(file, str_len)
|
|
end
|
|
|
|
-- get line defined from file
|
|
function get_line_defined(file, parsed)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["line_defined"] = get_int(binary_chunk)
|
|
end
|
|
|
|
-- get last line defined from file
|
|
function get_last_line_defined(file, parsed)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["last_line_defined"] = get_int(binary_chunk)
|
|
end
|
|
|
|
-- get function values from file
|
|
function get_function_values(file, parsed)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["number_of_upvalues"] = string.byte(binary_chunk,1)
|
|
parsed["number_of_parameters"] = string.byte(binary_chunk,2)
|
|
parsed["is_vararg"] = string.byte(binary_chunk,3)
|
|
parsed["max_stack_size"] = string.byte(binary_chunk,4)
|
|
end
|
|
|
|
-- get instruction from file
|
|
function get_instruction(file, parsed, n)
|
|
if not parsed["instruction"] then parsed["instruction"] = {} end
|
|
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["instruction"][n] = get_hex_string(binary_chunk)
|
|
end
|
|
|
|
-- get all instructions from file
|
|
function get_instructions(file, parsed)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["number_of_instructions"] = get_int(binary_chunk)
|
|
|
|
for n=1,parsed["number_of_instructions"] do
|
|
get_instruction(file, parsed, n)
|
|
end
|
|
end
|
|
|
|
-- get a constant from file
|
|
function get_constant(file, parsed, n)
|
|
if not parsed["constant"] then parsed["constant"] = {} end
|
|
if not parsed["constant"][n] then parsed["constant"][n] = {} end
|
|
|
|
binary_chunk = file:read(1)
|
|
c_type = string.byte(binary_chunk,1)
|
|
|
|
if c_type == types.LUA_TNIL then
|
|
parsed["constant"][n]["c_type"] = types.LUA_TNIL
|
|
parsed["constant"][n]["value"] = "nil"
|
|
elseif c_type == types.LUA_TBOOLEAN then
|
|
parsed["constant"][n]["c_type"] = types.LUA_TBOOLEAN
|
|
binary_chunk = file:read(1)
|
|
parsed["constant"][n]["value"] = get_boolean(binary_chunk)
|
|
elseif c_type == types.LUA_TNUMBER then
|
|
parsed["constant"][n]["c_type"] = types.LUA_TNUMBER
|
|
binary_chunk = file:read(config.LUA_NUMBER)
|
|
parsed["constant"][n]["value"] = convert_number(binary_chunk)
|
|
elseif c_type == types.LUA_TSTRING then
|
|
parsed["constant"][n]["c_type"] = types.LUA_TSTRING
|
|
binary_chunk = file:read(config.SIZE_T)
|
|
parsed["constant"][n]["str_len"] = get_str_len(binary_chunk)
|
|
parsed["constant"][n]["value"] = get_string(file, parsed["constant"][n]["str_len"])
|
|
end
|
|
end
|
|
|
|
-- get all constants from file
|
|
function get_constants(file, parsed)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["number_of_constants"] = get_int(binary_chunk)
|
|
|
|
for n=1,parsed["number_of_constants"] do
|
|
get_constant(file, parsed, n)
|
|
end
|
|
end
|
|
|
|
-- get number of functions from file
|
|
function get_number_of_functions(file, parsed)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["number_of_functions"] = get_int(binary_chunk)
|
|
end
|
|
|
|
-- get source line positions from file
|
|
function get_source_line_position(file, parsed, n)
|
|
if not parsed["instruction_line"] then parsed["instruction_line"] = {} end
|
|
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["instruction_line"][n] = get_int(binary_chunk)
|
|
end
|
|
|
|
-- get all source line positions from file
|
|
function get_source_line_positions(file, parsed)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["number_of_instruction_lines"] = get_int(binary_chunk)
|
|
|
|
for n=1,parsed["number_of_instruction_lines"] do
|
|
get_source_line_position(file, parsed, n)
|
|
end
|
|
end
|
|
|
|
-- get a local variable from file
|
|
function get_local(file, parsed, n)
|
|
if not parsed["local"] then parsed["local"] = {} end
|
|
if not parsed["local"][n] then parsed["local"][n] = {} end
|
|
|
|
binary_chunk = file:read(config.SIZE_T)
|
|
parsed["local"][n]["str_len"] = get_str_len(binary_chunk)
|
|
parsed["local"][n]["varname"] = get_string(file, parsed["local"][n]["str_len"])
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["local"][n]["startpc"] = string.byte(binary_chunk,1)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["local"][n]["endpc"] = string.byte(binary_chunk,1)
|
|
end
|
|
|
|
-- get all local variables from file
|
|
function get_locals(file, parsed)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["number_of_locals"] = get_int(binary_chunk)
|
|
|
|
for n=1,parsed["number_of_locals"] do
|
|
get_local(file, parsed, n)
|
|
end
|
|
end
|
|
|
|
-- get an upvalue from file
|
|
function get_upvalue(file, parsed, n)
|
|
if not parsed["upvalue"] then parsed["upvalue"] = {} end
|
|
if not parsed["upvalue"][n] then parsed["upvalue"][n] = {} end
|
|
|
|
binary_chunk = file:read(config.SIZE_T)
|
|
parsed["upvalue"][n]["str_len"] = get_str_len(binary_chunk)
|
|
parsed["upvalue"][n]["value"] = get_string(file, parsed["upvalue"][n]["str_len"])
|
|
end
|
|
|
|
-- get all upvalues from file
|
|
function get_upvalues(file, parsed)
|
|
binary_chunk = file:read(config.BLOCK)
|
|
parsed["size_of_upvalues"] = get_int(binary_chunk)
|
|
|
|
for n=1,parsed["size_of_upvalues"] do
|
|
get_upvalue(file, parsed, n)
|
|
end
|
|
end
|
|
|
|
-- get function block from file
|
|
function get_function_block(file, parsed, source_name, parent)
|
|
parsed["source_name"] = source_name
|
|
get_line_defined(file, parsed)
|
|
get_last_line_defined(file, parsed)
|
|
get_function_values(file, parsed)
|
|
get_instructions(file, parsed)
|
|
get_constants(file, parsed)
|
|
get_number_of_functions(file, parsed)
|
|
for n=1,parsed["number_of_functions"] do
|
|
binary_chunk = file:read(config.SIZE_T)
|
|
if not parsed["function"] then parsed["function"] = {} end
|
|
if not parsed["function"][n] then parsed["function"][n] = {} end
|
|
parsed["function"][n]["parent"] = parent
|
|
parsed["function"][n]["id"] = n
|
|
get_function_block(file, parsed["function"][n], source_name, parent+1)
|
|
end
|
|
get_source_line_positions(file, parsed)
|
|
get_locals(file, parsed)
|
|
get_upvalues(file, parsed)
|
|
end
|
|
|
|
-- output header
|
|
function print_header(parsed)
|
|
|
|
local function SS(x) if x == 1 then return "" else return "s" end end
|
|
local function S(x) return x,SS(X) end
|
|
|
|
func_name = "function"
|
|
is_vararg = ""
|
|
|
|
if parsed["line_defined"] == 0 and parsed["id"] == 0 then func_name = "main" end
|
|
if parsed["is_vararg"] ~= 0 then is_vararg = "+" end
|
|
|
|
io.write(string.format("\n%s <%s:%d,%d> (%d instruction%s, %d bytes at %d)\n",
|
|
func_name, parsed["source_name"], parsed["line_defined"], parsed["last_line_defined"],
|
|
parsed["number_of_instructions"], SS(parsed["number_of_instructions"]),
|
|
parsed["number_of_instructions"] * config.BLOCK, 0))
|
|
io.write(string.format("%d%s param%s, %d slot%s, %d upvalue%s, ",
|
|
parsed["number_of_parameters"], is_vararg, SS(parsed["number_of_parameters"]),
|
|
parsed["max_stack_size"], SS(parsed["max_stack_size"]),
|
|
parsed["number_of_upvalues"], SS(parsed["number_of_upvalues"])))
|
|
io.write(string.format("%d local%s, %d constant%s, %d function%s\n",
|
|
parsed["number_of_locals"], SS(parsed["number_of_locals"]),
|
|
parsed["number_of_constants"], SS(parsed["number_of_constants"]),
|
|
parsed["number_of_functions"], SS(parsed["number_of_functions"])))
|
|
|
|
end
|
|
|
|
-- get an opcode
|
|
function get_opcode(inst)
|
|
return get_bit(inst,config.POS_OP,config.SIZE_OP)
|
|
end
|
|
|
|
-- get argument A
|
|
function getarg_a(inst)
|
|
return get_bit(inst,config.POS_A,config.SIZE_A)
|
|
end
|
|
|
|
-- get argument B
|
|
function getarg_b(inst)
|
|
return get_bit(inst,config.POS_B,config.SIZE_B)
|
|
end
|
|
|
|
-- get argument C
|
|
function getarg_c(inst)
|
|
return get_bit(inst,config.POS_C,config.SIZE_C)
|
|
end
|
|
|
|
-- get argument Bx
|
|
function getarg_bx(inst)
|
|
return get_bit(inst,config.POS_Bx,config.SIZE_Bx)
|
|
end
|
|
|
|
-- get max argument for Bx
|
|
function maxarg_bx()
|
|
return (2 ^ config.SIZE_Bx) - 1
|
|
end
|
|
|
|
-- get max argument for sBx
|
|
function maxarg_sbx()
|
|
return get_bit(maxarg_bx(),1,32)
|
|
end
|
|
|
|
-- get argument sBx
|
|
function getarg_sbx(inst)
|
|
return getarg_bx(inst)-maxarg_sbx()
|
|
end
|
|
|
|
-- get opcode mode
|
|
function get_op_mode(o)
|
|
if opcode[o] == "JMP" or opcode[o] == "FORLOOP" or opcode[o] == "FORPREP" then
|
|
return opmode.iAsBx
|
|
elseif opcode[o] == "LOADK" or opcode[o] == "GETGLOBAL" or
|
|
opcode[o] == "SETGLOBAL" or opcode[o] == "CLOSURE" then
|
|
return opmode.iABx
|
|
end
|
|
return opmode.iABC
|
|
end
|
|
|
|
-- get B mask
|
|
function get_b_mode(o)
|
|
if opcode[o] == "TFORLOOP" or opcode[o] == "CLOSE" then
|
|
m = opargmask.OpArgN
|
|
elseif opcode[o] == "LOADBOOL" or opcode[o] == "GETUPVAL" or
|
|
opcode[o] == "SETUPVAL" or opcode[o] == "NEWTABLE" or
|
|
opcode[o] == "CALL" or opcode[o] == "TAILCALL" or
|
|
opcode[o] == "RETURN" or opcode[o] == "SETLIST" or
|
|
opcode[o] == "CLOSURE" or opcode[o] == "VARARG" then
|
|
m = opargmask.OpArgU
|
|
elseif opcode[o] == "MOVE" or opcode[o] == "LOADNIL" or
|
|
opcode[o] == "GETTABLE" or opcode[o] == "SELF" or
|
|
opcode[o] == "UNM" or opcode[o] == "NOT" or
|
|
opcode[o] == "LEN" or opcode[o] == "CONCAT" or
|
|
opcode[o] == "JMP" or opcode[o] == "TEST" or
|
|
opcode[o] == "TESTSET" or opcode[o] == "FORLOOP" or opcode[o] == "FORPREP" then
|
|
m = opargmask.OpArgR
|
|
elseif opcode[o] == "LOADK" or opcode[o] == "GETGLOBAL" or
|
|
opcode[o] == "SETGLOBAL" or opcode[o] == "SETTABLE" or
|
|
opcode[o] == "ADD" or opcode[o] == "SUB" or opcode[o] == "MUL" or
|
|
opcode[o] == "DIV" or opcode[o] == "MOD" or opcode[o] == "POW" or
|
|
opcode[o] == "EQ" or opcode[o] == "LT" or opcode[o] == "LE" then
|
|
m = opargmask.OpArgK
|
|
end
|
|
return m
|
|
end
|
|
|
|
-- get C mask
|
|
function get_c_mode(o)
|
|
if opcode[o] == "MOVE" or opcode[o] == "LOADK" or
|
|
opcode[o] == "LOADNIL" or opcode[o] == "GETUPVAL" or
|
|
opcode[o] == "GETGLOBAL" or opcode[o] == "SETGLOBAL" or
|
|
opcode[o] == "SETUPVAL" or opcode[o] == "UNM" or
|
|
opcode[o] == "NOT" or opcode[o] == "LEN" or opcode[o] == "JMP" or
|
|
opcode[o] == "RETURN" or opcode[o] == "FORLOOP" or
|
|
opcode[o] == "FORPREP" or opcode[o] == "CLOSE" or
|
|
opcode[o] == "CLOSURE" or opcode[o] == "VARARG" then
|
|
m = opargmask.OpArgN
|
|
elseif opcode[o] == "LOADBOOL" or opcode[o] == "NEWTABLE" or
|
|
opcode[o] == "TEST" or opcode[o] == "TESTSET" or
|
|
opcode[o] == "CALL" or opcode[o] == "TAILCALL" or
|
|
opcode[o] == "TFORLOOP" or opcode[o] == "SETLIST" then
|
|
m = opargmask.OpArgU
|
|
elseif opcode[o] == "CONCAT" then
|
|
m = opargmask.OpArgR
|
|
elseif opcode[o] == "GETTABLE" or opcode[o] == "SETTABLE" or
|
|
opcode[o] == "SELF" or opcode[o] == "ADD" or opcode[o] == "SUB" or
|
|
opcode[o] == "MUL" or opcode[o] == "DIV" or opcode[o] == "MOD" or
|
|
opcode[o] == "POW" or opcode[o] == "EQ" or opcode[o] == "LT" or opcode[o] == "LE" then
|
|
m = opargmask.OpArgK
|
|
end
|
|
return m
|
|
end
|
|
|
|
-- get BITRK
|
|
function bitrk()
|
|
return (2 ^ (config.SIZE_B - 1))
|
|
end
|
|
|
|
-- get ~BITRK
|
|
function not_bitrk()
|
|
return (bitrk() + 1) * -1
|
|
end
|
|
|
|
-- get ISK
|
|
function isk(x)
|
|
if x >= bitrk() then return true end
|
|
return false
|
|
end
|
|
|
|
-- get INDEXK
|
|
function indexk(x)
|
|
if x >= bitrk() then return not_bitrk() + (x + 1) else return x end
|
|
end
|
|
|
|
-- output string
|
|
function print_string(parsed)
|
|
io.write(string.format("\""))
|
|
for i=1,parsed["str_len"]-1 do
|
|
char = string.byte(parsed["value"],i)
|
|
if char == 34 then io.write(string.format("\\\""))
|
|
elseif char == 92 then io.write(string.format("\\\\"))
|
|
elseif char == 7 then io.write(string.format("\\a"))
|
|
elseif char == 8 then io.write(string.format("\\b"))
|
|
elseif char == 12 then io.write(string.format("\\f"))
|
|
elseif char == 10 then io.write(string.format("\\n"))
|
|
elseif char == 13 then io.write(string.format("\\r"))
|
|
elseif char == 9 then io.write(string.format("\\t"))
|
|
elseif char == 11 then io.write(string.format("\\v"))
|
|
else
|
|
if isprint(char) then
|
|
io.write(string.format("%c", char))
|
|
else
|
|
io.write(string.format("\\%03d", char))
|
|
end
|
|
end
|
|
end
|
|
io.write(string.format("\""))
|
|
end
|
|
|
|
-- output a constant
|
|
function print_constant(parsed, n)
|
|
c_type = parsed["constant"][n]["c_type"]
|
|
value = parsed["constant"][n]["value"]
|
|
if c_type == types.LUA_TNIL then
|
|
io.write(string.format("%s", value))
|
|
elseif c_type == types.LUA_TBOOLEAN then
|
|
io.write(string.format("%s", value))
|
|
elseif c_type == types.LUA_TNUMBER then
|
|
io.write(string.format("%s", value))
|
|
elseif c_type == types.LUA_TSTRING then
|
|
print_string(parsed["constant"][n])
|
|
end
|
|
end
|
|
|
|
-- output code
|
|
function print_code(parsed)
|
|
for n=1,parsed["number_of_instructions"] do
|
|
i = parsed["instruction"][n]
|
|
o = get_opcode(i) + 1
|
|
a = getarg_a(i)
|
|
b = getarg_b(i)
|
|
c = getarg_c(i)
|
|
bx = getarg_bx(i)
|
|
sbx = getarg_sbx(i)
|
|
if parsed["number_of_instruction_lines"] > 0 then line = parsed["instruction_line"][n] else line = 0 end
|
|
|
|
io.write(string.format("\t%d\t", n))
|
|
if line > 0 then
|
|
io.write(string.format("[%d]\t", line))
|
|
else
|
|
io.write(string.format("[-]\t"))
|
|
end
|
|
io.write(string.format("%-9s\t", opcode[o]))
|
|
|
|
if get_op_mode(o) == opmode.iABC then
|
|
io.write(string.format("%d", a))
|
|
if get_b_mode(o) ~= opargmask.OpArgN then
|
|
if isk(b) then
|
|
io.write(string.format(" %d", -1-indexk(b)))
|
|
else
|
|
io.write(string.format(" %d", b))
|
|
end
|
|
end
|
|
if get_c_mode(o) ~= opargmask.OpArgN then
|
|
if isk(c) then
|
|
io.write(string.format(" %d", -1-indexk(c)))
|
|
else
|
|
io.write(string.format(" %d", c))
|
|
end
|
|
end
|
|
elseif get_op_mode(o) == opmode.iABx then
|
|
if get_b_mode(o) == opargmask.OpArgK then
|
|
io.write(string.format("%d %d", a, -1-bx))
|
|
else
|
|
io.write(string.format("%d %d", a, bx))
|
|
end
|
|
elseif get_op_mode(o) == opmode.iAsBx then
|
|
if opcode[o] == "JMP" then
|
|
io.write(string.format("%d", sbx))
|
|
else
|
|
io.write(string.format("%d %d", a, sbx))
|
|
end
|
|
end
|
|
|
|
if opcode[o] == "LOADK" then
|
|
io.write(string.format("\t; "))
|
|
print_constant(parsed, bx+1)
|
|
elseif opcode[o] == "GETUPVAL" or opcode[o] == "SETUPVAL" then
|
|
if parsed["number_of_upvalues"] > 0 then myupvalue = parsed["upvalue"][b+1]["value"] else myupvalue = "-" end
|
|
io.write(string.format("\t; %s", myupvalue))
|
|
elseif opcode[o] == "GETGLOBAL" or opcode[o] == "SETGLOBAL" then
|
|
io.write(string.format("\t; %s", parsed["constant"][bx+1]["value"]))
|
|
elseif opcode[o] == "GETTABLE" or opcode[o] == "SELF" then
|
|
if isk(c) then
|
|
io.write(string.format("\t; "))
|
|
print_constant(parsed, indexk(c+1))
|
|
end
|
|
elseif opcode[o] == "SETTABLE" or opcode[o] == "ADD" or
|
|
opcode[o] == "SUB" or opcode[o] == "MUL" or
|
|
opcode[o] == "DIV" or opcode[o] == "POW" or
|
|
opcode[o] == "EQ" or opcode[o] == "LT" or opcode[o] == "LE" then
|
|
if isk(b) or isk(c) then
|
|
io.write(string.format("\t; "))
|
|
if isk(b) then print_constant(parsed, indexk(b+1)) else io.write(string.format("-")) end
|
|
io.write(string.format(" "))
|
|
if isk(c) then print_constant(parsed, indexk(c+1)) else io.write(string.format("-")) end
|
|
end
|
|
elseif opcode[o] == "JMP" or opcode[o] == "FORLOOP" or opcode[o] == "FORPREP" then
|
|
io.write(string.format("\t; to %d", sbx+n+1))
|
|
elseif opcode[o] == "CLOSURE" then
|
|
io.write(string.format("\t; 0x"))
|
|
elseif opcode[o] == "SETLIST" then
|
|
if c == 0 then
|
|
io.write(string.format("\t; %d", 0))
|
|
else
|
|
io.write(string.format("\t; %d", c))
|
|
end
|
|
end
|
|
|
|
io.write(string.format("\n"))
|
|
end
|
|
end
|
|
|
|
-- output a function
|
|
function print_function(parsed)
|
|
print_header(parsed)
|
|
print_code(parsed)
|
|
|
|
for i=1,parsed["number_of_functions"] do
|
|
print_function(parsed["function"][i])
|
|
end
|
|
end
|
|
|
|
-- output header to a file
|
|
function write_header(output, parsed)
|
|
if parsed["line_defined"] == 0 and parsed["id"] == 0 then
|
|
func_name = "main function:"
|
|
else
|
|
func_name = string.format("function F_%s_%s:", parsed["parent"], parsed["id"])
|
|
end
|
|
|
|
output:write(string.format("\n%s\n", func_name))
|
|
end
|
|
|
|
-- output a string to a file
|
|
function write_string(output, parsed)
|
|
output:write(string.format("\""))
|
|
for i=1,parsed["str_len"]-1 do
|
|
char = string.byte(parsed["value"],i)
|
|
if char == 34 then output:write(string.format("\\\""))
|
|
elseif char == 92 then output:write(string.format("\\\\"))
|
|
elseif char == 7 then output:write(string.format("\\a"))
|
|
elseif char == 8 then output:write(string.format("\\b"))
|
|
elseif char == 12 then output:write(string.format("\\f"))
|
|
elseif char == 10 then output:write(string.format("\\n"))
|
|
elseif char == 13 then output:write(string.format("\\r"))
|
|
elseif char == 9 then output:write(string.format("\\t"))
|
|
elseif char == 11 then output:write(string.format("\\v"))
|
|
else
|
|
if isprint(char) then
|
|
output:write(string.format("%c", char))
|
|
else
|
|
output:write(string.format("\\%d", char))
|
|
end
|
|
end
|
|
end
|
|
output:write(string.format("\""))
|
|
end
|
|
|
|
-- output a constant to a file
|
|
function write_constant(output, parsed, n)
|
|
c_type = parsed["constant"][n]["c_type"]
|
|
value = parsed["constant"][n]["value"]
|
|
if c_type == types.LUA_TNIL or c_type == types.LUA_TBOOLEAN or c_type == types.LUA_TNUMBER then
|
|
output:write(string.format("%s", value))
|
|
elseif c_type == types.LUA_TSTRING then
|
|
write_string(output, parsed["constant"][n])
|
|
end
|
|
end
|
|
|
|
-- output a boolean to a file
|
|
function write_boolean(output, b)
|
|
if b == 0 then
|
|
output:write(string.format("FALSE"))
|
|
else
|
|
output:write(string.format("TRUE"))
|
|
end
|
|
end
|
|
|
|
-- output a register to a file
|
|
function write_register(output, i)
|
|
output:write(string.format("R[%s]", i))
|
|
end
|
|
|
|
-- output a constant or register to a file
|
|
function write_rk(output, parsed, x)
|
|
if isk(x) then
|
|
write_constant(output, parsed, indexk(x+1))
|
|
else
|
|
write_register(output, x)
|
|
end
|
|
end
|
|
|
|
-- output an index to a file
|
|
function write_index(output, x)
|
|
if isk(x) then
|
|
output:write(string.format("%s", -1-indexk(x)))
|
|
else
|
|
output:write(string.format("%s", x))
|
|
end
|
|
end
|
|
|
|
-- output function id to a file
|
|
function write_function_id(output, parsed, i)
|
|
local parent = parsed["function"][i]["parent"]
|
|
local id = parsed["function"][i]["id"]
|
|
local np = parsed["function"][i]["number_of_parameters"]
|
|
output:write(string.format("F_%s_%s %d", parent, id, np))
|
|
end
|
|
|
|
-- output an space to a file
|
|
function write_space(output)
|
|
output:write(string.format(" "))
|
|
end
|
|
|
|
-- output instruction to a file
|
|
function write_code(output, parsed)
|
|
for n=1,parsed["number_of_instructions"] do
|
|
i = parsed["instruction"][n]
|
|
o = get_opcode(i) + 1
|
|
a = getarg_a(i)
|
|
b = getarg_b(i)
|
|
c = getarg_c(i)
|
|
bx = getarg_bx(i)
|
|
sbx = getarg_sbx(i)
|
|
if parsed["number_of_instruction_lines"] > 0 then line = parsed["instruction_line"][n] else line = 0 end
|
|
|
|
output:write(string.format("\t%d\t", n))
|
|
if line > 0 then
|
|
output:write(string.format("[%d]\t", line))
|
|
else
|
|
output:write(string.format("[0]\t"))
|
|
end
|
|
output:write(string.format("%-9s\t", opcode[o]))
|
|
|
|
if opcode[o] ~= "JMP" then
|
|
write_register(output, a)
|
|
else
|
|
output:write(string.format("%s", sbx+n+1))
|
|
end
|
|
|
|
if opcode[o] == "MOVE" or opcode[o] == "LOADNIL" or
|
|
opcode[o] == "UNM" or opcode[o] == "NOT" or opcode[o] == "LEN" then
|
|
write_space(output)
|
|
write_register(output, b)
|
|
elseif opcode[o] == "LOADK" then
|
|
write_space(output)
|
|
write_constant(output, parsed, bx+1)
|
|
elseif opcode[o] == "LOADBOOL" then
|
|
write_space(output)
|
|
write_boolean(output, b)
|
|
write_space(output)
|
|
write_index(output, c)
|
|
elseif opcode[o] == "GETUPVAL" or opcode[o] == "SETUPVAL" then
|
|
write_space(output)
|
|
output:write(string.format("%s", parsed["upvalue"][b+1]["value"]))
|
|
elseif opcode[o] == "GETGLOBAL" or opcode[o] == "SETGLOBAL" then
|
|
write_space(output)
|
|
output:write(string.format("%s", parsed["constant"][bx+1]["value"]))
|
|
elseif opcode[o] == "GETTABLE" or opcode[o] == "SELF" then
|
|
write_space(output)
|
|
write_register(output, b)
|
|
write_space(output)
|
|
write_rk(output, parsed, c)
|
|
elseif opcode[o] == "SETTABLE" or
|
|
opcode[o] == "ADD" or opcode[o] == "SUB" or opcode[o] == "MUL" or
|
|
opcode[o] == "DIV" or opcode[o] == "MOD" or opcode[o] == "POW" or
|
|
opcode[o] == "EQ" or opcode[o] == "LT" or opcode[o] == "LE" then
|
|
write_space(output)
|
|
write_rk(output, parsed, b)
|
|
write_space(output)
|
|
write_rk(output, parsed, c)
|
|
elseif opcode[o] == "NEWTABLE" or opcode[o] == "CALL" or opcode[o] == "TAILCALL" or
|
|
opcode[o] == "SETLIST" then
|
|
write_space(output)
|
|
write_index(output, b)
|
|
write_space(output)
|
|
write_index(output, c)
|
|
elseif opcode[o] == "CONCAT" then
|
|
write_space(output)
|
|
write_register(output, b)
|
|
write_space(output)
|
|
write_register(output, c)
|
|
elseif opcode[o] == "FORLOOP" or opcode[o] == "FORPREP" then
|
|
write_space(output)
|
|
output:write(string.format("%s", sbx+n+1))
|
|
elseif opcode[o] == "TEST" or opcode[o] == "TESTSET" then
|
|
write_space(output)
|
|
write_register(output, b)
|
|
write_space(output)
|
|
write_boolean(output, c)
|
|
elseif opcode[o] == "RETURN" or opcode[o] == "VARARG" then
|
|
write_space(output)
|
|
write_index(output, b)
|
|
elseif opcode[o] == "TFORLOOP" then
|
|
write_space(output)
|
|
write_index(output, c)
|
|
elseif opcode[o] == "CLOSE" then
|
|
elseif opcode[o] == "CLOSURE" then
|
|
write_space(output)
|
|
write_function_id(output, parsed, bx+1)
|
|
end
|
|
|
|
output:write(string.format("\n"))
|
|
end
|
|
|
|
end
|
|
|
|
-- output a function to a file
|
|
function write_function(output, parsed)
|
|
write_header(output, parsed)
|
|
write_code(output, parsed)
|
|
|
|
for i=1,parsed["number_of_functions"] do
|
|
write_function(output, parsed["function"][i])
|
|
end
|
|
end
|
|
|
|
if #arg < 1 then
|
|
usage("no input file given");
|
|
end
|
|
|
|
doargs()
|
|
|
|
if options.INPUT == nil then
|
|
usage("no input file given")
|
|
end
|
|
|
|
if not (loadfile(options.INPUT)) then
|
|
usage(options.INPUT .. " can not be loaded")
|
|
end
|
|
|
|
contents = string.dump(loadfile(options.INPUT))
|
|
|
|
tmp = assert(io.open(options.TMP, "wb"))
|
|
tmp:write(contents)
|
|
tmp:close()
|
|
|
|
input = assert(io.open(options.TMP, "rb"))
|
|
|
|
if not input then
|
|
io.write(string.format("Couldn't open %s!\n", options.TMP))
|
|
end
|
|
|
|
header_chunk = load_header(input)
|
|
|
|
-- check if Lua version is 5.1
|
|
if not (check_version(header_chunk)) then
|
|
io.write(string.format("Can't proceed. It works only with Lua 5.1.\n"))
|
|
input:close()
|
|
os.exit(1)
|
|
end
|
|
|
|
-- set flags got in the input file
|
|
set_auto_flags(header_chunk)
|
|
|
|
if not parsed["main"] then parsed["main"] = {} end
|
|
|
|
-- check source name
|
|
source_name = get_source_name(input, parsed["main"])
|
|
|
|
-- scan input file
|
|
parsed["main"]["id"] = 0
|
|
get_function_block(input, parsed["main"], source_name, parsed["main"]["id"])
|
|
|
|
input:close()
|
|
os.remove(options.TMP)
|
|
|
|
if options.LISTING then print_function(parsed["main"]) end
|
|
|
|
output = assert(io.open(options.OUTPUT, "w"))
|
|
|
|
if not output then
|
|
io.write(string.format("Couldn't open %s!\n", options.OUTPUT))
|
|
end
|
|
|
|
write_function(output, parsed["main"])
|
|
|
|
output:close()
|
|
|
|
os.exit(0)
|