├── .gitmodules ├── Dockerfile ├── README.md ├── conv.lua ├── lua51dock.sh ├── lua52dock.sh ├── lua53dock.sh ├── luadock.sh ├── scope.lua ├── scope_config.lua ├── scope_dechunk.lua ├── scope_decoder.lua └── scope_output.lua /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "b2b"] 2 | path = b2b 3 | url = https://github.com/codervijo/b2b 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | b2b/apps/lua52/Dockerfile -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # luascope 2 | 3 | Lua Scope is for looking into Lua chunk, or IOW, decode lua chunks. 4 | A Lua 5.1/5.2/5.3 binary chunk disassembler. 5 | LuaScope was inspired by Jein-Hong Man's ChunkSpy. 6 | LuaScope attempts to be lua version agnostic. 7 | 8 | - Based on ChunkSpy on Lua 5.1 9 | - Works as ChunkSpy on Lua 5.2 10 | - Works also as ChunkSpy on Lua 5.3 11 | 12 | 13 | Dockerfile and shell scripts are provided for you to run 5.1/5.2/5.3 inside a container. 14 | 15 | # Compatibility 16 | 17 | This was tested only on Ubuntu, but should work on any Unix-based system. 18 | 19 | # Steps to get started 20 | 21 | 1. Make sure b2b directory has git submodule by running 22 | either ```git clone --recurse-submodules https://github.com/codervijo/luascope``` 23 | or ```git submodule update --init``` 24 | 2. Make sure docker is installed 25 | 3. Run ```./lua53dock.sh``` to run luascope with Lua-5.3 26 | 27 | If you do not have docker for any reason, do the following instead of 3 steps above: 28 | ```lua ./scope.lua``` 29 | 30 | # How to use luascope 31 | ``` 32 | $ ./lua53dock.sh 33 | Found Lua Version Lua 5.3 34 | >print "hi" 35 | hi 36 | Pos Hex Data Description or Code 37 | ------------------------------------------------------------------------ 38 | 0000 ** source chunk: (interactive mode) 39 | ** global header start ** 40 | 0000 1B4C7561 header signature: "\27Lua" 41 | 0004 53 version (major:minor hex digits) 42 | 0005 00 format (0=official) 43 | ... 44 | .... 45 | ``` -------------------------------------------------------------------------------- /conv.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | --[[ 4 | -- Number handling functions 5 | -- * converts a string of bytes to and from a proper number 6 | -- * WARNING single() and double() can only do normal floats 7 | -- and zeros. Denormals, infinities and NaNs are not recognized. 8 | -- * See 5.0.2/ChunkSpy.lua for IEEE floating-point notes 9 | --]] 10 | 11 | convert_from = {} -- tables for number conversion function lookup 12 | convert_to = {} 13 | 14 | ----------------------------------------------------------------------- 15 | -- Converts an 8-byte little-endian string to a IEEE754 double number 16 | -- * NOTE: see warning about accuracy in the header comments! 17 | ----------------------------------------------------------------------- 18 | convert_from["double"] = function(x) 19 | local sign = 1 20 | local mantissa = string.byte(x, 7) % 16 21 | for i = 6, 1, -1 do mantissa = mantissa * 256 + string.byte(x, i) end 22 | if string.byte(x, 8) > 127 then sign = -1 end 23 | local exponent = (string.byte(x, 8) % 128) * 16 + 24 | math.floor(string.byte(x, 7) / 16) 25 | if exponent == 0 then return 0 end 26 | mantissa = (math.ldexp(mantissa, -52) + 1) * sign 27 | return math.ldexp(mantissa, exponent - 1023) 28 | end 29 | 30 | ----------------------------------------------------------------------- 31 | -- Converts a 4-byte little-endian string to a IEEE754 single number 32 | -- * TODO UNTESTED!!! * 33 | ----------------------------------------------------------------------- 34 | convert_from["single"] = function(x) 35 | local sign = 1 36 | local mantissa = string.byte(x, 3) % 128 37 | for i = 2, 1, -1 do mantissa = mantissa * 256 + string.byte(x, i) end 38 | if string.byte(x, 4) > 127 then sign = -1 end 39 | local exponent = (string.byte(x, 4) % 128) * 2 + 40 | math.floor(string.byte(x, 3) / 128) 41 | if exponent == 0 then return 0 end 42 | mantissa = (math.ldexp(mantissa, -23) + 1) * sign 43 | return math.ldexp(mantissa, exponent - 127) 44 | end 45 | 46 | ----------------------------------------------------------------------- 47 | -- Converts a little-endian integer string to a number 48 | -- * TODO UNTESTED!!! * 49 | ----------------------------------------------------------------------- 50 | convert_from["int"] = function(x) 51 | local sum = 0 52 | for i = config.size_lua_Number, 1, -1 do 53 | sum = sum * 256 + string.byte(x, i) 54 | end 55 | -- test for negative number 56 | if string.byte(x, config.size_lua_Number) > 127 then 57 | sum = sum - math.ldexp(1, 8 * config.size_lua_Number) 58 | end 59 | return sum 60 | end 61 | 62 | ----------------------------------------------------------------------- 63 | -- * WARNING this will fail for large long longs (64-bit numbers) 64 | -- because long longs exceeds the precision of doubles. 65 | ----------------------------------------------------------------------- 66 | convert_from["long long"] = convert_from["int"] 67 | 68 | ----------------------------------------------------------------------- 69 | -- Converts a IEEE754 double number to an 8-byte little-endian string 70 | -- * NOTE: see warning about accuracy in the header comments! 71 | ----------------------------------------------------------------------- 72 | convert_to["double"] = function(x) 73 | local sign = 0 74 | if x < 0 then sign = 1; x = -x end 75 | local mantissa, exponent = math.frexp(x) 76 | if x == 0 then -- zero 77 | mantissa, exponent = 0, 0 78 | else 79 | mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53) 80 | exponent = exponent + 1022 81 | end 82 | local v, byte = "" -- convert to bytes 83 | x = mantissa 84 | for i = 1,6 do 85 | x, byte = grab_byte(x); v = v..byte -- 47:0 86 | end 87 | x, byte = grab_byte(exponent * 16 + x); v = v..byte -- 55:48 88 | x, byte = grab_byte(sign * 128 + x); v = v..byte -- 63:56 89 | return v 90 | end 91 | 92 | ----------------------------------------------------------------------- 93 | -- Converts a IEEE754 single number to a 4-byte little-endian string 94 | -- * TODO UNTESTED!!! * 95 | ----------------------------------------------------------------------- 96 | convert_to["single"] = function(x) 97 | local sign = 0 98 | if x < 0 then sign = 1; x = -x end 99 | local mantissa, exponent = math.frexp(x) 100 | if x == 0 then -- zero 101 | mantissa = 0; exponent = 0 102 | else 103 | mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 24) 104 | exponent = exponent + 126 105 | end 106 | local v, byte = "" -- convert to bytes 107 | x, byte = grab_byte(mantissa); v = v..byte -- 7:0 108 | x, byte = grab_byte(x); v = v..byte -- 15:8 109 | x, byte = grab_byte(exponent * 128 + x); v = v..byte -- 23:16 110 | x, byte = grab_byte(sign * 128 + x); v = v..byte -- 31:24 111 | return v 112 | end 113 | 114 | ----------------------------------------------------------------------- 115 | -- Converts a number to a little-endian integer string 116 | -- * TODO UNTESTED!!! * 117 | ----------------------------------------------------------------------- 118 | 119 | convert_to["int"] = function(x) 120 | local v = "" 121 | x = math.floor(x) 122 | if x >= 0 then 123 | for i = 1, config.size_lua_Number do 124 | v = v..string.char(x % 256); x = math.floor(x / 256) 125 | end 126 | else-- x < 0 127 | x = -x 128 | local carry = 1 129 | for i = 1, config.size_lua_Number do 130 | local c = 255 - (x % 256) + carry 131 | if c == 256 then c = 0; carry = 1 else carry = 0 end 132 | v = v..string.char(c); x = math.floor(x / 256) 133 | end 134 | end 135 | -- optional overflow test; not enabled at the moment 136 | -- if x > 0 then error("number conversion overflow") end 137 | return v 138 | end 139 | 140 | ----------------------------------------------------------------------- 141 | -- * WARNING this will fail for large long longs (64-bit numbers) 142 | -- because long longs exceeds the precision of doubles. 143 | ----------------------------------------------------------------------- 144 | convert_to["long long"] = convert_to["int"] 145 | 146 | function get_convert_to() 147 | return convert_to 148 | end 149 | 150 | function get_convert_from() 151 | return convert_from 152 | end -------------------------------------------------------------------------------- /lua51dock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure b2b directory exists and is git clone of right project 4 | [ -d ./b2b ] || (echo "B2B GIT repo not found. Please get all GIT submodules. Exiting"; exit 77); [ "$?" -eq 77 ] && exit 2 5 | 6 | CONTAINER=lua51 7 | 8 | # Make symlink to docker for this dev environment 9 | ln -sf b2b/apps/${CONTAINER}/Dockerfile . 10 | 11 | if [[ "$(sudo docker images -q ${CONTAINER}:latest 2>/dev/null)" == "" ]]; 12 | then 13 | sudo docker build -t ${CONTAINER} . 14 | fi 15 | sudo docker run -v $PWD:/usr/src/ --rm -it ${CONTAINER} lua /usr/src/scope.lua 16 | -------------------------------------------------------------------------------- /lua52dock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure b2b directory exists and is git clone of right project 4 | [ -d ./b2b ] || (echo "B2B GIT repo not found. Please get all GIT submodules. Exiting"; exit 77); [ "$?" -eq 77 ] && exit 2 5 | 6 | CONTAINER=lua52 7 | 8 | # Make symlink to docker for this dev environment 9 | ln -sf b2b/apps/${CONTAINER}/Dockerfile . 10 | 11 | if [[ "$(sudo docker images -q ${CONTAINER}:latest 2>/dev/null)" == "" ]]; 12 | then 13 | sudo docker build -t ${CONTAINER} . 14 | fi 15 | sudo docker run -v $PWD:/usr/src/ --rm -it ${CONTAINER} lua /usr/src/scope.lua 16 | -------------------------------------------------------------------------------- /lua53dock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure b2b directory exists and is git clone of right project 4 | [ -d ./b2b ] || (echo "B2B GIT repo not found. Please get all GIT submodules. Exiting"; exit 77); [ "$?" -eq 77 ] && exit 2 5 | 6 | CONTAINER=lua53 7 | 8 | # Make symlink to docker for this dev environment 9 | ln -sf b2b/apps/${CONTAINER}/Dockerfile . 10 | 11 | if [[ "$(sudo docker images -q ${CONTAINER}:latest 2>/dev/null)" == "" ]]; 12 | then 13 | sudo docker build -t ${CONTAINER} . 14 | fi 15 | sudo docker run -v $PWD:/usr/src/ --rm -it ${CONTAINER} lua5.3 /usr/src/scope.lua 16 | -------------------------------------------------------------------------------- /luadock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo docker build -t lua51 . 3 | sudo docker run -v $PWD:/usr/src/ --rm -it lua51 lua /usr/src/scope.lua 4 | -------------------------------------------------------------------------------- /scope.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | --[[ 4 | Lua Scope 5 | A Lua 5.1/5.2/5.3 binary chunk disassembler 6 | LuaScope was inspired by Jein-Hong Man's ChunkSpy 7 | --]] 8 | 9 | 10 | title = [[ 11 | LuaScope: A Lua debugger/disassembler 12 | Version 0.0.1 (20200210) Copyright (c) 2019-2020 Vijo Cherian 13 | The COPYRIGHT file describes the conditions under which this 14 | software may be distributed (basically a Lua 5-style license.) 15 | ]] 16 | 17 | package.path = package.path .. ";./?.lua;/usr/src/?.lua" 18 | 19 | require("scope_config") 20 | require("conv") 21 | require("scope_output") 22 | require("scope_decoder") 23 | require("scope_dechunk") 24 | 25 | 26 | -- TODO Vijo Make the selection based on output of uname -m 27 | SetProfile("x86_64") -- default profile 28 | -- config.* profile parms set in Dechunk() call... 29 | local ok, _ = pcall(Dechunk, "", LUA_SAMPLE) 30 | --if not ok then error("error compiling sample to test local profile") end 31 | 32 | --[[ 33 | -- Other globals 34 | --]] 35 | 36 | other_files = {} -- non-chunks (may be source listings) 37 | arg_other = {} -- other arguments (for --run option) 38 | 39 | ----------------------------------------------------------------------- 40 | -- No more TEST_NUMBER in Lua 5.1, uses size_lua_Number + integral 41 | ----------------------------------------------------------------------- 42 | LUANUMBER_ID = { 43 | ["80"] = "double", -- IEEE754 double 44 | ["40"] = "single", -- IEEE754 single 45 | ["41"] = "int", -- int 46 | ["81"] = "long long", -- long long 47 | } 48 | 49 | -- 50 | -- set the default platform (can override with --auto auto-detection) 51 | -- * both in & out paths use config.* parms, a bit clunky for now 52 | -- 53 | config = get_config() 54 | convert_to = get_convert_to() 55 | convert_from = get_convert_from() 56 | 57 | 58 | -- 59 | -- initialize display formatting settings 60 | -- * chunk_size parameter used to set width of position column 61 | -- 62 | 63 | function main() 64 | print("Found Lua Version", _VERSION) 65 | while not done do 66 | if prevline then io.stdout:write(">>") else io.stdout:write(">") end 67 | io.stdout:flush() 68 | local l = io.stdin:read("*l") 69 | 70 | if l == nil or (l == "exit" or l == "quit" and not prevline) then 71 | done = true 72 | 73 | elseif l == "help" and not prevline then 74 | io.stdout:write(interactive_help, "\n") 75 | 76 | -- handle line continuation 77 | 78 | elseif string.sub(l, -1, -1) == "\\" then 79 | if not prevline then prevline = "" end 80 | prevline = prevline..string.sub(l, 1, -2) 81 | 82 | -- compose source chunk, compile, disassemble 83 | 84 | else 85 | if prevline then l = prevline..l; prevline = nil end 86 | -- loadstring function was present in lua 5.1, and 5.2 87 | -- it got deprecated in lua 5.3 and is now load() 88 | -- load() also seems to work in lua 5.2 89 | local func, msg 90 | oconfig = Oconfig 91 | if _VERSION == 'Lua 5.3' then 92 | func, msg = load(l, "(interactive mode)") 93 | else 94 | func, msg = loadstring(l, "(interactive mode)") 95 | end 96 | func() -- Call the loaded lua string as a function 97 | if not func then 98 | print("Dechunk: failed to compile your input") 99 | else 100 | binchunk = string.dump(func) 101 | Dechunk("(interactive mode)", binchunk, oconfig) 102 | end 103 | 104 | end--if l 105 | end--while 106 | end 107 | 108 | main() -------------------------------------------------------------------------------- /scope_config.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | --[[ 4 | Configuration for Lua Scope 5 | A Lua 5.1/5.2/5.3 binary chunk disassembler 6 | LuaScope was inspired by Jein-Hong Man's ChunkSpy 7 | --]] 8 | 9 | config = {} 10 | -- 11 | --[[ 12 | -- Configuration table 13 | -- * Contains fixed constants, display constants and options, and 14 | -- platform-dependent configuration constants 15 | -- 16 | -- Configuration settings of binary chunks to be processed. Such tables 17 | -- may be used to sort of "auto-detect" binary chunks from different 18 | -- platforms. More or less equivalent to the global header. 19 | -- * There is currently only one supported host, "x86 standard" 20 | -- * MAX_STACK is no longer required for decoding RK register indices, 21 | -- instead, the MSB bit in the field is used as a flag (Lua 5.1) 22 | -- * number_type is also used to lookup conversion function, etc. 23 | --]] 24 | -- 25 | 26 | CONFIGURATION = { 27 | ["x86 standard"] = { 28 | description = "x86 standard (32-bit, little endian, doubles)", 29 | endianness = 1, -- 1 = little endian 30 | size_int = 4, -- (data type sizes in bytes) 31 | size_size_t = 4, 32 | size_Instruction = 4, 33 | size_lua_Number = 8, -- this & integral identifies the 34 | integral = 0, -- type of lua_Number 35 | number_type = "double", -- used for lookups 36 | }, 37 | ["x86_64"] = { 38 | description = "x86_64 (64-bit, little endian, doubles)", 39 | endianness = 1, -- 1 = little endian 40 | size_int = 4, -- (data type sizes in bytes) 41 | size_size_t = 8, 42 | size_Instruction = 4, 43 | size_lua_Number = 8, -- this & integral identifies the 44 | integral = 0, -- type of lua_Number 45 | number_type = "double", -- used for lookups 46 | }, 47 | ["big endian int"] = { 48 | description = "(32-bit, big endian, ints)", 49 | endianness = 0, 50 | size_int = 4, 51 | size_size_t = 4, 52 | size_Instruction = 4, 53 | size_lua_Number = 4, 54 | integral = 1, 55 | number_type = "int", 56 | }, 57 | -- you can add more platforms here 58 | } 59 | 60 | -- 61 | -- chunk constants 62 | -- * changed in 5.1: VERSION, FPF, SIZE_* are now fixed; LUA_TBOOLEAN 63 | -- added for constant table; TEST_NUMBER removed; FORMAT added 64 | -- 65 | config.SIGNATURE = "\27Lua" 66 | -- TEST_NUMBER no longer needed, using size_lua_Number + integral 67 | config.LUA_TNIL = 0 68 | config.LUA_TBOOLEAN = 1 69 | config.LUA_TNUMBER = 3 70 | config.LUA_TSTRING = 4 71 | config.VERSION = 81 -- 0x51 72 | config.FORMAT = 0 -- LUAC_FORMAT (new in 5.1) 73 | config.FPF = 50 -- LFIELDS_PER_FLUSH 74 | config.SIZE_OP = 6 -- instruction field bits 75 | config.SIZE_A = 8 76 | config.SIZE_B = 9 77 | config.SIZE_C = 9 78 | -- MAX_STACK no longer needed for instruction decoding, removed 79 | -- LUA_FIRSTINDEX currently not supported; used in SETLIST 80 | config.LUA_FIRSTINDEX = 1 81 | 82 | -- 83 | -- display options: you can set your defaults here 84 | -- 85 | config.DISPLAY_FLAG = true -- global listing output on/off 86 | config.DISPLAY_BRIEF = nil -- brief listing style 87 | config.DISPLAY_INDENT = nil -- indent flag for brief style 88 | config.STATS = nil -- set if always display stats 89 | config.DISPLAY_OFFSET_HEX = true -- use hexadecimal for position 90 | config.DISPLAY_SEP = " " -- column separator 91 | config.DISPLAY_COMMENT = "; " -- comment sign 92 | config.DISPLAY_HEX_DATA = true -- show hex data column 93 | config.WIDTH_HEX = 8 -- width of hex data column 94 | config.WIDTH_OFFSET = nil -- width of position column 95 | config.DISPLAY_LOWERCASE = true -- lower-case operands 96 | config.WIDTH_OPCODE = nil -- width of opcode field 97 | config.VERBOSE_TEST = false -- more verbosity for --test 98 | 99 | -- 100 | -- Detected run time configs 101 | -- 102 | config.version = { "5.1", "5.2", "5.3" } 103 | 104 | -- 105 | -- primitive platform auto-detection 106 | -- 107 | function init_scope_config_description() 108 | if config.AUTO_DETECT then 109 | config.description = nil 110 | for _, cfg in pairs(CONFIGURATION) do 111 | if cfg.endianness == config.endianness and 112 | cfg.size_int == config.size_int and 113 | cfg.size_size_t == config.size_size_t and 114 | cfg.size_Instruction == config.size_Instruction and 115 | cfg.size_lua_Number == config.size_lua_Number and 116 | cfg.integral == config.integral and 117 | cfg.number_type == config.number_type then 118 | config.description = cfg.description 119 | end 120 | end 121 | if not config.description then 122 | config.description = "chunk platform unrecognized" 123 | end 124 | -- some parameters are not in the global header, e.g. FPF 125 | -- see the config table for more on these constants 126 | end 127 | end 128 | 129 | function init_print_width(size) 130 | if not config.WIDTH_OFFSET then config.WIDTH_OFFSET = 0 end 131 | if config.DISPLAY_OFFSET_HEX then 132 | local w = string.len(string.format("%X", size)) 133 | if w > config.WIDTH_OFFSET then config.WIDTH_OFFSET = w end 134 | if (config.WIDTH_OFFSET % 2) == 1 then 135 | config.WIDTH_OFFSET = config.WIDTH_OFFSET + 1 136 | end 137 | else 138 | config.WIDTH_OFFSET = string.len(tonumber(size)) 139 | end 140 | end 141 | 142 | function init_display_config() 143 | if config.WIDTH_OFFSET < 4 then config.WIDTH_OFFSET = 4 end 144 | if not config.DISPLAY_SEP then config.DISPLAY_SEP = " " end 145 | if config.DISPLAY_HEX_DATA == nil then config.DISPLAY_HEX_DATA = true end 146 | if not config.WIDTH_HEX then config.WIDTH_HEX = 8 end 147 | config.BLANKS_HEX_DATA = string.rep(" ", config.WIDTH_HEX * 2 + 1) 148 | end 149 | 150 | function SetProfile(profile) 151 | if profile == "local" then 152 | -- arrives here only for --rewrite and --run option 153 | local flag1, flag2 = config.DISPLAY_FLAG, config.AUTO_DETECT 154 | config.DISPLAY_FLAG, config.AUTO_DETECT = false, true 155 | local LUA_SAMPLE = string.dump(function() end) 156 | config.DISPLAY_FLAG, config.AUTO_DETECT = flag1, flag2 157 | -- resume normal operation 158 | else 159 | local c = CONFIGURATION[profile] 160 | if not c then return false end 161 | for i, v in pairs(c) do config[i] = v end 162 | end 163 | return true 164 | end 165 | 166 | function get_config() 167 | return config 168 | end 169 | 170 | function GetTypeNIL() 171 | return config.LUA_TNIL 172 | end 173 | 174 | function GetTypeBoolean() 175 | return config.LUA_TBOOLEAN 176 | end 177 | 178 | function GetTypeNumber() 179 | return config.LUA_TNUMBER 180 | end 181 | 182 | function GetTypeString() 183 | return config.LUA_TSTRING 184 | end 185 | 186 | function GetOutputSep() 187 | return config.DISPLAY_SEP 188 | end 189 | 190 | function GetOutputShowHexData() 191 | return config.DISPLAY_HEX_DATA 192 | end 193 | 194 | function GetOutputBlankHex() 195 | return config.BLANKS_HEX_DATA 196 | end 197 | 198 | function GetOutputHexWidth() 199 | return config.WIDTH_HEX 200 | end 201 | 202 | function GetOutputPosWidth() 203 | return config.WIDTH_OFFSET 204 | end 205 | 206 | function GetOutputComment() 207 | return config.DISPLAY_COMMENT 208 | end 209 | 210 | function GetOutputPosString(i) 211 | if config.DISPLAY_OFFSET_HEX then 212 | return string.format("%X", i - 1) 213 | else 214 | return tonumber(i - 1) 215 | end 216 | end 217 | 218 | function GetLuaDescription() 219 | return config.description 220 | end 221 | 222 | function GetLuaIntSize() 223 | return config.size_int 224 | end 225 | 226 | function GetLuaSizetSize() 227 | return config.size_size_t 228 | end 229 | 230 | function GetLuaNumberSize() 231 | return config.size_lua_Number 232 | end 233 | 234 | function GetLuaInstructionSize() 235 | return config.size_Instruction 236 | end 237 | 238 | function GetLuaNumberType() 239 | return config.number_type 240 | end 241 | 242 | function SetLuaNumberType(t) 243 | config.number_type = t 244 | end 245 | 246 | function GetLuaEndianness() 247 | return config.endianness 248 | end 249 | 250 | function SetLuaEndianness(e) 251 | config.endianness = e 252 | end 253 | 254 | function GetLuaIntegral() 255 | return config.integral 256 | end 257 | 258 | function SetLuaIntegral(b) 259 | config.integral = b 260 | end 261 | 262 | function ShouldIPrintStats() 263 | return config.STATS and not config.DISPLAY_BRIEF 264 | end 265 | 266 | -- TODO : combine next 4 functions 267 | function ShouldIPrintLess() 268 | return not config.DISPLAY_FLAG or config.DISPLAY_BRIEF 269 | end 270 | 271 | function ShouldIPrintBrief() 272 | return config.DISPLAY_BRIEF 273 | end 274 | 275 | function ShouldIPrintXYZ() 276 | return not config.DISPLAY_FLAG or not config.DISPLAY_BRIEF 277 | end 278 | 279 | function ShouldIPrintParts() 280 | return config.DISPLAY_FLAG and config.DISPLAY_BRIEF 281 | end 282 | 283 | function ShouldIPrintHexData() 284 | return config.DISPLAY_HEX_DATA 285 | end 286 | 287 | function ShouldIPrintLowercase() 288 | return config.DISPLAY_LOWERCASE 289 | end 290 | 291 | Oconfig = { 292 | GetVersion = function (self) 293 | vs = {} 294 | for k,v in pairs(config.version) do 295 | if v == "5.1" then 296 | vs[#vs+1] = 0x51 297 | elseif v == "5.2" then 298 | vs[#vs+1] = 0x52 299 | elseif v == "5.3" then 300 | vs[#vs+1] = 0x53 301 | else 302 | vs[#vs+1] = 0 303 | end 304 | end 305 | return vs 306 | end, 307 | IsVersionOK = function (self, v) 308 | vs = self.GetVersion() 309 | for _,vi in pairs(vs) do 310 | --print("Compare ", vi, v) 311 | if vi == v then return true end 312 | end 313 | return false 314 | end, 315 | GetSign = function (self) 316 | return config.SIGNATURE 317 | end, 318 | GetFormat = function (self) 319 | return config.FORMAT 320 | end, 321 | GetTypeNIL = function (self) 322 | return GetTypeNIL() 323 | end, 324 | GetTypeBoolean = function (self) 325 | return GetTypeBoolean() 326 | end, 327 | GetTypeNumber = function (self) 328 | return GetTypeNumber() 329 | end, 330 | GetTypeString = function (self) 331 | return GetTypeString() 332 | end, 333 | GetOutputSep = function (self) 334 | return GetOutputSep() 335 | end, 336 | GetOutputShowHexData = function (self) 337 | return GetOutputShowHexData() 338 | end, 339 | GetOutputBlankHex = function (self) 340 | return GetOutputBlankHex() 341 | end, 342 | GetOutputHexWidth = function (self) 343 | return GetOutputHexWidth() 344 | end, 345 | GetOutputPosWidth = function (self) 346 | return GetOutputPosWidth() 347 | end, 348 | GetOutputComment = function (self) 349 | return GetOutputComment() 350 | end, 351 | GetOutputPosString= function (self, i) 352 | return GetOutputPosString(i) 353 | end, 354 | GetLuaDescription = function (self) 355 | return GetLuaDescription() 356 | end, 357 | GetLuaIntSize = function (self) 358 | return GetLuaIntSize() 359 | end, 360 | GetLuaSizetSize = function (self) 361 | return GetLuaSizetSize() 362 | end, 363 | GetLuaNumberSize = function (self) 364 | return GetLuaNumberSize() 365 | end, 366 | GetLuaInstructionSize = function (self) 367 | return GetLuaInstructionSize() 368 | end, 369 | GetLuaNumberType = function (self) 370 | return GetLuaNumberType() 371 | end, 372 | GetLuaEndianness = function (self) 373 | return GetLuaEndianness() 374 | end, 375 | GetLuaIntegral = function (self) 376 | return GetLuaIntegral() 377 | end, 378 | GetConfigDetect = function (self) 379 | return config.AUTO_DETECT 380 | end, 381 | GetLuaSize_A = function (self) 382 | return config.SIZE_A 383 | end, 384 | GetLuaSize_B = function (self) 385 | return config.SIZE_B 386 | end, 387 | GetLuaSize_C = function (self) 388 | return config.SIZE_C 389 | end, 390 | GetLuaSize_OP = function (self) 391 | return config.SIZE_OP 392 | end, 393 | GetLuavm_Size_Int = function (self) 394 | return config.size_int 395 | end, 396 | GetLuavm_Size_Sizet = function (self) 397 | return config.size_size_t 398 | end, 399 | GetLuavm_Size_Instruction = function (self) 400 | return config.size_Instruction 401 | end, 402 | GetLuavm_Size_Number = function (self) 403 | return config.size_lua_Number 404 | end, 405 | SetLuavm_Size_Int = function (self, v) 406 | config.size_int = v 407 | end, 408 | SetLuavm_Size_Sizet = function (self, v) 409 | config.size_size_t = v 410 | end, 411 | SetLuavm_Size_Instruction = function (self, v) 412 | config.size_Instruction = v 413 | end, 414 | SetLuavm_Size_Number = function (self, v) 415 | config.size_lua_Number = v 416 | end, 417 | GetLuavmSizeTbl = function (self, type) 418 | l = {} 419 | l["int"] = {} 420 | l["int"]["get"] = self.GetLuavm_Size_Int 421 | l["int"]["set"] = self.SetLuavm_Size_Int 422 | l["size_t"] = {} 423 | l["size_t"]["get"] = self.GetLuavm_Size_Sizet 424 | l["size_t"]["set"] = self.SetLuavm_Size_Sizet 425 | l["Instruction"] = {} 426 | l["Instruction"]["get"] = self.GetLuavm_Size_Instruction 427 | l["Instruction"]["set"] = self.SetLuavm_Size_Instruction 428 | l["number"] = {} 429 | l["number"]["get"] = self.GetLuavm_Size_Number 430 | l["number"]["set"] = self.SetLuavm_Size_Number 431 | 432 | return l[type] 433 | end, 434 | GetLuaFPF = function (self) 435 | return config.FPF 436 | end, 437 | ShouldIPrintStats = function (self) 438 | return ShouldIPrintStats() 439 | end, 440 | 441 | } 442 | 443 | -------------------------------------------------------------------------------- /scope_dechunk.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | --[[ 4 | Decode Chunks for Lua Scope 5 | A Lua 5.1/5.2/5.3 binary chunk disassembler 6 | LuaScope was inspired by Jein-Hong Man's ChunkSpy 7 | --]] 8 | 9 | package.path = package.path .. ";./?.lua;/usr/src/?.lua" 10 | 11 | require("scope_config") 12 | 13 | -- XXX is a hack, only temporary, hopefully. 14 | config = get_config() 15 | 16 | srcinfo = {} 17 | 18 | -- 19 | -- brief display mode with indentation style option 20 | -- 21 | local function BriefLine(desc) 22 | if ShouldIPrintXYZ() then return end 23 | if DISPLAY_INDENT then 24 | WriteLine(string.rep(GetOutputSep(), level - 1)..desc) 25 | else 26 | WriteLine(desc) 27 | end 28 | end 29 | 30 | --[[ 31 | -- Source listing merging 32 | -- * for convenience, file name matching is first via case-sensitive 33 | -- comparison, then case-insensitive comparison, and the first 34 | -- match found using either method is the one that is used 35 | --]] 36 | 37 | ----------------------------------------------------------------------- 38 | -- initialize source list for merging 39 | -- * this will normally be called by the main chunk function 40 | -- * the source listing is read only once, upon initialization 41 | ----------------------------------------------------------------------- 42 | function SourceInit(source) 43 | if srcinfo.source then srcinfo.srcprev = 0; return end 44 | if not source or 45 | source == "" or 46 | string.sub(source, 1, 1) ~= "@" then 47 | return 48 | end 49 | source = string.sub(source, 2) -- chomp leading @ 50 | for _, fname in ipairs(other_files) do -- find a match 51 | if not srcinfo.source then 52 | if fname == source or 53 | string.lower(fname) == string.lower(source) then 54 | srcinfo.source = fname 55 | end 56 | end 57 | end 58 | if not srcinfo.source then return end -- no source file 59 | local INF = io.open(srcinfo.source, "rb") -- read in source file 60 | if not INF then 61 | error("cannot read file \""..filename.."\"") 62 | end 63 | srcinfo.srcline = {}; srcinfo.srcmark = {} 64 | local n, line = 1 65 | repeat 66 | line = INF:read("*l") 67 | if line then 68 | srcinfo.srcline[n], srcinfo.srcmark[n] = line, false 69 | n = n + 1 70 | end 71 | until not line 72 | io.close(INF) 73 | srcinfo.srcsize = n - 1 74 | srcinfo.DISPLAY_SRC_WIDTH = WidthOf(srcinfo.srcsize) 75 | srcinfo.srcprev = 0 76 | end 77 | 78 | ----------------------------------------------------------------------- 79 | -- mark source lines 80 | -- * marks source lines as a function is read to delineate stuff 81 | ----------------------------------------------------------------------- 82 | function SourceMark(func) 83 | if not srcinfo.source then return end 84 | if func.sizelineinfo == 0 then return end 85 | for i = 1, func.sizelineinfo do 86 | if i <= srcinfo.srcsize then 87 | srcinfo.srcmark[func.lineinfo[i]] = true 88 | end 89 | end 90 | end 91 | 92 | ----------------------------------------------------------------------- 93 | -- generate source lines 94 | -- * peek at lines above and print them if they have not been printed 95 | -- * mark all printed lines so all non-code lines are printed once only 96 | ----------------------------------------------------------------------- 97 | function SourceMerge(func, pc) 98 | if not srcinfo.source or not srcinfo.DISPLAY_FLAG then return end 99 | local lnum = func.lineinfo[pc] 100 | -- don't print anything new if instruction is on the same line 101 | if srcinfo.srcprev == lnum then return end 102 | srcinfo.srcprev = lnum 103 | if srcinfo.srcsize < lnum then return end -- something fishy 104 | local lfrom = lnum 105 | srcinfo.srcmark[lnum] = true 106 | while lfrom > 1 and srcinfo.srcmark[lfrom - 1] == false do 107 | lfrom = lfrom - 1 108 | srcinfo.srcmark[lfrom] = true 109 | end 110 | for i = lfrom, lnum do 111 | WriteLine(GetOutputComment() 112 | .."("..ZeroPad(i, srcinfo.DISPLAY_SRC_WIDTH)..")" 113 | ..srcinfo.DISPLAY_SEP..srcinfo.srcline[i]) 114 | end 115 | end 116 | 117 | -- 118 | -- describe a string (size, data pairs) 119 | -- 120 | local function DescString(chunk, s, pos) 121 | local len = string.len(s or "") 122 | if len > 0 then 123 | len = len + 1 -- add the NUL back 124 | s = s.."\0" -- was removed by LoadString 125 | end 126 | FormatLine(chunk, GetLuaSizetSize(), string.format("string size (%s)", len), pos) 127 | if len == 0 then return end 128 | pos = pos + GetLuaSizetSize() 129 | if len <= GetOutputHexWidth() then 130 | FormatLine(chunk, len, EscapeString(s, 1), pos) 131 | else 132 | -- split up long strings nicely, easier to view 133 | while len > 0 do 134 | local seg_len = GetOutputHexWidth() 135 | if len < seg_len then seg_len = len end 136 | local seg = string.sub(s, 1, seg_len) 137 | s = string.sub(s, seg_len + 1) 138 | len = len - seg_len 139 | FormatLine(chunk, seg_len, EscapeString(seg, 1), pos, len > 0) 140 | pos = pos + seg_len 141 | end 142 | end 143 | end 144 | 145 | -- 146 | -- describe line information 147 | -- 148 | local function DescLines(chunk, desc) 149 | local size = desc.sizelineinfo 150 | local pos = desc.pos_lineinfo 151 | DescLine("* lines:") 152 | FormatLine(chunk, GetLuaIntSize(), "sizelineinfo ("..size..")", pos) 153 | pos = pos + GetLuaIntSize() 154 | local WIDTH = WidthOf(size) 155 | DescLine("[pc] (line)") 156 | for i = 1, size do 157 | local s = string.format("[%s] (%s)", ZeroPad(i, WIDTH), desc.lineinfo[i]) 158 | FormatLine(chunk, GetLuaIntSize(), s, pos) 159 | pos = pos + GetLuaIntSize() 160 | end 161 | -- mark significant lines in source listing 162 | SourceMark(desc) 163 | end 164 | 165 | -- 166 | -- describe locals information 167 | -- 168 | local function DescLocals(chunk, desc) 169 | local n = desc.sizelocvars 170 | DescLine(chunk, "* locals:") 171 | FormatLine(chunk, GetLuaIntSize(), "sizelocvars ("..n..")", desc.pos_locvars) 172 | for i = 1, n do 173 | local locvar = desc.locvars[i] 174 | if locvar.pos_varname ~= 0 then -- FIXME hack for 5.3 175 | DescString(chunk, locvar.varname, locvar.pos_varname) 176 | DescLine(chunk, "local ["..(i - 1).."]: "..EscapeString(locvar.varname)) 177 | BriefLine(".local"..GetOutputSep()..EscapeString(locvar.varname, 1) 178 | ..GetOutputSep()..GetOutputComment()..(i - 1)) 179 | FormatLine(chunk, GetLuaIntSize(), " startpc ("..locvar.startpc..")", locvar.pos_startpc) 180 | FormatLine(chunk, GetLuaIntSize(), " endpc ("..locvar.endpc..")",locvar.pos_endpc) 181 | end 182 | end 183 | end 184 | 185 | -- 186 | -- describe upvalues information 187 | -- 188 | local function DescUpvalues(chunk, desc) 189 | local n = desc.sizeupvalues 190 | DescLine(chunk, "* upvalues:") 191 | if n == nil then return end 192 | if n == 1 then return end -- XX HACK by Vijo for lua 5.2 193 | FormatLine(chunk, GetLuaIntSize(), "sizeupvalues ("..n..")", desc.pos_upvalues) 194 | for i = 1, n do 195 | local upvalue = desc.upvalues[i] 196 | DescString(chunk, upvalue, desc.posupvalues[i]) 197 | DescLine(chunk, "upvalue ["..(i - 1).."]: "..EscapeString(upvalue)) 198 | BriefLine(".upvalue"..GetOutputSep()..EscapeString(upvalue, 1) 199 | ..GetOutputSep()..GetOutputComment()..(i - 1)) 200 | end 201 | end 202 | 203 | -- 204 | -- describe constants information (data) 205 | -- 206 | local function DescConstantKs(chunk, desc) 207 | local n = desc.sizek 208 | local pos = desc.pos_ks 209 | DescLine(chunk, "* constants:") 210 | FormatLine(chunk, GetLuaIntSize(), "sizek ("..n..")", pos) 211 | for i = 1, n do 212 | local posk = desc.posk[i] 213 | local CONST = "const ["..(i - 1).."]: " 214 | local CONSTB = GetOutputSep()..GetOutputComment()..(i - 1) 215 | local k = desc.k[i] 216 | if type(k) == "number" then 217 | FormatLine(chunk,1, "const type "..GetTypeNumber(), posk) 218 | FormatLine(chunk, GetLuaNumberSize(), CONST.."("..k..")", posk + 1) 219 | BriefLine(".const"..GetOutputSep()..k..CONSTB) 220 | elseif type(k) == "boolean" then 221 | FormatLine(chunk,1, "const type "..GetTypeBoolean(), posk) 222 | FormatLine(chunk,1, CONST.."("..tostring(k)..")", posk + 1) 223 | BriefLine(".const"..GetOutputSep()..tostring(k)..CONSTB) 224 | elseif type(k) == "string" then 225 | FormatLine(chunk, 1, "const type "..GetTypeString(), posk) 226 | DescString(chunk, k, posk + 1) 227 | DescLine(chunk, CONST..EscapeString(k, 1)) 228 | BriefLine(".const"..GetOutputSep()..EscapeString(k, 1)..CONSTB) 229 | elseif type(k) == "nil" then 230 | FormatLine(chunk, 1, "const type "..GetTypeNIL(), posk) 231 | DescLine(chunk, CONST.."nil") 232 | BriefLine(".const"..GetOutputSep().."nil"..CONSTB) 233 | end 234 | end--for 235 | end 236 | 237 | -- 238 | -- describe constants information (local functions) 239 | -- 240 | local function DescConstantPs(chunk, desc) 241 | local n = desc.sizep 242 | if n == nil then return end 243 | DescLine(chunk,"* functions:") 244 | FormatLine(chunk, GetLuaIntSize(), "sizep ("..n..")", desc.pos_ps) 245 | for i = 1, n do 246 | -- recursive call back on itself, next level 247 | DescFunction(chunk,desc.p[i], i - 1, level + 1) 248 | end 249 | end 250 | 251 | -- 252 | -- describe function code 253 | -- * inst decode subfunctions: DecodeInst() and DescribeInst() 254 | -- 255 | local function DescCode(chunk, desc, oconfig) 256 | local size = desc.sizecode 257 | local pos = desc.pos_code 258 | DescLine(chunk,"* code:") 259 | FormatLine(chunk, oconfig:GetLuaIntSize(), "sizecode ("..size..")", pos) 260 | pos = pos + oconfig:GetLuaIntSize() 261 | desc.inst = {} 262 | local ISIZE = WidthOf(size) 263 | for i = 1, size do 264 | desc.inst[i] = {} 265 | end 266 | for i = 1, size do 267 | DecodeInst(desc.code[i], desc.inst[i]) 268 | local inst = desc.inst[i] 269 | -- compose instruction: opcode operands [; comments] 270 | local d = DescribeInst(inst, i, desc, oconfig) 271 | d = string.format("[%s] %s", ZeroPad(i, ISIZE), d) 272 | -- source code insertion 273 | SourceMerge(desc, i) 274 | FormatLine(chunk, oconfig:GetLuaInstructionSize(), d, pos) 275 | BriefLine(d) 276 | pos = pos + oconfig:GetLuaInstructionSize() 277 | end 278 | end 279 | 280 | -- 281 | -- displays function information 282 | -- 283 | function DescFunction(chunk, desc, num, level, oconfig) 284 | DescLine(chunk, "") 285 | BriefLine("") 286 | FormatLine(chunk, 0, "** function ["..num.."] definition (level "..level..")", desc.pos_source) 287 | BriefLine("; function ["..num.."] definition (level "..level..")") 288 | DescLine(chunk, "** start of function **") 289 | 290 | -- source file name 291 | DescString(chunk, desc.source, desc.pos_source) 292 | if desc.source == nil then 293 | DescLine(chunk, "source name: (none)") 294 | else 295 | DescLine(chunk, "source name: "..EscapeString(desc.source)) 296 | end 297 | 298 | -- optionally initialize source listing merging 299 | SourceInit(desc.source) 300 | 301 | -- line where the function was defined 302 | local pos = desc.pos_linedefined 303 | FormatLine(chunk, oconfig:GetLuaIntSize(), "line defined ("..desc.linedefined..")", pos) 304 | pos = pos + oconfig:GetLuaIntSize() 305 | FormatLine(chunk, oconfig:GetLuaIntSize(), "last line defined ("..desc.lastlinedefined..")", pos) 306 | pos = pos + oconfig:GetLuaIntSize() 307 | 308 | -- display byte counts 309 | FormatLine(chunk, 1, "nups ("..desc.nups..")", pos) 310 | FormatLine(chunk, 1, "numparams ("..desc.numparams..")", pos + 1) 311 | FormatLine(chunk, 1, "is_vararg ("..desc.is_vararg..")", pos + 2) 312 | FormatLine(chunk, 1, "maxstacksize ("..desc.maxstacksize..")", pos + 3) 313 | BriefLine(string.format("; %d upvalues, %d params, %d stacks", 314 | desc.nups, desc.numparams, desc.maxstacksize)) 315 | BriefLine(string.format(".function%s%d %d %d %d", GetOutputSep(), 316 | desc.nups, desc.numparams, desc.is_vararg, desc.maxstacksize)) 317 | 318 | -- display parts of a chunk 319 | if ShouldIPrintParts() then 320 | DescLines(chunk,desc) -- brief displays 'declarations' first 321 | DescLocals(chunk, desc) 322 | DescUpvalues(chunk, desc) 323 | DescConstantKs(chunk, desc) 324 | DescConstantPs(chunk, desc) 325 | DescCode(chunk, desc, oconfig) 326 | else 327 | --DescCode(chunk, desc, oconfig) -- normal displays positional order 328 | DescConstantKs(chunk, desc) 329 | DescConstantPs(chunk, desc) 330 | DescLines(chunk,desc) 331 | DescLocals(chunk, desc) 332 | DescUpvalues(chunk, desc) 333 | end 334 | 335 | -- show function statistics block 336 | DisplayStat("* func header = "..desc.stat.header.." bytes", oconfig) 337 | DisplayStat("* lines size = "..desc.stat.lines.." bytes", oconfig) 338 | DisplayStat("* locals size = "..desc.stat.locals.." bytes", oconfig) 339 | DisplayStat("* upvalues size = "..desc.stat.upvalues.." bytes", oconfig) 340 | DisplayStat("* consts size = "..desc.stat.consts.." bytes", oconfig) 341 | DisplayStat("* funcs size = "..desc.stat.funcs.." bytes", oconfig) 342 | DisplayStat("* code size = "..desc.stat.code.." bytes", oconfig) 343 | desc.stat.total = desc.stat.header + desc.stat.lines + 344 | desc.stat.locals + desc.stat.upvalues + 345 | desc.stat.consts + desc.stat.funcs + 346 | desc.stat.code 347 | DisplayStat("* TOTAL size = "..desc.stat.total.." bytes", oconfig) 348 | DescLine(chunk, "** end of function **\n") 349 | BriefLine("; end of function\n") 350 | end 351 | 352 | -- 353 | -- tests if a given number of bytes is available 354 | -- 355 | local function IsChunkSizeOk(size, idx, total_size, errmsg) 356 | if idx + size - 1 > total_size then 357 | error(string.format("chunk too small for %s at offset %d", errmsg, idx - 1)) 358 | end 359 | end 360 | 361 | -- 362 | -- loads a single byte and returns it as a number 363 | -- 364 | local function LoadByte(chunk, chunkinfo) 365 | chunkinfo.previdx = chunkinfo.idx 366 | chunkinfo.idx = chunkinfo.idx + 1 367 | return string.byte(chunk, chunkinfo.previdx) 368 | end 369 | 370 | local function LoadByte53(chunk, chunkinfo) 371 | return string.byte(chunk, chunkinfo.idx) 372 | end 373 | 374 | -- 375 | -- loads a block of endian-sensitive bytes 376 | -- * rest of code assumes little-endian by default 377 | -- 378 | local function LoadBlock(size, chunk, chunkinfo) 379 | local total_size = chunkinfo.chunk_size 380 | local idx = chunkinfo.idx 381 | local previdx = chunkinfo.previdx 382 | 383 | print("LoadBlock Checking for size "..size.." size "..total_size.." Idx starts at "..string.format("%x", idx)) 384 | if not pcall(IsChunkSizeOk, size, idx, total_size, "LoadBlock") then return end 385 | chunkinfo.previdx = chunkinfo.idx 386 | chunkinfo.idx = chunkinfo.idx + size 387 | local b = string.sub(chunk, idx, idx + size - 1) 388 | if GetLuaEndianness() == 1 then 389 | return b 390 | else-- reverse bytes if big endian 391 | return string.reverse(b) 392 | end 393 | end 394 | 395 | -- 396 | -- loads a number (can be zero) - loadInt can't load zero - used by 5.2 397 | -- 398 | local function LoadNo(chunk, chunkinfo) 399 | local size = chunkinfo.chunk_size 400 | local idx = chunkinfo.idx 401 | local previdx = chunkinfo.previdx 402 | local x = LoadBlock(GetLuaIntSize(), chunk, chunkinfo) 403 | 404 | if x == nil then return 0 end 405 | local sum = 0 406 | for i = GetLuaIntSize(), 1, -1 do 407 | sum = sum * 256 + string.byte(x, i) 408 | end 409 | return sum 410 | end 411 | 412 | -- 413 | -- loads an integer (signed) 414 | -- 415 | local function LoadInt(chunk, chunkinfo) 416 | local size = chunkinfo.chunk_size 417 | local idx = chunkinfo.idx 418 | local previdx = chunkinfo.previdx 419 | 420 | local x = LoadBlock(GetLuaIntSize(), chunk, chunkinfo) 421 | if not x then 422 | error("could not load integer") 423 | else 424 | local sum = 0 425 | for i = GetLuaIntSize(), 1, -1 do 426 | sum = sum * 256 + string.byte(x, i) 427 | end 428 | -- test for negative number 429 | if string.byte(x, GetLuaIntSize()) > 127 then 430 | sum = sum - math.ldexp(1, 8 * GetLuaIntSize()) 431 | end 432 | -- from the looks of it, integers needed are positive 433 | if sum < 0 then error("bad integer") end 434 | return sum 435 | end 436 | end 437 | 438 | -- 439 | -- loads a size_t (assume unsigned) 440 | -- 441 | local function LoadSize(chunk, chunkinfo) 442 | local size = chunkinfo.chunk_size 443 | local idx = chunkinfo.idx 444 | local previdx = chunkinfo.previdx 445 | local x = LoadBlock(GetLuaSizetSize(), chunk, chunkinfo) 446 | 447 | if not x then 448 | print("total_size was ", total_size) 449 | error("could not load size_t at "..string.format("%x", idx)) --handled in LoadString() 450 | return 451 | else 452 | local sum = 0 453 | for i = GetLuaSizetSize(), 1, -1 do 454 | sum = sum * 256 + string.byte(x, i) 455 | end 456 | return sum 457 | end 458 | end 459 | 460 | -- 461 | -- loads a number (lua_Number type) 462 | -- 463 | local function LoadNumber(chunk, chunkinfo) 464 | local size = chunkinfo.chunk_size 465 | local idx = chunkinfo.idx 466 | local previdx = chunkinfo.previdx 467 | 468 | local x = LoadBlock(GetLuaNumberSize(), chunk, chunkinfo) 469 | if not x then 470 | error("could not load lua_Number") 471 | else 472 | local convert_func = convert_from[GetLuaNumberType()] 473 | if not convert_func then 474 | error("could not find conversion function for lua_Number") 475 | end 476 | return convert_func(x) 477 | end 478 | end 479 | 480 | -- 481 | -- load a string (size, data pairs) 482 | -- 483 | local function LoadString(chunk, chunkinfo) 484 | local size = chunkinfo.chunk_size 485 | local idx = chunkinfo.idx 486 | local previdx = chunkinfo.previdx 487 | 488 | print("Trying to load string at idx "..string.format("%x", idx).." from total size "..size) 489 | local len = LoadSize(chunk, chunkinfo) 490 | if not len then 491 | error("could not load String") 492 | else 493 | if len == 0 then -- there is no error, return a nil 494 | print("0-sized string at location "..string.format("%x", idx) ) 495 | return nil 496 | end 497 | print("Size in string: "..len.." "..string.format("%x", len)) 498 | idx = idx + GetLuaSizetSize() -- idx was incremented in our caller 499 | IsChunkSizeOk(len, idx, size, "LoadString") 500 | -- note that ending NUL is removed 501 | local s = string.sub(chunk, idx, idx + len ) 502 | chunkinfo.idx = chunkinfo.idx + len 503 | print("Loading string at idx "..string.format("%x", idx).. " of length "..len .. ">"..s.."<") 504 | Hexdump(s) 505 | return s 506 | end 507 | end 508 | 509 | -- 510 | -- Find size of string to be loaded (size, data pairs) 511 | -- 512 | local function SizeLoadString(chunk, total_size, idx) 513 | local len = LoadSize(chunk, total_size, idx) 514 | if not len then 515 | error("could not load String") 516 | else 517 | if len == 0 then -- there is no error, return a nil 518 | return 0 519 | end 520 | idx = idx + GetLuaSizetSize() -- idx was incremented in our caller 521 | IsChunkSizeOk(len, idx, total_size, "LoadString") 522 | -- note that ending NUL is removed 523 | local s = string.sub(chunk, idx, idx + len) 524 | print("Size of String to Load at idx "..string.format("%x", idx).. " of length "..len..s) 525 | Hexdump(s) 526 | --return s 527 | return len 528 | end 529 | end 530 | 531 | local function LoadLua53String(chunk, chunkinfo) 532 | local size = chunkinfo.chunk_size 533 | local idx = chunkinfo.idx 534 | 535 | print("idx: "..string.format("%x", idx)) 536 | local len = LoadByte53(chunk, chunkinfo) 537 | local islngstr = nil 538 | if not len then 539 | error("could not load String") 540 | return 541 | end 542 | chunkinfo.idx = chunkinfo.idx + 1 543 | print("Lua53string of len "..len.." at idx "..string.format("%x", idx)) 544 | if len == 255 then 545 | len = LoadSize(chunk, size, idx, {}) 546 | islngstr = true 547 | end 548 | 549 | if len == 0 then -- there is no error, return a nil 550 | return nil, len, islngstr 551 | end 552 | if len == 1 then 553 | return "", len, islngstr 554 | end 555 | --TestChunk(len - 1, idx, "LoadString") 556 | IsChunkSizeOk(len, idx+1, size, "LoadString") 557 | local s = string.sub(chunk, idx+1, idx + len) 558 | --idx = idx + len - 1 559 | --func_moveidx(len) 560 | chunkinfo.idx = chunkinfo.idx + len 561 | print("Loaded string at idx "..string.format("%x", chunkinfo.idx).. " of length "..len .. ">"..s.."<") 562 | return s, len, islngstr 563 | end 564 | 565 | -- 566 | -- load line information 567 | -- 568 | local function LoadLines(chunk, chunkinfo, desc) 569 | local size = chunkinfo.chunk_size 570 | local idx = chunkinfo.idx 571 | local previdx = chunkinfo.previdx 572 | local size = LoadInt(chunk, chunkinfo) 573 | 574 | desc.pos_lineinfo = previdx 575 | print("VCVCVC Loading lines "..previdx..desc.pos_lineinfo) 576 | desc.lineinfo = {} 577 | desc.sizelineinfo = size 578 | for i = 1, size do 579 | desc.lineinfo[i] = LoadInt(chunk, chunkinfo) 580 | end 581 | end 582 | 583 | -- 584 | -- load locals information 585 | -- 586 | local function LoadLocals(chunk, chunkinfo, desc) 587 | local size = chunkinfo.chunk_size 588 | local idx = chunkinfo.idx 589 | local previdx = chunkinfo.previdx 590 | local n = LoadInt(chunk, chunkinfo) 591 | 592 | desc.pos_locvars = previdx 593 | desc.locvars = {} 594 | desc.sizelocvars = n 595 | for i = 1, n do 596 | local locvar = {} 597 | locvar.varname = LoadString(chunk, chunkinfo) 598 | locvar.pos_varname = previdx 599 | locvar.startpc = LoadInt(chunk, chunkinfo) 600 | locvar.pos_startpc = previdx 601 | locvar.endpc = LoadInt(chunk, chunkinfo) 602 | locvar.pos_endpc = previdx 603 | desc.locvars[i] = locvar 604 | end 605 | end 606 | 607 | -- 608 | -- load function prototypes information (Used in 5.2) 609 | -- 610 | local function LoadFuncProto(chunk, chunkinfo, desc) 611 | local size = chunkinfo.chunk_size 612 | local idx = chunkinfo.idx 613 | local previdx = chunkinfo.previdx 614 | local n = LoadNo(chunk, chunkinfo) 615 | --if n > 1 then 616 | --error(string.format("bad Function prototypes: read %d, expected %d", n, func.nups)) 617 | --return 618 | --end 619 | print("No of Function prototypes:", n) 620 | --func.pos_upvalues = previdx 621 | --func.upvalues = {} 622 | --func.sizeupvalues = n 623 | --func.posupvalues = {} 624 | --for i = 1, n do 625 | -- func.upvalues[i] = LoadString(chunk, total_size, idx, func_movetonext, func_moveidx) 626 | -- func.posupvalues[i] = previdx 627 | -- if not func.upvalues[i] then 628 | -- error("empty string at index "..(i - 1).."in upvalue table") 629 | -- end 630 | --end 631 | end 632 | 633 | -- 634 | -- load upvalues information 635 | -- 636 | local function Load52Upvalues(chunk, chunkinfo, desc) 637 | local size = chunkinfo.chunk_size 638 | local idx = chunkinfo.idx 639 | local previdx = chunkinfo.previdx 640 | local n = LoadInt(chunk, chunkinfo) 641 | 642 | print("No of Upvalues", n) 643 | desc.pos_upvalues = previdx 644 | desc.upvalues = {} 645 | desc.sizeupvalues = n 646 | desc.posupvalues = {} 647 | local x = LoadBlock(2, chunk, chunkinfo) 648 | local y = 0 649 | for i = 2, 1, -1 do 650 | y = y * 256 + string.byte(x, i) 651 | end 652 | print("Read 2 bytes for upvalue", y) 653 | end 654 | 655 | -- 656 | -- load upvalues information 657 | -- 658 | local function Load53Upvalues(chunk, chunkinfo, desc) 659 | local size = chunkinfo.chunk_size 660 | local idx = chunkinfo.idx 661 | local previdx = chunkinfo.previdx 662 | local n = LoadInt(chunk, chunkinfo) 663 | 664 | print("No of Upvalues", n) 665 | desc.pos_upvalues = chunkinfo.previdx 666 | desc.upvalues = {} 667 | desc.sizeupvalues = n 668 | desc.posupvalues = {} 669 | for i = 1, n do 670 | local upvalue = {} 671 | upvalue.instack = LoadByte(chunk, chunkinfo) 672 | upvalue.pos_instack = chunkinfo.previdx 673 | upvalue.idx = LoadByte(chunk, chunkinfo) 674 | upvalue.pos_idx = chunkinfo.previdx 675 | desc.upvalues[i] = upvalue 676 | end 677 | print("Read n bytes for upvalue", y) 678 | end 679 | 680 | -- 681 | -- load upvalues information 682 | -- 683 | local function LoadUpvalues(chunk, chunkinfo, desc) 684 | local size = chunkinfo.chunk_size 685 | local idx = chunkinfo.idx 686 | local previdx = chunkinfo.previdx 687 | local n = LoadInt(chunk, chunkinfo) 688 | 689 | if n ~= 0 and n~= desc.nups then 690 | error(string.format("bad nupvalues: read %d, expected %d", n, desc.nups)) 691 | return 692 | end 693 | desc.pos_upvalues = previdx 694 | desc.upvalues = {} 695 | desc.sizeupvalues = n 696 | desc.posdescupvalues = {} 697 | for i = 1, n do 698 | desc.upvalues[i] = LoadString(chunk, chunkinfo) 699 | desc.posupvalues[i] = previdx 700 | if not desc.upvalues[i] then 701 | error("empty string at index "..(i - 1).."in upvalue table") 702 | end 703 | end 704 | end 705 | 706 | -- 707 | -- load function code 708 | -- 709 | local function LoadCode(chunk, chunkinfo, desc) 710 | local size = chunkinfo.chunk_size 711 | local idx = chunkinfo.idx 712 | local previdx = chunkinfo.previdx 713 | local size = LoadInt(chunk, chunkinfo) 714 | 715 | print("Loading instructions of Size "..size.." hex:" ..string.format("%x", size).." at idx "..string.format("%x", idx)) 716 | desc.pos_code = previdx 717 | desc.code = {} 718 | desc.sizecode = size 719 | for i = 1, size do 720 | desc.code[i] = LoadBlock(GetLuaInstructionSize(), chunk, chunkinfo) 721 | end 722 | end 723 | 724 | -- 725 | -- load constants information (data) 726 | -- 727 | local function LoadConstantKs(chunk, chunkinfo, desc) 728 | local size = chunkinfo.chunk_size 729 | local idx = chunkinfo.idx 730 | local previdx = chunkinfo.previdx 731 | local n = LoadInt(chunk, chunkinfo) 732 | 733 | desc.pos_ks = previdx 734 | desc.k = {} 735 | desc.sizek = n 736 | desc.posk = {} 737 | pidx = idx 738 | ix = idx + GetLuaIntSize() + 0 739 | print("Loading "..n.." constants") 740 | for i = 1, n do 741 | local t = LoadByte(chunk, chunkinfo) 742 | desc.posk[i] = chunkinfo.previdx 743 | if t == GetTypeNumber() then 744 | print("Got Number") 745 | desc.k[i] = LoadNumber(chunk, total_size, ix, func_movetonext) 746 | elseif t == GetTypeBoolean() then 747 | print("Got boolean") 748 | local b = LoadByte(chunk, chunkinfo) 749 | if b == 0 then b = false else b = true end 750 | desc.k[i] = b 751 | elseif t == GetTypeString() then 752 | print("Got string") 753 | desc.k[i] = LoadString(chunk, chunkinfo) 754 | --local strsize = SizeLoadString(chunk, total_size, ix) 755 | --ix = ix + GetLuaSizetSize() + strsize 756 | --pidx = pidx + GetLuaSizetSize() + strsize 757 | elseif t == GetTypeNIL() then 758 | print("NIL") 759 | desc.k[i] = nil 760 | else 761 | error(i.." bad constant type "..t.." at "..chunkinfo.previdx) 762 | end 763 | end--for 764 | end 765 | 766 | -- 767 | -- load constants information (data) 768 | -- 769 | local function LoadConstantsLua53(chunk, chunkinfo, desc) 770 | local size = chunkinfo.chunk_size 771 | local idx = chunkinfo.idx 772 | local previdx = chunkinfo.previdx 773 | local n = LoadInt(chunk, chunkinfo) 774 | 775 | desc.pos_ks = previdx 776 | desc.k = {} 777 | desc.sizek = n 778 | desc.posk = {} 779 | pidx = idx 780 | ix = idx + GetLuaIntSize() + 0 781 | chunkinfo.idx = ix 782 | print("Loading "..n.." constants") 783 | for i = 1, n do 784 | print("reading byte at "..string.format("%x", chunkinfo.idx)) 785 | local t = LoadByte(chunk, chunkinfo) 786 | pidx = pidx + 1 787 | ix = ix + 1 788 | desc.posk[i] = pidx 789 | if t == GetTypeNumber() then 790 | print("Got Number") 791 | desc.k[i] = LoadNumber(chunk, size, ix, func_movetonext) 792 | elseif t == GetTypeBoolean() then 793 | print("Got boolean at idx ".. (string.format("%x", idx)).." c.i : "..string.format("%x", chunkinfo.idx)) 794 | local b = LoadByte(chunk, chunkinfo) 795 | if b == 0 then b = false else b = true end 796 | desc.k[i] = b 797 | elseif t == GetTypeString() then 798 | print("Got string at "..string.format("%x", idx)) 799 | desc.k[i], strsize = LoadLua53String(chunk, chunkinfo) 800 | chunkinfo.idx = chunkinfo.idx -1 801 | chunkinfo.previdx = chunkinfo.previdx -1 802 | elseif t == GetTypeNIL() then 803 | print("NIL") 804 | desc.k[i] = nil 805 | else 806 | error(i.." bad constant type "..t.." at "..previdx) 807 | end 808 | end--for 809 | 810 | return n 811 | end 812 | 813 | -- 814 | -- load constants information (local functions) 815 | -- 816 | local function LoadConstantPs(chunk, chunkinfo, desc) 817 | local size = chunkinfo.chunk_size 818 | local idx = chunkinfo.idx 819 | local previdx = chunkinfo.previdx 820 | local n = LoadInt(chunk, chunkinfo) 821 | 822 | desc.pos_ps = previdx 823 | desc.p = {} 824 | desc.sizep = n 825 | for i = 1, n do 826 | -- recursive call back on itself, next leveldescdesc 827 | desc.p[i] = LoadFunction(chunk, total_size, idx, previdx, desc.source, i - 1, level + 1) 828 | end 829 | end 830 | 831 | -- 832 | -- load debug information (used in Lua 5.2) 833 | -- TODO: DOESN'T WORK 834 | -- 835 | local function LoadDebug(chunk, total_size, idx, previdx, func_movetonext, desc, func_moveidx, chunkinfo) 836 | desc.source = LoadString(chunk, total_size, idx, func_movetonext, func_moveidx) 837 | print("Source code: ", desc.source) 838 | --print("Source code length:", g) 839 | --local h = LoadNo(chunk, total_size, idx, MoveToNextTok) 840 | --print("Next 4 bytes:", h) 841 | --LoadLocals(chunk, total_size, idx, previdx, MoveToNextTok, func, MoveIdxLen) SetStat("locals") 842 | 843 | local i = LoadInt(chunk, chunkinfo) 844 | print("Next 4 bytes:", i) 845 | local j = LoadNo(chunk, total_size, idx, func_movetonext) 846 | print("Next 4 bytes:", j) 847 | local k = LoadNo(chunk, total_size, idx, func_movetonext) 848 | print("Next 4 bytes:", k) 849 | end 850 | 851 | function CheckSignature(chunk, chunkinfo, oconfig) 852 | local size = chunkinfo.chunk_size 853 | local idx = chunkinfo.idx 854 | local len = string.len(oconfig:GetSign()) 855 | 856 | IsChunkSizeOk(len, idx, size, "header signature") 857 | 858 | if string.sub(chunk, 1, len) ~= oconfig:GetSign() then 859 | error("header signature not found, this is not a Lua chunk") 860 | end 861 | 862 | chunkinfo.idx = chunkinfo.idx + len 863 | 864 | return len 865 | end 866 | 867 | function Check52Signature(size, idx, chunk) 868 | local lua52signature = "\x19\x93\x0d\x0a\x1a\x0a" 869 | 870 | len = string.len(lua52signature) 871 | IsChunkSizeOk(len, idx, size, "lua52 signature") 872 | 873 | if string.sub(chunk, idx, len) ~= lua52signature then 874 | print("Lua 5.2 signature not found, this is not a Lua5.2 chunk") 875 | end 876 | 877 | return idx+len 878 | end 879 | 880 | function CheckVersion(chunk, chunkinfo, oconfig) 881 | local size = chunkinfo.chunk_size 882 | local idx = chunkinfo.idx 883 | 884 | IsChunkSizeOk(1, idx, size, "version byte") 885 | ver = LoadByte(chunk, chunkinfo) 886 | 887 | if oconfig:IsVersionOK(ver) == false then 888 | error(string.format("Dechunk(%s) cannot read version %02X chunks", oconfig:GetVersion(), ver)) 889 | --print(string.format("Dechunk cannot read version %02X chunks", ver)) 890 | end 891 | 892 | return ver 893 | end 894 | 895 | function CheckFormat(chunk, chunkinfo, oconfig) 896 | local size = chunkinfo.chunk_size 897 | local idx = chunkinfo.idx 898 | 899 | IsChunkSizeOk(1, idx, size, "format byte") 900 | format = LoadByte(chunk, chunkinfo) 901 | if format ~= oconfig:GetFormat() then 902 | error(string.format("Dechunk cannot read format %02X chunks", format)) 903 | end 904 | 905 | return format 906 | end 907 | 908 | function CheckEndianness(chunk, chunkinfo, oconfig) 909 | local size = chunkinfo.chunk_size 910 | local idx = chunkinfo.idx 911 | 912 | IsChunkSizeOk(1, idx, size, "endianness byte") 913 | local endianness = LoadByte(chunk, chunkinfo) 914 | if not oconfig:GetConfigDetect() then 915 | if endianness ~= oconfig:GetLuaEndianness() then 916 | error(string.format("unsupported endianness %s vs %s", 917 | endianness, oconfig:GetLuaEndianness())) 918 | end 919 | else 920 | SetLuaEndianness(endianness) 921 | end 922 | return endianness 923 | end 924 | 925 | function CheckSizes(chunk, chunkinfo, sizename, oconfig) 926 | local size = chunkinfo.chunk_size 927 | local idx = chunkinfo.idx 928 | local previdx = chunkinfo.previdx 929 | 930 | IsChunkSizeOk(4, idx, size, "size bytes") 931 | local byte = LoadByte(chunk, chunkinfo) 932 | lt = oconfig:GetLuavmSizeTbl(sizename)["get"] 933 | if not oconfig:GetConfigDetect() then 934 | if byte ~= lt() then 935 | error(string.format("mismatch in %s size (needs %d but read %d)", 936 | sizename, lt(), byte)) 937 | end 938 | else 939 | lt.set(byte) 940 | end 941 | end 942 | 943 | function CheckIntegral(chunk, chunkinfo) 944 | local size = chunkinfo.chunk_size 945 | local idx = chunkinfo.idx 946 | 947 | IsChunkSizeOk(1, idx, size, "integral byte") 948 | SetLuaIntegral(LoadByte(chunk, chunkinfo)) 949 | end 950 | 951 | function CheckLuaNumber(oconfig) 952 | local num_id = oconfig:GetLuaNumberSize() .. oconfig:GetLuaIntegral() 953 | if not oconfig:GetConfigDetect() then 954 | if oconfig:GetLuaNumberType() ~= LUANUMBER_ID[num_id] then 955 | error("incorrect lua_Number format or bad test number") 956 | end 957 | else 958 | -- look for a number type match in our table 959 | SetLuaNumberType(nil) 960 | for i, v in pairs(LUANUMBER_ID) do 961 | if num_id == i then SetLuaNumberType(v) end 962 | end 963 | if not oconfig:GetLuaNumberType() then 964 | error("unrecognized lua_Number type") 965 | end 966 | end 967 | end 968 | 969 | -- 970 | -- this is recursively called to load the chunk or function body 971 | -- 972 | function Load51Function(dechunker, chunk, chunkinfo, funcname, num, level) 973 | local desc = {} 974 | local idx = chunkinfo.idx 975 | local previdx = chunkinfo.previdx 976 | local total_size = chunkinfo.chunk_size 977 | 978 | ------------------------------------------------------------- 979 | -- body of LoadFunction() starts here 980 | ------------------------------------------------------------- 981 | -- statistics handler 982 | local start = idx 983 | desc.stat = {} 984 | local function SetStat(item) 985 | desc.stat[item] = idx - start 986 | start = idx 987 | end 988 | 989 | -- source file name 990 | print("Loading string at "..string.format("%x", idx)) 991 | desc.source = LoadString(chunk, chunkinfo) 992 | desc.pos_source = previdx 993 | if desc.source == "" and level == 1 then desc.source = funcname end 994 | 995 | -- line where the function was defined 996 | print("Func source:" .. desc.source) 997 | desc.linedefined = LoadInt(chunk, chunkinfo) 998 | print("Pos ".. desc.linedefined) 999 | desc.pos_linedefined = previdx 1000 | desc.lastlinedefined = LoadInt(chunk, chunkinfo) 1001 | print("Last line"..desc.lastlinedefined) 1002 | print "1" 1003 | 1004 | ------------------------------------------------------------- 1005 | -- some byte counts 1006 | ------------------------------------------------------------- 1007 | if IsChunkSizeOk(4, idx, total_size, "function header") then return end 1008 | desc.nups = LoadByte(chunk, chunkinfo) 1009 | desc.numparams = LoadByte(chunk, chunkinfo) 1010 | desc.is_vararg = LoadByte(chunk, chunkinfo) 1011 | desc.maxstacksize = LoadByte(chunk, chunkinfo) 1012 | SetStat("header") 1013 | print("Num params"..desc.numparams) 1014 | print("Max stack size"..desc.maxstacksize) 1015 | print "2" 1016 | 1017 | ------------------------------------------------------------- 1018 | -- these are lists, LoadConstantPs() may be recursive 1019 | ------------------------------------------------------------- 1020 | -- load parts of a chunk (rearranged in 5.1) 1021 | LoadCode(chunk, chunkinfo, desc) SetStat("code") 1022 | print "2.4" 1023 | LoadConstantKs(chunk, chunkinfo, desc) SetStat("consts") 1024 | print "2.8" 1025 | LoadConstantPs(chunk, chunkinfo, desc) SetStat("funcs") 1026 | print "3" 1027 | LoadLines(chunk, chunkinfo, desc) SetStat("lines") 1028 | LoadLocals(chunk, chunkinfo, desc) SetStat("locals") 1029 | LoadUpvalues(chunk, chunkinfo, desc) SetStat("upvalues") 1030 | 1031 | -- XXX this should get redundant once chunkinfo is propogated everywhere 1032 | chunkinfo.idx = idx 1033 | chunkinfo.previdx = previdx 1034 | 1035 | return desc 1036 | -- end of Load51Function 1037 | end 1038 | 1039 | -- References : lundump.[ch], http://files.catwell.info/misc/mirror/lua-5.2-bytecode-vm-dirk-laurie/lua52vm.html 1040 | function Load52Function(dechunker, chunk, chunkinfo, funcname, num, level) 1041 | local desc = {} 1042 | local idx = chunkinfo.idx 1043 | local previdx = chunkinfo.previdx 1044 | local total_size = chunkinfo.chunk_size 1045 | 1046 | ------------------------------------------------------------- 1047 | -- body of LoadFunction() starts here 1048 | ------------------------------------------------------------- 1049 | -- statistics handler 1050 | local start = idx 1051 | desc.stat = {} 1052 | local function SetStat(item) 1053 | desc.stat[item] = idx - start 1054 | start = idx 1055 | end 1056 | print("Loading string at "..string.format("%x", idx)) 1057 | Hexdump(chunk) 1058 | previdx = idx 1059 | chunkinfo.idx = dechunker.Func_CheckSignature(total_size, idx, chunk) 1060 | 1061 | -- line where the function was defined 1062 | desc.linedefined = LoadInt(chunk, chunkinfo) 1063 | print("Pos :".. desc.linedefined) 1064 | desc.pos_linedefined = chunkinfo.previdx 1065 | desc.lastlinedefined = LoadInt(chunk, chunkinfo) 1066 | print("Last line :"..desc.lastlinedefined) 1067 | print "1" 1068 | 1069 | ------------------------------------------------------------- 1070 | -- some byte counts 1071 | --------------------desc----------------------------------------- 1072 | if IsChunkSizeOk(4, idx, total_size, "function header") then return end 1073 | desc.numparams = LoadByte(chunk, chunkinfo) 1074 | desc.is_vararg = LoadByte(chunk, chunkinfo) 1075 | desc.maxstacksize = LoadByte(chunk, chunkinfo) 1076 | SetStat("header") 1077 | print("Num params :"..desc.numparams) 1078 | print("Max stack size :"..desc.maxstacksize) 1079 | print "2" 1080 | 1081 | -- load parts of a chunk 1082 | LoadCode(chunk, chunkinfo, desc) SetStat("code") 1083 | print "2.4" 1084 | LoadConstantKs(chunk, chunkinfo, desc) SetStat("consts") 1085 | --LoadConstantPs(chunk, total_size, idx, previdx, MoveToNextTok,func) SetStat("funcs") 1086 | --LoadLines(chunk, total_size, idx, previdx, MoveToNextTok,func) 1087 | LoadFuncProto(chunk, chunkinfo, desc) SetStat("upvalues") 1088 | --SetStat("fproto") 1089 | SetStat("funcs") 1090 | Hexdump(string.sub(chunk, idx, idx+32)) 1091 | 1092 | Load52Upvalues(chunk, chunkinfo, desc) SetStat("upvalues") 1093 | SetStat("upvalues") 1094 | 1095 | Hexdump(string.sub(chunk, idx, idx+32)) 1096 | --LoadDebug(chunk, total_size, idx, previdx, MoveToNextTok, func, MoveIdxLen) 1097 | desc.source = LoadString(chunk, chunkinfo) 1098 | print("Source code: ", desc.source) 1099 | desc.pos_source = chunkinfo.previdx 1100 | 1101 | local n = LoadInt(chunk, chunkinfo) 1102 | print("No of Line Numbers:", n) 1103 | desc.lineinfo = {} 1104 | if n ~= 0 then 1105 | for i = 1, n do 1106 | local j = LoadNo(chunk, chunkinfo) 1107 | print("\t Line number:", j) 1108 | desc.lineinfo[i] = j 1109 | end 1110 | end 1111 | desc.pos_lineinfo = chunkinfo.previdx 1112 | desc.sizelineinfo = n 1113 | SetStat("lines") 1114 | 1115 | local k = LoadNo(chunk, chunkinfo) 1116 | desc.pos_locvars = chunkinfo.previdx 1117 | desc.locvars = {} 1118 | desc.sizelocvars = k 1119 | print("No of Local variables:", k) 1120 | if k ~= 0 then 1121 | for i = 1, k do 1122 | lvars = LoadString(chunk, chunkinfo) 1123 | end 1124 | end 1125 | SetStat("locals") 1126 | 1127 | Hexdump(string.sub(chunk, idx, idx+32)) 1128 | local start = LoadNo(chunk, chunkinfo) 1129 | print("Goes into scope at instruction:", start) 1130 | local stop = LoadNo(chunk, chunkinfo) 1131 | print("Goes out of scope at instruction:", stop) 1132 | desc.nups = LoadNo(chunk, chunkinfo) 1133 | print("No of Upvalues:", nups) 1134 | 1135 | return desc 1136 | -- end of Load52Function 1137 | end 1138 | 1139 | -- References : https://github.com/viruscamp/luadec/blob/master/ChunkSpy/ChunkSpy53.lua 1140 | -- https://the-ravi-programming-language.readthedocs.io/en/latest/lua_bytecode_reference.html 1141 | -- https://raw.githubusercontent.com/viruscamp/luadec/master/ChunkSpy/ChunkSpy53.lua 1142 | function Load53Function(dechunker, chunk, chunkinfo, funcname, num, level) 1143 | local desc = {} 1144 | local idx = chunkinfo.idx 1145 | local previdx = chunkinfo.previdx 1146 | local total_size = chunkinfo.chunk_size 1147 | 1148 | local function CheckLuaSignature(size, idx, chunk) 1149 | local lua53signature = "\x19\x93\x0d\x0a\x1a\x0a" 1150 | 1151 | len = string.len(lua53signature) 1152 | IsChunkSizeOk(len, idx, size, "lua53 signature") 1153 | 1154 | if string.sub(chunk, idx, len) ~= lua53signature then 1155 | print("Lua 5.3 signature not found, this is not a Lua5.3 chunk") 1156 | end 1157 | 1158 | return idx+len 1159 | end 1160 | 1161 | ------------------------------------------------------------- 1162 | -- body of LoadFunction() starts here 1163 | ------------------------------------------------------------- 1164 | -- statistics handler 1165 | local start = chunkinfo.idx 1166 | desc.stat = {} 1167 | desc.stat.funcs = 0 1168 | desc.stat.locvars = {} 1169 | local function SetStat(item, chunkinfo) 1170 | desc.stat[item] = chunkinfo.idx - start 1171 | start = chunkinfo.idx 1172 | end 1173 | print("Loading string at "..string.format("%x", chunkinfo.idx).." 0x"..string.format("%x", chunkinfo.idx)) 1174 | Hexdump(chunk) 1175 | 1176 | --idx = CheckLuaSignature(total_size, idx, chunk) 1177 | local str = {} 1178 | 1179 | chunkinfo.idx = chunkinfo.idx + 1 1180 | --desc.source 1181 | print("idx: "..string.format("%x", chunkinfo.idx).." previdx: "..chunkinfo.previdx) 1182 | str.val, str.len, str.islong= LoadLua53String(chunk, chunkinfo) 1183 | print("Source code: ", str.val, str.len) 1184 | desc.pos_source = chunkinfo.previdx 1185 | print("idx: "..string.format("%x", chunkinfo.idx).." previdx: "..chunkinfo.previdx) 1186 | 1187 | -- line where the function was defined 1188 | desc.linedefined = LoadInt(chunk, chunkinfo) 1189 | print("Pos :".. desc.linedefined.." 0x"..string.format("%x", desc.linedefined)) 1190 | desc.pos_linedefined = previdx 1191 | desc.lastlinedefined = LoadInt(chunk, chunkinfo) 1192 | print("Last line :"..desc.lastlinedefined.." 0x"..string.format("%x", desc.lastlinedefined)) 1193 | print "1" 1194 | 1195 | ------------------------------------------------------------- 1196 | -- some byte counts 1197 | ------------------------------------------------------------- 1198 | if IsChunkSizeOk(4, idx, total_size, "function header") then return end 1199 | desc.numparams = LoadByte(chunk, chunkinfo) 1200 | desc.is_vararg = LoadByte(chunk, chunkinfo) 1201 | desc.maxstacksize = LoadByte(chunk, chunkinfo) 1202 | SetStat("header", chunkinfo) 1203 | chunkinfo.idx = chunkinfo.idx - 1 -- FIXME 1204 | print("Num params :"..desc.numparams) 1205 | print("Max stack size :"..desc.maxstacksize.." idx "..string.format("%x", chunkinfo.idx)) 1206 | print "3" 1207 | 1208 | LoadCode(chunk, chunkinfo, desc) SetStat("code", chunkinfo) 1209 | print "3.4" 1210 | print("idx "..string.format("%x", chunkinfo.idx)) 1211 | 1212 | nc = LoadConstantsLua53(chunk, chunkinfo, desc) SetStat("consts", chunkinfo) 1213 | Load53Upvalues(chunk, chunkinfo, desc) SetStat("upvalues", chunkinfo) 1214 | chunkinfo.idx = chunkinfo.idx + 4 1215 | chunkinfo.previdx = chunkinfo.previdx + 4 1216 | LoadFuncProto(chunk, chunkinfo, desc) SetStat("proto", chunkinfo) 1217 | SetStat("funcs", chunkinfo) 1218 | 1219 | local n = LoadInt(chunk, chunkinfo) 1220 | print("No of Line Numbers:", n) 1221 | desc.lineinfo = {} 1222 | if n ~= 0 then 1223 | for i = 1, n do 1224 | local j = LoadNo(chunk, chunkinfo) 1225 | print("\t Line number:", j) 1226 | desc.lineinfo[i] = j 1227 | end 1228 | end 1229 | desc.pos_lineinfo = chunkinfo.previdx 1230 | desc.sizelineinfo = n 1231 | SetStat("lines", chunkinfo) 1232 | 1233 | local k = LoadNo(chunk, chunkinfo) 1234 | desc.pos_locvars = chunkinfo.previdx 1235 | desc.locvars = {} 1236 | desc.sizelocvars = k 1237 | print("No of Local variables:", k) 1238 | if k ~= 0 then 1239 | for i = 1, k do 1240 | lvars = LoadString(chunk, chunkinfo) 1241 | end 1242 | end 1243 | SetStat("locals", chunkinfo) 1244 | 1245 | --local start = LoadNo(chunk, total_size, idx, MoveToNextTok) 1246 | --print("Goes into scope at instruction:", start) 1247 | --local stop = LoadNo(chunk, total_size, idx, MoveToNextTok) 1248 | --print("Goes out of scope at instruction:", stop) 1249 | --desc.nups = LoadNo(chunk, total_size, idx, MoveToNextTok) 1250 | desc.nups = 0 1251 | print("No of Upvalues:", nups) 1252 | -- Fixups 1253 | desc.upvalues = {} -- FIXME : desc.upvalues stores a table that messes with DescribeInst 1254 | local locvar = {} 1255 | locvar.varname = "NONE" 1256 | locvar.pos_varname = 0 1257 | locvar.startpc = 0 1258 | locvar.pos_startpc = 0 1259 | locvar.endpc = 0 1260 | locvar.pos_endpc = 0 1261 | desc.locvars[1] = locvar 1262 | return desc 1263 | -- end of Load53Function 1264 | end 1265 | 1266 | -- Lua 5.1 and 5.2 Header structures are identical 1267 | -- From lua source file lundump.c 1268 | function LuaChunkHeader(dechunker, chunk, chunkinfo, oconfig) 1269 | local size = chunkinfo.chunk_size 1270 | local name = chunkinfo.chunk_name 1271 | local chunkdets = {} 1272 | local stat = chunkinfo.stats 1273 | local idx = chunkinfo.idx 1274 | local previdx = chunkinfo.previdx 1275 | 1276 | -- 1277 | -- initialize listing display 1278 | -- 1279 | OutputHeader(size, name, chunk, idx) 1280 | 1281 | -- 1282 | -- test signature 1283 | -- 1284 | len = dechunker.Func_CheckSignature(chunk, chunkinfo, oconfig) 1285 | FormatLine(chunk, len, "header signature: "..EscapeString(config.SIGNATURE, 1), idx) 1286 | 1287 | -- 1288 | -- test version 1289 | -- 1290 | chunkinfo.version = dechunker.Func_CheckVersion(chunk, chunkinfo, oconfig) 1291 | FormatLine(chunk, 1, "version (major:minor hex digits)", chunkinfo.previdx) 1292 | chunkdets.version = chunkinfo.version 1293 | 1294 | -- 1295 | -- test format (5.1) 1296 | -- * Dechunk does not accept anything other than 0. For custom 1297 | -- * binary chunks, modify Dechunk to read it properly. 1298 | -- 1299 | chunkinfo.format = dechunker.Func_CheckFormat(chunk, chunkinfo, oconfig) 1300 | FormatLine(chunk, 1, "format (0=official)", chunkinfo.previdx) 1301 | 1302 | if chunkdets.version == 83 then 1303 | local cfg_LUAC_DATA = "\25\147\r\n\26\n" 1304 | local len = string.len(cfg_LUAC_DATA) 1305 | print("Length of LUAC_DATA is ",len) 1306 | local LUAC_DATA = LoadBlock(len, chunk, chunkinfo) 1307 | --print("Read Luac_data as", LUAC_DATA) 1308 | if LUAC_DATA ~= cfg_LUAC_DATA then 1309 | error("header LUAC_DATA not found, this is not a Lua chunk") 1310 | end 1311 | FormatLine(chunk, len, "LUAC_DATA: "..cfg_LUAC_DATA, chunkinfo.previdx) 1312 | else 1313 | 1314 | -- 1315 | -- test endianness 1316 | -- 1317 | endianness = CheckEndianness(chunk, chunkinfo, oconfig) 1318 | FormatLine(chunk, 1, "endianness (1=little endian)", chunkinfo.previdx) 1319 | chunkdets.endianness = endianness 1320 | end 1321 | 1322 | Hexdump(chunk) 1323 | 1324 | -- 1325 | -- test sizes 1326 | -- 1327 | -- byte sizes 1328 | dechunker.Func_CheckSizes(chunk, chunkinfo, "int", oconfig) 1329 | FormatLine(chunk, 1, string.format("size of %s (%s)", 1330 | "int", "bytes"), chunkinfo.previdx) 1331 | dechunker.Func_CheckSizes(chunk, chunkinfo, "size_t", oconfig) 1332 | FormatLine(chunk, 1, string.format("size of %s (%s)", 1333 | "size_t", "bytes"), chunkinfo.previdx) 1334 | dechunker.Func_CheckSizes(chunk, chunkinfo, "Instruction", oconfig) 1335 | FormatLine(chunk, 1, string.format("size of %s (%s)", 1336 | "Instruction", "bytes"), chunkinfo.previdx) 1337 | dechunker.Func_CheckSizes(chunk, chunkinfo, "number", oconfig) 1338 | FormatLine(chunk, 1, string.format("size of %s (%s)", 1339 | "number", "bytes"), chunkinfo.previdx) 1340 | -- initialize decoder (see the 5.0.2 script if you want to customize 1341 | -- bit field sizes; Lua 5.1 has fixed instruction bit field sizes) 1342 | DecodeInit(oconfig) 1343 | 1344 | if chunkdets.version ~= 83 then 1345 | 1346 | -- 1347 | -- test integral flag (5.1) 1348 | -- 1349 | CheckIntegral(chunk, chunkinfo) 1350 | FormatLine(chunk, 1, "integral (1=integral)", chunkinfo.previdx) 1351 | 1352 | -- 1353 | -- verify or determine lua_Number type 1354 | -- 1355 | CheckLuaNumber(oconfig) 1356 | DescLine("* number type: "..oconfig:GetLuaNumberType()) 1357 | else 1358 | GetLuaIntSize() 1359 | CheckLuaNumber(oconfig) 1360 | DescLine("* number type: "..oconfig:GetLuaNumberType()) 1361 | 1362 | --- TODO Next : convert the following into luascope mechanism 1363 | --------------------------------------------------------------- 1364 | -- test endianness 1365 | -- LUAC_INT = 0x5678 in lua 5.3 1366 | --------------------------------------------------------------- 1367 | local convert_from_int = convert_from["int"] 1368 | if not convert_from_int then 1369 | error("could not find conversion function for int") 1370 | end 1371 | IsChunkSizeOk(8, idx, size, "endianness bytes") 1372 | local endianness_bytes = LoadBlock(8, chunk, chunkinfo) 1373 | local endianness_value = convert_from_int(endianness_bytes, 8) 1374 | --MoveToNextTok(size) 1375 | -- 1376 | --if not config.AUTO_DETECT then 1377 | -- if endianness ~= config.endianness then 1378 | -- error("unsupported endianness") 1379 | -- end 1380 | --else 1381 | -- config.endianness = endianness 1382 | --end 1383 | -- 1384 | FormatLine(chunk, 8, "endianness bytes "..string.format("0x%x", endianness_value), chunkinfo.previdx) 1385 | 1386 | --------------------------------------------------------------- 1387 | -- test endianness 1388 | -- LUAC_NUM = cast_num(370.5) in lua 5.3 1389 | --------------------------------------------------------------- 1390 | local convert_from_double = convert_from["double"] 1391 | if not convert_from_double then 1392 | error("could not find conversion function for double") 1393 | end 1394 | print("Total Size"..size.."Current index"..string.format("%x", idx)) 1395 | IsChunkSizeOk(8, idx, size, "float format bytes") 1396 | local float_format_bytes = LoadBlock(8, chunk, chunkinfo) 1397 | print("float bytes "..float_format_bytes) 1398 | local float_format_value = convert_from_double(float_format_bytes) 1399 | FormatLine(chunk, 8, "float format "..float_format_value, chunkinfo.previdx) 1400 | 1401 | IsChunkSizeOk(1, idx, size, "global closure nupvalues") 1402 | local global_closure_nupvalues = LoadByte(chunk, chunkinfo) 1403 | FormatLine(chunk, 1, "global closure nupvalues "..global_closure_nupvalues, chunkinfo.previdx) 1404 | 1405 | -- end of global header 1406 | stat.header = idx - 1 1407 | DisplayStat("* global header = "..stat.header.." bytes", oconfig) 1408 | DescLine("** global header end **") 1409 | end 1410 | 1411 | 1412 | init_scope_config_description() 1413 | DescLine("* "..oconfig:GetLuaDescription()) 1414 | if ShouldIPrintBrief() then WriteLine(oconfig:GetOutputComment()..oconfig:GetLuaDescription()) end 1415 | -- end of global header 1416 | stat.header = idx - 1 1417 | DisplayStat("* global header = "..stat.header.." bytes", oconfig) 1418 | DescLine("** global header end **") 1419 | 1420 | return chunkinfo.idx, chunkinfo.previdx, chunkdets 1421 | end 1422 | 1423 | local LuaDechunker = {} 1424 | local Lua51Dechunker = {} 1425 | local Lua52Dechunker = {} 1426 | local Lua53Dechunker = {} 1427 | LuaDechunker.__index = LuaDechunker 1428 | 1429 | LuaDechunker = { 1430 | Func_DescFunction = DescFunction, 1431 | Func_DechunkHeader = LuaChunkHeader, 1432 | Func_DechunkGlobalHeader, 1433 | Func_LoadFunction = LoadFunction, 1434 | Func_LoadUpvalues = LoadUpvalues, 1435 | Func_LoadString = LoadString, 1436 | Func_LoadByte = LoadByte, 1437 | Func_LoadBlock = LoadBlock, 1438 | Func_LoadNo = LoadNo, 1439 | Func_LoadInt = LoadInt, 1440 | Func_LoadSize = LoadSize, 1441 | Func_LoadNumber = LoadNumber, 1442 | Func_LoadLines = LoadLines, 1443 | Func_LoadLocals = LoadLocals, 1444 | Func_LoadFuncProto = LoadFuncProto, 1445 | Func_LoadCode = LoadCode, 1446 | Func_LoadConstantPs = LoadConstantPs, 1447 | Func_LoadDebug = LoadDebug, 1448 | Func_LoadConstantKs = LoadConstantKs, 1449 | Func_CheckSignature = CheckSignature, 1450 | Func_CheckVersion = CheckVersion, 1451 | Func_CheckFormat = CheckFormat, 1452 | Func_CheckEndianness = CheckEndianness, 1453 | Func_CheckSizes = CheckSizes, 1454 | Func_CheckIntegral = CheckIntegral, 1455 | Func_CheckLuaNumber = CheckLuaNumber, 1456 | Func_CheckLuaSignature = CheckLuaSignature 1457 | } 1458 | 1459 | Lua51Dechunker = { 1460 | Func_DescFunction = DescFunction, 1461 | Func_DechunkHeader = LuaChunkHeader, 1462 | Func_DechunkGlobalHeader, 1463 | Func_LoadFunction = Load51Function, 1464 | Func_LoadUpvalues = Load51Upvalues, 1465 | Func_LoadString = LoadString, 1466 | Func_LoadByte = LoadByte, 1467 | Func_LoadFuncProto = LoadFuncProto, 1468 | } 1469 | 1470 | Lua52Dechunker = { 1471 | Func_DescFunction = DescFunction, 1472 | Func_DechunkHeader = LuaChunkHeader, 1473 | Func_DechunkGlobalHeader, 1474 | Func_LoadFunction = Load52Function, 1475 | Func_LoadUpvalues = Load52Upvalues, 1476 | Func_LoadString = LoadString, 1477 | Func_LoadByte = LoadByte, 1478 | Func_LoadFuncProto = LoadFuncProto, 1479 | Func_CheckSignature = Check52Signature, 1480 | } 1481 | 1482 | Lua53Dechunker = { 1483 | Func_DescFunction = DescFunction, 1484 | Func_DechunkHeader = LuaChunkHeader, 1485 | Func_DechunkGlobalHeader, 1486 | Func_LoadFunction = Load53Function, 1487 | Func_LoadUpvalues = Load52Upvalues, 1488 | Func_LoadString = LoadString, 1489 | Func_LoadByte = LoadByte53, 1490 | Func_LoadFuncProto = LoadFuncProto, 1491 | } 1492 | 1493 | -- 1494 | -- Dechunk main processing function 1495 | -- * in order to maintain correct positional order, the listing will 1496 | -- show functions as nested; a level number is kept to help the 1497 | -- user trace the extent of functions in the listing 1498 | -- 1499 | function Dechunk(chunk_name, chunk, oconfig) 1500 | local chunkinfo = {} -- table with all parsed data, descriptor for chunk 1501 | chunkinfo.chunk_name = chunk_name or "" 1502 | chunkinfo.chunk_size = string.len(chunk) 1503 | chunkinfo.stats = {} 1504 | chunkinfo.idx = 1 1505 | chunkinfo.previdx = 0 1506 | 1507 | setmetatable(Lua51Dechunker, LuaDechunker) 1508 | setmetatable(Lua52Dechunker, LuaDechunker) 1509 | setmetatable(Lua53Dechunker, LuaDechunker) 1510 | 1511 | --[[ 1512 | -- Display support functions 1513 | -- * considerable work is done to maintain nice alignments 1514 | -- * some widths are initialized at chunk start 1515 | -- * this is meant to make output customization easy 1516 | --]] 1517 | 1518 | chunkinfo.idx, chunkinfo.previdx, dets = LuaDechunker:Func_DechunkHeader(chunk, chunkinfo, oconfig) 1519 | 1520 | if dets.version == 81 then 1521 | -- 1522 | -- Lua version 5.1 1523 | -- 1524 | -- actual call to start the function loading process 1525 | -- 1526 | chunkinfo.desc = Lua51Dechunker:Func_LoadFunction(chunk, chunkinfo, "(chunk)", 0, 1) 1527 | DescFunction(chunk, chunkinfo.desc, 0, 1, oconfig) 1528 | chunkinfo.stats.total = chunkinfo.idx - 1 1529 | -- TODO DisplayStat(chunk, "* TOTAL size = "..stat.total.." bytes", oconfig) 1530 | FormatLine(chunk, 0, "** end of chunk **", chunkinfo.idx) 1531 | elseif dets.version == 82 then 1532 | -- 1533 | -- Lua Version 5.2 1534 | -- 1535 | print "Found Lua 52 chucnk" 1536 | chunkinfo.desc = Lua52Dechunker:Func_LoadFunction(chunk, chunkinfo, "(chunk)", 0, 1) 1537 | DescFunction(chunk, chunkinfo.desc, 0, 1, oconfig) 1538 | elseif dets.version == 83 then 1539 | -- 1540 | -- Lua Version 5.3 1541 | -- 1542 | print "Found Lua 53 Chunk" 1543 | print "Lua 5.3 is not supported yet" 1544 | chunkinfo.desc = Lua53Dechunker:Func_LoadFunction(chunk, chunkinfo, "(chunk)", 0, 1) 1545 | DescFunction(chunk, chunkinfo.desc, 0, 1, oconfig) 1546 | end 1547 | 1548 | return chunkinfo 1549 | -- end of Dechunk 1550 | end -------------------------------------------------------------------------------- /scope_decoder.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | --[[ 4 | Decode Instructions for Lua Scope 5 | A Lua 5.1/5.2/5.3 binary chunk disassembler 6 | LuaScope was inspired by Jein-Hong Man's ChunkSpy 7 | --]] 8 | 9 | 10 | package.path = package.path .. ";./?.lua;/usr/src/?.lua" 11 | 12 | require("scope_config") 13 | 14 | -- XXX is a hack, only temporary, hopefully. 15 | config = get_config() 16 | l51vm = {} 17 | 18 | --[[------------------------------------------------------------------- 19 | -- Instruction decoder functions (changed in Lua 5.1) 20 | -- * some fixed decode data is placed in the config table 21 | -- * these function are quite flexible, they can accept non-standard 22 | -- instruction field sizes as long as the arrangement is the same. 23 | ----------------------------------------------------------------------- 24 | Visually, an instruction can be represented as one of: 25 | 26 | 31 | | | 0 bit position 27 | +-----+-----+-----+----------+ 28 | | B | C | A | Opcode | iABC format 29 | +-----+-----+-----+----------+ 30 | - 9 - 9 - 8 - 6 - field sizes (standard Lua) 31 | +-----+-----+-----+----------+ 32 | | [s]Bx | A | Opcode | iABx | iAsBx format 33 | +-----+-----+-----+----------+ 34 | 35 | The signed argument sBx is represented in excess K, with the range 36 | of -max to +max represented by 0 to 2*max. 37 | 38 | For RK(x) constants, MSB is set and constant number is in the rest 39 | of the bits. 40 | 41 | --]]------------------------------------------------------------------- 42 | 43 | ----------------------------------------------------------------------- 44 | -- instruction decoder initialization 45 | ----------------------------------------------------------------------- 46 | function DecodeInit(oconfig) 47 | local sizea = oconfig:GetLuaSize_A() 48 | local sizeb = oconfig:GetLuaSize_B() 49 | local sizec = oconfig:GetLuaSize_C() 50 | local sizeop = oconfig:GetLuaSize_OP() 51 | 52 | --------------------------------------------------------------- 53 | -- calculate masks 54 | --------------------------------------------------------------- 55 | l51vm.SIZE_Bx = sizeb + sizec 56 | local MASK_OP = math.ldexp(1, sizeop) 57 | local MASK_A = math.ldexp(1, sizea) 58 | local MASK_B = math.ldexp(1, sizeb) 59 | local MASK_C = math.ldexp(1, sizec) 60 | local MASK_Bx = math.ldexp(1, l51vm.SIZE_Bx) 61 | l51vm.MAXARG_sBx = math.floor((MASK_Bx - 1) / 2) 62 | l51vm.BITRK = math.ldexp(1, sizeb - 1) 63 | 64 | --------------------------------------------------------------- 65 | -- iABC instruction segment tables 66 | --------------------------------------------------------------- 67 | l51vm.iABC = { -- tables allows field sequence to be extracted 68 | sizeop, -- using a loop; least significant field first 69 | sizea, -- additional lookups below, kludgy 70 | sizec, 71 | sizeb, 72 | } 73 | l51vm.mABC = { MASK_OP, MASK_A, MASK_C, MASK_B, } 74 | l51vm.nABC = { "OP", "A", "C", "B", } 75 | 76 | --------------------------------------------------------------- 77 | -- Lua VM opcode name table (5.1) 78 | --------------------------------------------------------------- 79 | local opcodes = 80 | "MOVE LOADK LOADBOOL LOADNIL GETUPVAL \ 81 | GETGLOBAL GETTABLE SETGLOBAL SETUPVAL SETTABLE \ 82 | NEWTABLE SELF ADD SUB MUL \ 83 | DIV MOD POW UNM NOT \ 84 | LEN CONCAT JMP EQ LT \ 85 | LE TEST TESTSET CALL TAILCALL RETURN \ 86 | FORLOOP FORPREP TFORLOOP SETLIST \ 87 | CLOSE CLOSURE VARARG" 88 | 89 | --------------------------------------------------------------- 90 | -- build opcode name table 91 | --------------------------------------------------------------- 92 | l51vm.opnames = {} 93 | l51vm.NUM_OPCODES = 0 94 | if not l51vm.WIDTH_OPCODE then l51vm.WIDTH_OPCODE = 0 end 95 | for v in string.gmatch(opcodes, "[^%s]+") do 96 | if ShouldIPrintLowercase() then v = string.lower(v) end 97 | l51vm.opnames[l51vm.NUM_OPCODES] = v 98 | local vlen = string.len(v) 99 | -- find maximum opcode length 100 | if vlen > l51vm.WIDTH_OPCODE then 101 | l51vm.WIDTH_OPCODE = vlen 102 | end 103 | l51vm.NUM_OPCODES = l51vm.NUM_OPCODES + 1 104 | end 105 | -- opmode: 0=ABC, 1=ABx, 2=AsBx 106 | l51vm.opmode = "01000101000000000000002000000002200010" 107 | 108 | --------------------------------------------------------------- 109 | -- initialize text widths and formats for display 110 | --------------------------------------------------------------- 111 | l51vm.WIDTH_A = WidthOf(MASK_A) 112 | l51vm.WIDTH_B = WidthOf(MASK_B) 113 | l51vm.WIDTH_C = WidthOf(MASK_C) 114 | l51vm.WIDTH_Bx = WidthOf(MASK_Bx) + 1 -- with minus sign 115 | l51vm.FORMAT_A = string.format("%%-%dd", l51vm.WIDTH_A) 116 | l51vm.FORMAT_B = string.format("%%-%dd", l51vm.WIDTH_B) 117 | l51vm.FORMAT_C = string.format("%%-%dd", l51vm.WIDTH_C) 118 | l51vm.PAD_Bx = l51vm.WIDTH_A + l51vm.WIDTH_B + l51vm.WIDTH_C + 2 119 | - l51vm.WIDTH_Bx 120 | if l51vm.PAD_Bx > 0 then 121 | l51vm.PAD_Bx = string.rep(" ", l51vm.PAD_Bx) 122 | else 123 | l51vm.PAD_Bx = "" 124 | end 125 | l51vm.FORMAT_Bx = string.format("%%-%dd", l51vm.WIDTH_Bx) 126 | l51vm.FORMAT_AB = string.format("%s %s %s", l51vm.FORMAT_A, l51vm.FORMAT_B, string.rep(" ", l51vm.WIDTH_C)) 127 | l51vm.FORMAT_ABC = string.format("%s %s %s", l51vm.FORMAT_A, l51vm.FORMAT_B, l51vm.FORMAT_C) 128 | l51vm.FORMAT_AC = string.format("%s %s %s", l51vm.FORMAT_A, string.rep(" ", l51vm.WIDTH_B), l51vm.FORMAT_C) 129 | l51vm.FORMAT_ABx = string.format("%s %s", l51vm.FORMAT_A, l51vm.FORMAT_Bx) 130 | end 131 | 132 | ----------------------------------------------------------------------- 133 | -- instruction decoder 134 | -- * decoder loops starting from the least-significant byte, this allow 135 | -- a field to be extracted using % operations 136 | -- * returns a table populated with the appropriate fields 137 | -- * WARNING B,C arrangement is hard-coded here for calculating [s]Bx 138 | ----------------------------------------------------------------------- 139 | function DecodeInst(code, iValues) 140 | local iSeq, iMask = l51vm.iABC, l51vm.mABC 141 | local cValue, cBits, cPos = 0, 0, 1 142 | -- decode an instruction 143 | for i = 1, #iSeq do 144 | -- if need more bits, suck in a byte at a time 145 | while cBits < iSeq[i] do 146 | cValue = string.byte(code, cPos) * math.ldexp(1, cBits) + cValue 147 | cPos = cPos + 1; cBits = cBits + 8 148 | end 149 | -- extract and set an instruction field 150 | iValues[l51vm.nABC[i]] = cValue % iMask[i] 151 | cValue = math.floor(cValue / iMask[i]) 152 | cBits = cBits - iSeq[i] 153 | end 154 | iValues.opname = l51vm.opnames[iValues.OP] -- get mnemonic 155 | iValues.opmode = string.sub(l51vm.opmode, iValues.OP + 1, iValues.OP + 1) 156 | if iValues.opmode == "1" then -- set Bx or sBx 157 | iValues.Bx = iValues.B * iMask[3] + iValues.C 158 | elseif iValues.opmode == "2" then 159 | iValues.sBx = iValues.B * iMask[3] + iValues.C - l51vm.MAXARG_sBx 160 | end 161 | return iValues 162 | end 163 | 164 | ----------------------------------------------------------------------- 165 | -- describe an instruction 166 | -- * make instructions descriptions more verbose and readable 167 | ----------------------------------------------------------------------- 168 | function DescribeInst(inst, pos, func, oconfig) 169 | local Operand 170 | local Comment = "" 171 | 172 | --------------------------------------------------------------- 173 | -- operand formatting helpers 174 | --------------------------------------------------------------- 175 | local function OperandAB(i) return string.format(l51vm.FORMAT_AB, i.A, i.B) end 176 | local function OperandABC(i) return string.format(l51vm.FORMAT_ABC, i.A, i.B, i.C) end 177 | local function OperandAC(i) return string.format(l51vm.FORMAT_AC, i.A, i.C) end 178 | local function OperandABx(i) return string.format(l51vm.FORMAT_ABx, i.A, i.Bx) end 179 | local function OperandAsBx(i) return string.format(l51vm.FORMAT_ABx, i.A, i.sBx) end 180 | 181 | --------------------------------------------------------------- 182 | -- comment formatting helpers 183 | -- calculate jump location, conditional flag 184 | --------------------------------------------------------------- 185 | local function CommentLoc(sBx, cond) 186 | local loc = string.format("to [%d]", pos + 1 + sBx) 187 | if cond then loc = loc..cond end 188 | return loc 189 | end 190 | 191 | --------------------------------------------------------------- 192 | -- Kst(x) - constant (in constant table) 193 | --------------------------------------------------------------- 194 | local function CommentK(index, quoted) 195 | local c = func.k[index + 1] 196 | if type(c) == "string" then 197 | return EscapeString(c, quoted) 198 | elseif type(c) == "number" or type(c) == "boolean" then 199 | return tostring(c) 200 | else 201 | return "nil" 202 | end 203 | end 204 | 205 | --------------------------------------------------------------- 206 | -- RK(x) == if BITRK then Kst(x&~BITRK) else R(x) 207 | --------------------------------------------------------------- 208 | local function CommentRK(index, quoted) 209 | if index >= l51vm.BITRK then 210 | return CommentK(index - l51vm.BITRK, quoted) 211 | else 212 | return "" 213 | end 214 | end 215 | 216 | --------------------------------------------------------------- 217 | -- comments for RK(B), RK(C) 218 | --------------------------------------------------------------- 219 | local function CommentBC(inst) 220 | local B, C = CommentRK(inst.B, true), CommentRK(inst.C, true) 221 | if B == "" then 222 | if C == "" then return "" else return C end 223 | elseif C == "" then 224 | return B 225 | else 226 | return B.." "..C 227 | end 228 | end 229 | 230 | --------------------------------------------------------------- 231 | -- floating point byte conversion 232 | -- bit positions: mmmmmxxx, actual: (1xxx) * 2^(m-1) 233 | --------------------------------------------------------------- 234 | local function fb2int(x) 235 | local e = math.floor(x / 8) % 32 236 | if e == 0 then return x end 237 | return math.ldexp((x % 8) + 8, e - 1) 238 | end 239 | 240 | --------------------------------------------------------------- 241 | -- yeah, I know this is monstrous... 242 | -- * see the descriptions in lopcodes.h for more information 243 | ---- 244 | if inst.prev then -- continuation of SETLIST 245 | Operand = string.format(l51vm.FORMAT_Bx, func.code[pos]) 246 | -- 247 | elseif inst.OP == 0 then -- MOVE A B 248 | Operand = OperandAB(inst) 249 | -- 250 | elseif inst.OP == 1 then -- LOADK A Bx 251 | Operand = OperandABx(inst) 252 | Comment = CommentK(inst.Bx, true) 253 | -- 254 | elseif inst.OP == 2 then -- LOADBOOL A B C 255 | Operand = OperandABC(inst) 256 | if inst.B == 0 then Comment = "false" else Comment = "true" end 257 | if inst.C > 0 then Comment = Comment..", "..CommentLoc(1) end 258 | -- 259 | elseif inst.OP == 3 then -- LOADNIL A B 260 | Operand = OperandAB(inst) 261 | -- 262 | elseif inst.OP == 4 then -- GETUPVAL A B 263 | Operand = OperandAB(inst) 264 | Comment = func.upvalues[inst.B + 1] 265 | -- 266 | elseif inst.OP == 5 or -- GETGLOBAL A Bx 267 | inst.OP == 7 then -- SETGLOBAL A Bx 268 | Operand = OperandABx(inst) 269 | Comment = CommentK(inst.Bx) 270 | -- 271 | elseif inst.OP == 6 then -- GETTABLE A B C 272 | Operand = OperandABC(inst) 273 | Comment = CommentRK(inst.C, true) 274 | -- 275 | elseif inst.OP == 8 then -- SETUPVAL A B 276 | Operand = OperandAB(inst) 277 | Comment = func.upvalues[inst.B + 1] 278 | -- 279 | elseif inst.OP == 9 then -- SETTABLE A B C 280 | Operand = OperandABC(inst) 281 | Comment = CommentBC(inst) 282 | -- 283 | elseif inst.OP == 10 then -- NEWTABLE A B C 284 | Operand = OperandABC(inst) 285 | local ar = fb2int(inst.B) -- array size 286 | local hs = fb2int(inst.C) -- hash size 287 | Comment = "array="..ar..", hash="..hs 288 | -- 289 | elseif inst.OP == 11 then -- SELF A B C 290 | Operand = OperandABC(inst) 291 | Comment = CommentRK(inst.C, true) 292 | -- 293 | elseif inst.OP == 12 or -- ADD A B C 294 | inst.OP == 13 or -- SUB A B C 295 | inst.OP == 14 or -- MUL A B C 296 | inst.OP == 15 or -- DIV A B C 297 | inst.OP == 16 or -- MOD A B C 298 | inst.OP == 17 then -- POW A B C 299 | Operand = OperandABC(inst) 300 | Comment = CommentBC(inst) 301 | -- 302 | elseif inst.OP == 18 or -- UNM A B 303 | inst.OP == 19 or -- NOT A B 304 | inst.OP == 20 then -- LEN A B 305 | Operand = OperandAB(inst) 306 | -- 307 | elseif inst.OP == 21 then -- CONCAT A B C 308 | Operand = OperandABC(inst) 309 | -- 310 | elseif inst.OP == 22 then -- JMP sBx 311 | Operand = string.format(l51vm.FORMAT_Bx, inst.sBx) 312 | Comment = CommentLoc(inst.sBx) 313 | -- 314 | elseif inst.OP == 23 or -- EQ A B C 315 | inst.OP == 24 or -- LT A B C 316 | inst.OP == 25 or -- LE A B C 317 | inst.OP == 27 then -- TESTSET A B C 318 | Operand = OperandABC(inst) 319 | if inst.OP ~= 27 then Comment = CommentBC(inst) end 320 | if Comment ~= "" then Comment = Comment..", " end 321 | -- since the pc++ is in the 'else' path, the sense is opposite 322 | local sense = " if false" 323 | if inst.OP == 27 then 324 | if inst.C == 0 then sense = " if true" end 325 | else 326 | if inst.A == 0 then sense = " if true" end 327 | end 328 | Comment = Comment..CommentLoc(1, sense) 329 | elseif inst.OP == 26 then -- TEST A C 330 | Operand = OperandAC(inst) 331 | local sense = " if false" 332 | if inst.C == 0 then sense = " if true" end 333 | Comment = Comment..CommentLoc(1, sense) 334 | -- 335 | elseif inst.OP == 28 or -- CALL A B C 336 | inst.OP == 29 then -- TAILCALL A B C 337 | Operand = OperandABC(inst) 338 | -- 339 | elseif inst.OP == 30 then -- RETURN A B 340 | Operand = OperandAB(inst) 341 | -- 342 | elseif inst.OP == 31 then -- FORLOOP A sBx 343 | Operand = OperandAsBx(inst) 344 | Comment = CommentLoc(inst.sBx, " if loop") 345 | -- 346 | elseif inst.OP == 32 then -- FORPREP A sBx 347 | Operand = OperandAsBx(inst) 348 | Comment = CommentLoc(inst.sBx) 349 | -- 350 | elseif inst.OP == 33 then -- TFORLOOP A C 351 | Operand = OperandAC(inst) 352 | Comment = CommentLoc(1, " if exit") 353 | -- 354 | elseif inst.OP == 34 then -- SETLIST A B C 355 | Operand = OperandABC(inst) 356 | -- R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B 357 | local n = inst.B 358 | local c = inst.C 359 | if c == 0 then 360 | -- grab next inst when index position is large 361 | c = func.code[pos + 1] 362 | func.inst[pos + 1].prev = true 363 | end 364 | local start = (c - 1) * oconfig:GetLuaFPF() + 1 365 | local last = start + n - 1 366 | Comment = "index "..start.." to " 367 | if n ~= 0 then 368 | Comment = Comment..last 369 | else 370 | Comment = Comment.."top" 371 | end 372 | -- 373 | elseif inst.OP == 35 then -- CLOSE A 374 | Operand = string.format(l51vm.FORMAT_A, inst.A) 375 | -- 376 | elseif inst.OP == 36 then -- CLOSURE A Bx 377 | Operand = OperandABx(inst) 378 | -- lets user know how many following instructions are significant 379 | -- FIXME Comment = func.p[inst.Bx + 1].nups.." upvalues" 380 | -- 381 | elseif inst.OP == 37 then -- VARARG A B 382 | Operand = OperandAB(inst) 383 | -- 384 | else 385 | -- add your VM extensions here 386 | Operand = string.format("OP %d", inst.OP) 387 | end 388 | 389 | -- 390 | -- compose operands and comments 391 | -- 392 | if Comment and Comment ~= "" then 393 | Operand = Operand..GetOutputSep() 394 | ..GetOutputComment()..Comment 395 | end 396 | return LeftJustify(inst.opname, l51vm.WIDTH_OPCODE) 397 | ..GetOutputSep()..Operand 398 | end 399 | -------------------------------------------------------------------------------- /scope_output.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | --[[ 4 | Output for Lua Scope 5 | Display / Output handler 6 | A Lua 5.1/5.2/5.3 binary chunk disassembler 7 | LuaScope was inspired by Jein-Hong Man's ChunkSpy 8 | --]] 9 | 10 | package.path = package.path .. ";./?.lua;/usr/src/?.lua" 11 | 12 | require("scope_config") 13 | 14 | outfile = {} 15 | oconfig = Oconfig 16 | 17 | --[[ 18 | -- Display support functions 19 | -- * considerable work is done to maintain nice alignments 20 | -- * some widths are initialized at chunk start 21 | -- * this is meant to make output customization easy 22 | --]] 23 | 24 | -- 25 | -- width of number, left justify, zero padding 26 | -- 27 | function WidthOf(n) return string.len(tostring(n)) end 28 | function LeftJustify(s, width) return s..string.rep(" ", width - string.len(s)) end 29 | function ZeroPad(s, width) return string.rep("0", width - string.len(s))..s end 30 | 31 | -- 32 | -- initialize display formatting settings 33 | -- * chunk_size parameter used to set width of position column 34 | -- 35 | function DisplayInit(chunk_size) 36 | -- 37 | -- set up printing widths 38 | -- 39 | init_print_width(chunk_size) 40 | 41 | -- 42 | -- sane defaults 43 | -- 44 | init_display_config() 45 | 46 | -- default output path 47 | if not WriteLine then WriteLine = print end 48 | end 49 | 50 | -- 51 | -- initialize listing output path (an optional redirect) 52 | -- * this is done before calling Dechunk to redirect output 53 | -- 54 | function OutputInit() 55 | if outfile.OUTPUT_FILE then 56 | if type(outfile.OUTPUT_FILE) == "string" then 57 | local INF = io.open(outfile.OUTPUT_FILE, "wb") 58 | if not INF then 59 | error("cannot open \""..outfile.OUTPUT_FILE.."\" for writing") 60 | end 61 | outfile.OUTPUT_FILE = INF 62 | WriteLine = PrintToFile 63 | end 64 | end 65 | end 66 | 67 | function PrintToFile(msg) 68 | outfile.OUTPUT_FILE:write(msg, "\n") 69 | end 70 | 71 | function CloseOutput() 72 | io.close(outfile.OUTPUT_FILE) 73 | end 74 | 75 | -- 76 | -- cleanup listing output path 77 | -- 78 | function OutputExit() 79 | if WriteLine and WriteLine ~= print then CloseOutput() end 80 | end 81 | 82 | -- 83 | -- escape control bytes in strings 84 | -- 85 | function EscapeString(s, quoted) 86 | local v = "" 87 | for i = 1, string.len(s) do 88 | local c = string.byte(s, i) 89 | -- other escapees with values > 31 are "(34), \(92) 90 | if c < 32 or c == 34 or c == 92 then 91 | if c >= 7 and c <= 13 then 92 | c = string.sub("abtnvfr", c - 6, c - 6) 93 | elseif c == 34 or c == 92 then 94 | c = string.char(c) 95 | end 96 | v = v.."\\"..c 97 | else-- 32 <= v <= 255 98 | v = v..string.char(c) 99 | end 100 | end 101 | if quoted then return string.format("\"%s\"", v) end 102 | return v 103 | end 104 | 105 | -- 106 | -- listing legend/header 107 | -- 108 | function HeaderLine() 109 | if ShouldIPrintLess() then return end 110 | WriteLine(LeftJustify("Pos", oconfig:GetOutputPosWidth())..oconfig:GetOutputSep() 111 | ..LeftJustify("Hex Data", oconfig:GetOutputPosWidth() * 2 + 1)..oconfig:GetOutputSep() 112 | .."Description or Code\n" 113 | ..string.rep("-", 72)) 114 | end 115 | 116 | -- 117 | -- description-only line, no position or hex data 118 | -- 119 | function DescLine(desc) 120 | if ShouldIPrintLess() then return end 121 | WriteLine(string.rep(" ", oconfig:GetOutputPosWidth())..oconfig:GetOutputSep()..oconfig:GetOutputBlankHex()..oconfig:GetOutputSep() 122 | ..desc) 123 | end 124 | 125 | -- 126 | -- optionally display a pre-formatted statistic 127 | -- 128 | function DisplayStat(stat, oconfig) 129 | if oconfig:ShouldIPrintStats() then DescLine(stat) end 130 | end 131 | 132 | -- 133 | -- returns position, i uses string index (starts from 1) 134 | -- 135 | function FormatPos(i) 136 | local pos = oconfig:GetOutputPosString(i) 137 | return ZeroPad(pos, oconfig:GetOutputPosWidth()) 138 | end 139 | 140 | -- 141 | -- display a position, hex data, description line 142 | -- 143 | function FormatLine(chunk, size, desc, index, segment) 144 | if ShouldIPrintLess() then return end 145 | if ShouldIPrintHexData() then 146 | -- nicely formats binary chunk data in multiline hexadecimal 147 | if size == 0 then 148 | WriteLine(FormatPos(index)..oconfig:GetOutputSep()..oconfig:GetOutputBlankHex()..oconfig:GetOutputSep() 149 | ..desc) 150 | else 151 | -- split hex data into config.WIDTH_HEX byte strings 152 | while size > 0 do 153 | local d, dlen = "", size 154 | if size > oconfig:GetOutputHexWidth() then dlen = oconfig:GetOutputHexWidth() end 155 | -- build hex data digits 156 | for i = 0, dlen - 1 do 157 | d = d..string.format("%02X", string.byte(chunk, index + i)) 158 | end 159 | -- add padding or continuation indicator 160 | d = d..string.rep(" ", oconfig:GetOutputHexWidth() - dlen) 161 | if segment or size > oconfig:GetOutputHexWidth() then 162 | d = d.."+"; size = size - oconfig:GetOutputHexWidth() 163 | else 164 | d = d.." "; size = 0 165 | end 166 | -- description only on first line of a multiline 167 | if desc then 168 | WriteLine(FormatPos(index)..oconfig:GetOutputSep() 169 | ..d..oconfig:GetOutputSep() 170 | ..desc) 171 | desc = nil 172 | else 173 | WriteLine(FormatPos(index)..oconfig:GetOutputSep()..d) 174 | end 175 | index = index + dlen 176 | end--while 177 | end--if size 178 | else--no hex data mode 179 | WriteLine(FormatPos(index)..oconfig:GetOutputSep()..desc) 180 | end 181 | -- end of FormatLine 182 | end 183 | 184 | function OutputHeader(size, name, chunk, idx) 185 | DisplayInit(size) 186 | HeaderLine() -- listing display starts here 187 | if name then 188 | FormatLine(chunk, 0, "** source chunk: "..name, idx) 189 | if ShouldIPrintBrief() then 190 | WriteLine(oconfig:GetOutputComment().."source chunk: "..name) 191 | end 192 | end 193 | DescLine("** global header start **") 194 | end 195 | 196 | -- Taken from http://lua-users.org/wiki/HexDump 197 | function Hexdump(buf) 198 | for i=1,math.ceil(#buf/16) * 16 do 199 | if (i-1) % 16 == 0 then io.write(string.format('%08X ', i-1)) end 200 | io.write( i > #buf and ' ' or string.format('%02X ', buf:byte(i)) ) 201 | if i % 8 == 0 then io.write(' ') end 202 | if i % 16 == 0 then io.write( buf:sub(i-16+1, i):gsub('%c','.'), '\n' ) end 203 | end 204 | end 205 | 206 | --------------------------------------------------------------------------------