Created
June 2, 2017 07:11
-
-
Save x5lcfd/15ab4eed1a4a8417bf8e37ed3b9792b3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env lua | |
--[[------------------------------------------------------------------- | |
ChunkSpy.lua | |
A Lua 5.1 binary chunk disassembler | |
ChunkSpy was inspired by Nick Trout's vmmerge5.lua | |
Copyright (c) 2004-2006 Kein-Hong Man <[email protected]> | |
The COPYRIGHT file describes the conditions under which this | |
software may be distributed (basically a Lua 5-style license.) | |
http://luaforge.net/projects/chunkspy/ | |
http://www.geocities.com/keinhong/chunkspy.html | |
See the ChangeLog for more information. | |
--]]------------------------------------------------------------------- | |
--[[------------------------------------------------------------------- | |
-- Notes: | |
-- * See 5.0.2/ChunkSpy.lua for script notes and outline | |
--]]------------------------------------------------------------------- | |
--[[------------------------------------------------------------------- | |
-- description and help texts | |
--]]------------------------------------------------------------------- | |
title = [[ | |
ChunkSpy: A Lua 5.1 binary chunk disassembler | |
Version 0.9.8 (20060307) Copyright (c) 2004-2006 Kein-Hong Man | |
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 table | |
-- * Contains fixed constants, display constants and options, and | |
-- platform-dependent configuration constants | |
----------------------------------------------------------------------- | |
-- Configuration settings of binary chunks to be processed. Such tables | |
-- may be used to sort of "auto-detect" binary chunks from different | |
-- platforms. More or less equivalent to the global header. | |
-- * There is currently only one supported host, "x86 standard" | |
-- * MAX_STACK is no longer required for decoding RK register indices, | |
-- instead, the MSB bit in the field is used as a flag (Lua 5.1) | |
-- * number_type is also used to lookup conversion function, etc. | |
--]]------------------------------------------------------------------- | |
CONFIGURATION = { | |
["x86 standard"] = { | |
description = "x86 standard (32-bit, little endian, doubles)", | |
endianness = 1, -- 1 = little endian | |
size_int = 4, -- (data type sizes in bytes) | |
size_size_t = 4, | |
size_Instruction = 4, | |
size_lua_Number = 8, -- this & integral identifies the | |
integral = 0, -- type of lua_Number | |
number_type = "double", -- used for lookups | |
}, | |
["x64 standard"] = { | |
description = "x64 standard (64-bit, little endian, doubles)", | |
endianness = 1, -- 1 = little endian | |
size_int = 4, -- (data type sizes in bytes) | |
size_size_t = 8, | |
size_Instruction = 4, | |
size_lua_Number = 8, -- this & integral identifies the | |
integral = 0, -- type of lua_Number | |
number_type = "double", -- used for lookups | |
}, | |
["big endian int"] = { | |
description = "(32-bit, big endian, ints)", | |
endianness = 0, | |
size_int = 4, | |
size_size_t = 4, | |
size_Instruction = 4, | |
size_lua_Number = 4, | |
integral = 1, | |
number_type = "int", | |
}, | |
-- you can add more platforms here | |
} | |
----------------------------------------------------------------------- | |
-- set the default platform (can override with --auto auto-detection) | |
-- * both in & out paths use config.* parms, a bit clunky for now | |
----------------------------------------------------------------------- | |
config = {} | |
function SetProfile(profile) | |
if profile == "local" then | |
-- arrives here only for --rewrite and --run option | |
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.* profile parms set in ChunkSpy() call... | |
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 | |
-- resume normal operation | |
else | |
local c = CONFIGURATION[profile] | |
if not c then return false end | |
for i, v in pairs(c) do config[i] = v end | |
end | |
return true | |
end | |
-- SetProfile("x86 standard") -- default profile | |
SetProfile("x64 standard") -- default profile | |
----------------------------------------------------------------------- | |
-- chunk constants | |
-- * changed in 5.1: VERSION, FPF, SIZE_* are now fixed; LUA_TBOOLEAN | |
-- added for constant table; TEST_NUMBER removed; FORMAT added | |
----------------------------------------------------------------------- | |
config.SIGNATURE = "\27Lua" | |
-- TEST_NUMBER no longer needed, using size_lua_Number + integral | |
config.LUA_TNIL = 0 | |
config.LUA_TBOOLEAN = 1 | |
config.LUA_TNUMBER = 3 | |
config.LUA_TSTRING = 4 | |
config.VERSION = 81 -- 0x51 | |
config.FORMAT = 0 -- LUAC_FORMAT (new in 5.1) | |
config.FPF = 50 -- LFIELDS_PER_FLUSH | |
config.SIZE_OP = 6 -- instruction field bits | |
config.SIZE_A = 8 | |
config.SIZE_B = 9 | |
config.SIZE_C = 9 | |
-- MAX_STACK no longer needed for instruction decoding, removed | |
-- LUA_FIRSTINDEX currently not supported; used in SETLIST | |
config.LUA_FIRSTINDEX = 1 | |
----------------------------------------------------------------------- | |
-- display options: you can set your defaults here | |
----------------------------------------------------------------------- | |
config.DISPLAY_FLAG = true -- global listing output on/off | |
config.DISPLAY_BRIEF = nil -- brief listing style | |
config.DISPLAY_INDENT = nil -- indent flag for brief style | |
config.STATS = nil -- set if always display stats | |
config.DISPLAY_OFFSET_HEX = true -- use hexadecimal for position | |
config.DISPLAY_SEP = " " -- column separator | |
config.DISPLAY_COMMENT = "; " -- comment sign | |
config.DISPLAY_HEX_DATA = true -- show hex data column | |
config.WIDTH_HEX = 8 -- width of hex data column | |
config.WIDTH_OFFSET = nil -- width of position column | |
config.DISPLAY_LOWERCASE = true -- lower-case operands | |
config.WIDTH_OPCODE = nil -- width of opcode field | |
config.VERBOSE_TEST = false -- more verbosity for --test | |
--[[------------------------------------------------------------------- | |
-- Other globals | |
--]]------------------------------------------------------------------- | |
other_files = {} -- non-chunks (may be source listings) | |
arg_other = {} -- other arguments (for --run option) | |
--[[------------------------------------------------------------------- | |
-- Number handling functions | |
-- * converts a string of bytes to and from a proper number | |
-- * WARNING single() and double() can only do normal floats | |
-- and zeros. Denormals, infinities and NaNs are not recognized. | |
-- * See 5.0.2/ChunkSpy.lua for IEEE floating-point notes | |
--]]------------------------------------------------------------------- | |
convert_from = {} -- tables for number conversion function lookup | |
convert_to = {} | |
----------------------------------------------------------------------- | |
-- support function for convert_to functions | |
----------------------------------------------------------------------- | |
function grab_byte(v) | |
return math.floor(v / 256), string.char(math.floor(v) % 256) | |
end | |
----------------------------------------------------------------------- | |
-- No more TEST_NUMBER in Lua 5.1, uses size_lua_Number + integral | |
----------------------------------------------------------------------- | |
LUANUMBER_ID = { | |
["80"] = "double", -- IEEE754 double | |
["40"] = "single", -- IEEE754 single | |
["41"] = "int", -- int | |
["81"] = "long long", -- long long | |
} | |
----------------------------------------------------------------------- | |
-- Converts an 8-byte little-endian string to a IEEE754 double number | |
-- * NOTE: see warning about accuracy in the header comments! | |
----------------------------------------------------------------------- | |
convert_from["double"] = function(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 end | |
mantissa = (math.ldexp(mantissa, -52) + 1) * sign | |
return math.ldexp(mantissa, exponent - 1023) | |
end | |
----------------------------------------------------------------------- | |
-- Converts a 4-byte little-endian string to a IEEE754 single number | |
-- * TODO UNTESTED!!! * | |
----------------------------------------------------------------------- | |
convert_from["single"] = function(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 end | |
mantissa = (math.ldexp(mantissa, -23) + 1) * sign | |
return math.ldexp(mantissa, exponent - 127) | |
end | |
----------------------------------------------------------------------- | |
-- Converts a little-endian integer string to a number | |
-- * TODO UNTESTED!!! * | |
----------------------------------------------------------------------- | |
convert_from["int"] = function(x) | |
local sum = 0 | |
for i = config.size_lua_Number, 1, -1 do | |
sum = sum * 256 + string.byte(x, i) | |
end | |
-- test for negative number | |
if string.byte(x, config.size_lua_Number) > 127 then | |
sum = sum - math.ldexp(1, 8 * config.size_lua_Number) | |
end | |
return sum | |
end | |
----------------------------------------------------------------------- | |
-- * WARNING this will fail for large long longs (64-bit numbers) | |
-- because long longs exceeds the precision of doubles. | |
----------------------------------------------------------------------- | |
convert_from["long long"] = convert_from["int"] | |
----------------------------------------------------------------------- | |
-- Converts a IEEE754 double number to an 8-byte little-endian string | |
-- * NOTE: see warning about accuracy in the header comments! | |
----------------------------------------------------------------------- | |
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 -- zero | |
mantissa, exponent = 0, 0 | |
else | |
mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53) | |
exponent = exponent + 1022 | |
end | |
local v, byte = "" -- convert to bytes | |
x = mantissa | |
for i = 1,6 do | |
x, byte = grab_byte(x); v = v..byte -- 47:0 | |
end | |
x, byte = grab_byte(exponent * 16 + x); v = v..byte -- 55:48 | |
x, byte = grab_byte(sign * 128 + x); v = v..byte -- 63:56 | |
return v | |
end | |
----------------------------------------------------------------------- | |
-- Converts a IEEE754 single number to a 4-byte little-endian string | |
-- * TODO UNTESTED!!! * | |
----------------------------------------------------------------------- | |
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 -- zero | |
mantissa = 0; exponent = 0 | |
else | |
mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 24) | |
exponent = exponent + 126 | |
end | |
local v, byte = "" -- convert to bytes | |
x, byte = grab_byte(mantissa); v = v..byte -- 7:0 | |
x, byte = grab_byte(x); v = v..byte -- 15:8 | |
x, byte = grab_byte(exponent * 128 + x); v = v..byte -- 23:16 | |
x, byte = grab_byte(sign * 128 + x); v = v..byte -- 31:24 | |
return v | |
end | |
----------------------------------------------------------------------- | |
-- Converts a number to a little-endian integer string | |
-- * TODO UNTESTED!!! * | |
----------------------------------------------------------------------- | |
convert_to["int"] = function(x) | |
local v = "" | |
x = math.floor(x) | |
if x >= 0 then | |
for i = 1, config.size_lua_Number do | |
v = v..string.char(x % 256); x = math.floor(x / 256) | |
end | |
else-- x < 0 | |
x = -x | |
local carry = 1 | |
for i = 1, config.size_lua_Number 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 | |
-- optional overflow test; not enabled at the moment | |
-- if x > 0 then error("number conversion overflow") end | |
return v | |
end | |
----------------------------------------------------------------------- | |
-- * WARNING this will fail for large long longs (64-bit numbers) | |
-- because long longs exceeds the precision of doubles. | |
----------------------------------------------------------------------- | |
convert_to["long long"] = convert_to["int"] | |
--[[------------------------------------------------------------------- | |
-- Display support functions | |
-- * considerable work is done to maintain nice alignments | |
-- * some widths are initialized at chunk start | |
-- * this is meant to make output customization easy | |
--]]------------------------------------------------------------------- | |
----------------------------------------------------------------------- | |
-- width of number, left justify, zero padding | |
----------------------------------------------------------------------- | |
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 | |
----------------------------------------------------------------------- | |
-- initialize display formatting settings | |
-- * chunk_size parameter used to set width of position column | |
----------------------------------------------------------------------- | |
function DisplayInit(chunk_size) | |
--------------------------------------------------------------- | |
-- set up printing widths | |
--------------------------------------------------------------- | |
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 | |
--------------------------------------------------------------- | |
-- sane defaults | |
--------------------------------------------------------------- | |
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) | |
-- default output path | |
if not WriteLine then WriteLine = print end | |
end | |
----------------------------------------------------------------------- | |
-- initialize listing output path (an optional redirect) | |
-- * this is done before calling ChunkSpy() to redirect output | |
----------------------------------------------------------------------- | |
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 | |
----------------------------------------------------------------------- | |
-- cleanup listing output path | |
----------------------------------------------------------------------- | |
function OutputExit() | |
if WriteLine and WriteLine ~= print then io.close(config.OUTPUT_FILE) end | |
end | |
----------------------------------------------------------------------- | |
-- escape control bytes in strings | |
----------------------------------------------------------------------- | |
function EscapeString(s, quoted) | |
local v = "" | |
for i = 1, string.len(s) do | |
local c = string.byte(s, i) | |
-- other escapees with values > 31 are "(34), \(92) | |
if c < 32 or c == 34 or c == 92 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-- 32 <= v <= 255 | |
v = v..string.char(c) | |
end | |
end | |
if quoted then return string.format("\"%s\"", v) end | |
return v | |
end | |
----------------------------------------------------------------------- | |
-- listing legend/header | |
----------------------------------------------------------------------- | |
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 | |
----------------------------------------------------------------------- | |
-- description-only line, no position or hex data | |
----------------------------------------------------------------------- | |
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 | |
----------------------------------------------------------------------- | |
-- optionally display a pre-formatted statistic | |
----------------------------------------------------------------------- | |
function DisplayStat(stat) | |
if config.STATS and not config.DISPLAY_BRIEF then DescLine(stat) end | |
end | |
----------------------------------------------------------------------- | |
-- returns position, i uses string index (starts from 1) | |
----------------------------------------------------------------------- | |
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 | |
--[[------------------------------------------------------------------- | |
-- Instruction decoder functions (changed in Lua 5.1) | |
-- * some fixed decode data is placed in the config table | |
-- * these function are quite flexible, they can accept non-standard | |
-- instruction field sizes as long as the arrangement is the same. | |
----------------------------------------------------------------------- | |
Visually, an instruction can be represented as one of: | |
31 | | | 0 bit position | |
+-----+-----+-----+----------+ | |
| B | C | A | Opcode | iABC format | |
+-----+-----+-----+----------+ | |
- 9 - 9 - 8 - 6 - field sizes (standard Lua) | |
+-----+-----+-----+----------+ | |
| [s]Bx | A | Opcode | iABx | iAsBx format | |
+-----+-----+-----+----------+ | |
The signed argument sBx is represented in excess K, with the range | |
of -max to +max represented by 0 to 2*max. | |
For RK(x) constants, MSB is set and constant number is in the rest | |
of the bits. | |
--]]------------------------------------------------------------------- | |
----------------------------------------------------------------------- | |
-- instruction decoder initialization | |
----------------------------------------------------------------------- | |
function DecodeInit() | |
--------------------------------------------------------------- | |
-- calculate masks | |
--------------------------------------------------------------- | |
config.SIZE_Bx = config.SIZE_B + config.SIZE_C | |
local MASK_OP = math.ldexp(1, config.SIZE_OP) | |
local MASK_A = math.ldexp(1, config.SIZE_A) | |
local MASK_B = math.ldexp(1, config.SIZE_B) | |
local MASK_C = math.ldexp(1, config.SIZE_C) | |
local MASK_Bx = math.ldexp(1, config.SIZE_Bx) | |
config.MAXARG_sBx = math.floor((MASK_Bx - 1) / 2) | |
config.BITRK = math.ldexp(1, config.SIZE_B - 1) | |
--------------------------------------------------------------- | |
-- iABC instruction segment tables | |
--------------------------------------------------------------- | |
config.iABC = { -- tables allows field sequence to be extracted | |
config.SIZE_OP, -- using a loop; least significant field first | |
config.SIZE_A, -- additional lookups below, kludgy | |
config.SIZE_C, | |
config.SIZE_B, | |
} | |
config.mABC = { MASK_OP, MASK_A, MASK_C, MASK_B, } | |
config.nABC = { "OP", "A", "C", "B", } | |
--------------------------------------------------------------- | |
-- opcode name table | |
--------------------------------------------------------------- | |
local op = | |
"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" | |
--------------------------------------------------------------- | |
-- build opcode name table | |
--------------------------------------------------------------- | |
config.opnames = {} | |
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 | |
local vlen = string.len(v) | |
-- find maximum opcode length | |
if vlen > config.WIDTH_OPCODE then | |
config.WIDTH_OPCODE = vlen | |
end | |
config.NUM_OPCODES = config.NUM_OPCODES + 1 | |
end | |
-- opmode: 0=ABC, 1=ABx, 2=AsBx | |
config.opmode = "01000101000000000000002000000002200010" | |
--------------------------------------------------------------- | |
-- initialize text widths and formats for display | |
--------------------------------------------------------------- | |
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 -- with minus sign | |
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.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) | |
end | |
----------------------------------------------------------------------- | |
-- instruction decoder | |
-- * decoder loops starting from the least-significant byte, this allow | |
-- a field to be extracted using % operations | |
-- * returns a table populated with the appropriate fields | |
-- * WARNING B,C arrangement is hard-coded here for calculating [s]Bx | |
----------------------------------------------------------------------- | |
function DecodeInst(code, iValues) | |
local iSeq, iMask = config.iABC, config.mABC | |
local cValue, cBits, cPos = 0, 0, 1 | |
-- decode an instruction | |
for i = 1, #iSeq do | |
-- if need more bits, suck in a byte at a time | |
while cBits < iSeq[i] do | |
cValue = string.byte(code, cPos) * math.ldexp(1, cBits) + cValue | |
cPos = cPos + 1; cBits = cBits + 8 | |
end | |
-- extract and set an instruction field | |
iValues[config.nABC[i]] = cValue % iMask[i] | |
cValue = math.floor(cValue / iMask[i]) | |
cBits = cBits - iSeq[i] | |
end | |
iValues.opname = config.opnames[iValues.OP] -- get mnemonic | |
iValues.opmode = string.sub(config.opmode, iValues.OP + 1, iValues.OP + 1) | |
if iValues.opmode == "1" then -- set Bx or sBx | |
iValues.Bx = iValues.B * iMask[3] + iValues.C | |
elseif iValues.opmode == "2" then | |
iValues.sBx = iValues.B * iMask[3] + iValues.C - config.MAXARG_sBx | |
end | |
return iValues | |
end | |
----------------------------------------------------------------------- | |
-- encodes an instruction into a little-endian byte string | |
-- * encodes from OP/A/B/C fields, to enable bit field size changes | |
----------------------------------------------------------------------- | |
function EncodeInst(inst) | |
local v, i = "", 0 | |
local cValue, cBits, cPos = 0, 0, 1 | |
-- encode an instruction | |
while i < config.size_Instruction do | |
-- if need more bits, suck in a field at a time | |
while cBits < 8 do | |
cValue = inst[config.nABC[cPos]] * math.ldexp(1, cBits) + cValue | |
cBits = cBits + config.iABC[cPos]; cPos = cPos + 1 | |
end | |
-- extract bytes to instruction string | |
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 | |
----------------------------------------------------------------------- | |
-- describe an instruction | |
-- * make instructions descriptions more verbose and readable | |
----------------------------------------------------------------------- | |
function DescribeInst(inst, pos, func) | |
local Operand | |
local Comment = "" | |
--------------------------------------------------------------- | |
-- operand formatting helpers | |
--------------------------------------------------------------- | |
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 | |
--------------------------------------------------------------- | |
-- comment formatting helpers | |
-- calculate jump location, conditional flag | |
--------------------------------------------------------------- | |
local function CommentLoc(sBx, cond) | |
local loc = string.format("to [%d]", pos + 1 + sBx) | |
if cond then loc = loc..cond end | |
return loc | |
end | |
--------------------------------------------------------------- | |
-- Kst(x) - constant (in constant table) | |
--------------------------------------------------------------- | |
local function CommentK(index, quoted) | |
local c = func.k[index + 1] | |
if type(c) == "string" then | |
return EscapeString(c, quoted) | |
elseif type(c) == "number" or type(c) == "boolean" then | |
return tostring(c) | |
else | |
return "nil" | |
end | |
end | |
--------------------------------------------------------------- | |
-- RK(x) == if BITRK then Kst(x&~BITRK) else R(x) | |
--------------------------------------------------------------- | |
local function CommentRK(index, quoted) | |
if index >= config.BITRK then | |
return CommentK(index - config.BITRK, quoted) | |
else | |
return "" | |
end | |
end | |
--------------------------------------------------------------- | |
-- comments for RK(B), RK(C) | |
--------------------------------------------------------------- | |
local function CommentBC(inst) | |
local B, C = CommentRK(inst.B, true), CommentRK(inst.C, true) | |
if B == "" then | |
if C == "" then return "" else return C end | |
elseif C == "" then | |
return B | |
else | |
return B.." "..C | |
end | |
end | |
--------------------------------------------------------------- | |
-- floating point byte conversion | |
-- bit positions: mmmmmxxx, actual: (1xxx) * 2^(m-1) | |
--------------------------------------------------------------- | |
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 | |
--------------------------------------------------------------- | |
-- yeah, I know this is monstrous... | |
-- * see the descriptions in lopcodes.h for more information | |
--------------------------------------------------------------- | |
if inst.prev then -- continuation of SETLIST | |
Operand = string.format(config.FORMAT_Bx, func.code[pos])..config.PAD_Bx | |
--------------------------------------------------------------- | |
elseif inst.OP == 0 then -- MOVE A B | |
Operand = OperandAB(inst) | |
--------------------------------------------------------------- | |
elseif inst.OP == 1 then -- LOADK A Bx | |
Operand = OperandABx(inst) | |
Comment = CommentK(inst.Bx, true) | |
--------------------------------------------------------------- | |
elseif inst.OP == 2 then -- LOADBOOL A B C | |
Operand = OperandABC(inst) | |
if inst.B == 0 then Comment = "false" else Comment = "true" end | |
if inst.C > 0 then Comment = Comment..", "..CommentLoc(1) end | |
--------------------------------------------------------------- | |
elseif inst.OP == 3 then -- LOADNIL A B | |
Operand = OperandAB(inst) | |
--------------------------------------------------------------- | |
elseif inst.OP == 4 then -- GETUPVAL A B | |
Operand = OperandAB(inst) | |
Comment = func.upvalues[inst.B + 1] | |
--------------------------------------------------------------- | |
elseif inst.OP == 5 or -- GETGLOBAL A Bx | |
inst.OP == 7 then -- SETGLOBAL A Bx | |
Operand = OperandABx(inst) | |
Comment = CommentK(inst.Bx) | |
--------------------------------------------------------------- | |
elseif inst.OP == 6 then -- GETTABLE A B C | |
Operand = OperandABC(inst) | |
Comment = CommentRK(inst.C, true) | |
--------------------------------------------------------------- | |
elseif inst.OP == 8 then -- SETUPVAL A B | |
Operand = OperandAB(inst) | |
Comment = func.upvalues[inst.B + 1] | |
--------------------------------------------------------------- | |
elseif inst.OP == 9 then -- SETTABLE A B C | |
Operand = OperandABC(inst) | |
Comment = CommentBC(inst) | |
--------------------------------------------------------------- | |
elseif inst.OP == 10 then -- NEWTABLE A B C | |
Operand = OperandABC(inst) | |
local ar = fb2int(inst.B) -- array size | |
local hs = fb2int(inst.C) -- hash size | |
Comment = "array="..ar..", hash="..hs | |
--------------------------------------------------------------- | |
elseif inst.OP == 11 then -- SELF A B C | |
Operand = OperandABC(inst) | |
Comment = CommentRK(inst.C, true) | |
--------------------------------------------------------------- | |
elseif inst.OP == 12 or -- ADD A B C | |
inst.OP == 13 or -- SUB A B C | |
inst.OP == 14 or -- MUL A B C | |
inst.OP == 15 or -- DIV A B C | |
inst.OP == 16 or -- MOD A B C | |
inst.OP == 17 then -- POW A B C | |
Operand = OperandABC(inst) | |
Comment = CommentBC(inst) | |
--------------------------------------------------------------- | |
elseif inst.OP == 18 or -- UNM A B | |
inst.OP == 19 or -- NOT A B | |
inst.OP == 20 then -- LEN A B | |
Operand = OperandAB(inst) | |
--------------------------------------------------------------- | |
elseif inst.OP == 21 then -- CONCAT A B C | |
Operand = OperandABC(inst) | |
--------------------------------------------------------------- | |
elseif inst.OP == 22 then -- JMP sBx | |
Operand = string.format(config.FORMAT_Bx, inst.sBx)..config.PAD_Bx | |
Comment = CommentLoc(inst.sBx) | |
--------------------------------------------------------------- | |
elseif inst.OP == 23 or -- EQ A B C | |
inst.OP == 24 or -- LT A B C | |
inst.OP == 25 or -- LE A B C | |
inst.OP == 27 then -- TESTSET A B C | |
Operand = OperandABC(inst) | |
if inst.OP ~= 27 then Comment = CommentBC(inst) end | |
if Comment ~= "" then Comment = Comment..", " end | |
-- since the pc++ is in the 'else' path, the sense is opposite | |
local sense = " if false" | |
if inst.OP == 27 then | |
if inst.C == 0 then sense = " if true" end | |
else | |
if inst.A == 0 then sense = " if true" end | |
end | |
Comment = Comment..CommentLoc(1, sense) | |
elseif inst.OP == 26 then -- TEST A C | |
Operand = OperandAC(inst) | |
local sense = " if false" | |
if inst.C == 0 then sense = " if true" end | |
Comment = Comment..CommentLoc(1, sense) | |
--------------------------------------------------------------- | |
elseif inst.OP == 28 or -- CALL A B C | |
inst.OP == 29 then -- TAILCALL A B C | |
Operand = OperandABC(inst) | |
--------------------------------------------------------------- | |
elseif inst.OP == 30 then -- RETURN A B | |
Operand = OperandAB(inst) | |
--------------------------------------------------------------- | |
elseif inst.OP == 31 then -- FORLOOP A sBx | |
Operand = OperandAsBx(inst) | |
Comment = CommentLoc(inst.sBx, " if loop") | |
--------------------------------------------------------------- | |
elseif inst.OP == 32 then -- FORPREP A sBx | |
Operand = OperandAsBx(inst) | |
Comment = CommentLoc(inst.sBx) | |
--------------------------------------------------------------- | |
elseif inst.OP == 33 then -- TFORLOOP A C | |
Operand = OperandAC(inst) | |
Comment = CommentLoc(1, " if exit") | |
--------------------------------------------------------------- | |
elseif inst.OP == 34 then -- SETLIST A B C | |
Operand = OperandABC(inst) | |
-- R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B | |
local n = inst.B | |
local c = inst.C | |
if c == 0 then | |
-- grab next inst when index position is large | |
c = func.code[pos + 1] | |
func.inst[pos + 1].prev = true | |
end | |
local start = (c - 1) * config.FPF + 1 | |
local last = start + n - 1 | |
Comment = "index "..start.." to " | |
if n ~= 0 then | |
Comment = Comment..last | |
else | |
Comment = Comment.."top" | |
end | |
--------------------------------------------------------------- | |
elseif inst.OP == 35 then -- CLOSE A | |
Operand = string.format(config.FORMAT_A, inst.A) | |
--------------------------------------------------------------- | |
elseif inst.OP == 36 then -- CLOSURE A Bx | |
Operand = OperandABx(inst) | |
-- lets user know how many following instructions are significant | |
Comment = func.p[inst.Bx + 1].nups.." upvalues" | |
--------------------------------------------------------------- | |
elseif inst.OP == 37 then -- VARARG A B | |
Operand = OperandAB(inst) | |
--------------------------------------------------------------- | |
else | |
-- add your VM extensions here | |
Operand = string.format("OP %d", inst.OP) | |
end | |
--------------------------------------------------------------- | |
-- compose operands and comments | |
--------------------------------------------------------------- | |
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 | |
--[[------------------------------------------------------------------- | |
-- Source listing merging | |
-- * for convenience, file name matching is first via case-sensitive | |
-- comparison, then case-insensitive comparison, and the first | |
-- match found using either method is the one that is used | |
--]]------------------------------------------------------------------- | |
----------------------------------------------------------------------- | |
-- initialize source list for merging | |
-- * this will normally be called by the main chunk function | |
-- * the source listing is read only once, upon initialization | |
----------------------------------------------------------------------- | |
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) -- chomp leading @ | |
for _, fname in ipairs(other_files) do -- find a match | |
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 -- no source file | |
local INF = io.open(config.source, "rb") -- read in source file | |
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 | |
----------------------------------------------------------------------- | |
-- mark source lines | |
-- * marks source lines as a function is read to delineate stuff | |
----------------------------------------------------------------------- | |
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 | |
----------------------------------------------------------------------- | |
-- generate source lines | |
-- * peek at lines above and print them if they have not been printed | |
-- * mark all printed lines so all non-code lines are printed once only | |
----------------------------------------------------------------------- | |
function SourceMerge(func, pc) | |
if not config.source or not config.DISPLAY_FLAG then return end | |
local lnum = func.lineinfo[pc] | |
-- don't print anything new if instruction is on the same line | |
if config.srcprev == lnum then return end | |
config.srcprev = lnum | |
if config.srcsize < lnum then return end -- something fishy | |
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 | |
--[[------------------------------------------------------------------- | |
-- ChunkSpy main processing function | |
-- * in order to maintain correct positional order, the listing will | |
-- show functions as nested; a level number is kept to help the | |
-- user trace the extent of functions in the listing | |
--]]------------------------------------------------------------------- | |
function ChunkSpy(chunk_name, chunk) | |
--------------------------------------------------------------- | |
-- variables | |
--------------------------------------------------------------- | |
local idx = 1 | |
local previdx, len | |
local result = {} -- table with all parsed data | |
local stat = {} | |
result.chunk_name = chunk_name or "" | |
result.chunk_size = string.len(chunk) | |
--------------------------------------------------------------- | |
-- tests if a given number of bytes is available | |
--------------------------------------------------------------- | |
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 | |
--------------------------------------------------------------- | |
-- loads a single byte and returns it as a number | |
--------------------------------------------------------------- | |
local function LoadByte() | |
previdx = idx | |
idx = idx + 1 | |
return string.byte(chunk, previdx) | |
end | |
------------------------------------------------------------- | |
-- loads a block of endian-sensitive bytes | |
-- * rest of code assumes little-endian by default | |
------------------------------------------------------------- | |
local function LoadBlock(size) | |
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 then | |
return b | |
else-- reverse bytes if big endian | |
return string.reverse(b) | |
end | |
end | |
--------------------------------------------------------------- | |
-- display a position, hex data, description line | |
--------------------------------------------------------------- | |
function FormatLine(size, desc, index, segment) | |
if not config.DISPLAY_FLAG or config.DISPLAY_BRIEF then return end | |
if config.DISPLAY_HEX_DATA then | |
-- nicely formats binary chunk data in multiline hexadecimal | |
if size == 0 then | |
WriteLine(FormatPos(index)..config.DISPLAY_SEP | |
..config.BLANKS_HEX_DATA..config.DISPLAY_SEP | |
..desc) | |
else | |
-- split hex data into config.WIDTH_HEX byte strings | |
while size > 0 do | |
local d, dlen = "", size | |
if size > config.WIDTH_HEX then dlen = config.WIDTH_HEX end | |
-- build hex data digits | |
for i = 0, dlen - 1 do | |
d = d..string.format("%02X", string.byte(chunk, index + i)) | |
end | |
-- add padding or continuation indicator | |
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 | |
-- description only on first line of a multiline | |
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--while | |
end--if size | |
else--no hex data mode | |
WriteLine(FormatPos(index)..config.DISPLAY_SEP..desc) | |
end | |
-- end of FormatLine | |
end | |
--------------------------------------------------------------- | |
-- initialize listing display | |
--------------------------------------------------------------- | |
DisplayInit(result.chunk_size) | |
HeaderLine() -- listing display starts here | |
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 **") | |
--------------------------------------------------------------- | |
-- test signature | |
--------------------------------------------------------------- | |
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 | |
--------------------------------------------------------------- | |
-- test version | |
--------------------------------------------------------------- | |
TestChunk(1, idx, "version byte") | |
result.version = LoadByte() | |
if 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) | |
--------------------------------------------------------------- | |
-- test format (5.1) | |
-- * ChunkSpy does not accept anything other than 0. For custom | |
-- * binary chunks, modify ChunkSpy to read it properly. | |
--------------------------------------------------------------- | |
TestChunk(1, idx, "format byte") | |
result.format = LoadByte() | |
if result.format ~= config.FORMAT then | |
error(string.format("ChunkSpy cannot read format %02X chunks", result.format)) | |
end | |
FormatLine(1, "format (0=official)", previdx) | |
--------------------------------------------------------------- | |
-- test endianness | |
--------------------------------------------------------------- | |
TestChunk(1, idx, "endianness byte") | |
local endianness = LoadByte() | |
if not config.AUTO_DETECT then | |
if endianness ~= config.endianness then | |
error("unsupported endianness") | |
end | |
else | |
config.endianness = endianness | |
end | |
FormatLine(1, "endianness (1=little endian)", previdx) | |
--------------------------------------------------------------- | |
-- test sizes | |
--------------------------------------------------------------- | |
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 | |
-- byte sizes | |
TestSize("size_int", "int", "bytes") | |
TestSize("size_size_t", "size_t", "bytes") | |
TestSize("size_Instruction", "Instruction", "bytes") | |
TestSize("size_lua_Number", "number", "bytes") | |
-- initialize decoder (see the 5.0.2 script if you want to customize | |
-- bit field sizes; Lua 5.1 has fixed instruction bit field sizes) | |
DecodeInit() | |
--------------------------------------------------------------- | |
-- test integral flag (5.1) | |
--------------------------------------------------------------- | |
TestChunk(1, idx, "integral byte") | |
config.integral = LoadByte() | |
FormatLine(1, "integral (1=integral)", previdx) | |
--------------------------------------------------------------- | |
-- verify or determine lua_Number type | |
--------------------------------------------------------------- | |
local num_id = config.size_lua_Number .. config.integral | |
if not config.AUTO_DETECT then | |
if config.number_type ~= LUANUMBER_ID[num_id] then | |
error("incorrect lua_Number format or bad test number") | |
end | |
else | |
-- look for a number type match in our table | |
config.number_type = nil | |
for i, v in pairs(LUANUMBER_ID) do | |
if num_id == i then config.number_type = v end | |
end | |
if not config.number_type then | |
error("unrecognized lua_Number type") | |
end | |
end | |
DescLine("* number type: "..config.number_type) | |
--------------------------------------------------------------- | |
-- primitive platform auto-detection | |
--------------------------------------------------------------- | |
if config.AUTO_DETECT then | |
config.description = nil | |
for _, cfg in pairs(CONFIGURATION) do | |
if cfg.endianness == config.endianness and | |
cfg.size_int == config.size_int and | |
cfg.size_size_t == config.size_size_t and | |
cfg.size_Instruction == config.size_Instruction and | |
cfg.size_lua_Number == config.size_lua_Number and | |
cfg.integral == config.integral and | |
cfg.number_type == config.number_type then | |
config.description = cfg.description | |
end | |
end | |
if not config.description then | |
config.description = "chunk platform unrecognized" | |
end | |
-- some parameters are not in the global header, e.g. FPF | |
-- see the config table for more on these constants | |
end | |
DescLine("* "..config.description) | |
if config.DISPLAY_BRIEF then WriteLine(config.DISPLAY_COMMENT..config.description) end | |
-- end of global header | |
stat.header = idx - 1 | |
DisplayStat("* global header = "..stat.header.." bytes") | |
DescLine("** global header end **") | |
--------------------------------------------------------------- | |
-- this is recursively called to load the chunk or function body | |
--------------------------------------------------------------- | |
local function LoadFunction(funcname, num, level) | |
local func = {} | |
------------------------------------------------------------- | |
-- loads an integer (signed) | |
------------------------------------------------------------- | |
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 | |
-- test for negative number | |
if string.byte(x, config.size_int) > 127 then | |
sum = sum - math.ldexp(1, 8 * config.size_int) | |
end | |
-- from the looks of it, integers needed are positive | |
if sum < 0 then error("bad integer") end | |
return sum | |
end | |
end | |
------------------------------------------------------------- | |
-- loads a size_t (assume unsigned) | |
------------------------------------------------------------- | |
local function LoadSize() | |
local x = LoadBlock(config.size_size_t) | |
if not x then | |
--error("could not load size_t") handled in LoadString() | |
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 | |
------------------------------------------------------------- | |
-- loads a number (lua_Number type) | |
------------------------------------------------------------- | |
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 | |
------------------------------------------------------------- | |
-- load a string (size, data pairs) | |
------------------------------------------------------------- | |
local function LoadString() | |
local len = LoadSize() | |
if not len then | |
error("could not load String") | |
else | |
if len == 0 then -- there is no error, return a nil | |
return nil | |
end | |
TestChunk(len, idx, "LoadString") | |
-- note that ending NUL is removed | |
local s = string.sub(chunk, idx, idx + len - 2) | |
idx = idx + len | |
return s | |
end | |
end | |
------------------------------------------------------------- | |
-- load line information | |
------------------------------------------------------------- | |
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 | |
------------------------------------------------------------- | |
-- load locals information | |
------------------------------------------------------------- | |
local function LoadLocals() | |
local n = LoadInt() | |
func.pos_locvars = previdx | |
func.locvars = {} | |
func.sizelocvars = n | |
for i = 1, n do | |
local locvar = {} | |
locvar.varname = LoadString() | |
locvar.pos_varname = previdx | |
locvar.startpc = LoadInt() | |
locvar.pos_startpc = previdx | |
locvar.endpc = LoadInt() | |
locvar.pos_endpc = previdx | |
func.locvars[i] = locvar | |
end | |
end | |
------------------------------------------------------------- | |
-- load upvalues information | |
------------------------------------------------------------- | |
local function LoadUpvalues() | |
local n = LoadInt() | |
if n ~= 0 and n~= func.nups then | |
error(string.format("bad nupvalues: read %d, expected %d", n, func.nups)) | |
return | |
end | |
func.pos_upvalues = previdx | |
func.upvalues = {} | |
func.sizeupvalues = n | |
func.posupvalues = {} | |
for i = 1, n do | |
func.upvalues[i] = LoadString() | |
func.posupvalues[i] = previdx | |
if not func.upvalues[i] then | |
error("empty string at index "..(i - 1).."in upvalue table") | |
end | |
end | |
end | |
------------------------------------------------------------- | |
-- load constants information (data) | |
------------------------------------------------------------- | |
local function LoadConstantKs() | |
local n = LoadInt() | |
func.pos_ks = previdx | |
func.k = {} | |
func.sizek = n | |
func.posk = {} | |
for i = 1, n do | |
local t = LoadByte() | |
func.posk[i] = previdx | |
if t == config.LUA_TNUMBER then | |
func.k[i] = LoadNumber() | |
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_TSTRING then | |
func.k[i] = LoadString() | |
elseif t == config.LUA_TNIL then | |
func.k[i] = nil | |
else | |
error("bad constant type "..t.." at "..previdx) | |
end | |
end--for | |
end | |
------------------------------------------------------------- | |
-- load constants information (local functions) | |
------------------------------------------------------------- | |
local function LoadConstantPs() | |
local n = LoadInt() | |
func.pos_ps = previdx | |
func.p = {} | |
func.sizep = n | |
for i = 1, n do | |
-- recursive call back on itself, next level | |
func.p[i] = LoadFunction(func.source, i - 1, level + 1) | |
end | |
end | |
------------------------------------------------------------- | |
-- load function code | |
------------------------------------------------------------- | |
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 | |
------------------------------------------------------------- | |
-- body of LoadFunction() starts here | |
------------------------------------------------------------- | |
-- statistics handler | |
local start = idx | |
func.stat = {} | |
local function SetStat(item) | |
func.stat[item] = idx - start | |
start = idx | |
end | |
-- source file name | |
func.source = LoadString() | |
func.pos_source = previdx | |
if func.source == "" and level == 1 then func.source = funcname end | |
-- line where the function was defined | |
func.linedefined = LoadInt() | |
func.pos_linedefined = previdx | |
func.lastlinedefined = LoadInt() | |
------------------------------------------------------------- | |
-- some byte counts | |
------------------------------------------------------------- | |
if TestChunk(4, idx, "function header") then return end | |
func.nups = LoadByte() | |
func.numparams = LoadByte() | |
func.is_vararg = LoadByte() | |
func.maxstacksize = LoadByte() | |
SetStat("header") | |
------------------------------------------------------------- | |
-- these are lists, LoadConstantPs() may be recursive | |
------------------------------------------------------------- | |
-- load parts of a chunk (rearranged in 5.1) | |
LoadCode() SetStat("code") | |
LoadConstantKs() SetStat("consts") | |
LoadConstantPs() SetStat("funcs") | |
LoadLines() SetStat("lines") | |
LoadLocals() SetStat("locals") | |
LoadUpvalues() SetStat("upvalues") | |
return func | |
-- end of LoadFunction | |
end | |
--------------------------------------------------------------- | |
-- displays function information | |
-- * decoupled from LoadFunction due to 5.1 chunk rearrangement | |
--------------------------------------------------------------- | |
function DescFunction(func, num, level) | |
------------------------------------------------------------- | |
-- brief display mode with indentation style option | |
------------------------------------------------------------- | |
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 | |
------------------------------------------------------------- | |
-- describe a string (size, data pairs) | |
------------------------------------------------------------- | |
local function DescString(s, pos) | |
local len = string.len(s or "") | |
if len > 0 then | |
len = len + 1 -- add the NUL back | |
s = s.."\0" -- was removed by LoadString | |
end | |
FormatLine(config.size_size_t, string.format("string size (%s)", len), pos) | |
if len == 0 then return end | |
pos = pos + config.size_size_t | |
if len <= config.WIDTH_HEX then | |
FormatLine(len, EscapeString(s, 1), pos) | |
else | |
-- split up long strings nicely, easier to view | |
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 | |
------------------------------------------------------------- | |
-- describe line information | |
------------------------------------------------------------- | |
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 | |
-- mark significant lines in source listing | |
SourceMark(func) | |
end | |
------------------------------------------------------------- | |
-- describe locals information | |
------------------------------------------------------------- | |
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.varname, locvar.pos_varname) | |
DescLine("local ["..(i - 1).."]: "..EscapeString(locvar.varname)) | |
BriefLine(".local"..config.DISPLAY_SEP..EscapeString(locvar.varname, 1) | |
..config.DISPLAY_SEP..config.DISPLAY_COMMENT..(i - 1)) | |
FormatLine(config.size_int, " startpc ("..locvar.startpc..")", locvar.pos_startpc) | |
FormatLine(config.size_int, " endpc ("..locvar.endpc..")",locvar.pos_endpc) | |
end | |
end | |
------------------------------------------------------------- | |
-- describe upvalues information | |
------------------------------------------------------------- | |
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] | |
DescString(upvalue, func.posupvalues[i]) | |
DescLine("upvalue ["..(i - 1).."]: "..EscapeString(upvalue)) | |
BriefLine(".upvalue"..config.DISPLAY_SEP..EscapeString(upvalue, 1) | |
..config.DISPLAY_SEP..config.DISPLAY_COMMENT..(i - 1)) | |
end | |
end | |
------------------------------------------------------------- | |
-- describe constants information (data) | |
------------------------------------------------------------- | |
local function DescConstantKs() | |
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] | |
if type(k) == "number" then | |
FormatLine(1, "const type "..config.LUA_TNUMBER, posk) | |
FormatLine(config.size_lua_Number, CONST.."("..k..")", posk + 1) | |
BriefLine(".const"..config.DISPLAY_SEP..k..CONSTB) | |
elseif type(k) == "boolean" then | |
FormatLine(1, "const type "..config.LUA_TBOOLEAN, posk) | |
FormatLine(1, CONST.."("..tostring(k)..")", posk + 1) | |
BriefLine(".const"..config.DISPLAY_SEP..tostring(k)..CONSTB) | |
elseif type(k) == "string" then | |
FormatLine(1, "const type "..config.LUA_TSTRING, posk) | |
DescString(k, posk + 1) | |
DescLine(CONST..EscapeString(k, 1)) | |
BriefLine(".const"..config.DISPLAY_SEP..EscapeString(k, 1)..CONSTB) | |
elseif type(k) == "nil" then | |
FormatLine(1, "const type "..config.LUA_TNIL, posk) | |
DescLine(CONST.."nil") | |
BriefLine(".const"..config.DISPLAY_SEP.."nil"..CONSTB) | |
end | |
end--for | |
end | |
------------------------------------------------------------- | |
-- describe constants information (local functions) | |
------------------------------------------------------------- | |
local function DescConstantPs() | |
local n = func.sizep | |
DescLine("* functions:") | |
FormatLine(config.size_int, "sizep ("..n..")", func.pos_ps) | |
for i = 1, n do | |
-- recursive call back on itself, next level | |
DescFunction(func.p[i], i - 1, level + 1) | |
end | |
end | |
------------------------------------------------------------- | |
-- describe function code | |
-- * inst decode subfunctions: DecodeInst() and DescribeInst() | |
------------------------------------------------------------- | |
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] | |
-- compose instruction: opcode operands [; comments] | |
local d = DescribeInst(inst, i, func) | |
d = string.format("[%s] %s", ZeroPad(i, ISIZE), d) | |
-- source code insertion | |
SourceMerge(func, i) | |
FormatLine(config.size_Instruction, d, pos) | |
BriefLine(d) | |
pos = pos + config.size_Instruction | |
end | |
end | |
------------------------------------------------------------- | |
-- body of DescFunction() starts here | |
------------------------------------------------------------- | |
DescLine("") | |
BriefLine("") | |
FormatLine(0, "** function ["..num.."] definition (level "..level..")", | |
func.pos_source) | |
BriefLine("; function ["..num.."] definition (level "..level..")") | |
DescLine("** start of function **") | |
-- source file name | |
DescString(func.source, func.pos_source) | |
if func.source == nil then | |
DescLine("source name: (none)") | |
else | |
DescLine("source name: "..EscapeString(func.source)) | |
end | |
-- optionally initialize source listing merging | |
SourceInit(func.source) | |
-- line where the function was defined | |
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 | |
-- display byte counts | |
FormatLine(1, "nups ("..func.nups..")", pos) | |
FormatLine(1, "numparams ("..func.numparams..")", pos + 1) | |
FormatLine(1, "is_vararg ("..func.is_vararg..")", pos + 2) | |
FormatLine(1, "maxstacksize ("..func.maxstacksize..")", pos + 3) | |
BriefLine(string.format("; %d upvalues, %d params, %d stacks", | |
func.nups, func.numparams, func.maxstacksize)) | |
BriefLine(string.format(".function%s%d %d %d %d", config.DISPLAY_SEP, | |
func.nups, func.numparams, func.is_vararg, func.maxstacksize)) | |
-- display parts of a chunk | |
if config.DISPLAY_FLAG and config.DISPLAY_BRIEF then | |
DescLines() -- brief displays 'declarations' first | |
DescLocals() | |
DescUpvalues() | |
DescConstantKs() | |
DescConstantPs() | |
DescCode() | |
else | |
DescCode() -- normal displays positional order | |
DescConstantKs() | |
DescConstantPs() | |
DescLines() | |
DescLocals() | |
DescUpvalues() | |
end | |
-- show function statistics block | |
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("* 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 | |
DisplayStat("* TOTAL size = "..func.stat.total.." bytes") | |
DescLine("** end of function **\n") | |
BriefLine("; end of function\n") | |
end | |
--------------------------------------------------------------- | |
-- actual call to start the function loading process | |
--------------------------------------------------------------- | |
result.func = LoadFunction("(chunk)", 0, 1) | |
DescFunction(result.func, 0, 1) | |
stat.total = idx - 1 | |
DisplayStat("* TOTAL size = "..stat.total.." bytes") | |
result.stat = stat | |
FormatLine(0, "** end of chunk **", idx) | |
return result | |
-- end of ChunkSpy | |
end | |
--[[------------------------------------------------------------------- | |
-- Writes out a binary chunk as a string or returns it | |
-- * writer generally assumes the set configuration is good | |
-- * TODO add tests to ensure that binary chunk rules are followed | |
--]]------------------------------------------------------------------- | |
function WriteBinaryChunk(parsed, tofile) | |
local Buffer = {} | |
-- setup for binary file I/O | |
if tofile then | |
if not config.OUTPUT_FILE then | |
error("must specify an output filename for rewrites") | |
else -- a kludge | |
WriteLine = function(s) config.OUTPUT_FILE:write(s) end | |
end | |
end | |
--------------------------------------------------------------- | |
-- output functions | |
--------------------------------------------------------------- | |
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 | |
--------------------------------------------------------------- | |
-- writes a block of bytes to little/big-endian output | |
--------------------------------------------------------------- | |
local function WriteBlock(v) | |
if config.endianness == 1 then | |
Dump(v) | |
else-- reverse bytes if big endian | |
Dump(string.reverse(v)) | |
end | |
end | |
--------------------------------------------------------------- | |
-- write header | |
--------------------------------------------------------------- | |
Dump(config.SIGNATURE) | |
DumpByte(config.VERSION) | |
DumpByte(config.FORMAT) | |
DumpByte(config.endianness) | |
DumpByte(config.size_int) -- byte sizes | |
DumpByte(config.size_size_t) | |
DumpByte(config.size_Instruction) | |
DumpByte(config.size_lua_Number) | |
DecodeInit() | |
DumpByte(config.integral) | |
-- no more test number in 5.1 | |
--------------------------------------------------------------- | |
-- recursively called to write function data | |
--------------------------------------------------------------- | |
local function WriteFunction(func) | |
------------------------------------------------------------- | |
-- writes an unsigned integer (for integers, size_ts) | |
-- * unsigned because the relevant data is always positive | |
------------------------------------------------------------- | |
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 | |
------------------------------------------------------------- | |
-- writes a number (lua_Number type) | |
------------------------------------------------------------- | |
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 | |
------------------------------------------------------------- | |
-- writes a string | |
------------------------------------------------------------- | |
local function WriteString(str) | |
if not str then | |
WriteUnsigned(0, config.size_size_t) | |
return | |
end | |
str = str.."\0" -- mandatory NUL termination | |
WriteUnsigned(string.len(str), config.size_size_t) | |
Dump(str) | |
end | |
------------------------------------------------------------- | |
-- write line information | |
------------------------------------------------------------- | |
local function WriteLines() | |
WriteUnsigned(func.sizelineinfo) | |
for i = 1, func.sizelineinfo do WriteUnsigned(func.lineinfo[i]) end | |
end | |
------------------------------------------------------------- | |
-- write locals information | |
------------------------------------------------------------- | |
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 | |
------------------------------------------------------------- | |
-- write upvalues information | |
------------------------------------------------------------- | |
local function WriteUpvalues() | |
-- func.sizeupvalues == func.nups | |
WriteUnsigned(func.sizeupvalues) | |
for i = 1, func.sizeupvalues do WriteString(func.upvalues[i]) end | |
end | |
------------------------------------------------------------- | |
-- write constants information (data) | |
------------------------------------------------------------- | |
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--for | |
end | |
------------------------------------------------------------- | |
-- write constants information (local functions) | |
------------------------------------------------------------- | |
local function WriteConstantPs() | |
WriteUnsigned(func.sizep) | |
for i = 1, func.sizep do WriteFunction(func.p[i]) end | |
end | |
------------------------------------------------------------- | |
-- write function code | |
------------------------------------------------------------- | |
local function WriteCode() | |
WriteUnsigned(func.sizecode) | |
for i = 1, func.sizecode do WriteBlock(EncodeInst(func.inst[i])) end | |
end | |
------------------------------------------------------------- | |
-- body of WriteFunction() starts here | |
------------------------------------------------------------- | |
WriteString(func.source) | |
WriteUnsigned(func.linedefined) | |
WriteUnsigned(func.lastlinedefined) | |
DumpByte(func.nups) -- some byte counts | |
DumpByte(func.numparams) | |
DumpByte(func.is_vararg) | |
DumpByte(func.maxstacksize) | |
WriteCode() | |
WriteConstantKs() | |
WriteConstantPs() -- may be recursive | |
WriteLines() -- these are lists | |
WriteLocals() | |
WriteUpvalues() | |
-- end of WriteFunction | |
end | |
--------------------------------------------------------------- | |
-- actual call to start the function writing process | |
--------------------------------------------------------------- | |
WriteFunction(parsed.func) | |
if not tofile then return table.concat(Buffer) end | |
end | |
--[[------------------------------------------------------------------- | |
-- ChunkSpy internal tests | |
-- * not very comprehensive at the moment, more useful during dev... | |
-- * to make it more chatty, set config.VERBOSE_TEST to true | |
--]]------------------------------------------------------------------- | |
function ChunkSpy_Test() | |
--------------------------------------------------------------- | |
-- In A.D. 2101 | |
-- War was beginning. | |
--------------------------------------------------------------- | |
local FAIL, SUCCEED = false, true | |
local GotError = false | |
-- force to x86_standard because test data follows this | |
SetProfile("x86 standard") | |
config.DISPLAY_FLAG = false -- don't show listing | |
config.AUTO_DETECT = false | |
print(title) | |
--------------------------------------------------------------- | |
-- Captain: What happen ? | |
--------------------------------------------------------------- | |
local function expected(sample, outcome, errmatch, message) | |
local ok, msg = pcall(ChunkSpy, "test", sample) | |
-- deduce outcome | |
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 | |
--------------------------------------------------------------- | |
-- Mechanic: Somebody set up us the bomb. | |
--------------------------------------------------------------- | |
expected("\0\0\0\0", FAIL, | |
"header signature not found", "incorrect header signature") | |
--------------------------------------------------------------- | |
-- Operator: We get signal. | |
-- Captain: What ! | |
--------------------------------------------------------------- | |
expected("\27Lua\64", FAIL, | |
"cannot read version", "incorrect version byte") | |
expected("\27Lua\81\1", FAIL, | |
"cannot read format", "incorrect format byte") | |
--------------------------------------------------------------- | |
-- Operator: Main screen turn on. | |
-- Captain: It's you !! | |
--------------------------------------------------------------- | |
expected("\27Lua\81\0\0", FAIL, | |
"unsupported endianness", "incorrect endianness byte") | |
--------------------------------------------------------------- | |
-- Cats: How are you gentlemen !! | |
-- Cats: All your base are belong to us. | |
-- Cats: You are on the way to destruction. | |
--------------------------------------------------------------- | |
expected("\27Lua\81\0\1\0\0\0\0", FAIL, | |
"int size", "incorrect int size byte") | |
expected("\27Lua\81\0\1\4\0\0\0", FAIL, | |
"size_t size", "incorrect size_t size byte") | |
expected("\27Lua\81\0\1\4\4\0\0", FAIL, | |
"Instruction size", "incorrect Instruction size byte") | |
expected("\27Lua\81\0\1\4\4\4\0", FAIL, | |
"number size", "incorrect lua_Number size byte") | |
--------------------------------------------------------------- | |
-- Captain: What you say !! | |
-- Cats: You have no chance to survive make your time. | |
-- Cats: Ha Ha Ha Ha .... | |
--------------------------------------------------------------- | |
expected("\27Lua\81\0\1\4\4\4\8\1", FAIL, | |
"incorrect lua_Number", "incorrect integral byte") | |
--------------------------------------------------------------- | |
-- Operator: Captain '!!'* | |
-- Captain: Take off every 'Zig'!! | |
-- Captain: You know what you doing. | |
-- Captain: Move 'Zig'. | |
--------------------------------------------------------------- | |
local LUA_HEADER = "\27Lua\81\0\1\4\4\4\8\1" | |
-- add more tests here | |
--------------------------------------------------------------- | |
-- Captain: For great justice. | |
--------------------------------------------------------------- | |
if not GotError then | |
print("ChunkSpy_Test: completed simple tests without errors") | |
end | |
end | |
--[[------------------------------------------------------------------- | |
-- ChunkSpy sample generator | |
-- * try to exercise the code, especially instruction decoding | |
-- * currently, the following opcodes are not generated: | |
-- SELF, SUB, MUL, DIV, POW, NOT, LT, LE, TEST, TAILCALL, CLOSE | |
-- * the code is nonsensical, don't try to run it!!! | |
-- * check correctness by visual inspection... | |
--]]------------------------------------------------------------------- | |
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 | |
--[[------------------------------------------------------------------- | |
-- Top-level file/chunk handling, processes user's binary chunks | |
--]]------------------------------------------------------------------- | |
function ChunkSpy_DoFiles(files) | |
local binary_chunks = {} | |
--------------------------------------------------------------- | |
-- pre-processing of file list | |
--------------------------------------------------------------- | |
for i, v in pairs(files) do | |
local filename, binchunk | |
if type(i) == "number" then -- normally specified files | |
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 -- binary chunk supplied by --source, after compilation | |
filename = i | |
binchunk = v | |
end | |
if binchunk then | |
local sig = string.sub(binchunk, 1, string.len(config.SIGNATURE)) | |
-- identify all binary chunks via signature | |
if sig == config.SIGNATURE then | |
-- duplicate filenames eliminated here | |
binary_chunks[filename] = binchunk | |
else | |
-- may be a source code listing | |
table.insert(other_files, filename) | |
end | |
end | |
end | |
--------------------------------------------------------------- | |
-- per-chunk processing | |
--------------------------------------------------------------- | |
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 | |
-- returns parsed table, for further optional processing | |
local result = ChunkSpy(i, v); done = true | |
------------------------------------------------------------- | |
-- write out a binary chunk option | |
------------------------------------------------------------- | |
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 | |
-- force --source consistency with luac output | |
if string.sub(result.func.source, 1, 1) ~= "@" then | |
result.func.source = "@"..result.func.source | |
end | |
end | |
WriteBinaryChunk(result, true) | |
------------------------------------------------------------- | |
-- run a binary chunk option | |
-- * global environment is inherited if read, quite dangerous | |
------------------------------------------------------------- | |
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 func, msg = loadstring(binchunk, i) -- load | |
if not func then error(msg) end | |
local sandbox = {} | |
arg_other[0] = i -- propagate rest of args | |
arg = arg_other | |
setmetatable(sandbox, {__index = _G}) -- sandbox (see PIL book) | |
setfenv(func, sandbox) | |
func() -- execute | |
return | |
end | |
end | |
if not done then | |
print(title) print("ChunkSpy: no binary chunks processed!") | |
end | |
end | |
--[[------------------------------------------------------------------- | |
-- ChunkSpy interactive mode; instant feedback! | |
--]]------------------------------------------------------------------- | |
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") | |
------------------------------------------------------------- | |
-- handle line continuation | |
------------------------------------------------------------- | |
elseif string.sub(l, -1, -1) == "\\" then | |
if not prevline then prevline = "" end | |
prevline = prevline..string.sub(l, 1, -2) | |
------------------------------------------------------------- | |
-- compose source chunk, compile, disassemble | |
------------------------------------------------------------- | |
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--if l | |
end--while | |
end | |
--[[------------------------------------------------------------------- | |
-- loads a Lua source file and tries to compile it using loadstring() | |
-- * integration with ChunkSpy_DoFiles() is a little messy... | |
--]]------------------------------------------------------------------- | |
function CompileSourceFile(filename) | |
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 | |
--[[------------------------------------------------------------------- | |
-- Command-line interface | |
--]]------------------------------------------------------------------- | |
function main() | |
--------------------------------------------------------------- | |
-- arg[0] is not set if ChunkSpy.lua is embedded, for ChunkSpy.c | |
--------------------------------------------------------------- | |
local usage, exec | |
if arg[0] then exec = "lua ChunkSpy.lua" else exec = "ChunkSpy" end | |
usage = string.format(USAGE, exec, exec) | |
--------------------------------------------------------------- | |
-- check Lua version | |
--------------------------------------------------------------- | |
if _VERSION ~= "Lua 5.1" then | |
error("this version of ChunkSpy requires Lua 5.1") | |
end | |
--------------------------------------------------------------- | |
-- handle arguments | |
--------------------------------------------------------------- | |
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 -- handle options here | |
--------------------------------------------------------- | |
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 | |
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 == "--rewrite" then | |
if not b then error("--rewrite option needs a profile name") 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 | |
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]) -- gather rest of args | |
j = j + 1 | |
end | |
break | |
--------------------------------------------------------- | |
else | |
error("unrecognized option "..a) | |
end--if a | |
--------------------------------------------------------- | |
else | |
table.insert(files, a); gotfile = true -- potential filename | |
end--if == "-" | |
i = i + 1 | |
end--while | |
------------------------------------------------------------- | |
-- make calls to perform functions | |
-- * OutputInit() and OutputExit() brackets listing output | |
------------------------------------------------------------- | |
OutputInit() | |
if perform then | |
perform() | |
elseif gotfile then | |
ChunkSpy_DoFiles(files) | |
else | |
print(title) print("ChunkSpy: nothing to do!") | |
end | |
OutputExit() | |
end | |
end | |
----------------------------------------------------------------------- | |
-- program entry point | |
----------------------------------------------------------------------- | |
ok, msg = pcall(main) -- call main() for proper traceback | |
if not ok then -- error | |
print(title) | |
print("* Run with option -h or --help for usage information") | |
print(msg) | |
end | |
----end-of-script---- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
add x64 support.