#!/usr/bin/env lua
title = [[
ChunkSpy: A Lua 5.3 binary chunk disassembler
Version 0.9.9 (20150329)
Copyright (c) 2004-2006 Kein-Hong Man , 2014-2015 VirusCamp
The COPYRIGHT file describes the conditions under which this
software may be distributed (basically a Lua 5-style license.)
]]
USAGE = [[
usage: %s [options] [filenames]
options:
-h, --help prints usage information
--stats prints some statistical information
--brief generate assembly-style brief listing
--auto auto detects binary chunk profile
-o <file> specify file name to write output listing
--source <file> generate listing from a Lua source file
--rewrite "plat" generate binary chunk using given profile;
use "local" for local binary chunk format
--run convert to local format, load and execute
--test perform internal tests only
--sample generate sample listing only
--interact *interactive mode*, output brief listings
-- stop handling arguments
example:
>luac myscript.lua
>%s luac.out myscript.lua -o myscript.lst
* Other configuration settings can be customized in the script
* If source filename is specified, the source listing is merged in
]]
interactive_help = [[
Type 'exit' or 'quit' to end the interactive session. 'help' displays
this message. ChunkSpy will attempt to turn anything else into a
binary chunk and process it into an assembly-style listing.
A '\' can be used as a line continuation symbol; this allows multiple
lines to be strung together.
]]
CONFIGURATION = {
["x86 standard"] = {
description = "x86 standard (32-bit, little endian, long long, double)",
endianness = 1, size_int = 4, size_size_t = 4,
size_Instruction = 4,
size_lua_Integer = 8,
integer_type = "long long",
size_lua_Number = 8, integral = 0, number_type = "double", },
["x64 standard"] = {
description = "x64 standard (64-bit, little endian, long long, double)",
endianness = 1, size_int = 4, size_size_t = 8,
size_Instruction = 4,
size_lua_Integer = 8,
integer_type = "long long",
size_lua_Number = 8, integral = 0, number_type = "double", },
["big endian int"] = {
description = "(32-bit, big endian, int, int)",
endianness = 0,
size_int = 4,
size_size_t = 4,
size_Instruction = 4,
size_lua_Integer = 4,
integer_type = "int",
size_lua_Number = 4,
integral = 1,
number_type = "int",
},
["x86 single"] = {
description = "x86 single (32-bit, little endian, int, single)",
endianness = 1,
size_int = 4,
size_size_t = 4,
size_Instruction = 4,
size_lua_Integer = 4,
integer_type = "int",
size_lua_Number = 4,
integral = 0,
number_type = "single",
},
}
config = {}
function CheckLuaVersion(fmt)
if _VERSION ~= "Lua 5.3" then
if type(fmt) == "string" then
msg = string.format(fmt, "Lua 5.3")
else
msg = "needs Lua 5.3"
end
error(msg)
end
end
function SetProfile(profile)
if profile == "local" then
local flag1, flag2 = config.DISPLAY_FLAG, config.AUTO_DETECT
config.DISPLAY_FLAG, config.AUTO_DETECT = false, true
local LUA_SAMPLE = string.dump(function() end)
config.SIGNATURE = "\27Lua"
config.LUAC_DATA = "\25\147\r\n\26\n"
local ok, _ = pcall(ChunkSpy, "", LUA_SAMPLE)
if not ok then error("error compiling sample to test local profile") end
config.DISPLAY_FLAG, config.AUTO_DETECT = flag1, flag2
else
local c = CONFIGURATION[profile]
if not c then return false end
if not c.SIGNATURE then c.SIGNATURE = "\27Lua" end
if not c.LUAC_DATA then c.LUAC_DATA = "\25\147\r\n\26\n" end
for i, v in pairs(c) do config[i] = v end
end
return true
end
SetProfile("x64 standard")
config.SIGNATURE = "\27Lua"
config.LUAC_DATA = "\25\147\r\n\26\n" config.LUA_TNIL = 0
config.LUA_TBOOLEAN = 1
config.LUA_TNUMBER = 3
config.LUA_TNUMFLT = config.LUA_TNUMBER | (0 << 4)
config.LUA_TNUMINT = config.LUA_TNUMBER | (1 << 4)
config.LUA_TSTRING = 4
config.LUA_TSHRSTR = config.LUA_TSTRING | (0 << 4)
config.LUA_TLNGSTR = config.LUA_TSTRING | (1 << 4)
config.VERSION = 83 config.FORMAT = 0 config.FPF = 50 config.SIZE_OP = 6 config.SIZE_A = 8
config.SIZE_B = 9
config.SIZE_C = 9
config.LUA_FIRSTINDEX = 1
config.typestr = {
[config.LUA_TNIL] = "LUA_TNIL",
[config.LUA_TBOOLEAN] = "LUA_TBOOLEAN",
[config.LUA_TNUMFLT] = "LUA_TNUMFLT",
[config.LUA_TNUMINT] = "LUA_TNUMINT",
[config.LUA_TSHRSTR] = "LUA_TSHRSTR",
[config.LUA_TLNGSTR] = "LUA_TLNGSTR",
}
config.DISPLAY_FLAG = true config.DISPLAY_BRIEF = nil config.DISPLAY_INDENT = nil config.STATS = nil config.DISPLAY_OFFSET_HEX = true config.DISPLAY_SEP = " " config.DISPLAY_COMMENT = "; " config.DISPLAY_HEX_DATA = true config.WIDTH_HEX = 8 config.WIDTH_OFFSET = nil config.DISPLAY_LOWERCASE = true config.WIDTH_OPCODE = nil config.VERBOSE_TEST = false
other_files = {} arg_other = {}
convert_from = {} convert_to = {}
function grab_byte(v)
return math.floor(v / 256), string.char(math.floor(v) % 256)
end
LUANUMBER_ID = {
["80"] = "double", ["40"] = "single", ["41"] = "int", ["81"] = "long long", }
local function convert_from_double(x)
local sign = 1
local 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
local exponent = (string.byte(x, 8) % 128) * 16 +
math.floor(string.byte(x, 7) / 16)
if exponent == 0 then return 0.0 end
mantissa = (math.ldexp(mantissa, -52) + 1.0) * sign
return math.ldexp(mantissa, exponent - 1023)
end
convert_from["double"] = convert_from_double
local function convert_from_single(x)
local sign = 1
local mantissa = string.byte(x, 3) % 128
for i = 2, 1, -1 do mantissa = mantissa * 256 + string.byte(x, i) end
if string.byte(x, 4) > 127 then sign = -1 end
local exponent = (string.byte(x, 4) % 128) * 2 +
math.floor(string.byte(x, 3) / 128)
if exponent == 0 then return 0.0 end
mantissa = (math.ldexp(mantissa, -23) + 1.0) * sign
return math.ldexp(mantissa, exponent - 127)
end
convert_from["single"] = convert_from_single
local function convert_from_int(x, size_int)
size_int = size_int or 8
local sum = 0
local highestbyte = string.byte(x, size_int)
if highestbyte <= 127 then
sum = highestbyte
else
sum = highestbyte - 256
end
for i = size_int-1, 1, -1 do
sum = sum * 256 + string.byte(x, i)
end
return sum
end
convert_from["int"] = function(x) return convert_from_int(x, 4) end
convert_from["long long"] = convert_from_int
convert_to["double"] = function(x)
local sign = 0
if x < 0 then sign = 1; x = -x end
local mantissa, exponent = math.frexp(x)
if x == 0 then mantissa, exponent = 0, 0
else
mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53)
exponent = exponent + 1022
end
local v, byte = "" x = mantissa
for i = 1,6 do
x, byte = grab_byte(x); v = v..byte end
x, byte = grab_byte(exponent * 16 + x); v = v..byte x, byte = grab_byte(sign * 128 + x); v = v..byte return v
end
convert_to["single"] = function(x)
local sign = 0
if x < 0 then sign = 1; x = -x end
local mantissa, exponent = math.frexp(x)
if x == 0 then mantissa = 0; exponent = 0
else
mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 24)
exponent = exponent + 126
end
local v, byte = "" x, byte = grab_byte(mantissa); v = v..byte x, byte = grab_byte(x); v = v..byte x, byte = grab_byte(exponent * 128 + x); v = v..byte x, byte = grab_byte(sign * 128 + x); v = v..byte return v
end
convert_to["int"] = function(x, size_int)
size_int = size_int or config.size_lua_Integer or 4
local v = ""
x = math.floor(x)
if x >= 0 then
for i = 1, size_int do
v = v..string.char(x % 256); x = math.floor(x / 256)
end
else x = -x
local carry = 1
for i = 1, size_int do
local c = 255 - (x % 256) + carry
if c == 256 then c = 0; carry = 1 else carry = 0 end
v = v..string.char(c); x = math.floor(x / 256)
end
end
return v
end
convert_to["long long"] = convert_to["int"]
function WidthOf(n) return string.len(tostring(n)) end
function LeftJustify(s, width) return s..string.rep(" ", width - string.len(s)) end
function ZeroPad(s, width) return string.rep("0", width - string.len(s))..s end
function DisplayInit(chunk_size)
if not config.WIDTH_OFFSET then config.WIDTH_OFFSET = 0 end
if config.DISPLAY_OFFSET_HEX then
local w = string.len(string.format("%X", chunk_size))
if w > config.WIDTH_OFFSET then config.WIDTH_OFFSET = w end
if (config.WIDTH_OFFSET % 2) == 1 then
config.WIDTH_OFFSET = config.WIDTH_OFFSET + 1
end
else
config.WIDTH_OFFSET = string.len(tonumber(chunk_size))
end
if config.WIDTH_OFFSET < 4 then config.WIDTH_OFFSET = 4 end
if not config.DISPLAY_SEP then config.DISPLAY_SEP = " " end
if config.DISPLAY_HEX_DATA == nil then config.DISPLAY_HEX_DATA = true end
if not config.WIDTH_HEX then config.WIDTH_HEX = 8 end
config.BLANKS_HEX_DATA = string.rep(" ", config.WIDTH_HEX * 2 + 1)
if not WriteLine then WriteLine = print end
end
function OutputInit()
if config.OUTPUT_FILE then
if type(config.OUTPUT_FILE) == "string" then
local INF = io.open(config.OUTPUT_FILE, "wb")
if not INF then
error("cannot open \""..config.OUTPUT_FILE.."\" for writing")
end
config.OUTPUT_FILE = INF
WriteLine = function(s) config.OUTPUT_FILE:write(s, "\n") end
end
end
end
function OutputExit()
if WriteLine and WriteLine ~= print then io.close(config.OUTPUT_FILE) end
end
function EscapeString(s, quoted)
local v = ""
for i = 1, string.len(s) do
local c = string.byte(s, i)
if c < 32 or c == 34 or c == 92 or c > 126 then
if c >= 7 and c <= 13 then
c = string.sub("abtnvfr", c - 6, c - 6)
elseif c == 34 or c == 92 then
c = string.char(c)
end
v = v.."\\"..c
else v = v..string.char(c)
end
end
if quoted then return string.format("\"%s\"", v) end
return v
end
function HeaderLine()
if not config.DISPLAY_FLAG or config.DISPLAY_BRIEF then return end
WriteLine(LeftJustify("Pos", config.WIDTH_OFFSET)..config.DISPLAY_SEP
..LeftJustify("Hex Data", config.WIDTH_HEX * 2 + 1)..config.DISPLAY_SEP
.."Description or Code\n"
..string.rep("-", 72))
end
function DescLine(desc)
if not config.DISPLAY_FLAG or config.DISPLAY_BRIEF then return end
WriteLine(string.rep(" ", config.WIDTH_OFFSET)..config.DISPLAY_SEP
..config.BLANKS_HEX_DATA..config.DISPLAY_SEP
..desc)
end
function DisplayStat(stat)
if config.STATS and not config.DISPLAY_BRIEF then DescLine(stat) end
end
function FormatPos(i)
local pos
if config.DISPLAY_OFFSET_HEX then
pos = string.format("%X", i - 1)
else
pos = tonumber(i - 1)
end
return ZeroPad(pos, config.WIDTH_OFFSET)
end
local iABC, iABx, iAsBx, iAx = 0, 1, 2, 3
function DecodeInit()
config.SIZE_Bx = config.SIZE_B + config.SIZE_C
config.SIZE_Ax = config.SIZE_A + config.SIZE_B + config.SIZE_C
local MASK_OP = 1 << config.SIZE_OP
local MASK_A = 1 << config.SIZE_A
local MASK_B = 1 << config.SIZE_B
local MASK_C = 1 << config.SIZE_C
local MASK_Bx = 1 << config.SIZE_Bx
local MASK_Ax = 1 << config.SIZE_Ax
config.MAXARG_sBx = (MASK_Bx - 1) >> 1
config.BITRK = 1 << (config.SIZE_B - 1)
config.iABC = { config.SIZE_OP, config.SIZE_A, config.SIZE_C,
config.SIZE_B,
}
config.mABC = { MASK_OP, MASK_A, MASK_C, MASK_B, }
config.nABC = { "OP", "A", "C", "B", }
local op = [[
MOVE LOADK LOADKX LOADBOOL LOADNIL
GETUPVAL GETTABUP GETTABLE SETTABUP SETUPVAL
SETTABLE NEWTABLE SELF ADD SUB
MUL MOD POW DIV IDIV
BAND BOR BXOR SHL SHR
UNM BNOT NOT LEN CONCAT
JMP EQ LT LE TEST
TESTSET CALL TAILCALL RETURN FORLOOP
FORPREP TFORCALL TFORLOOP SETLIST CLOSURE
VARARG EXTRAARG
]]
iABC=0; iABx=1; iAsBx=2; iAx=3
config.opmode = {
[0]=iABC,iABx,iABx,iABC,iABC,
iABC,iABC,iABC,iABC,iABC,
iABC,iABC,iABC,iABC,iABC,
iABC,iABC,iABC,iABC,iABC,
iABC,iABC,iABC,iABC,iABC,
iABC,iABC,iABC,iABC,iABC,
iAsBx,iABC,iABC,iABC,iABC,
iABC,iABC,iABC,iABC,iAsBx,
iAsBx,iABC,iAsBx,iABC,iABx,
iABC,iAx
}
config.opnames = {}
config.opcodes = {}
config.NUM_OPCODES = 0
if not config.WIDTH_OPCODE then config.WIDTH_OPCODE = 0 end
for v in string.gmatch(op, "[^%s]+") do
if config.DISPLAY_LOWERCASE then v = string.lower(v) end
config.opnames[config.NUM_OPCODES] = v
config.opcodes[v] = config.NUM_OPCODES
local vlen = string.len(v)
if vlen > config.WIDTH_OPCODE then
config.WIDTH_OPCODE = vlen
end
config.NUM_OPCODES = config.NUM_OPCODES + 1
end
config.operators={
[config.opcodes["add"]]="+",
[config.opcodes["sub"]]="-",
[config.opcodes["mul"]]="*",
[config.opcodes["div"]]="/",
[config.opcodes["mod"]]="%",
[config.opcodes["pow"]]="^",
[config.opcodes["unm"]]="-",
[config.opcodes["not"]]="not ",
[config.opcodes["len"]]="#",
[config.opcodes["eq"]]="==",
[config.opcodes["lt"]]="<",
[config.opcodes["le"]]="<=",
[config.opcodes["idiv"]]="//",
[config.opcodes["band"]]="&",
[config.opcodes["bor"]]="|",
[config.opcodes["bxor"]]="~",
[config.opcodes["shl"]]="<<",
[config.opcodes["shr"]]=">>",
[config.opcodes["bnot"]]="~",
}
config.WIDTH_A = WidthOf(MASK_A)
config.WIDTH_B = WidthOf(MASK_B)
config.WIDTH_C = WidthOf(MASK_C)
config.WIDTH_Bx = WidthOf(MASK_Bx) + 1 config.WIDTH_Ax = WidthOf(MASK_Ax)
config.FORMAT_A = string.format("%%-%dd", config.WIDTH_A)
config.FORMAT_B = string.format("%%-%dd", config.WIDTH_B)
config.FORMAT_C = string.format("%%-%dd", config.WIDTH_C)
config.PAD_Bx = config.WIDTH_A + config.WIDTH_B + config.WIDTH_C + 2
- config.WIDTH_Bx
if config.PAD_Bx > 0 then
config.PAD_Bx = string.rep(" ", config.PAD_Bx)
else
config.PAD_Bx = ""
end
config.PAD_Ax = config.WIDTH_A + config.WIDTH_B + config.WIDTH_C + 2
- config.WIDTH_Ax
if config.PAD_Ax > 0 then
config.PAD_Ax = string.rep(" ", config.PAD_Ax)
else
config.PAD_Ax = ""
end
config.FORMAT_Bx = string.format("%%-%dd", config.WIDTH_Bx)
config.FORMAT_AB = string.format("%s %s %s", config.FORMAT_A, config.FORMAT_B, string.rep(" ", config.WIDTH_C))
config.FORMAT_ABC = string.format("%s %s %s", config.FORMAT_A, config.FORMAT_B, config.FORMAT_C)
config.FORMAT_AC = string.format("%s %s %s", config.FORMAT_A, string.rep(" ", config.WIDTH_B), config.FORMAT_C)
config.FORMAT_ABx = string.format("%s %s", config.FORMAT_A, config.FORMAT_Bx)
config.FORMAT_A1 = string.format("%s %s %s", config.FORMAT_A, string.rep(" ", config.WIDTH_B), string.rep(" ", config.WIDTH_C))
config.FORMAT_Ax = string.format("%%-%dd", config.WIDTH_Ax)
end
function DecodeInst(code, iValues)
local iSeq, iMask = config.iABC, config.mABC
local cValue, cBits, cPos = 0, 0, 1
for i = 1, #iSeq do
while cBits < iSeq[i] do
cValue = string.byte(code, cPos) * (1 << cBits) + cValue
cPos = cPos + 1; cBits = cBits + 8
end
iValues[config.nABC[i]] = cValue % iMask[i]
cValue = cValue // iMask[i]
cBits = cBits - iSeq[i]
end
iValues.opname = config.opnames[iValues.OP] iValues.opmode = config.opmode[iValues.OP]
if iValues.opmode == iABx then iValues.Bx = iValues.B * iMask[3] + iValues.C
elseif iValues.opmode == iAsBx then
iValues.sBx = iValues.B * iMask[3] + iValues.C - config.MAXARG_sBx
elseif iValues.opmode == iAx then
iValues.Ax = iValues.B * iMask[3] * iMask[2] + iValues.C * iMask[2] + iValues.A
end
return iValues
end
function EncodeInst(inst)
local v, i = "", 0
local cValue, cBits, cPos = 0, 0, 1
while i < config.size_Instruction do
while cBits < 8 do
cValue = inst[config.nABC[cPos]] << cBits + cValue
cBits = cBits + config.iABC[cPos]; cPos = cPos + 1
end
while cBits >= 8 do
v = v..string.char(cValue % 256)
cValue = math.floor(cValue / 256)
cBits = cBits - 8; i = i + 1
end
end
return v
end
function DescribeInst(inst, pos, func)
local Operand
local Comment = ""
local CommentArg = ""
local CommentRtn = ""
local function OperandAB(i) return string.format(config.FORMAT_AB, i.A, i.B) end
local function OperandABC(i) return string.format(config.FORMAT_ABC, i.A, i.B, i.C) end
local function OperandAC(i) return string.format(config.FORMAT_AC, i.A, i.C) end
local function OperandABx(i) return string.format(config.FORMAT_ABx, i.A, i.Bx) end
local function OperandAsBx(i) return string.format(config.FORMAT_ABx, i.A, i.sBx) end
local function OperandA1(i) return string.format(config.FORMAT_A1, i.A) end
local function OperandAx(i) return string.format(config.FORMAT_Ax, i.Ax) end
local function CommentLoc(sbx, cond)
local loc = string.format("pc+=%d (goto [%d])", sbx, pos + 1 + sbx)
if cond then loc = loc..cond end
return loc
end
local function IS_CONSTANT(r)
return (r >= config.BITRK)
end
local function Kst(index, quoted)
local typec = func.typek[index + 1]
local c = func.k[index + 1]
if typec == config.LUA_TSHRSTR or typec == config.LUA_TLNGSTR then
return EscapeString(c.val, quoted)
elseif type(c) == "number" or type(c) == "boolean" then
return tostring(c)
else
return "nil"
end
end
local function K(index)
return "K"..tostring(index).."(="..Kst(index, true)..")"
end
local function RName(index)
return nil
end
local function R(index)
local name = RName(index)
if name and name ~= "" then
return "R"..tostring(index).."(="..name..")"
else
return "R"..tostring(index)
end
end
local function RK(index)
if IS_CONSTANT(index) then
return K(index - config.BITRK)
else
return R(index)
end
end
local function UName(x)
local upvalue = func.upvalues[x + 1]
if upvalue and upvalue.name then
return EscapeString(upvalue.name)
else
return nil
end
end
local function U(x)
local name = UName(x)
if name and name ~= "" then
return 'U'..tostring(x).."(="..name..")"
else
return 'U'..tostring(x)
end
end
local function RList(start,num)
if (num>2) then
return "R"..start.." to R"..(start+num-1)
elseif (num==2) then
return "R"..start..", R"..(start+1)
elseif (num==1) then
return "R"..start
elseif (num==0) then
return ""
else
return "R"..start.." to top"
end
end
local function fb2int(x)
local e = math.floor(x / 8) % 32
if e == 0 then return x end
return math.ldexp((x % 8) + 8, e - 1)
end
local a=inst.A
local b=inst.B
local c=inst.C
local bx=inst.Bx
local sbx=inst.sBx
local ax=inst.Ax
local o=inst.OP
local pc=pos
local isop_opc = config.opcodes
local isop_lower = string.lower
local function isop(opname)
return o == isop_opc[isop_lower(opname)]
end
if inst.prev then Operand = string.format(config.FORMAT_Ax, inst.Ax)..config.PAD_Ax
elseif isop("MOVE") then Operand = OperandAB(inst)
Comment = string.format("%s := %s",R(a),R(b))
elseif isop("LOADK") then Operand = OperandABx(inst)
Comment = string.format("%s := %s",R(a),K(bx))
elseif isop("LOADKX") then Operand = OperandA1(inst)
Comment = string.format("%s :=",R(a))
elseif isop("EXTRAARG") then Operand = OperandAx(inst)
Comment = K(ax)
elseif isop("LOADBOOL") then Operand = OperandABC(inst)
local v
if b == 0 then v = "false" else v = "true" end
if c > 0 then
Comment = string.format("%s := %s; %s",R(a),v,CommentLoc(1));
else
Comment = string.format("%s := %s",R(a),v)
end
v=nil
elseif isop("LOADNIL") then Operand = OperandAB(inst)
Comment = RList(a,b+1).." := nil"
elseif isop("GETUPVAL") then Operand = OperandAB(inst)
Comment = string.format("%s := %s", R(a), U(b));
elseif isop("SETUPVAL") then Operand = OperandAB(inst)
Comment = string.format("%s := %s", U(b), R(a))
elseif isop("GETTABUP") then Operand = OperandABC(inst)
Comment = string.format("%s := %s[%s]", R(a), U(b), RK(c))
elseif isop("SETTABUP") then Operand = OperandABC(inst)
Comment = string.format("%s[%s] := %s", U(a), RK(b), RK(c))
elseif isop("GETTABLE") then Operand = OperandABC(inst)
Comment = string.format("%s := %s[%s]",R(a),R(b),RK(c))
elseif isop("SETTABLE") then Operand = OperandABC(inst)
Comment = string.format("%s[%s] := %s",R(a),RK(b),RK(c))
elseif isop("NEWTABLE") then Operand = OperandABC(inst)
local ar = fb2int(b) local hs = fb2int(c) Comment = string.format("%s := {} , array_size=%d, hash_size=%d",R(a),ar,hs)
elseif isop("SELF") then Operand = OperandABC(inst)
Comment = string.format("R%d := %s; %s := %s[%s]",a+1,R(b),R(a),R(b),RK(c))
elseif isop("ADD") or isop("SUB") or isop("MUL") or isop("DIV") or isop("MOD") or isop("POW") or isop("IDIV") or isop("BAND") or isop("BOR") or isop("BXOR") or isop("SHL") or isop("SHR") then Operand = OperandABC(inst)
Comment = string.format("%s := %s %s %s",R(a),RK(b),config.operators[inst.OP],RK(c))
elseif isop("UNM") or isop("NOT") or isop("LEN") or isop("BNOT") then Operand = OperandAB(inst)
Comment = string.format("%s := %s %s",R(a),config.operators[inst.OP],RK(b))
elseif isop("CONCAT") then Operand = OperandABC(inst)
Comment = string.format("%s := %s",R(a),RList(b,c-b+1))
elseif isop("JMP") then Operand = OperandAsBx(inst)
if a>0 then
Comment="close all upvalues >= R"..(a-1).."; "
else
Comment=""
end
Comment = Comment..CommentLoc(sbx)
elseif isop("EQ") or isop("LT") or isop("LE") then Operand = OperandABC(inst)
Comment = string.format("%s %s %s, ",RK(b),config.operators[inst.OP],RK(c))
local sense = " if false"
if inst.A == 0 then sense = " if true" end
Comment = Comment..CommentLoc(1, sense)
elseif isop("TESTSET") then Operand = OperandABC(inst)
local sense = " "
if c == 0 then sense = " not " end
Comment = string.format("if%s%s then %s = %s else ",sense,R(b),R(a),R(b))
Comment = Comment..CommentLoc(1)
elseif isop("TEST") then Operand = OperandAC(inst)
local sense = " not "
if c == 0 then sense = " " end
Comment = string.format("if%s%s then ",sense,R(a))
Comment = Comment..CommentLoc(1)
elseif isop("CALL") or isop("TAILCALL") then Operand = OperandABC(inst)
CommentArg = RList(a+1,b-1)
CommentRtn = RList(a,c-1)
Comment = string.format("%s := %s(%s)",CommentRtn,R(a),CommentArg);
elseif isop("RETURN") then Operand = OperandAB(inst)
CommentRtn = RList(a,b-1)
Comment = "return "..CommentRtn
elseif isop("FORLOOP") then Operand = OperandAsBx(inst)
Comment = string.format("R%d += R%d; if R%d <= R%d then { R%d := R%d; %s }",a,a+2,a,a+1,a+3,a,CommentLoc(sbx));
elseif isop("FORPREP") then Operand = OperandAsBx(inst)
Comment = string.format("R%d -= R%d; %s",a,a+2,CommentLoc(sbx));
elseif isop("TFORCALL") then Operand = OperandAC(inst)
if (c>0) then
CommentRtn = RList(a+3,c)
else
CommentRtn = "Error Regs"
end
Comment = string.format("%s := R%d(R%d,R%d)", CommentRtn, a, a+1,a+2);
elseif isop("TFORLOOP") then Operand = OperandAsBx(inst)
Comment = string.format("if R%d ~= nil then { R%d := R%d; %s", a+1,a, a+1, CommentLoc(sbx));
elseif isop("SETLIST") then Operand = OperandABC(inst)
if c == 0 then
local ninst = {}
DecodeInst(func.code[pos + 1], ninst)
c = ninst.Ax
func.inst[pos + 1].prev = true
end
local start = (c - 1) * config.FPF + 1
local last = start + b - 1
CommentArg = start.." to "
local EndReg
if b ~= 0 then
CommentArg = CommentArg..last
EndReg = "R"..(a+last)
else
CommentArg = CommentArg.."top"
EndReg = "top"
end
Comment = string.format("%s[%s] := R%d to %s",R(a),CommentArg,a+1,EndReg)
elseif isop("CLOSURE") then Operand = OperandABx(inst)
Comment = func.p[bx + 1].sizeupvalues.." upvalues"
Comment = string.format("%s := closure(function[%d]) %s",R(a),bx,Comment)
elseif isop("VARARG") then Operand = OperandAB(inst)
CommentRtn = RList(a,b-1)
if ( b==1 or b<0 ) then
CommentRtn = "Error Regs"
end
Comment = CommentRtn.." := ..."
else
Operand = string.format("OP %d %s", inst.OP, config.opnames[inst.OP])
end
if Comment and Comment ~= "" then
Operand = Operand..config.DISPLAY_SEP
..config.DISPLAY_COMMENT..Comment
end
return LeftJustify(inst.opname, config.WIDTH_OPCODE)
..config.DISPLAY_SEP..Operand
end
function SourceInit(source)
if config.source then config.srcprev = 0; return end
if not source or source == "" or
string.sub(source, 1, 1) ~= "@" then
return
end
source = string.sub(source, 2) for _, fname in ipairs(other_files) do if not config.source then
if fname == source or
string.lower(fname) == string.lower(source) then
config.source = fname
end
end
end
if not config.source then return end local INF = io.open(config.source, "rb") if not INF then
error("cannot read file \""..filename.."\"")
end
config.srcline = {}; config.srcmark = {}
local n, line = 1
repeat
line = INF:read("*l")
if line then
config.srcline[n], config.srcmark[n] = line, false
n = n + 1
end
until not line
io.close(INF)
config.srcsize = n - 1
config.DISPLAY_SRC_WIDTH = WidthOf(config.srcsize)
config.srcprev = 0
end
function SourceMark(func)
if not config.source then return end
if func.sizelineinfo == 0 then return end
for i = 1, func.sizelineinfo do
if i <= config.srcsize then
config.srcmark[func.lineinfo[i]] = true
end
end
end
function SourceMerge(func, pc)
if not config.source or not config.DISPLAY_FLAG then return end
local lnum = func.lineinfo[pc]
if config.srcprev == lnum then return end
config.srcprev = lnum
if config.srcsize < lnum then return end local lfrom = lnum
config.srcmark[lnum] = true
while lfrom > 1 and config.srcmark[lfrom - 1] == false do
lfrom = lfrom - 1
config.srcmark[lfrom] = true
end
for i = lfrom, lnum do
WriteLine(config.DISPLAY_COMMENT
.."("..ZeroPad(i, config.DISPLAY_SRC_WIDTH)..")"
..config.DISPLAY_SEP..config.srcline[i])
end
end
function ChunkSpy(chunk_name, chunk)
local idx = 1
local previdx, len
local result = {} local stat = {}
result.chunk_name = chunk_name or ""
result.chunk_size = string.len(chunk)
local function TestChunk(size, idx, errmsg)
if idx + size - 1 > result.chunk_size then
error(string.format("chunk too small for %s at offset %d", errmsg, idx - 1))
end
end
local function LoadByte()
previdx = idx
idx = idx + 1
return string.byte(chunk, previdx)
end
local function LoadBlock(size, notreverse)
if not pcall(TestChunk, size, idx, "LoadBlock") then return end
previdx = idx
idx = idx + size
local b = string.sub(chunk, idx - size, idx - 1)
if config.endianness == 1 or notreverse then
return b
else return string.reverse(b)
end
end
function FormatLine(size, desc, index, segment)
if not config.DISPLAY_FLAG or config.DISPLAY_BRIEF then return end
if config.DISPLAY_HEX_DATA then
if size == 0 then
WriteLine(FormatPos(index)..config.DISPLAY_SEP
..config.BLANKS_HEX_DATA..config.DISPLAY_SEP
..desc)
else
while size > 0 do
local d, dlen = "", size
if size > config.WIDTH_HEX then dlen = config.WIDTH_HEX end
for i = 0, dlen - 1 do
d = d..string.format("%02X", string.byte(chunk, index + i))
end
d = d..string.rep(" ", config.WIDTH_HEX - dlen)
if segment or size > config.WIDTH_HEX then
d = d.."+"; size = size - config.WIDTH_HEX
else
d = d.." "; size = 0
end
if desc then
WriteLine(FormatPos(index)..config.DISPLAY_SEP
..d..config.DISPLAY_SEP
..desc)
desc = nil
else
WriteLine(FormatPos(index)..config.DISPLAY_SEP..d)
end
index = index + dlen
end end else WriteLine(FormatPos(index)..config.DISPLAY_SEP..desc)
end
end
DisplayInit(result.chunk_size)
HeaderLine() if result.chunk_name then
FormatLine(0, "** source chunk: "..result.chunk_name, idx)
if config.DISPLAY_BRIEF then WriteLine(config.DISPLAY_COMMENT.."source chunk: "..result.chunk_name) end
end
DescLine("** global header start **")
len = string.len(config.SIGNATURE)
TestChunk(len, idx, "header signature")
if string.sub(chunk, 1, len) ~= config.SIGNATURE then
error("header signature not found, this is not a Lua chunk")
end
FormatLine(len, "header signature: "..EscapeString(config.SIGNATURE, 1), idx)
idx = idx + len
TestChunk(1, idx, "version byte")
result.version = LoadByte()
if not config.IgnoreVersion and result.version ~= config.VERSION then
error(string.format("ChunkSpy cannot read version %02X chunks", result.version))
end
FormatLine(1, "version (major:minor hex digits)", previdx)
TestChunk(1, idx, "format byte")
result.format = LoadByte()
if not config.IgnoreFormat and result.format ~= config.FORMAT then
error(string.format("ChunkSpy cannot read format %02X chunks", result.format))
end
FormatLine(1, "format (0=official)", previdx)
len = string.len(config.LUAC_DATA)
TestChunk(len, idx, "LUAC_DATA")
local LUAC_DATA = LoadBlock(len, true)
if LUAC_DATA ~= config.LUAC_DATA then
error("header LUAC_DATA not found, this is not a Lua chunk")
end
FormatLine(len, "LUAC_DATA: "..EscapeString(LUAC_DATA, 1), previdx)
TestChunk(4, idx, "size bytes")
local function TestSize(mysize, sizename, typename)
local byte = LoadByte()
if not config.AUTO_DETECT then
if byte ~= config[mysize] then
error(string.format("mismatch in %s size (needs %d but read %d)",
sizename, config[mysize], byte))
end
else
config[mysize] = byte
end
FormatLine(1, string.format("size of %s (%s)", sizename, typename), previdx)
end
TestSize("size_int", "int", "bytes")
TestSize("size_size_t", "size_t", "bytes")
TestSize("size_Instruction", "Instruction", "bytes")
TestSize("size_lua_Integer", "Integer", "bytes")
TestSize("size_lua_Number", "Number", "bytes")
DecodeInit()
TestChunk(8, idx, "endianness bytes")
local endianness_bytes = LoadBlock(8)
local endianness_value = convert_from_int(endianness_bytes, 8)
FormatLine(8, "endianness bytes "..string.format("0x%x", endianness_value), previdx)
TestChunk(8, idx, "float format bytes")
local float_format_bytes = LoadBlock(8)
local float_format_value = convert_from_double(float_format_bytes)
FormatLine(8, "float format "..float_format_value, previdx)
TestChunk(1, idx, "global closure nupvalues")
local global_closure_nupvalues = LoadByte()
FormatLine(1, "global closure nupvalues "..global_closure_nupvalues, previdx)
stat.header = idx - 1
DisplayStat("* global header = "..stat.header.." bytes")
DescLine("** global header end **")
local function LoadFunction(funcname, num, level)
local func = {}
local function LoadInt()
local x = LoadBlock(config.size_int)
if not x then
error("could not load integer")
else
local sum = 0
for i = config.size_int, 1, -1 do
sum = sum * 256 + string.byte(x, i)
end
if string.byte(x, config.size_int) > 127 then
sum = sum - math.ldexp(1, 8 * config.size_int)
end
if sum < 0 then error("bad integer") end
return sum
end
end
local function LoadSize()
local x = LoadBlock(config.size_size_t)
if not x then
return
else
local sum = 0
for i = config.size_size_t, 1, -1 do
sum = sum * 256 + string.byte(x, i)
end
return sum
end
end
local function LoadInteger()
local x = LoadBlock(config.size_lua_Integer)
if not x then
error("could not load lua_Integer")
else
local convert_func = convert_from[config.integer_type]
if not convert_func then
error("could not find conversion function for lua_Integer")
end
return convert_func(x)
end
end
local function LoadNumber()
local x = LoadBlock(config.size_lua_Number)
if not x then
error("could not load lua_Number")
else
local convert_func = convert_from[config.number_type]
if not convert_func then
error("could not find conversion function for lua_Number")
end
return convert_func(x)
end
end
local function LoadString()
local len = LoadByte()
local islngstr = nil
if not len then
error("could not load String")
return
end
if len == 255 then
len = LoadSize()
islngstr = true
end
if len == 0 then return nil, len, islngstr
end
if len == 1 then
return "", len, islngstr
end
TestChunk(len - 1, idx, "LoadString")
local s = string.sub(chunk, idx, idx + len - 2)
idx = idx + len - 1
return s, len, islngstr
end
local function LoadString53()
local str = {}
str.val, str.len, str.islngstr = LoadString()
return str
end
local function LoadLines()
local size = LoadInt()
func.pos_lineinfo = previdx
func.lineinfo = {}
func.sizelineinfo = size
for i = 1, size do
func.lineinfo[i] = LoadInt()
end
end
local function LoadLocals()
local n = LoadInt()
func.pos_locvars = previdx
func.locvars = {}
func.sizelocvars = n
for i = 1, n do
local locvar = {}
locvar.varname53 = LoadString53()
locvar.varname = locvar.varname53.val
locvar.pos_varname = previdx
locvar.startpc = LoadInt()
locvar.pos_startpc = previdx
locvar.endpc = LoadInt()
locvar.pos_endpc = previdx
func.locvars[i] = locvar
end
end
local function LoadUpvalues()
local n = LoadInt()
func.pos_upvalues = previdx
func.upvalues = {}
func.sizeupvalues = n
for i = 1, n do
local upvalue = {}
upvalue.instack = LoadByte()
upvalue.pos_instack = previdx
upvalue.idx = LoadByte()
upvalue.pos_idx = previdx
func.upvalues[i] = upvalue
end
end
local function LoadConstants()
local n = LoadInt()
func.pos_ks = previdx
func.k = {}
func.typek = {}
func.sizek = n
func.posk = {}
for i = 1, n do
local t = LoadByte()
func.typek[i] = t
func.posk[i] = previdx
if t == config.LUA_TNIL then
func.k[i] = nil
elseif t == config.LUA_TBOOLEAN then
local b = LoadByte()
if b == 0 then b = false else b = true end
func.k[i] = b
elseif t == config.LUA_TNUMFLT then
func.k[i] = LoadNumber()
elseif t == config.LUA_TNUMINT then
func.k[i] = LoadInteger()
elseif t == config.LUA_TSHRSTR or t == config.LUA_TLNGSTR then
func.k[i] = LoadString53()
else
error("bad constant type "..t.." at "..previdx)
end
end end
local function LoadProtos()
local n = LoadInt()
func.pos_ps = previdx
func.p = {}
func.sizep = n
for i = 1, n do
func.p[i] = LoadFunction(func.source, i - 1, level + 1)
end
end
local function LoadCode()
local size = LoadInt()
func.pos_code = previdx
func.code = {}
func.sizecode = size
for i = 1, size do
func.code[i] = LoadBlock(config.size_Instruction)
end
end
local function LoadUpvalueNames()
local n = LoadInt()
if n > func.sizeupvalues then
error(string.format("bad upvalue_names: read %d, expected %d", n, func.sizeupvalues))
return
end
func.size_upvalue_names = n
func.pos_upvalue_names = previdx
for i = 1, n do
local upvalue = func.upvalues[i]
upvalue.name53 = LoadString53()
upvalue.name = upvalue.name53.val
upvalue.pos_name = previdx
end
end
local start = idx
func.stat = {}
local function SetStat(item)
func.stat[item] = idx - start
start = idx
end
func.source53 = LoadString53()
func.source = func.source53.val
func.pos_source = previdx
if func.source == nil and level == 1 then func.source = funcname end
func.linedefined = LoadInt()
func.pos_linedefined = previdx
func.lastlinedefined = LoadInt()
func.pos_lastlinedefined = previdx
if TestChunk(3, idx, "function header") then return end
func.numparams = LoadByte()
func.is_vararg = LoadByte()
func.maxstacksize = LoadByte()
SetStat("header")
LoadCode() SetStat("code")
LoadConstants() SetStat("consts")
LoadUpvalues() SetStat("upvalues")
LoadProtos() SetStat("funcs")
LoadLines() SetStat("lines")
LoadLocals() SetStat("locals")
LoadUpvalueNames() SetStat("upvalue_names")
return func
end
function DescFunction(func, num, level, funcnumstr)
local function BriefLine(desc)
if not config.DISPLAY_FLAG or not config.DISPLAY_BRIEF then return end
if DISPLAY_INDENT then
WriteLine(string.rep(config.DISPLAY_SEP, level - 1)..desc)
else
WriteLine(desc)
end
end
local function DescString(str, pos)
local len = str.len
local s = str.val
if str.islngstr then
FormatLine(1 + config.size_size_t, string.format("long string size (%d)", len), pos)
pos = pos + 1 + config.size_size_t
else
FormatLine(1, string.format("string size (%d)", len), pos)
pos = pos + 1
end
if len == 0 then return end
len = len - 1
if len <= config.WIDTH_HEX then
FormatLine(len, EscapeString(s, 1), pos)
else
while len > 0 do
local seg_len = config.WIDTH_HEX
if len < seg_len then seg_len = len end
local seg = string.sub(s, 1, seg_len)
s = string.sub(s, seg_len + 1)
len = len - seg_len
FormatLine(seg_len, EscapeString(seg, 1), pos, len > 0)
pos = pos + seg_len
end
end
end
local function DescLines()
local size = func.sizelineinfo
local pos = func.pos_lineinfo
DescLine("* lines:")
FormatLine(config.size_int, "sizelineinfo ("..size..")", pos)
pos = pos + config.size_int
local WIDTH = WidthOf(size)
DescLine("[pc] (line)")
for i = 1, size do
local s = string.format("[%s] (%s)", ZeroPad(i, WIDTH), func.lineinfo[i])
FormatLine(config.size_int, s, pos)
pos = pos + config.size_int
end
SourceMark(func)
end
local function DescLocals()
local n = func.sizelocvars
DescLine("* locals:")
FormatLine(config.size_int, "sizelocvars ("..n..")", func.pos_locvars)
for i = 1, n do
local locvar = func.locvars[i]
DescString(locvar.varname53, locvar.pos_varname)
DescLine("local ["..(i - 1).."]: "..EscapeString(locvar.varname))
FormatLine(config.size_int, " startpc ("..locvar.startpc..")", locvar.pos_startpc)
FormatLine(config.size_int, " endpc ("..locvar.endpc..")",locvar.pos_endpc)
BriefLine(".local"..config.DISPLAY_SEP..EscapeString(locvar.varname, 1)
..config.DISPLAY_SEP..config.DISPLAY_COMMENT..(i - 1))
end
end
local function DescUpvaluesAll()
local n = func.sizeupvalues
for i = 1, n do
local upvalue = func.upvalues[i]
local name = upvalue.name or ''
BriefLine(".upvalue"..config.DISPLAY_SEP..EscapeString(name, 1)
..config.DISPLAY_SEP..tostring(upvalue.instack)
..config.DISPLAY_SEP..tostring(upvalue.idx)
..config.DISPLAY_SEP..config.DISPLAY_COMMENT..(i - 1)
..config.DISPLAY_SEP.."instack="..tostring(upvalue.instack)
..config.DISPLAY_SEP.."idx="..tostring(upvalue.idx))
end
end
local function DescUpvalues()
local n = func.sizeupvalues
DescLine("* upvalues:")
FormatLine(config.size_int, "sizeupvalues ("..n..")", func.pos_upvalues)
for i = 1, n do
local upvalue = func.upvalues[i]
local name = upvalue.name or ''
DescLine("upvalue ["..(i - 1).."]: "..EscapeString(name))
FormatLine(1, " instack ("..upvalue.instack..")", upvalue.pos_instack)
FormatLine(1, " idx ("..upvalue.idx..")",upvalue.pos_idx)
end
end
local function DescUpvalueNames()
local n = func.size_upvalue_names
DescLine("* upvalue names:")
FormatLine(config.size_int, "size_upvalue_names ("..n..")", func.pos_upvalue_names)
for i = 1, n do
local upvalue = func.upvalues[i]
DescLine("upvalue ["..(i - 1).."]: "..EscapeString(upvalue.name))
DescString(upvalue.name53, upvalue.pos_name)
end
end
local function DescConstants()
local n = func.sizek
local pos = func.pos_ks
DescLine("* constants:")
FormatLine(config.size_int, "sizek ("..n..")", pos)
for i = 1, n do
local posk = func.posk[i]
local CONST = "const ["..(i - 1).."]: "
local CONSTB = config.DISPLAY_SEP..config.DISPLAY_COMMENT..(i - 1)
local k = func.k[i]
local typek = func.typek[i]
local typestrk = config.typestr[typek]
FormatLine(1, "const type "..typestrk, posk)
if typek == config.LUA_TNUMFLT then
FormatLine(config.size_lua_Number, CONST.."("..k..")", posk + 1)
BriefLine(".const"..config.DISPLAY_SEP..k..CONSTB)
elseif typek == config.LUA_TNUMINT then
FormatLine(config.size_lua_Integer, CONST.."("..k..")", posk + 1)
BriefLine(".const"..config.DISPLAY_SEP..k..CONSTB)
elseif typek == config.LUA_TBOOLEAN then
FormatLine(1, CONST.."("..tostring(k)..")", posk + 1)
BriefLine(".const"..config.DISPLAY_SEP..tostring(k)..CONSTB)
elseif typek == config.LUA_TSHRSTR or typek == config.LUA_TLNGSTR then
DescString(k, posk + 1)
DescLine(CONST..EscapeString(k.val, 1))
BriefLine(".const"..config.DISPLAY_SEP..EscapeString(k.val, 1)..CONSTB)
elseif typek == config.LUA_TNIL then
DescLine(CONST.."nil")
BriefLine(".const"..config.DISPLAY_SEP.."nil"..CONSTB)
end
end end
local function DescProtos()
local n = func.sizep
DescLine("* functions:")
FormatLine(config.size_int, "sizep ("..n..")", func.pos_ps)
for i = 1, n do
local newfuncnumstr = funcnumstr..'_'..(i - 1)
DescFunction(func.p[i], i - 1, level + 1, newfuncnumstr)
end
end
local function DescCode()
local size = func.sizecode
local pos = func.pos_code
DescLine("* code:")
FormatLine(config.size_int, "sizecode ("..size..")", pos)
pos = pos + config.size_int
func.inst = {}
local ISIZE = WidthOf(size)
for i = 1, size do
func.inst[i] = {}
end
for i = 1, size do
DecodeInst(func.code[i], func.inst[i])
local inst = func.inst[i]
local d = DescribeInst(inst, i, func)
d = string.format("[%s] %s", ZeroPad(i, ISIZE), d)
SourceMerge(func, i)
FormatLine(config.size_Instruction, d, pos)
BriefLine(d)
pos = pos + config.size_Instruction
end
end
local function DescSource()
DescString(func.source53, func.pos_source)
if func.source == nil then
DescLine("source name: (none)")
else
DescLine("source name: "..EscapeString(func.source))
end
end
DescLine("")
BriefLine("")
FormatLine(0, "** function ["..num.."] definition (level "..level..") "..funcnumstr,
func.pos_source)
BriefLine("; function ["..num.."] definition (level "..level..") "..funcnumstr)
DescLine("** start of function "..funcnumstr.." **")
SourceInit(func.source)
DescSource()
local pos = func.pos_linedefined
FormatLine(config.size_int, "line defined ("..func.linedefined..")", pos)
pos = pos + config.size_int
FormatLine(config.size_int, "last line defined ("..func.lastlinedefined..")", pos)
pos = pos + config.size_int
FormatLine(1, "numparams ("..func.numparams..")", pos)
FormatLine(1, "is_vararg ("..func.is_vararg..")", pos + 1)
FormatLine(1, "maxstacksize ("..func.maxstacksize..")", pos + 2)
BriefLine(string.format("; %d upvalues, %d params, is_vararg = %d, %d stacks",
func.sizeupvalues, func.numparams, func.is_vararg, func.maxstacksize))
BriefLine(string.format(".function%s%d %d %d %d", config.DISPLAY_SEP,
func.sizeupvalues, func.numparams, func.is_vararg, func.maxstacksize))
if config.DISPLAY_FLAG and config.DISPLAY_BRIEF then
DescLocals()
DescUpvaluesAll()
DescConstants()
DescCode()
DescProtos()
else
DescCode()
DescConstants()
DescUpvalues()
DescProtos()
DescLines()
DescLocals()
DescUpvalueNames()
end
DisplayStat("* func header = "..func.stat.header.." bytes")
DisplayStat("* lines size = "..func.stat.lines.." bytes")
DisplayStat("* locals size = "..func.stat.locals.." bytes")
DisplayStat("* upvalues size = "..func.stat.upvalues.." bytes")
DisplayStat("* upvalue names size = "..func.stat.upvalue_names.." bytes")
DisplayStat("* consts size = "..func.stat.consts.." bytes")
DisplayStat("* funcs size = "..func.stat.funcs.." bytes")
DisplayStat("* code size = "..func.stat.code.." bytes")
func.stat.total = func.stat.header + func.stat.lines +
func.stat.locals + func.stat.upvalues +
func.stat.consts + func.stat.funcs +
func.stat.code + func.stat.upvalue_names
DisplayStat("* TOTAL size = "..func.stat.total.." bytes")
DescLine("** end of function "..funcnumstr.." **\n")
BriefLine("; end of function "..funcnumstr.."\n")
end
result.func = LoadFunction("(chunk)", 0, 1)
DescFunction(result.func, 0, 1, "0")
stat.total = idx - 1
DisplayStat("* TOTAL size = "..stat.total.." bytes")
result.stat = stat
FormatLine(0, "** end of chunk **", idx)
return result
end
function WriteBinaryChunk(parsed, tofile)
local Buffer = {}
if tofile then
if not config.OUTPUT_FILE then
error("must specify an output filename for rewrites")
else WriteLine = function(s) config.OUTPUT_FILE:write(s) end
end
end
local function Dump(s)
if tofile then WriteLine(s) else table.insert(Buffer, s) end
end
local function DumpByte(b)
Dump(string.char(b))
end
local function WriteBlock(v)
if config.endianness == 1 then
Dump(v)
else Dump(string.reverse(v))
end
end
Dump(config.SIGNATURE)
DumpByte(config.VERSION)
DumpByte(config.FORMAT)
DumpByte(config.endianness)
DumpByte(config.size_int) DumpByte(config.size_size_t)
DumpByte(config.size_Instruction)
DumpByte(config.size_lua_Number)
DumpByte(config.integral)
Dump(config.LUAC_DATA)
DecodeInit()
local function WriteFunction(func)
local function WriteUnsigned(num, type_size)
if not type_size then type_size = config.size_int end
local v = ""
for i = 1, type_size do
v = v..string.char(num % 256); num = math.floor(num / 256)
end
WriteBlock(v)
end
local function WriteNumber(num)
local convert_func = convert_to[config.number_type]
if not convert_func then
error("could not find conversion function for lua_Number")
end
WriteBlock(convert_func(num))
end
local function WriteString(str)
if not str then
WriteUnsigned(0, config.size_size_t)
return
end
str = str.."\0" WriteUnsigned(string.len(str), config.size_size_t)
Dump(str)
end
local function WriteLines()
WriteUnsigned(func.sizelineinfo)
for i = 1, func.sizelineinfo do WriteUnsigned(func.lineinfo[i]) end
end
local function WriteLocals()
WriteUnsigned(func.sizelocvars)
for i = 1, func.sizelocvars do
local locvar = func.locvars[i]
WriteString(locvar.varname)
WriteUnsigned(locvar.startpc)
WriteUnsigned(locvar.endpc)
end
end
local function WriteUpvalues()
WriteUnsigned(func.sizeupvalues)
for i = 1, func.sizeupvalues do
local upvalue = func.upvalues[i]
DumpByte(upvalue.instack)
DumpByte(upvalue.idx)
end
end
local function WriteUpvalueNames()
WriteUnsigned(func.size_upvalue_names)
for i = 1, func.size_upvalue_names do
local upvalue = func.upvalues[i]
WriteString(upvalue.name)
end
end
local function WriteConstantKs()
WriteUnsigned(func.sizek)
for i = 1, func.sizek do
local v = func.k[i]
if type(v) == "number" then
DumpByte(config.LUA_TNUMBER); WriteNumber(v)
elseif type(v) == "boolean" then
local b = 0; if v then b = 1 end
DumpByte(config.LUA_TBOOLEAN); DumpByte(b)
elseif type(v) == "string" then
DumpByte(config.LUA_TSTRING); WriteString(v)
elseif type(v) == "nil" then
DumpByte(config.LUA_TNIL)
else
error("bad constant type \""..type(v).."\" at "..i)
end
end end
local function WriteConstantPs()
WriteUnsigned(func.sizep)
for i = 1, func.sizep do WriteFunction(func.p[i]) end
end
local function WriteCode()
WriteUnsigned(func.sizecode)
for i = 1, func.sizecode do WriteBlock(EncodeInst(func.inst[i])) end
end
WriteUnsigned(func.linedefined)
WriteUnsigned(func.lastlinedefined)
DumpByte(func.numparams)
DumpByte(func.is_vararg)
DumpByte(func.maxstacksize)
WriteCode()
WriteConstantKs()
WriteConstantPs() WriteUpvalues()
WriteString(func.source)
WriteLines() WriteLocals()
WriteUpvalueNames()
end
WriteFunction(parsed.func)
if not tofile then return table.concat(Buffer) end
end
function ChunkSpy_Test()
local FAIL, SUCCEED = false, true
local GotError = false
SetProfile("x64 standard")
config.DISPLAY_FLAG = false config.AUTO_DETECT = false
print(title)
local function expected(sample, outcome, errmatch, message)
local ok, msg = pcall(ChunkSpy, "test", sample)
if outcome == SUCCEED and not ok then
print("ChunkSpy_Test: failed instead of success!\nTest was for: "..message)
GotError = true; return
elseif outcome == FAIL and ok then
print("ChunkSpy_Test: success instead of failed!\nTest was for: "..message)
GotError = true; return
elseif outcome == FAIL and not ok then
if not string.find(msg, errmatch, 1, 1) then
print("ChunkSpy_Test: wrong error message returned!\nTest was for: "
..message.."\nError returned: "..msg)
GotError = true; return
end
end
if config.VERBOSE_TEST then
print("ChunkSpy_Test: successful test!\nTest was for: "..message)
end
end
expected("\0\0\0\0", FAIL,
"header signature not found", "incorrect header signature")
expected("\27Lua\64", FAIL,
"cannot read version", "incorrect version byte")
expected("\27Lua\82\1", FAIL,
"cannot read format", "incorrect format byte")
expected("\27Lua\82\0\0", FAIL,
"unsupported endianness", "incorrect endianness byte")
expected("\27Lua\82\0\1\0\0\0\0", FAIL,
"int size", "incorrect int size byte")
expected("\27Lua\82\0\1\4\0\0\0", FAIL,
"size_t size", "incorrect size_t size byte")
expected("\27Lua\82\0\1\4\4\0\0", FAIL,
"Instruction size", "incorrect Instruction size byte")
expected("\27Lua\82\0\1\4\4\4\0", FAIL,
"number size", "incorrect lua_Number size byte")
expected("\27Lua\82\0\1\4\4\4\8\1", FAIL,
"incorrect lua_Number", "incorrect integral byte")
local LUA_HEADER = "\27Lua\82\0\1\4\4\4\8\1"
if not GotError then
print("ChunkSpy_Test: completed simple tests without errors")
end
end
function ChunkSpy_Sample()
local LUA_SAMPLE = string.dump(
function()
local a = 1; b = "the quick brown fox\r\n"
function c() b = a a = b end
c = nil; c = -a; c = not b
for i = 1, 10 do a = a + 2 c() end
a = {}; a[1] = false; b = a[1]
a = d..c..b; a = b == c; a = {1,2,}
for i in b() do b = 1 end
return
end
)
local ok, msg = pcall(ChunkSpy, "test sample", LUA_SAMPLE)
if not ok then
print(title)
print("* Test sample has failed with the following error:")
print(msg)
end
end
function ChunkSpy_DoFiles(files)
local binary_chunks = {}
local function CheckAndAdd(binchunk, filename)
if config.SearchSign then
local i,j = string.find(binchunk, string.gsub(config.SearchSign, "(%W)", "%%%1"))
if i then
binary_chunks[filename] = binchunk
config.SIGNATURE = string.sub(binchunk, 1, j)
print(string.format("--sign=%q found at %d for %s", config.SearchSign, i-1, filename))
return true
end
print(string.format("--sign=%q not found for %s", config.SearchSign, filename))
else
local sign = string.sub(binchunk, 1, string.len(config.SIGNATURE))
if sign == config.SIGNATURE then
binary_chunks[filename] = binchunk
return true
end
end
return false
end
for i, v in pairs(files) do
local filename, binchunk
if type(i) == "number" then filename = v
local INF = io.open(filename, "rb")
if not INF then
error("cannot open \""..filename.."\" for reading")
end
binchunk = INF:read("*a")
io.close(INF)
else filename = i
binchunk = v
end
if binchunk then
if not CheckAndAdd(binchunk, filename) then
CheckLuaVersion("compiling needs %s")
local func, msg = loadstring(binchunk, "@"..filename)
if not func then
print(string.format("failed to compile %s", msg))
end
binchunk = func and string.dump(func)
if binchunk then
print(string.format("success compiling %s", filename))
binary_chunks[filename] = binchunk
else
table.insert(other_files, filename)
end
end
end
end
local done
for i,v in pairs(binary_chunks) do
if done and (config.REWRITE_FLAG or config.RUN_FLAG) then
error("can rewrite or run only one file at a time")
end
local result = ChunkSpy(i, v); done = true
if config.REWRITE_FLAG then
if not SetProfile(config.REWRITE_PROFILE) then
error("could not load profile for writing binary chunk")
end
if files[i] then
if string.sub(result.func.source, 1, 1) ~= "@" then
result.func.source = "@"..result.func.source
end
end
WriteBinaryChunk(result, true)
elseif config.RUN_FLAG then
if not SetProfile("local") then
error("could not load profile for writing binary chunk")
end
local binchunk = WriteBinaryChunk(result)
local sandbox = {}
arg_other[0] = i _ENV.arg = arg_other
setmetatable(sandbox, {__index = _ENV})
local func, msg = load(binchunk, i, nil, sandbox) if not func then error(msg) end
func(table.unpack(arg_other)) return
end
end
if not done then
print(title) print("ChunkSpy: no binary chunks processed!")
end
end
function ChunkSpy_Interact()
config.DISPLAY_BRIEF = true
config.OUTPUT_FILE = nil
local prevline, done
print(title)
print(interactive_help)
while not done do
if prevline then io.stdout:write(">>") else io.stdout:write(">") end
io.stdout:flush()
local l = io.stdin:read("*l")
if l == nil or (l == "exit" or l == "quit" and not prevline) then
done = true
elseif l == "help" and not prevline then
io.stdout:write(interactive_help, "\n")
elseif string.sub(l, -1, -1) == "\\" then
if not prevline then prevline = "" end
prevline = prevline..string.sub(l, 1, -2)
else
if prevline then l = prevline..l; prevline = nil end
local func, msg = loadstring(l, "(interactive mode)")
if not func then
print("ChunkSpy: failed to compile your input")
else
binchunk = string.dump(func)
ChunkSpy("(interactive mode)", binchunk)
end
end endend
function CompileSourceFile(filename)
CheckLuaVersion("compiling needs %s")
local INF = io.open(filename, "rb")
if not INF then
error("cannot open \""..filename.."\" for reading")
end
local src = INF:read("*a")
local func, msg = loadstring(src, "@"..filename)
if not func then
print(msg)
error("failed to compile source file \""..filename.."\"")
end
io.close(INF)
return string.dump(func)
end
function unescape(str)
str = string.gsub(str, [[\(%d%d?%d?)]],
function (c)
local n = tonumber (c)
if (n and 0 <= n and n < 256) then
return string.char(n)
else
return '\\'..c
end
end)
return str
end
function main()
local usage, exec
if arg[0] then exec = "lua ChunkSpy.lua" else exec = "ChunkSpy" end
usage = string.format(USAGE, exec, exec)
if _VERSION ~= "Lua 5.3" then
error("this version of ChunkSpy requires Lua 5.3")
end
loadstring = load
if math.ldexp == nil then
math.ldexp = function(x, exp)
return x * 2^exp
end
end
if not arg[1] then
print(title) print(usage)
else
local i, perform, gotfile = 1
local files = {}
while arg[i] do
local a, b = arg[i], arg[i + 1]
if string.sub(a, 1, 1) == "-" then if a == "--test" then
perform = ChunkSpy_Test
elseif a == "--sample" then
perform = ChunkSpy_Sample
elseif a == "-h" or a == "--help" then
print(title) print(usage) return
elseif a == "--stats" then
config.STATS = true
elseif a == "--auto" then
config.AUTO_DETECT = true
elseif a == "--brief" then
config.DISPLAY_BRIEF = true
elseif a == "--interact" then
CheckLuaVersion("--interact needs %s")
perform = ChunkSpy_Interact
elseif a == "-o" or a == "--source" then
if not b then error("-o option needs a file name") end
if a == "-o" then
config.OUTPUT_FILE = b
else
local binchunk = CompileSourceFile(b)
if binchunk then files[b] = binchunk; gotfile = true end
end
i = i + 1
elseif a == "--sign" then
config.SearchSign = config.SIGNATURE
config.IgnoreVersion = true
config.IgnoreFormat = true
elseif string.sub(a, 1, string.len("--sign:")) == "--sign:" then
config.SearchSign = string.sub(a, string.len("--sign:")+1)
config.IgnoreVersion = true
config.IgnoreFormat = true
elseif a == "--rewrite" then
if not b then error("--rewrite option needs a profile name") end
if b == "local" then
CheckLuaVersion("--rewrite local needs %s")
end
config.DISPLAY_FLAG = false
config.REWRITE_FLAG = true
if b == "local" or CONFIGURATION[b] then
config.REWRITE_PROFILE = b
else
error("specified profile \""..b.."\"not found")
end
i = i + 1
elseif a == "--run" then
CheckLuaVersion("--run needs %s")
config.DISPLAY_FLAG = false
config.AUTO_DETECT = true
config.RUN_FLAG = true
elseif a == "--" then
local j = i + 1
while arg[j] do
table.insert(arg_other, arg[j]) j = j + 1
end
break
else
error("unrecognized option "..a)
end else
table.insert(files, a); gotfile = true end i = i + 1
end OutputInit()
if perform then
perform()
elseif gotfile then
ChunkSpy_DoFiles(files)
else
print(title)
print("ChunkSpy: nothing to do!")
end
OutputExit()
end
end
ok, msg = pcall(main) if not ok then print(title)
print("* Run with option -h or --help for usage information")
print(msg)
end