1
0
forked from GitHub/lad
Files
lad/5.2/disassembler.lua
T
2013-08-04 10:41:28 -03:00

755 lines
21 KiB
Lua

local ladconf = require("ladconf")
local function check_signature(bytecode)
local sig = string.format(string.format("%o", string.byte(bytecode, 1)))
sig = sig .. string.char(string.byte(bytecode, 2))
sig = sig .. string.char(string.byte(bytecode, 3))
sig = sig .. string.char(string.byte(bytecode, 4))
sig = string.gsub(sig, "33", "\033")
if sig ~= ladconf.LUA_SIGNATURE then
error ("Not a Lua bytecode!")
end
end
local function check_version(bytecode)
if string.byte(bytecode, 5) ~= ladconf.LUA_VERSION then
error ("Disassembler works only with Lua 5.2!")
end
end
local function check_header(bytecode)
check_signature(bytecode)
check_version(bytecode)
end
local function set_auto_flags(bytecode)
ladconf.ENDIANNESS = string.byte(bytecode, 7)
ladconf.INT = string.byte(bytecode, 8)
ladconf.SIZE_T = string.byte(bytecode, 9)
ladconf.INSTRUCTION = string.byte(bytecode, 10)
ladconf.LUA_NUMBER = string.byte(bytecode, 11)
ladconf.INTEGRAL = string.byte(bytecode, 12)
end
local function parse_int(bytecode, k, n)
local i = "0x"
local s,e = k + (n-1), k + 1
for p=s,e,-1 do
i = i .. string.format("%02x", string.byte(bytecode, p))
end
i = i .. string.format("%02x", string.byte(bytecode, k))
return tonumber(i),k + (n-1)
end
local function parse_instruction(bytecode, k)
local code = "0x"
local n = k + (ladconf.INSTRUCTION - 1)
for i=n,k,-1 do
code = code .. string.format("%02x", string.byte(bytecode, i))
end
return code,k + (ladconf.INSTRUCTION - 1)
end
local function parse_code(bytecode, k, parsed)
parsed.sizecode,k = parse_int(bytecode, k, ladconf.INT)
parsed.code = {}
for i=1,parsed.sizecode do
parsed.code[i],k = parse_instruction(bytecode, k + 1)
end
return k
end
local function setnilvalue(parsed)
parsed.type = ladconf.LUA_TNIL
parsed.value = 'nil'
end
local function setbvalue(parsed, boolean)
parsed.type = ladconf.LUA_TBOOLEAN
if boolean == 0 then
parsed.value = 'false'
end
parsed.value = 'true'
end
local function setnvalue(parsed, number)
parsed.type = ladconf.LUA_TNUMBER
parsed.value = number
end
local function iscntrl(x)
if (x >= 0 and x <= 31) or (x == 127) then return true end
return false
end
local function isprint(x)
return not iscntrl(x)
end
local function fixed_string(str)
local new_str = ""
for i=1,string.len(str) do
char = string.byte(str, i)
if char == 34 then new_str = new_str .. string.format("\\\"")
elseif char == 92 then new_str = new_str .. string.format("\\\\")
elseif char == 7 then new_str = new_str .. string.format("\\a")
elseif char == 8 then new_str = new_str .. string.format("\\b")
elseif char == 12 then new_str = new_str .. string.format("\\f")
elseif char == 10 then new_str = new_str .. string.format("\\n")
elseif char == 13 then new_str = new_str .. string.format("\\r")
elseif char == 9 then new_str = new_str .. string.format("\\t")
elseif char == 11 then new_str = new_str .. string.format("\\v")
else
if isprint(char) then
new_str = new_str .. string.format("%c", char)
else
new_str = new_str .. string.format("\\%03d", char)
end
end
end
return new_str
end
local function setsvalue(parsed, string)
parsed.type = ladconf.LUA_TSTRING
parsed.value = fixed_string(string)
end
local function parse_number(bytecode, k)
sign = 1
mantissa = string.byte(bytecode, k+6) % 16
for i=k+5,k,-1 do mantissa = mantissa * 256 + string.byte(bytecode, i) end
if string.byte(bytecode,k+7) > 127 then sign = -1 end
exponent = (string.byte(bytecode, k+7) % 128) * 16 + math.floor(string.byte(bytecode, k+6) / 16)
if exponent == 0 then return 0 end
mantissa = (math.ldexp(mantissa, -52) + 1) * sign
return math.ldexp(mantissa, exponent - 1023)
end
local function parse_string(bytecode, k, len)
local str = ""
for i=k+1,k+(len-1) do
local byte = string.byte(bytecode, i)
if byte ~= 0 then
str = str .. string.char(byte)
else
str = str .. '\0'
end
end
return str
end
local function parse_constants(bytecode, k, parsed)
parsed.sizek,k = parse_int(bytecode, k, ladconf.INT)
parsed.k = {}
for i=1,parsed.sizek do
k = k + 1
local t = string.byte(bytecode, k)
parsed.k[i] = {}
if t == ladconf.LUA_TNIL then
setnilvalue(parsed.k[i])
elseif t == ladconf.LUA_TBOOLEAN then
k = k + 1
setbvalue(parsed.k[i], string.byte(bytecode, k))
elseif t == ladconf.LUA_TNUMBER then
setnvalue(parsed.k[i], parse_number(bytecode, k + 1))
k = k + ladconf.LUA_NUMBER
elseif t == ladconf.LUA_TSTRING then
local len
len,k = parse_int(bytecode, k + 1, ladconf.SIZE_T)
setsvalue(parsed.k[i], parse_string(bytecode, k, len))
k = k + len
end
end
return k
end
local function parse_upvalues(bytecode, k, parsed)
parsed.sizeupvalues,k = parse_int(bytecode, k, ladconf.INT)
parsed.upvalues = {}
for i=1,parsed.sizeupvalues do
parsed.upvalues[i] = {}
parsed.upvalues[i].instack = string.byte(bytecode, k + 1)
parsed.upvalues[i].idx = string.byte(bytecode, k + 2)
k = k + 2
end
return k
end
local function parse_debug(bytecode, k, parsed)
local len,n
len,k = parse_int(bytecode, k, ladconf.SIZE_T)
parsed.source = parse_string(bytecode, k + 1, len)
k = k + len
parsed.sizelineinfo,k = parse_int(bytecode, k + 1, ladconf.INT)
parsed.lineinfo = {}
for i=1,parsed.sizelineinfo do
parsed.lineinfo[i],k = parse_int(bytecode, k + 1, ladconf.INT)
end
parsed.sizelocvars,k = parse_int(bytecode, k + 1, ladconf.INT)
parsed.locvars = {}
for i=1,parsed.sizelocvars do
parsed.locvars[i] = {}
len,k = parse_int(bytecode, k + 1, ladconf.SIZE_T)
parsed.locvars[i].varname = parse_string(bytecode, k, len)
k = k + len
parsed.locvars[i].startpc,k = parse_int(bytecode, k + 1, ladconf.INT)
parsed.locvars[i].endpc,k = parse_int(bytecode, k + 1, ladconf.INT)
end
n,k = parse_int(bytecode, k + 1, ladconf.INT)
for i=1,n do
len,k = parse_int(bytecode, k + 1, ladconf.SIZE_T)
parsed.upvalues[i].name = parse_string(bytecode, k, len)
k = k + len
end
return k
end
local function parse_function(bytecode, k, parsed)
parsed.linedefined,k = parse_int(bytecode, k + 1, ladconf.INT)
parsed.lastlinedefined,k = parse_int(bytecode, k + 1, ladconf.INT)
parsed.numparams,k = string.byte(bytecode, k + 1), k + 1
parsed.is_vararg,k = string.byte(bytecode, k + 1), k + 1
parsed.maxstacksize,k = string.byte(bytecode, k + 1), k + 1
k = parse_code(bytecode, k + 1, parsed)
k = parse_constants(bytecode, k + 1, parsed)
parsed.sizep,k = parse_int(bytecode, k + 1, ladconf.INT)
parsed.p = {}
for i=1,parsed.sizep do
parsed.p[i] = {}
parsed.p[i].parent = parsed.id
parsed.p[i].id = i
k = parse_function(bytecode, k, parsed.p[i])
end
k = parse_upvalues(bytecode, k + 1, parsed)
k = parse_debug(bytecode, k + 1, parsed)
return k
end
local function parse(bytecode)
local parsed = {}
set_auto_flags(bytecode)
parsed.id = 0
parse_function(bytecode, ladconf.LUAC_HEADERSIZE, parsed)
return parsed
end
local function get_opcode(i)
return ladconf.get_bit(i, ladconf.POS_OP, ladconf.SIZE_OP) + 1
end
local function getarg_A(i)
return ladconf.get_bit(i, ladconf.POS_A, ladconf.SIZE_A)
end
local function getarg_B(i)
return ladconf.get_bit(i, ladconf.POS_B, ladconf.SIZE_B)
end
local function getarg_C(i)
return ladconf.get_bit(i, ladconf.POS_C, ladconf.SIZE_C)
end
local function getarg_Bx(i)
return ladconf.get_bit(i, ladconf.POS_Bx, ladconf.SIZE_Bx)
end
local function getarg_Ax(i)
return ladconf.get_bit(i, ladconf.POS_Ax, ladconf.SIZE_Ax)
end
local function getarg_sBx(i)
return getarg_Bx(i) - ladconf.MAXARG_sBx
end
local function get_b_mode(o)
return ladconf.opmodes[o].B
end
local function get_c_mode(o)
return ladconf.opmodes[o].C
end
local function ISK(x)
return ladconf.ISK(x)
end
local function INDEXK(x)
return ladconf.INDEXK(x)
end
local function MYK(x)
return (-1-(x))
end
local function UPVALNAME(parsed, x)
if parsed.upvalues[x].name then
return parsed.upvalues[x].name
end
return "-"
end
local function get_funcline(parsed, pc)
if parsed.sizelineinfo > 0 then
return parsed.lineinfo[pc]
end
return 0
end
local 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
local func_name = "function"
local is_vararg = ""
if parsed.linedefined == 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 at %d)\n",
func_name, parsed.source,
parsed.linedefined, parsed.lastlinedefined,
parsed.sizecode, SS(parsed.sizecode), 0))
io.write(string.format("%d%s param%s, %d slot%s, %d upvalue%s, ",
parsed.numparams, is_vararg, SS(parsed.numparams),
parsed.maxstacksize, SS(parsed.maxstacksize),
parsed.sizeupvalues, SS(parsed.sizeupvalues)))
io.write(string.format("%d local%s, %d constant%s, %d function%s\n",
parsed.sizelocvars, SS(parsed.sizelocvars),
parsed.sizek, SS(parsed.sizek),
parsed.sizep, SS(parsed.sizep)))
end
local function print_string(str)
io.write(string.format('"%s"', str))
end
local function print_constant(parsed, i)
if not parsed.k[i] then
io.write("nil")
return
end
local t = parsed.k[i].type
local v = parsed.k[i].value
if t == ladconf.LUA_TNIL or
t == ladconf.LUA_TBOOLEAN or
t == ladconf.LUA_TNUMBER then
io.write(string.format("%s", v))
elseif t == ladconf.LUA_TSTRING then
print_string(v)
end
end
local function print_code(parsed)
local n = parsed.sizecode
local pc = 1
while pc <= n do
local i = parsed.code[pc]
local o = get_opcode(i)
local a = getarg_A(i)
local b = getarg_B(i)
local c = getarg_C(i)
local ax = getarg_Ax(i)
local bx = getarg_Bx(i)
local sbx = getarg_sBx(i)
local line = get_funcline(parsed, pc)
local opcode = ladconf.OPCODE[o]
io.write(string.format("\t%d\t", pc))
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))
local opmode = ladconf.get_op_mode(o)
if opmode == ladconf.iABC then
io.write(string.format("%d", a))
local bmode = get_b_mode(o)
local cmode = get_c_mode(o)
if bmode ~= ladconf.OpArgN then
if ISK(b) then
io.write(string.format(" %d", MYK(INDEXK(b))))
else
io.write(string.format(" %d", b))
end
end
if cmode ~= ladconf.OpArgN then
if ISK(c) then
io.write(string.format(" %d", MYK(INDEXK(c))))
else
io.write(string.format(" %d", c))
end
end
elseif opmode == ladconf.iABx then
io.write(string.format("%d", a))
local bmode = get_b_mode(o)
if bmode == ladconf.OpArgK then
io.write(string.format(" %d", MYK(bx)))
end
if bmode == ladconf.OpArgU then
io.write(string.format(" %d", bx))
end
elseif opmode == ladconf.iAsBx then
io.write(string.format("%d %d", a, sbx))
elseif opmode == ladconf.iAx then
io.write(string.format("%d", MYK(ax)))
end
if opcode == "LOADK" then
io.write(string.format("\t; "))
print_constant(parsed, bx+1)
elseif opcode == "GETUPVAL" or
opcode == "SETUPVAL" then
io.write(string.format("\t; %s", UPVALNAME(parsed, b+1)))
elseif opcode == "GETTABUP" then
io.write(string.format("\t; %s", UPVALNAME(parsed, b+1)))
if ISK(c) then
io.write(string.format(" "))
print_constant(parsed, INDEXK(c+1))
end
elseif opcode == "SETTABUP" then
io.write(string.format("\t; %s", UPVALNAME(parsed, a+1)))
if ISK(b) then
io.write(string.format(" "))
print_constant(parsed, INDEXK(b+1))
end
if ISK(c) then
io.write(string.format(" "))
print_constant(parsed, INDEXK(c+1))
end
elseif opcode == "GETTABLE" or
opcode == "SELF" then
if ISK(c) then
io.write(string.format("\t; "))
print_constant(parsed, INDEXK(c+1))
end
elseif opcode == "SETTABLE" or
opcode == "ADD" or
opcode == "SUB" or
opcode == "MUL" or
opcode == "DIV" or
opcode == "POW" or
opcode == "EQ" or
opcode == "LT" or
opcode == "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 == "JMP" or
opcode == "FORLOOP" or
opcode == "FORPREP" or
opcode == "TFORLOOP" then
io.write(string.format("\t; to %d", sbx+pc+1))
elseif opcode == "CLOSURE" then
io.write(string.format("\t; 0x"))
elseif opcode == "SETLIST" then
if c == 0 then
pc = pc + 1
io.write(string.format("\t; %d", parsed.code[pc]))
else
io.write(string.format("\t; %d", c))
end
elseif opcode == "EXTRAARG" then
io.write(string.format("\t; "))
print_constant(parsed, ax+1)
end
io.write(string.format("\n"))
pc = pc + 1
end
end
local function print_debug(parsed)
local n
n = parsed.sizek
io.write(string.format("constants (%d) for 0:\n", n))
for i=1,n do
io.write(string.format("\t%d\t", i))
print_constant(parsed, i)
io.write(string.format("\n"))
end
n = parsed.sizelocvars
io.write(string.format("locals (%d) for 0:\n", n))
for i=1,n do
io.write(string.format("\t%d\t%s\t%d\t%d\n", i-1,
parsed.locvars[i].varname,
parsed.locvars[i].startpc+1,
parsed.locvars[i].endpc+1))
end
n = parsed.sizeupvalues
io.write(string.format("upvalues (%d) for 0:\n", n))
for i=1,n do
io.write(string.format("\t%d\t%s\t%d\t%d\n", i-1,
UPVALNAME(parsed, i),
parsed.upvalues[i].instack,
parsed.upvalues[i].idx))
end
end
local function print_function(parsed, full)
local n = parsed.sizep
print_header(parsed)
print_code(parsed)
if full then print_debug(parsed) end
for i=1,n do
print_function(parsed.p[i], full)
end
end
local function write_header(output, parsed)
local fname
local np = parsed.numparams
if parsed.linedefined == 0 and parsed.id == 0 then
fname = "main"
else
fname = string.format("F_%s_%s", parsed.parent, parsed.id)
end
output:write(string.format("\nfunction %s(%s):\n", fname, np))
end
local function write_par(output, x)
output:write(string.format("%s", x))
end
local function write_sep(output)
output:write(string.format(", "))
end
local function R(x)
return "$" .. x
end
local function UpValue(parsed, x)
return parsed.upvalues[x+1].name
end
local function KST(parsed, x)
return parsed.k[x+1].value
end
local function Kst(parsed, x)
local t = parsed.k[x+1].type
local v = parsed.k[x+1].value
if t == ladconf.LUA_TNIL or
t == ladconf.LUA_TBOOLEAN or
t == ladconf.LUA_TNUMBER then
return v
elseif t == ladconf.LUA_TSTRING then
return '"' .. v .. '"'
end
end
local function RKST(parsed, x)
if ISK(x) then
return KST(parsed, INDEXK(x))
else
return R(x)
end
end
local function RK(parsed, x)
if ISK(x) then
return Kst(parsed, INDEXK(x))
else
return R(x)
end
end
local function KPROTO(parsed, x)
local parent = parsed.p[x+1].parent
local id = parsed.p[x+1].id
return string.format("F_%s_%s", parent, id)
end
local function write_code(output, parsed)
local n = parsed.sizecode
for pc=1,n do
local i = parsed.code[pc]
local o = get_opcode(i)
local a = getarg_A(i)
local b = getarg_B(i)
local c = getarg_C(i)
local ax = getarg_Ax(i)
local bx = getarg_Bx(i)
local sbx = getarg_sBx(i)
local line = get_funcline(parsed, pc)
local opcode = ladconf.OPCODE[o]
output:write(string.format("\t%d\t", pc))
output:write(string.format("[%d]\t", line))
output:write(string.format("%-9s\t", opcode))
if opcode == "MOVE" or
opcode == "UNM" or
opcode == "NOT" or
opcode == "LEN" then
-- R(A) R(B)
write_par(output, R(a))
write_sep(output)
write_par(output, R(b))
elseif opcode == "LOADK" then
-- R(A) Kst(Bx)
write_par(output, R(a))
write_sep(output)
write_par(output, Kst(parsed, bx))
elseif opcode == "LOADKX" then
-- R(A)
write_par(output, R(a))
elseif opcode == "LOADBOOL" or
opcode == "NEWTABLE" or
opcode == "CALL" or
opcode == "TAILCALL" or
opcode == "SETLIST" then
-- R(A) B C
write_par(output, R(a))
write_sep(output)
write_par(output, b)
write_sep(output)
write_par(output, c)
elseif opcode == "LOADNIL" or
opcode == "RETURN" or
opcode == "VARARG" then
-- R(A) B
write_par(output, R(a))
write_sep(output)
write_par(output, b)
elseif opcode == "GETUPVAL" or
opcode == "SETUPVAL" then
-- R(A) UpValue(B)
write_par(output, R(a))
write_sep(output)
write_par(output, UpValue(parsed, b))
elseif opcode == "GETTABUP" then
-- R(A) UpValue(B) RK(C)
write_par(output, R(a))
write_sep(output)
write_par(output, UpValue(parsed, b))
write_sep(output)
write_par(output, RK(parsed, c))
elseif opcode == "GETTABLE" or
opcode == "SELF" then
-- R(A) R(B) RK(C)
write_par(output, R(a))
write_sep(output)
write_par(output, R(b))
write_sep(output)
write_par(output, RK(parsed, c))
elseif opcode == "SETTABUP" then
-- UpValue(A) RK(B) RK(C)
write_par(output, UpValue(parsed, a))
write_sep(output)
write_par(output, RK(parsed, b))
write_sep(output)
write_par(output, RK(parsed, c))
elseif opcode == "SETTABLE" or
opcode == "ADD" or
opcode == "SUB" or
opcode == "MUL" or
opcode == "DIV" or
opcode == "MOD" or
opcode == "POW" then
-- R(A) RK(B) RK(C)
write_par(output, R(a))
write_sep(output)
write_par(output, RK(parsed, b))
write_sep(output)
write_par(output, RK(parsed, c))
elseif opcode == "CONCAT" then
-- R(A) R(B) R(C)
write_par(output, R(a))
write_sep(output)
write_par(output, R(b))
write_sep(output)
write_par(output, R(c))
elseif opcode == "JMP" then
-- A sBx
write_par(output, a)
write_sep(output)
write_par(output, sbx + pc + 1)
elseif opcode == "EQ" or
opcode == "LT" or
opcode == "LE" then
-- A RK(B) RK(C)
write_par(output, a)
write_sep(output)
write_par(output, RK(parsed, b))
write_sep(output)
write_par(output, RK(parsed, c))
elseif opcode == "TEST" or
opcode == "TFORCALL" then
-- R(A) C
write_par(output, R(a))
write_sep(output)
write_par(output, c)
elseif opcode == "TESTSET" then
-- R(A) R(B) C
write_par(output, R(a))
write_sep(output)
write_par(output, R(b))
write_sep(output)
write_par(output, c)
elseif opcode == "FORLOOP" or
opcode == "FORPREP" or
opcode == "TFORLOOP" then
-- R(A) sBx
write_par(output, R(a))
write_sep(output)
write_par(output, sbx + pc + 1)
elseif opcode == "CLOSURE" then
-- R(A) KPROTO(Bx)
write_par(output, R(a))
write_sep(output)
write_par(output, KPROTO(parsed, bx))
elseif opcode == "EXTRAARG" then
-- Ax
po = get_opcode(parsed.code[pc-1])
popcode = ladconf.OPCODE[po]
if popcode == "LOADKX" then
write_par(output, Kst(parsed, ax))
elseif popcode == "SETLIST" then
write_par(output, ax)
end
end
output:write(string.format("\n"))
end
end
local function write_function(output, parsed)
local n = parsed.sizep
write_header(output, parsed)
write_code(output, parsed)
for i=1,n do
write_function(output, parsed.p[i])
end
end
local function write(filename, parsed)
local output = assert(io.open(filename, "w"))
write_function(output, parsed)
output:close()
end
local disassembler = {
parse = parse,
write = write,
print_function = print_function,
}
return disassembler