Last active
August 29, 2015 14:09
-
-
Save mniip/70497b2a39b486a120e6 to your computer and use it in GitHub Desktop.
where.lua - traverses a lua state and finds a path to a given object or hex address.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local _G, assert, newproxy, pairs, tostring, type = _G, assert, newproxy, pairs, tostring, type | |
local coroutine_create = coroutine.create | |
local debug_getfenv, debug_gethook, debug_getinfo, debug_getlocal, debug_getmetatable, debug_getregistry, debug_getupvalue, debug_upvalueid, debug_getuservalue = debug.getfenv, debug.gethook, debug.getinfo, debug.getlocal, debug.getmetatable, debug.getregistry, debug.getupvalue, debug.upvalueid, debug.getuservalue | |
local string_byte, string_format, string_gsub, string_match = string.byte, string.format, string.gsub, string.match | |
local where = {} | |
-- Auxiliary functions for finding the n'th key and n'th value in a table. Note | |
-- that the index may be invalidated even if the table is not modified, for | |
-- example if the table is weak | |
function where.nthkey(t, n) | |
for k in pairs(t) do | |
n = n - 1 | |
if n == 0 then | |
return k | |
end | |
end | |
end | |
function where.nthvalue(t, n) | |
for _, v in pairs(t) do | |
n = n - 1 | |
if n == 0 then | |
return v | |
end | |
end | |
end | |
-- Check equality of 2 objects or an object and its address | |
local function equal(a, b) | |
if type(a) == "string" then | |
return string_match(tostring(b), "%S*$") == string_match(a, "%S*$") | |
else | |
return b == a | |
end | |
end | |
local seen | |
local searchobject | |
-- Traverse a table object | |
local function searchtable(v, obj, name) | |
local n = 0 | |
for k, val in pairs(obj) do | |
n = n + 1 | |
-- Try the key | |
local path = searchobject(v, k, "where.nthkey(" .. name .."," .. n .. ")") | |
if path then return path end | |
-- Construct a nice-looking index name | |
local newname = "where.nthvalue(" .. name .. "," .. n ..")" | |
if type(k) == "string" then | |
if string_match(k, "^[a-zA-Z_][a-zA-Z0-9_]*$") then | |
newname = name .. "." .. k | |
else | |
k = string_gsub(k, "[\a\b\f\n\r\t\\\"]", {["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ["\\"] = "\\\\", ["\""] = "\\\""}) | |
k = string_gsub(k, "[^ -~]", function(x) return string_format("\\%03d", string_byte(x)) end) | |
newname = name .. "[\"" .. k .. "\"]" | |
end | |
elseif type(k) == "number" or type(k) == "boolean" then | |
newname = name .. "[" .. tostring(k) .. "]" | |
end | |
-- Try the value | |
local path = searchobject(v, val, newname) | |
if path then return path end | |
end | |
-- Try the metatable | |
local mt = debug_getmetatable(obj) | |
if mt then | |
return searchobject(v, mt, "debug.getmetatable(" .. name .. ")") | |
end | |
end | |
-- Traverse a function object | |
local function searchfunction(v, obj, name) | |
local n = 0 | |
while true do | |
n = n + 1 | |
local k, val = debug_getupvalue(obj, n) | |
if not k then break end | |
local path = searchobject(v, val, "select(2,debug.getupvalue(" .. name .. "," .. n .. "))") | |
if path then return path end | |
if debug_upvalueid then | |
local path = searchobject(v, debug_upvalueid(obj, n), "debug.upvalueid(" .. name .. "," .. n .. ")") | |
if path then return path end | |
end | |
end | |
if debug_getfenv then | |
return searchobject(v, debug_getfenv(obj), "debug.getfenv(" .. name ..")") | |
end | |
end | |
-- Traverse a userdata object | |
local function searchuserdata(v, obj, name) | |
if debug_getuservalue then | |
local path = searchobject(v, debug_getuservalue(obj), "debug.getuservalue(" .. name .. ")") | |
if path then return path end | |
end | |
local mt = debug_getmetatable(obj) | |
if mt then | |
return searchobject(v, mt, "debug.getmetatable(" .. name .. ")") | |
end | |
end | |
-- Traverse a coroutine object or the main stack | |
local function searchstack(v, obj, name, stack_unwind) | |
local path = searchobject(v, debug_gethook(obj), "debug.gethook(" .. (name or "") .. ")") | |
if path then return path end | |
local level = name and 0 or 3 | |
while true do | |
if name then | |
if not debug_getinfo(obj, level) then break end | |
else | |
if not debug_getinfo(level) then break end | |
end | |
local n = 0 | |
while true do | |
n = n + 1 | |
local k, val | |
if name then | |
k, val = debug_getlocal(obj, level, n) | |
else | |
k, val = debug_getlocal(level, n) | |
end | |
if not k then break end | |
local path = searchobject(v, val, "select(2,debug.getlocal(" .. (name and name .. "," or "") .. (name and level or level - 3 + stack_unwind) .. "," .. n .. "))") | |
if path then return path end | |
end | |
if name then | |
local path = searchobject(v, debug_getinfo(obj, level, "f").func , "debug.getinfo(" .. name .. "," .. level .. ",\"f\").func") | |
if path then return path end | |
else | |
local path = searchobject(v, debug_getinfo(level, "f").func , "debug.getinfo(" .. level .. ",\"f\").func") | |
if path then return path end | |
end | |
level = level + 1 | |
end | |
end | |
searchobject = function(v, obj, name) | |
if type(obj) == "table" or type(obj) == "function" or type(obj) == "userdata" or type(obj) == "thread" then | |
if equal(v, obj) then | |
return name | |
end | |
if not seen[obj] then | |
seen[obj] = true | |
if type(obj) == "table" then | |
return searchtable(v, obj, name) | |
elseif type(obj) == "function" then | |
return searchfunction(v, obj, name) | |
elseif type(obj) == "userdata" then | |
return searchuserdata(v, obj, name) | |
elseif type(obj) == "thread" then | |
return searchstack(v, obj, name) | |
end | |
end | |
end | |
end | |
-- Find a lua-like code representing a way to obtain the object v, or the | |
-- object with the address v. In case stack backreferences (debug.getlocal) have | |
-- to be used, stack_unwind adjusts the level. Default is 1 and corresponds to | |
-- debug.getlocal being called at the same level as where.where | |
function where.where(v, stack_unwind) | |
assert(type(v) == "string" or type(v) == "table" or type(v) == "function" or type(v) == "thread" or type(v) == "userdata", "Bad argument #1 to 'where.where' (table, function, thread, userdata, or address string expected, got " .. type(v) .. ")") | |
stack_unwind = stack_unwind or 1 | |
assert(type(stack_unwind) == "number", "Bad argument #2 to 'where.where' (number expected, got " .. type(stack_unwind) .. ")") | |
seen = {} | |
-- Start by looking in the global table | |
local path = searchobject(v, _G, "_G") | |
if path then return path end | |
-- Then check the main call stack | |
path = searchstack(v, nil, nil, stack_unwind) | |
if path then return path end | |
-- The registry | |
path = searchobject(v, debug_getregistry(), "debug.getregistry()") | |
if path then return path end | |
-- Metatables of built-in types | |
local mt = debug_getmetatable(false) | |
if mt then | |
path = searchobject(v, mt, "debug.getmetatable(false)") | |
if path then return path end | |
end | |
local mt = debug_getmetatable(0) | |
if mt then | |
path = searchobject(v, mt, "debug.getmetatable(0)") | |
if path then return path end | |
end | |
local mt = debug_getmetatable"" | |
if mt then | |
path = searchobject(v, mt, "debug.getmetatable\"\"") | |
if path then return path end | |
end | |
local mt = debug_getmetatable(function()end) | |
if mt then | |
path = searchobject(v, mt, "debug.getmetatable(function()end)") | |
if path then return path end | |
end | |
local mt = debug_getmetatable(coroutine_create(function()end)) | |
if mt then | |
path = searchobject(v, mt, "debug.getmetatable(coroutine.create(function()end))") | |
if path then return path end | |
end | |
return nil | |
end | |
return where |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment