--[[ Copyright (c) 2019 Raidho36 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- --[[ dump file format: HEADER MESSAGE Stack: dump of functions on the stack Memory: dump of all other objects in the memory function dump: function id, line defined, last line defined, current line (if on stack), function name (if any), type of function variable (global, method, etc.), source code file, shortened source code file function locals dump (if on stack) function upvalues dump table dump: table id, metatable key-value pairs dump userdata dump: userdata id, metatable key-value pairs dump (if '__pairs' exists) cdata dump: cdata id key-value pairs dump (if '__pairs' exists) function local variable dump: local name, local type, local value function upvalue dump: upvalue name, upvalue type, upvalue value key-value pair dump: key type, key, value type, value ]]-- local function memorydump ( filename, message, stackoffset, ignoreobjects ) local file = assert ( io.open ( filename, 'w' ) ) if message then file:write ( message ) file:write ( "\n\n" ) end file:write ( "Stack:\n" ) local list = { } local proc = type ( ignoreobjects == 'table' ) and ignoreobjects or { } local function escape ( str ) return tostring ( str ):gsub ( "\t", "\\t" ):gsub ( "\r", "\\r" ):gsub ( "\n", "\\n" ) end local function checkpairs ( x ) return x.__pairs end local function getobjstring ( obj, objtype ) local objstring = tostring ( obj ) if not objstring:find ( string.format ( "^%s: .+$", objtype ) ) then -- in case __tostring is misbehaving, prepend with class name and add additional colon as a flag objstring = string.format ( '%s:: %s', objtype, objstring ) end return objstring end local function pushlist ( obj ) if not ( type ( obj ) == 'nil' or type ( obj ) == 'boolean' or type ( obj ) == 'string' or type ( obj ) == 'number' ) then list[ #list + 1 ] = obj end end local function writeobj ( obj, meta ) if meta then file:write ( string.format ( "%s\t(meta %s)\n", obj, meta ) ) else file:write ( string.format ( "%s\n", obj ) ) end end local function writefunc ( data ) file:write ( string.format ( "%s\t%d\t%d\t%d\t%s\t%s\t%s\t%s\n", data.func, data.linedefined, data.lastlinedefined, data.currentline, escape ( data.name ), escape ( data.namewhat ), escape ( data.source ), escape ( data.short_src ) ) ) end local function writelocal ( lname, lval ) file:write ( string.format ( "local: %s\t=(%s)%s\n", lname, type ( lval ), escape ( lval ) ) ) end local function writeupvalue ( uname, uval ) file:write ( string.format ( "upvalue: %s\t=(%s)%s\n", uname, type ( uval ), escape ( uval ) ) ) end local function writepair ( k, v ) file:write ( string.format ( "pair: (%s)%s\t=(%s)%s\n", type( k ), escape ( k ), type ( v ), escape ( v ) ) ) end -- dump stack data local level = 3 + stackoffset or 0 while true do local data = debug.getinfo ( level ) if not data then break end proc[ data.func ] = true writefunc ( data ) local k = 1 while true do local lname, lval = debug.getlocal ( level, k ) if not lname then break end writelocal ( lname, lval ) pushlist ( lval ) k = k + 1 end k = 1 while true do local uname, uval = debug.getupvalue ( data.func, k ) if not uname then break end writeupvalue ( uname, uval ) pushlist ( uval ) k = k + 1 end level = level + 1 end file:write ( "\nMemory:\n" ) -- find and dump all data while #list > 0 do local obj = list[ #list ] list[ #list ] = nil if not proc[ obj ] then if type ( obj ) == 'function' then proc[ obj ] = true writefunc ( debug.getinfo ( obj ) ) local i = 1 while true do local uname, uval = debug.getupvalue ( obj, i ) if not uname then break end writeupvalue ( uname, uval ) pushlist ( uval ) i = i + 1 end elseif type ( obj ) == 'table' then proc[ obj ] = true local meta = getmetatable ( obj ) writeobj ( getobjstring ( obj, 'table' ), meta ) for k, v in pairs ( obj ) do writepair ( k, v ) pushlist ( v ) end pushlist ( meta ) elseif type ( obj ) == 'userdata' then proc[ obj ] = true local meta = getmetatable ( obj ) writeobj ( getobjstring ( obj, 'userdata' ), meta ) local status, __pairs = pcall ( checkpairs, obj ) if status and type ( __pairs ) == 'function' then -- userdata object might not have __pairs metamethod and it might misbehave for k, v in pairs ( obj ) do writepair ( k, v ) pushlist ( v ) end end pushlist ( meta ) elseif type ( obj ) == 'cdata' then proc[ obj ] = true writeobj ( getobjstring ( obj, 'cdata' ) ) local status, __pairs = pcall ( checkpairs, obj ) if status and type ( __pairs ) == 'function' then -- cdata object might not have __pairs metamethod and trying to access it will crash the program instantly for k, v in pairs ( obj ) do writepair ( k, v ) pushlist ( v ) end end end end end file:close ( ) return true end --[[ function object: linedefined, lastlinedefined - position in the source code currentline - currently executed line, applicable if the function is on the stack name - name of the function, if available namewhat - what kind of variable the function was stored in (global, method, etc.) source - full name of the source code file where it appears short_src - shortened name of the source code file upvalues - array of upvalue objects: name - name of the upvalue type - type of the upvalue value - value of the upvalue locals - array of local variables, applicable if the function is on the stack: name - name of the variable type - type of the variable value - value of the variable table object: meta - metatable assigned to this table each table object is an array that stores its key-value pairs: keytype - type of the key key - variable used as the key valuetype - type of the value value - variable stored as the value userdata object: meta - metatable assigned to this userdata each userdata object is an array that stores its key-value pairs (if applicable - requires '__pairs'): keytype - type of the key key - variable used as the key valuetype - type of the value value - variable stored as the value cdata object: each cdata object is an array that stores its key-value pairs (if applicable - requires '__pairs'): keytype - type of the key key - variable used as the key valuetype - type of the value value - variable stored as the value memory.memory is a hash table containing all function, table, userdata and cdata objects, using its ID as key, and object as value memory.stack is a list of functions currently on the stack, in order in which they appear ]]-- local function memoryload ( filename ) local file = assert ( io.open ( filename, 'r' ) ) local sections = { ['Stack:'] = 'stack', ['Memory:'] = 'memory' } local memory = { stack = { }, memory = { } } local section, object for l in file:lines ( ) do if sections[ l ] then section = sections[ l ]; goto continue end local class, isfullid, data = string.match ( l, "^(.-):(:?) (.*)$" ) if not ( class and data ) then goto continue end isfullid = isfullid == ':' -- flag indicating that the ID extracted from 'data' is the entire ID and it needs not be merged with 'class' do local c_id = string.match ( class, "^cdata(<.+>)$") if c_id then class, data = 'cdata', string.format ( "%s: %s", c_id, data ) end end if class == 'function' then local id, linedefined, lastlinedefined, currentline, name, namewhat, source, short_src = data:match ( "^(.-)\t(%-?%d+)\t(%-?%d+)\t(%-?%d+)\t(.-)\t(.-)\t(.-)\t(.-)$" ) if not ( id and linedefined and lastlinedefined and currentline and name and namewhat and source and short_src ) then goto continue end object = string.format ( "function: %s", id ) memory.memory[ object ] = { id = object, type = 'function', linedefined = linedefined, lastlinedefined = lastlinedefined, currentline = currentline, name = name, namewhat = namewhat, source = source, short_src = short_src, upvalues = { }, locals = { } } if section == 'stack' then memory.stack[ #memory.stack + 1 ] = object end elseif class == 'table' then local id, meta = data:match ( "^(.*)\t%(meta (.*)%)$" ) if not ( id and meta ) then id = data end object = isfullid and id or string.format ( "table: %s", id ) memory.memory[ object ] = { id = object, type = 'table', meta = meta } elseif class == 'userdata' then local id, meta = data:match ( "^(.*)\t%(meta (.*)%)$" ) if not ( id and meta ) then id = data end object = isfullid and id or string.format ( "userdata: %s", id ) memory.memory[ object ] = { id = object, type = 'userdata', meta = meta } elseif class == 'cdata' then local id = data object = isfullid and id or string.format ( 'cdata%s', data ) memory.memory[ object ] = { id = object, type = 'cdata' } elseif class == 'upvalue' then local uname, utype, uvalue = data:match ( "^(.-)\t=%((%l+)%)(.*)$" ) if not ( uname and utype and uvalue ) then goto continue end local func = memory.memory[ object ] if not func then goto continue end func.upvalues[ #func.upvalues + 1 ] = { name = uname, type = utype, value = uvalue } elseif class == 'local' then local lname, ltype, lvalue = data:match ( "^(.-)\t=%((%l+)%)(.*)$" ) if not ( lname and ltype and lvalue ) then goto continue end local func = memory.memory[ object ] if not func then goto continue end func.locals[ #func.locals + 1 ] = { name = lname, type = ltype, value = lvalue } elseif class == 'field' then local ktype, kdata, vtype, vdata = data:match ( "^%((%l+)%)(.-)\t=%((%l+)%)(.-)$" ) if not ( ktype and kdata and vtype and vdata ) then goto continue end local obj = memory.memory[ object ] if not obj then goto continue end obj[ #obj + 1 ] = { keytype = ktype, key = kdata, valuetype = vtype, value = vdata } end ::continue:: end file:close ( ) return memory end return { dump = memorydump, load = memoryload }