You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

671 lines
18 KiB
Python

from __future__ import print_function
import re
import sys
print("Loading Lua Runtime support.", file=sys.stderr)
#http://python3porting.com/differences.html
if sys.version > '3':
xrange = range
# allow to manually reload while developing
objfile = gdb.current_objfile() or gdb.objfiles()[0]
objfile.pretty_printers = []
# gdb.Value to specific type tt
def cast_to_type_pointer(o, tt):
t = gdb.lookup_type(tt)
return o.cast(t.pointer())
# basic types
LUA_TNIL =0
LUA_TBOOLEAN =1
LUA_TLIGHTUSERDATA =2
LUA_TNUMBER =3
LUA_TSTRING =4
LUA_TTABLE =5
LUA_TFUNCTION =6
LUA_TUSERDATA =7
LUA_TTHREAD =8
def makevariant(t, v): return t | (v << 4)
def ctb(t): return t | (1 << 6)
LUA_VFALSE = makevariant(LUA_TBOOLEAN, 0)
LUA_VTRUE = makevariant(LUA_TBOOLEAN, 1)
# test type
def checktype(o, t): return o['tt_']&0x0f == t
def checktag(o, t): return o['tt_'] == t
# input GCObject*
def cast_u(o):
return cast_to_type_pointer(o, "union GCUnion")
# <TValue> -> <int>
def ivalue(o): return o['value_']['i']
# <TValue> -> <float>
def fltvalue(o): return o['value_']['n']
# <TValue> -> <lightuserdata>
def pvalue(o): return o['value_']['p']
# <TValue> -> <TString>
def tsvalue(o): return cast_u(o['value_']['gc'])['ts']
# <TValue> -> <LClosure>
def clLvalue(o): return cast_u(o['value_']['gc'])['cl']['l']
# <TValue> -> <CClosure>
def clCvalue(o): return cast_u(o['value_']['gc'])['cl']['c']
# <TValue> -> <lua_CFunction>
def fvalue(o): return o['value_']['f']
# <TValue> -> <Table>
def hvalue(o): return cast_u(o['value_']['gc'])['h']
# <TValue> -> <boolean>
def bvalue(o): return checktype(o, LUA_VTRUE)
# <TValue> -> <lua_State>
def thvalue(o): return cast_u(o['value_']['gc'])['th']
LUA_VNUMINT = makevariant(LUA_TNUMBER, 0)
LUA_VNUMFLT = makevariant(LUA_TNUMBER, 1)
def ttisnumber(o): return checktype(o, LUA_TNUMBER)
def ttisfloat(o): return checktag(o, LUA_VNUMFLT)
def ttisinteger(o): return checktag(o, LUA_VNUMINT)
def ttisnil(o): return checktype(o, LUA_TNIL)
def ttisboolean(o): return checktype(o, LUA_TBOOLEAN)
LUA_VLIGHTUSERDATA = makevariant(LUA_TLIGHTUSERDATA, 0)
LUA_VUSERDATA = makevariant(LUA_TUSERDATA, 0)
def ttislightuserdata(o): return checktag(o, LUA_VLIGHTUSERDATA)
def ttisfulluserdata(o): return checktag(o, ctb(LUA_VUSERDATA))
LUA_VSHRSTR = makevariant(LUA_TSTRING, 0)
LUA_VLNGSTR = makevariant(LUA_TSTRING, 1)
def ttisstring(o): return checktype(o, LUA_TSTRING)
def ttisshrstring(o): return checktype(o, ctb(LUA_VSHRSTR))
def ttislngstring(o): return checktype(o, ctb(LUA_VLNGSTR))
LUA_VTABLE = makevariant(LUA_TTABLE, 0)
def ttistable(o): return checktag(o, ctb(LUA_VTABLE))
LUA_VLCL = makevariant(LUA_TFUNCTION, 0)
LUA_VLCF = makevariant(LUA_TFUNCTION, 1)
LUA_VCCL = makevariant(LUA_TFUNCTION, 2)
def ttisfunction(o): return checktype(o, LUA_TFUNCTION)
def ttisclosure(o): return o['tt_'] & 0x1f == LUA_VLCL
def ttisCclosure(o): return checktag(o, ctb(LUA_VCCL))
def ttisLclosure(o): return checktag(o, ctb(LUA_VLCL))
def ttislcf(o): return checktag(o, LUA_VLCF)
LUA_VTHREAD = makevariant(LUA_TTHREAD, 0)
def ttisthread(o): return checktag(o, ctb(LUA_VTHREAD))
# gdb.Value to string
def value_to_string(val):
s = str(val.dereference())
if len(s) > 1 and s[0] == '"' and s[-1] == '"':
return s[1:-1]
return s
def cast_luaState(o):
return cast_to_type_pointer(o, "struct lua_State")
#
# Value wrappers
#
# StackValue to TValue
def s2v(stk): return stk['val']
# struct lua_TValue
class TValueValue:
"Wrapper for TValue value."
def __init__(self, val):
self.val = val
def upvars(self):
if ttisCclosure(self.val):
f = clCvalue(self.val)
for i in xrange(f['nupvalues']):
yield "(%d)" % (i+1), cast_to_type_pointer(f['upvalue'], "TValue") + i
elif ttisLclosure(self.val):
f = clLvalue(self.val)
proto = f['p']
for i in xrange(int(proto['sizeupvalues'])):
uv = cast_to_type_pointer(f['upvals'][i], "struct UpVal")
value = uv['v']
name = (proto['upvalues'] + i)['name']
if name:
yield value_to_string(name), value
else:
yield "(no name)", value
# struct CallInfo
class CallInfoValue:
"Wrapper for CallInfo value."
CIST_C = 1<<1
CIST_TAIL = 1<<5
CIST_FIN = 1<<7
def __init__(self, L, ci):
self.L = L
self.ci = ci
self.name = None
self.namewhat = None
if self.is_lua():
proto = clLvalue(s2v(self.ci['func']))['p']
self.proto = proto
if not proto['source']:
self.source = "?"
else:
self.source = proto['source'].dereference()
self.linedefined = proto['linedefined']
self.lastlinedefined = proto['lastlinedefined']
if self.linedefined == 0:
self.what = "main"
else:
self.what = "Lua"
self.currentpc = (self.ci['u']['l']['savedpc'] - proto['code']) - 1
self.currentline = self.getfuncline(proto, self.currentpc)
else:
self.source = "[C]"
self.linedefined = -1
self.lastlinedefined = -1
self.what = "C"
self.currentline = -1
if self.is_fin():
self.name = "__gc"
self.namewhat = "metamethod"
def getfuncline(self, proto, pc):
"""
ldebug.c luaG_getfuncline
"""
if not proto['lineinfo']:
return -1
def getbaseline(proto, pc):
if proto['sizeabslineinfo'] == 0 or pc < proto['abslineinfo'][0]['pc']:
return -1, proto['linedefined']
if pc >= proto['abslineinfo'][proto['sizeabslineinfo']-1]['pc']:
i = proto['sizeabslineinfo']-1
else:
j = proto['sizeabslineinfo']-1
i = 0
while i < j - 1:
m = (j + i) / 2
if pc >= proto['abslineinfo'][m]['pc']:
i = m
else:
j = m
return proto['abslineinfo'][i]['pc'], proto['abslineinfo'][i]['line']
basepc, baseline = getbaseline(proto, pc)
while basepc < pc:
basepc+=1
baseline += proto['lineinfo'][basepc]
return baseline
@property
def funcname(self):
if self.what == "main":
return "main chunk"
if self.namewhat:
return "%s '%s'" % (self.namewhat, self.name)
func = s2v(self.ci['func'])
if ttislcf(func):
return "%s" % fvalue(func)
if ttisCclosure(func):
return "%s" % clCvalue(func)['f']
return '?'
def is_lua(self):
return not (self.ci['callstatus'] & CallInfoValue.CIST_C)
def is_tailcall(self):
return self.ci['callstatus'] & CallInfoValue.CIST_TAIL
def is_fin(self):
return self.ci['callstatus'] & CallInfoValue.CIST_FIN
# stack frame information
def frame_info(self):
return '%s:%d: in %s' % (self.source, self.currentline, self.funcname)
# luastack:
# vararg(1)
# ...
# vararg(nextraargs) <- ci->u.l.nextraargs nextra vararg
# callee <- ci->func
# arg(1)
# ...
# arg(n)
# local(1)
# ...
# local(n)
@property
def stack_base(self):
return self.ci['func'] + 1
@property
def stack_top(self):
if self.ci == self.L['ci']:
return self.L['top']
else:
nextcv = CallInfoValue(self.L, self.ci['next'])
return nextcv.stack_base - 1
def getlocalname(self, n):
if not self.is_lua():
return None
proto = self.proto
currentpc = self.currentpc
i = 0
while i< proto['sizelocvars']:
locvar = proto['locvars'] + i
if locvar['startpc'] <= currentpc and currentpc < locvar['endpc']:
n = n - 1
if n == 0:
return value_to_string(locvar['varname'])
i = i + 1
return None
def upvars(self):
tv = TValueValue(s2v(self.ci['func']))
return tv.upvars()
def varargs(self):
if not self.is_lua():
return
if self.proto['is_vararg'] != 1:
return
nextra = self.ci['u']['l']['nextraargs']
for i in xrange(nextra):
yield "(*vararg)", s2v(self.ci['func'] - (i+1)).address
def locvars(self):
base = self.stack_base
limit = self.stack_top
i = 1
while True:
name = self.getlocalname(i)
if not name:
if (limit - base) >= i:
if self.is_lua():
name = "(temporary)"
else:
name = "(C temporary)"
else:
return
yield name, s2v(base + i - 1).address
i = i + 1
#
# Pretty Printers
#
def tvaluestring(value):
if ttisnil(value): # nil
return "nil"
elif ttisboolean(value): # boolean
if bvalue(value) > 0:
return "True"
else:
return "False"
elif ttisnumber(value): # number
if ttisfloat(value):
return fltvalue(value)
elif ttisinteger(value):
return ivalue(value)
elif ttisstring(value): # string
return tsvalue(value)
elif ttistable(value): # table
return hvalue(value)
elif ttisfunction(value):
if ttisLclosure(value): # lua closure
return clLvalue(value)
elif ttislcf(value): # light C function
return fvalue(value)
elif ttisCclosure(value): # 2 C closure
return clCvalue(value)
elif ttisfulluserdata(value):
return "Userdata"
elif ttislightuserdata(value): # lightuserdata
return "<lightuserdata 0x%x>" % int(pvalue(value))
elif ttisthread(value):
return thvalue(value)
assert False, value['tt_']
class TValuePrinter:
"Pretty print lua value."
pattern = re.compile(r'^(struct TValue)|(TValue)$')
def __init__(self, val):
self.val = val
def to_string(self):
return tvaluestring(self.val)
def display_hint(self):
return "string"
class TStringPrinter:
"Pretty print lua string."
pattern = re.compile(r'^(struct TString)|(TString)$')
def __init__(self, val):
self.val = val
def display_hint(self):
return "string"
def to_string(self):
s = self.val["contents"]
return s.cast(gdb.lookup_type('char').pointer())
class TablePrinter:
"Pretty print lua table."
pattern = re.compile(r'^(struct Table)|(Table)$')
marked = None
def __init__(self, val):
self.val = val
def display_hint(self):
return "map"
def to_string(self):
return "<table 0x%x>" % int(self.val.address)
def children(self):
setMarked = False
if TablePrinter.marked == None:
TablePrinter.marked = {}
setMarked = True
address = int(self.val.address)
if address in TablePrinter.marked:
return TablePrinter.marked[address]
TablePrinter.marked[address] = self.to_string()
# array part
sizearray = self.realasize()
i = 0
while i < sizearray:
val = self.val['array'][i]
if ttisnil(val):
continue
yield str(2*i), i
yield str(2*i + 1), val
i = i + 1
# hash part
j = 0
last = 1 << self.val['lsizenode']
while j < last:
node = self.val['node'][j]
j = j + 1
value = node['i_val']
if ttisnil(value):
continue
fakeTValue = {
"tt_": node['u']['key_tt'],
"value_": node['u']['key_val']
}
yield str(2*i + 2*j), tvaluestring(fakeTValue)
yield str(2*i + 2*j + 1), value
if setMarked:
TablePrinter.marked = None
def realasize(self):
def isrealasize(self): return (self.val['flags'] & (1<<7)) == 0
def ispow2(x): return (((x) & ((x) - 1)) == 0)
if (isrealasize(self) or ispow2(self.val['alimit'])):
return self.val['alimit']
else:
size = self.val['alimit']
size |= (size >> 1)
size |= (size >> 2)
size |= (size >> 4)
size |= (size >> 8)
size |= (size >> 16)
size += 1
return size
class LClosurePrinter:
"Pretty print lua closure."
pattern = re.compile(r'^(struct LClosure)|(LClosure)$')
def __init__(self, val):
self.val = val
def display_hint(self):
return "map"
def to_string(self):
return "<lclosure 0x%x>" % int(self.val.address)
def children(self):
p = self.val['p']
yield "1", "file"
yield "2", p['source'].dereference()
yield "3", "linestart"
yield "4", p['linedefined']
yield "5", "lineend"
yield "6", p['lastlinedefined']
yield "7", "nupvalues"
yield "8", self.val['nupvalues']
class CClosurePrinter:
"Pretty print lua closure."
pattern = re.compile(r'^(struct CClosure)|(CClosure)$')
def __init__(self, val):
self.val = val
def display_hint(self):
return "map"
def to_string(self):
return "<cclosure 0x%x>" % int(self.val.address)
def children(self):
yield "1", "nupvalues"
yield "2", self.val['nupvalues']
class LuaStatePrinter:
"Pretty print lua_State."
pattern = re.compile(r'^struct lua_State$')
def __init__(self, val):
self.val = val
def display_hint(self):
return "map"
def to_string(self):
return "<coroutine 0x%x>" % int(self.val.address)
def children(self):
cv = CallInfoValue(self.val, self.val['ci'])
yield "1", "source"
yield "2", "%s:%d" % (cv.source, cv.currentline)
yield "3", "func"
yield "4", cv.funcname
#
# Register all the *Printer classes above.
#
def makematcher(klass):
def matcher(val):
try:
if klass.pattern.match(str(val.type)):
return klass(val)
except Exception:
pass
return matcher
objfile.pretty_printers.extend([makematcher(var) for var in vars().values() if hasattr(var, 'pattern')])
class LuaStackCmd(gdb.Command):
"""luastack [L]
Prints values on the Lua C stack. Without arguments, uses the current value of "L"
as the lua_State*. You can provide an alternate lua_State as the first argument."""
def __init__(self):
gdb.Command.__init__(self, "luastack", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, args, _from_tty):
argv = gdb.string_to_argv(args)
if len(argv) > 0:
L = cast_luaState(gdb.parse_and_eval(argv[0]))
else:
L = gdb.parse_and_eval("L")
stack = L['top'] - 1
i = 0
while stack > L['stack']:
print("#%d\t0x%x\t%s" % (i, int(stack), stack.dereference()))
stack = stack - 1
i = i + 1
class LuaTracebackCmd(gdb.Command):
"""luabacktrace [L]
Dumps Lua execution stack, as debug.traceback() does. Without
arguments, uses the current value of "L" as the
lua_State*. You can provide an alternate lua_State as the
first argument.
"""
def __init__(self):
gdb.Command.__init__(self, "luatraceback", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, args, _from_tty):
argv = gdb.string_to_argv(args)
if len(argv) > 0:
L = cast_luaState(gdb.parse_and_eval(argv[0]))
else:
L = gdb.parse_and_eval("L")
ci = L['ci']
print("stack traceback:")
while ci != L['base_ci'].address:
cv = CallInfoValue(L, ci)
print('\t%s' % (cv.frame_info()))
if cv.is_tailcall():
print('\t(...tail calls...)')
ci = ci['previous']
class LuaCoroutinesCmd(gdb.Command):
"""luacoroutines [L]
List all coroutines. Without arguments, uses the current value of "L" as the
lua_State*. You can provide an alternate lua_State as the
first argument.
"""
def __init__(self):
gdb.Command.__init__(self, "luacoroutines", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, args, _from_tty):
argv = gdb.string_to_argv(args)
if len(argv) > 0:
L = cast_luaState(gdb.parse_and_eval(argv[0]))
else:
L = gdb.parse_and_eval("L")
# global_State
lG = L['l_G']
# mainthread
print("m", lG['mainthread'].dereference())
obj = lG['allgc']
while obj:
if obj['tt'] == 8:
print(" ", cast_u(obj)['th'])
obj = obj['next']
class LuaGetLocalCmd(gdb.Command):
"""luagetlocal [L [f]]
Print all local variables of the function at level 'f' of the stack 'thread'.
With no arguments, Dump all local variable of the current funtion in the stack of 'L';
"""
def __init__(self):
gdb.Command.__init__(self, "luagetlocal", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, args, _from_tty):
argv = gdb.string_to_argv(args)
if len(argv) > 0:
L = cast_luaState(gdb.parse_and_eval(argv[0]))
else:
L = gdb.parse_and_eval("L")
if len(argv) > 1:
arg2 = gdb.parse_and_eval(argv[1])
else:
arg2 = gdb.parse_and_eval("0")
level = arg2
ci = L['ci']
while level > 0:
ci = ci['previous']
if ci == L['base_ci'].address:
break
level = level - 1
if level != 0:
print("No function at level %d" % arg2)
return
cv = CallInfoValue(L, ci)
print("call info: %s" % cv.frame_info())
for name, var in cv.upvars():
print("\tupval %s = %s" % (name, var.dereference()))
for name, var in cv.varargs():
print("\t..... %s = %s" % (name, var.dereference()))
for name, var in cv.locvars():
print("\tlocal %s = %s" % (name, var.dereference()))
LuaStackCmd()
LuaTracebackCmd()
LuaCoroutinesCmd()
LuaGetLocalCmd()