diff --git a/src/luajit_lldb.py b/src/luajit_lldb.py index 5ac11b651f..03dd09f48c 100644 --- a/src/luajit_lldb.py +++ b/src/luajit_lldb.py @@ -10,19 +10,33 @@ LJ_GC64 = None LJ_FR2 = None LJ_DUALNUM = None -PADDING = None +LJ_TISNUM = None # Constants IRT_P64 = 9 -LJ_GCVMASK = ((1 << 47) - 1) -LJ_TISNUM = None +LJ_GCVMASK = (1 << 47) - 1 +NO_BCPOS = ~0 +PROTO_VARARG = 0x02 +FF_LUA = 0 +FF_C = 1 +VARNAMES = [ + '(for index)', + '(for limit)', + '(for step)', + '(for generator)', + '(for state)', + '(for control)', +] +VARNAME_END = 0 +VARNAME__MAX = len(VARNAMES) + 1 # Debugger specific {{{ - # Global target = None - +luajit_module = None +PADDING = None +is_tarantool = False class Ptr: def __init__(self, value, normal_type): @@ -43,15 +57,17 @@ def __add__(self, other): self.value.unsigned + other * self.value.deref.size, ), ), - ) + ) # pyright: ignore[reportCallIssue] def __sub__(self, other): assert isinstance(other, int) or isinstance(other, Ptr) if isinstance(other, int): return self.__add__(-other) else: - return int((self.value.unsigned - other.value.unsigned) - / sizeof(self.normal_type.__name__)) + return int( + (self.value.unsigned - other.value.unsigned) + / sizeof(self.normal_type.__name__) + ) def __eq__(self, other): assert isinstance(other, Ptr) or isinstance(other, int) and other >= 0 @@ -78,6 +94,9 @@ def __int__(self): return self.value.unsigned def __str__(self): + if not self.value: + return 'NULL' + return self.value.value def __getattr__(self, name): @@ -92,17 +111,17 @@ def __init__(cls, name, bases, nmspc): def make_general(field, tp): builtin = { - 'uint': 'unsigned', - 'int': 'signed', - 'string': 'value', - } + 'uint': 'unsigned', + 'int': 'signed', + 'string': 'value', + } if tp in builtin.keys(): return lambda self: getattr(self[field], builtin[tp]) else: return lambda self: globals()[tp](self[field]) if hasattr(cls, 'metainfo'): - for field in cls.metainfo: + for field in cls.metainfo: # pyright: ignore[reportAttributeAccessIssue] if not isinstance(field[0], str): setattr(cls, field[1], field[0]) else: @@ -127,12 +146,16 @@ def addr(self): c_structs = { 'MRef': [ - (property(lambda self: self['ptr64'].unsigned if LJ_GC64 - else self['ptr32'].unsigned), 'ptr') + ( + property(lambda self: self['ptr64' if LJ_GC64 else 'ptr32'].unsigned), + 'ptr', + ) ], 'GCRef': [ - (property(lambda self: self['gcptr64'].unsigned if LJ_GC64 - else self['gcptr32'].unsigned), 'gcptr') + ( + property(lambda self: self['gcptr64' if LJ_GC64 else 'gcptr32'].unsigned), + 'gcptr', + ) ], 'TValue': [ ('GCRef', 'gcr'), @@ -141,8 +164,7 @@ def addr(self): ('int', 'it64'), ('string', 'n'), (property(lambda self: FR(self['fr']) if not LJ_GC64 else None), 'fr'), - (property(lambda self: self['ftsz'].signed if LJ_GC64 else None), - 'ftsz') + (property(lambda self: self['ftsz'].signed if LJ_GC64 else None), 'ftsz'), ], 'GCState': [ ('GCRef', 'root'), @@ -157,80 +179,67 @@ def addr(self): ('uint', 'estimate'), ('uint', 'stepmul'), ('uint', 'pause'), - ('uint', 'sweepstr') + ('uint', 'sweepstr'), ], 'lua_State': [ + ('uint', 'status'), ('MRef', 'glref'), - ('MRef', 'stack'), - ('MRef', 'maxstack'), + ('TValuePtr', 'base'), ('TValuePtr', 'top'), - ('TValuePtr', 'base') + ('MRef', 'maxstack'), + ('MRef', 'stack'), + ('uint', 'stacksize'), ], 'global_State': [ ('GCState', 'gc'), ('uint', 'vmstate'), - ('uint', 'strmask') - ], - 'jit_State': [ - ('uint', 'state') - ], - 'GChead': [ - ('GCRef', 'nextgc') - ], - 'GCobj': [ - ('GChead', 'gch') - ], - 'GCstr': [ - ('uint', 'hash'), - ('uint', 'len') - ], - 'FrameLink': [ - ('MRef', 'pcr'), - ('int', 'ftsz') - ], - 'FR': [ - ('FrameLink', 'tp') - ], - 'GCfuncC': [ - ('MRef', 'pc'), - ('uint', 'ffid'), - ('uint', 'nupvalues'), - ('uint', 'f') + ('uint', 'strmask'), + ('GCRef', 'mainthref'), + ('GCRef', 'cur_L'), ], + 'jit_State': [('uint', 'state')], + 'GChead': [('GCRef', 'nextgc'), ('uint', 'gct')], + 'GCobj': [('GChead', 'gch')], + 'GCstr': [('uint', 'hash'), ('uint', 'len')], + 'FrameLink': [('MRef', 'pcr'), ('int', 'ftsz')], + 'FR': [('FrameLink', 'tp')], + 'GCfuncC': [('MRef', 'pc'), ('uint', 'ffid'), ('uint', 'nupvalues'), ('uint', 'f')], 'GCtab': [ ('MRef', 'array'), ('MRef', 'node'), ('GCRef', 'metatable'), ('uint', 'asize'), - ('uint', 'hmask') + ('uint', 'hmask'), ], 'GCproto': [ ('GCRef', 'chunkname'), - ('int', 'firstline') - ], - 'GCtrace': [ - ('uint', 'traceno') + ('int', 'firstline'), + ('int', 'numline'), + ('int', 'numparams'), + ('int', 'flags'), + ('MRef', 'varinfo'), + ('int', 'sizebc') ], - 'Node': [ - ('TValue', 'key'), - ('TValue', 'val'), - ('MRef', 'next') - ], - 'BCIns': [] + 'GCtrace': [('uint', 'traceno')], + 'Node': [('TValue', 'key'), ('TValue', 'val'), ('MRef', 'next')], + 'BCIns': [], } - for cls in c_structs.keys(): - globals()[cls] = type(cls, (Struct, ), {'metainfo': c_structs[cls]}) - + globals()[cls] = type(cls, (Struct,), {'metainfo': c_structs[cls]}) for cls in Struct.__subclasses__(): ptr_name = cls.__name__ + 'Ptr' - globals()[ptr_name] = type(ptr_name, (Ptr,), { - '__init__': - lambda self, value: super(type(self), self).__init__(value, cls) - }) + globals()[ptr_name] = type( + ptr_name, + (Ptr,), + { + '__init__': lambda self, value, cls=cls: super(type(self), self).__init__( + value, cls + ) + }, + ) class Command(object): @@ -261,7 +270,8 @@ def parse(self, command): ret = frame.EvaluateExpression(command) return ret - @abc.abstractproperty + @property + @abc.abstractmethod def command(self): """Command name. This name will be used by LLDB in order to unique/ly identify an @@ -280,8 +290,8 @@ def execute(self, debugger, args, result): """ -def cast(typename, value): - pointer_type = False +def cast(typename=None, value=None): + levels_of_indirection = 0 name = None if isinstance(value, Struct) or isinstance(value, Ptr): # Get underlying value, if passed object is a wrapper. @@ -291,41 +301,37 @@ def cast(typename, value): if isinstance(typename, type): name = typename.__name__ if name.endswith('Ptr'): - pointer_type = True + levels_of_indirection = 1 name = name[:-3] else: name = typename - if name[-1] == '*': - name = name[:-1].strip() - pointer_type = True + + while name.endswith('*'): + levels_of_indirection += 1 + name = name[:-1] + + name = name.strip() # Get the lldb type representation. - t = target.FindFirstType(name) - if pointer_type: + t = find_type(name) + + for _ in range(levels_of_indirection): t = t.GetPointerType() if isinstance(value, int): - # Integer casts require some black magic for lldb to behave properly. - if pointer_type: - casted = target.CreateValueFromAddress( - 'value', - lldb.SBAddress(value, target), - t.GetPointeeType(), - ).address_of - else: - casted = target.CreateValueFromData( - name='value', - data=lldb.SBData.CreateDataFromInt(value, size=8), - type=t, - ) + casted = target.CreateValueFromData( + name='value', + data=lldb.SBData.CreateDataFromInt(value, size=8), + type=t, + ) else: casted = value.Cast(t) if isinstance(typename, type): # Wrap lldb object, if possible return typename(casted) - else: - return casted + + return casted def lookup_global(name): @@ -337,7 +343,7 @@ def type_member(type_obj, name): def find_type(typename): - return target.FindFirstType(typename) + return luajit_module.FindFirstType(typename) def offsetof(typename, membername): @@ -349,6 +355,7 @@ def offsetof(typename, membername): def sizeof(typename): type_obj = find_type(typename) + assert(type_obj) return type_obj.GetByteSize() @@ -371,13 +378,14 @@ def dbg_eval(expr): def gcval(obj): - return cast(GCobjPtr, cast('uintptr_t', obj.gcptr & LJ_GCVMASK) if LJ_GC64 - else cast('uintptr_t', obj.gcptr)) + return cast( + GCobjPtr, + cast('uintptr_t', obj.gcr.gcptr & LJ_GCVMASK if LJ_GC64 else obj.gcr.gcptr), + ) def gcref(obj): - return cast(GCobjPtr, obj.gcptr if LJ_GC64 - else cast('uintptr_t', obj.gcptr)) + return cast(GCobjPtr, obj.gcptr if LJ_GC64 else cast('uintptr_t', obj.gcptr)) def gcnext(obj): @@ -386,7 +394,7 @@ def gcnext(obj): def gclistlen(root, end=0x0): count = 0 - while (gcref(root) != end): + while gcref(root) != end: count += 1 root = gcnext(root) return count @@ -402,31 +410,34 @@ def gcringlen(root): gclen = { - 'root': gclistlen, - 'gray': gclistlen, + 'root': gclistlen, + 'gray': gclistlen, 'grayagain': gclistlen, - 'weak': gclistlen, + 'weak': gclistlen, # XXX: gc.mmudata is a ring-list. - 'mmudata': gcringlen, + 'mmudata': gcringlen, } def dump_gc(g): gc = g.gc - stats = ['{key}: {value}'.format(key=f, value=getattr(gc, f)) for f in ( - 'total', 'threshold', 'debt', 'estimate', 'stepmul', 'pause' - )] - - stats += ['sweepstr: {sweepstr}/{strmask}'.format( - sweepstr=gc.sweepstr, - # String hash mask (size of hash table - 1). - strmask=g.strmask + 1, - )] - - stats += ['{key}: {number} objects'.format( - key=stat, - number=handler(getattr(gc, stat)) - ) for stat, handler in gclen.items()] + stats = [ + '{key}: {value}'.format(key=f, value=getattr(gc, f)) + for f in ('total', 'threshold', 'debt', 'estimate', 'stepmul', 'pause') + ] + + stats += [ + 'sweepstr: {sweepstr}/{strmask}'.format( + sweepstr=gc.sweepstr, + # String hash mask (size of hash table - 1). + strmask=g.strmask + 1, + ) + ] + + stats += [ + '{key}: {number} objects'.format(key=stat, number=handler(getattr(gc, stat))) + for stat, handler in gclen.items() + ] return '\n'.join(map(lambda s: '\t' + s, stats)) @@ -439,7 +450,7 @@ def J(g): J_offset = offsetof('GG_State', 'J') return cast( jit_StatePtr, - vtou64(cast('char *', g)) - g_offset + J_offset, + vtou64(cast('char *', g)) - (g_offset + J_offset), ) @@ -451,17 +462,30 @@ def L(L=None): # lookup a symbol for the main coroutine considering the host app # XXX Fragile: though the loop initialization looks like a crap but it # respects both Python 2 and Python 3. - for lstate in [L] + list(map(lambda main: lookup_global(main), ( - # LuaJIT main coro (see luajit/src/luajit.c) - 'globalL', - # Tarantool main coro (see tarantool/src/lua/init.h) - 'tarantool_L', - # TODO: Add more - ))): + for lstate in [L] + list( + map( + lambda main: lookup_global(main), + ( + # LuaJIT main coro (see luajit/src/luajit.c) + 'globalL', + # Tarantool main coro (see tarantool/src/lua/init.h) + 'tarantool_L', + # TODO: Add more + ), + ) + ): if lstate: return lua_State(lstate) +def mainthread(g): + return gcref(g.mainthref) + + +def cur_L(g): + return gcref(g.cur_L) + + def tou32(val): return val & 0xFFFFFFFF @@ -471,16 +495,27 @@ def i2notu32(val): def vm_state(g): + if is_tarantool: + return { + i2notu32(0): 'INTERP', + i2notu32(1): 'LFUNC', + i2notu32(2): 'FFUNC', + i2notu32(3): 'CFUNC', + i2notu32(4): 'GC', + i2notu32(5): 'EXIT', + i2notu32(6): 'RECORD', + i2notu32(7): 'OPT', + i2notu32(8): 'ASM', + }.get(int(tou32(g.vmstate)), 'TRACE') + return { i2notu32(0): 'INTERP', - i2notu32(1): 'LFUNC', - i2notu32(2): 'FFUNC', - i2notu32(3): 'CFUNC', - i2notu32(4): 'GC', - i2notu32(5): 'EXIT', - i2notu32(6): 'RECORD', - i2notu32(7): 'OPT', - i2notu32(8): 'ASM', + i2notu32(1): 'CFUNC', + i2notu32(2): 'GC', + i2notu32(3): 'EXIT', + i2notu32(4): 'RECORD', + i2notu32(5): 'OPT', + i2notu32(6): 'ASM', }.get(int(tou32(g.vmstate)), 'TRACE') @@ -498,7 +533,7 @@ def gc_state(g): def jit_state(g): return { - 0: 'IDLE', + 0: 'IDLE', 0x10: 'ACTIVE', 0x11: 'RECORD', 0x12: 'START', @@ -509,12 +544,23 @@ def jit_state(g): def strx64(val): - return re.sub('L?$', '', - hex(int(val) & 0xFFFFFFFFFFFFFFFF)) + return re.sub('L?$', '', hex(int(val) & 0xFFFFFFFFFFFFFFFF)) + + +def isluafunc(func): + return func.ffid == FF_LUA + + +def iscfunc(func): + return func.ffid == FF_C + + +def isffunc(func): + return func.ffid > FF_C def funcproto(func): - assert func.ffid == 0 + assert isluafunc(func) proto_size = sizeof('GCproto') value = cast('uintptr_t', vtou64(mref('char *', func.pc)) - proto_size) return cast(GCprotoPtr, value) @@ -523,6 +569,7 @@ def funcproto(func): def strdata(obj): try: ptr = cast('char *', obj + 1) + return ptr.summary except UnicodeEncodeError: return "" @@ -560,60 +607,194 @@ def dump_lj_ttrue(tv): def dump_lj_tlightud(tv): - return 'light userdata @ {}'.format(strx64(gcval(tv.gcr))) + return 'light userdata @ {}'.format(strx64(gcval(tv))) -def dump_lj_tstr(tv): +def dump_lj_tstr(o): return 'string {body} @ {address}'.format( - body=strdata(cast(GCstrPtr, gcval(tv.gcr))), - address=strx64(gcval(tv.gcr)) + body=strdata(cast(GCstrPtr, o)), address=strx64(o) ) -def dump_lj_tupval(tv): - return 'upvalue @ {}'.format(strx64(gcval(tv.gcr))) +def dump_lj_tupval(o): + return 'upvalue @ {}'.format(strx64(o)) -def dump_lj_tthread(tv): - return 'thread @ {}'.format(strx64(gcval(tv.gcr))) +def thread_state(t): + return { + 0: 'OK', + 1: 'YIELD', + 2: 'ERRRUN', + 3: 'ERRSYNTAX', + 4: 'ERRMEM', + 5: 'ERRERR', + 5 + + 1: 'ERRERR+1', # Undocumented status used for `cpluaopen` function that initializes minimal required stuff to run Lua + }.get(t.status, 'INVALID') + + +def dump_lj_tthread(o): + thread = cast(lua_StatePtr, o) + + info = [ + 'main' if thread == mainthread(G(thread)) else None, + 'curL' if thread == cur_L(G(thread)) else None, + thread_state(thread), + ] + + info = ' '.join(item for item in info if item) + + if info: + info += ' ' + + return '{info}thread @ {addr}'.format(info=info, addr=thread) + + +def dump_lj_tproto(gcobj): + proto = cast(GCprotoPtr, gcobj) + + return 'proto {chunkname}:{firstline}-{lastline} @ {addr}'.format( + chunkname=proto_chunkname(proto), + firstline=proto.firstline, + lastline=proto.firstline + proto.numline, + addr=strx64(proto), + ) -def dump_lj_tproto(tv): - return 'proto @ {}'.format(strx64(gcval(tv.gcr))) +def proto_chunkname(pt): + if not pt.chunkname: + return None + return strdata(cast(GCstrPtr, gcref(pt.chunkname))) -def dump_lj_tfunc(tv): - func = cast(GCfuncCPtr, gcval(tv.gcr)) - ffid = func.ffid - if ffid == 0: +def proto_bcpos(pt, pc): + proto_size = sizeof("GCproto") + + return pc - cast(BCInsPtr, pt.value.unsigned + proto_size) + +def dump_lj_tfunc(o): + func = cast(GCfuncCPtr, o) + + if isluafunc(func): pt = funcproto(func) - return 'Lua function @ {addr}, {nups} upvalues, {chunk}:{line}'.format( + return 'Lua function @ {addr}, {nparams} params, {vararg}{nups} upvalues, {chunk}:{line}'.format( addr=strx64(func), + nparams=pt.numparams, + vararg='vararg, ' if pt.flags & PROTO_VARARG else '', nups=func.nupvalues, - chunk=strdata(cast(GCstrPtr, gcval(pt.chunkname))), - line=pt.firstline + chunk=proto_chunkname(pt), + line=pt.firstline, + ) + elif iscfunc(func): + return 'C function @ {} ({})'.format( + strx64(func.f), target.ResolveLoadAddress(func.f) + ) + else: + return 'fast function #{} ({})'.format( + func.ffid, target.ResolveLoadAddress(func.f).function.name + ) + + +def debug_framepc(L, func, nextframe): + return dbg_eval( + f'(BCPos)debug_framepc((lua_State*){L.value.value}, (GCfunc*){func}, (cTValue*){nextframe or 0})' + ).signed + + +def debug_frameline(L, func, nextframe): + pc = debug_framepc(L, func, nextframe) + + if pc != NO_BCPOS: + pt = funcproto(func) + + return dbg_eval(f'(BCLine)lj_debug_line((GCproto *){pt}, (BCPos){pc})').signed + + return -1 + + +def lj_debug_funcname(L, frame): + FUNCNAME_VARIABLE_NAME = '$lj_debug_funcname_name' + str(target.GetProcess().id) # Must be unique in case we restart the debugger + + name = dbg_eval(f'{FUNCNAME_VARIABLE_NAME} = NULL; &{FUNCNAME_VARIABLE_NAME}') + + if not name.value: + name = dbg_eval( + f'const char* {FUNCNAME_VARIABLE_NAME} = NULL; &{FUNCNAME_VARIABLE_NAME}' + ) + + try: + namewhat = dbg_eval( + f'(const char *)lj_debug_funcname((lua_State *){L.value.value}, (cTValue *){frame}, &{FUNCNAME_VARIABLE_NAME})' ) - elif ffid == 1: - return 'C function @ {}'.format(strx64(func.f)) + + if namewhat.unsigned == 0: + return None, None + + return namewhat.summary, name.Dereference().summary + except UnicodeEncodeError: + error = '' + return error, error + except: + return None, None + + +def lj_debug_getinfo(L, tv, frame, nextframe): + func = cast(GCfuncCPtr, gcval(tv)) + linedefined = -1 + + if isluafunc(func): + pt = funcproto(func) + + source = proto_chunkname(pt) + if not source: + return None + + what = 'Lua' if (pt.firstline or not pt.numline) else 'm' + linedefined = pt.firstline + else: + source = '[C]' + what = 'C' + + currentline = debug_frameline(L, func, nextframe) + namewhat, name = lj_debug_funcname(L, frame) + + result = '' + + if isffunc(func) and not namewhat: + result += f'[builtin#{func.ffid}]:' else: - return 'fast function #{}'.format(ffid) + result += f'{source}:' + + if currentline > 0: + result += f'{currentline}:' + + if namewhat: + result += f' in function {name}' + else: + if what == 'm': + result += ' in main chunk' + elif what == 'C': + return '' # Not useful + else: + result += f' in function <{source}:{linedefined}>' + + return result def dump_lj_ttrace(tv): - trace = cast(GCtracePtr, gcval(tv.gcr)) + trace = cast(GCtracePtr, gcval(tv)) return 'trace {traceno} @ {addr}'.format( - traceno=strx64(trace.traceno), - addr=strx64(trace) + traceno=strx64(trace.traceno), addr=strx64(trace) ) -def dump_lj_tcdata(tv): - return 'cdata @ {}'.format(strx64(gcval(tv.gcr))) +def dump_lj_tcdata(o): + return 'cdata @ {}'.format(strx64(o)) -def dump_lj_ttab(tv): - table = cast(GCtabPtr, gcval(tv.gcr)) +def dump_lj_ttab(o): + table = cast(GCtabPtr, o) return 'table @ {gcr} (asize: {asize}, hmask: {hmask})'.format( gcr=strx64(table), asize=table.asize, @@ -621,8 +802,8 @@ def dump_lj_ttab(tv): ) -def dump_lj_tudata(tv): - return 'userdata @ {}'.format(strx64(gcval(tv.gcr))) +def dump_lj_tudata(o): + return 'userdata @ {}'.format(strx64(o)) def dump_lj_tnumx(tv): @@ -632,62 +813,81 @@ def dump_lj_tnumx(tv): return 'number {}'.format(tv.n) -def dump_lj_invalid(tv): - return 'not valid type @ {}'.format(strx64(gcval(tv.gcr))) +def dump_lj_invalid_gcobj(o): + return 'not valid type in GCobj @ {}'.format(strx64(o)) -dumpers = { - 'LJ_TNIL': dump_lj_tnil, - 'LJ_TFALSE': dump_lj_tfalse, - 'LJ_TTRUE': dump_lj_ttrue, - 'LJ_TLIGHTUD': dump_lj_tlightud, - 'LJ_TSTR': dump_lj_tstr, - 'LJ_TUPVAL': dump_lj_tupval, - 'LJ_TTHREAD': dump_lj_tthread, - 'LJ_TPROTO': dump_lj_tproto, - 'LJ_TFUNC': dump_lj_tfunc, - 'LJ_TTRACE': dump_lj_ttrace, - 'LJ_TCDATA': dump_lj_tcdata, - 'LJ_TTAB': dump_lj_ttab, - 'LJ_TUDATA': dump_lj_tudata, - 'LJ_TNUMX': dump_lj_tnumx, +def dump_lj_invalid_tvalue(tv): + return 'not valid type in TValue\'s GCobj @ {}'.format(strx64(gcval(tv))) + + +gcobj_dumpers = { + 'LJ_TSTR': dump_lj_tstr, + 'LJ_TUPVAL': dump_lj_tupval, + 'LJ_TTHREAD': dump_lj_tthread, + 'LJ_TPROTO': dump_lj_tproto, + 'LJ_TFUNC': dump_lj_tfunc, + 'LJ_TCDATA': dump_lj_tcdata, + 'LJ_TTAB': dump_lj_ttab, + 'LJ_TUDATA': dump_lj_tudata, } +tvalue_dumpers = { + 'LJ_TNIL': dump_lj_tnil, + 'LJ_TFALSE': dump_lj_tfalse, + 'LJ_TTRUE': dump_lj_ttrue, + 'LJ_TLIGHTUD': dump_lj_tlightud, + 'LJ_TSTR': lambda tv: dump_lj_tstr(o=gcval(tv)), + 'LJ_TUPVAL': lambda tv: dump_lj_tupval(gcval(tv)), + 'LJ_TTHREAD': lambda tv: dump_lj_tthread(gcval(tv)), + 'LJ_TPROTO': lambda tv: dump_lj_tproto(gcval(tv)), + 'LJ_TFUNC': lambda tv: dump_lj_tfunc(gcval(tv)), + 'LJ_TTRACE': dump_lj_ttrace, + 'LJ_TCDATA': lambda tv: dump_lj_tcdata(gcval(tv)), + 'LJ_TTAB': lambda tv: dump_lj_ttab(gcval(tv)), + 'LJ_TUDATA': lambda tv: dump_lj_tudata(gcval(tv)), + 'LJ_TNUMX': dump_lj_tnumx, +} LJ_T = { - 'NIL': i2notu32(0), - 'FALSE': i2notu32(1), - 'TRUE': i2notu32(2), + 'NIL': i2notu32(0), + 'FALSE': i2notu32(1), + 'TRUE': i2notu32(2), 'LIGHTUD': i2notu32(3), - 'STR': i2notu32(4), - 'UPVAL': i2notu32(5), - 'THREAD': i2notu32(6), - 'PROTO': i2notu32(7), - 'FUNC': i2notu32(8), - 'TRACE': i2notu32(9), - 'CDATA': i2notu32(10), - 'TAB': i2notu32(11), - 'UDATA': i2notu32(12), - 'NUMX': i2notu32(13), + 'STR': i2notu32(4), + 'UPVAL': i2notu32(5), + 'THREAD': i2notu32(6), + 'PROTO': i2notu32(7), + 'FUNC': i2notu32(8), + 'TRACE': i2notu32(9), + 'CDATA': i2notu32(10), + 'TAB': i2notu32(11), + 'UDATA': i2notu32(12), + 'NUMX': i2notu32(13), } def itypemap(o): if LJ_64 and not LJ_GC64: - return LJ_T['NUMX'] if tvisnumber(o) \ + return ( + LJ_T['NUMX'] + if tvisnumber(o) else LJ_T['LIGHTUD'] if tvislightud(o) else itype(o) + ) else: return LJ_T['NUMX'] if tvisnumber(o) else itype(o) def typenames(value): - return { - LJ_T[k]: 'LJ_T' + k for k in LJ_T.keys() - }.get(int(value), 'LJ_TINVALID') + return {LJ_T[k]: 'LJ_T' + k for k in LJ_T.keys()}.get(int(value), 'LJ_TINVALID') def dump_tvalue(tvptr): - return dumpers.get(typenames(itypemap(tvptr)), dump_lj_invalid)(tvptr) + return tvalue_dumpers.get(typenames(itypemap(tvptr)), dump_lj_invalid_tvalue)(tvptr) + + +def dump_gcobj(o): + return gcobj_dumpers.get(typenames(i2notu32(o.gch.gct)), dump_lj_invalid_gcobj)(o) FRAME_TYPE = 0x3 @@ -695,38 +895,73 @@ def dump_tvalue(tvptr): FRAME_TYPEP = FRAME_TYPE | FRAME_P FRAME = { - 'LUA': 0x0, - 'C': 0x1, - 'CONT': 0x2, - 'VARG': 0x3, - 'LUAP': 0x4, - 'CP': 0x5, - 'PCALL': 0x6, - 'PCALLH': 0x7, + 'LUA': 0x0, # Lua frame + 'C': 0x1, # C frame + 'CONT': 0x2, # Continuation frame + 'VARG': 0x3, # Lua vararg frame + 'LUAP': 0x4, # Lua protected frame (not documented?) + 'CP': 0x5, # cpcall() frame + 'PCALL': 0x6, # ff pcall() frame + 'PCALLH': 0x7, # ff pcall() frame with active hook } def frametypes(ft): return { - FRAME['LUA']: 'L', - FRAME['C']: 'C', + FRAME['LUA']: 'L', + FRAME['C']: 'C', FRAME['CONT']: 'M', FRAME['VARG']: 'V', }.get(ft, '?') +def frametypeps(ftp): + return { + FRAME['LUA']: 'L', + FRAME['C']: 'C', + FRAME['CONT']: 'M', + FRAME['VARG']: 'V', + FRAME['LUAP']: 'LP', + FRAME['CP']: 'CP', + FRAME['PCALL']: 'PP', + FRAME['PCALLH']: 'PPH', + }.get(ftp, '?') + + +def bc_op(ins): + return ins & 0xFF + + def bc_a(ins): - return (ins >> 8) & 0xff + return (ins >> 8) & 0xFF + + +def bc_b(ins): + return ins >> 24 + + +def bc_c(ins): + return (ins >> 16) & 0xff + + +def bc_d(ins): + return ins >> 16 + + +def frame_gc(framelink): + return gcval(framelink - LJ_FR2) def frame_ftsz(framelink): - return vtou64(cast('ptrdiff_t', framelink.ftsz if LJ_FR2 - else framelink.fr.tp.ftsz)) + return vtou64(cast('ptrdiff_t', framelink.ftsz if LJ_FR2 else framelink.fr.tp.ftsz)) def frame_pc(framelink): - return cast(BCInsPtr, frame_ftsz(framelink)) if LJ_FR2 \ + return ( + cast(BCInsPtr, frame_ftsz(framelink)) + if LJ_FR2 else mref(BCInsPtr, framelink.fr.tp.pcr) + ) def frame_prevl(framelink): @@ -735,16 +970,25 @@ def frame_prevl(framelink): # a struct member of 32-bit type to 64-bit type without getting onto # the next property bits, despite the fact that it's an actual value, not # a pointer to it. - bcins = vtou64(dbg_eval('((BCIns *)' + str(frame_pc(framelink)) + ')[-1]')) - return framelink - (1 + LJ_FR2 + bc_a(bcins)) + pc = frame_pc(framelink) + # Invalid data + # if not pc.value: + # return None -def frame_ispcall(framelink): - return (frame_ftsz(framelink) & FRAME['PCALL']) == FRAME['PCALL'] + bcins = vtou64(dbg_eval('((BCIns *)' + str(pc) + ')[-1]')) + + # Test: is LLDB api faulty? + new_bcins2 = (pc - 1).value.Dereference() + assert (framelink - (1 + LJ_FR2 + bc_a(bcins))) == ( + framelink - (1 + LJ_FR2 + bc_a(new_bcins2.unsigned)) + ) + + return framelink - (1 + LJ_FR2 + bc_a(bcins)) def frame_sized(framelink): - return (frame_ftsz(framelink) & ~FRAME_TYPEP) + return frame_ftsz(framelink) & ~FRAME_TYPEP def frame_prevd(framelink): @@ -760,13 +1004,23 @@ def frame_typep(framelink): def frame_islua(framelink): - return frametypes(frame_type(framelink)) == 'L' \ - and frame_ftsz(framelink) > 0 + return frametypes(frame_type(framelink)) == 'L' + + +def frame_isc(framelink): + return frametypes(frame_type(framelink)) == 'C' + + +def frame_isvarg(framelink): + return frametypeps(frame_typep(framelink)) == 'V' + + +def frame_ispcall(framelink): + return (frame_ftsz(framelink) & FRAME['PCALL']) == FRAME['PCALL'] def frame_prev(framelink): - return frame_prevl(framelink) if frame_islua(framelink) \ - else frame_prevd(framelink) + return frame_prevl(framelink) if frame_islua(framelink) else frame_prevd(framelink) def frame_sentinel(L): @@ -775,49 +1029,68 @@ def frame_sentinel(L): # The generator that implements frame iterator. # Every frame is represented as a tuple of framelink and frametop. -def frames(L): +def frames(L, base, limit): frametop = L.top - framelink = L.base - 1 + framelink = base - 1 + nextframe = framelink framelink_sentinel = frame_sentinel(L) - while True: - yield framelink, frametop - frametop = framelink - (1 + LJ_FR2) + + for _ in range(limit): + yield framelink, frametop, nextframe + if framelink <= framelink_sentinel: break + + frametop = framelink - (1 + LJ_FR2) + nextframe = framelink framelink = frame_prev(framelink) + # Invalid frame + if not framelink: + break + def dump_framelink_slot_address(fr): - return '{start:{padding}}:{end:{padding}}'.format( - start=hex(int(fr - 1)), - end=hex(int(fr)), - padding=len(PADDING), - ) if LJ_FR2 else '{addr:{padding}}'.format( - addr=hex(int(fr)), - padding=len(PADDING), + return ( + '{start:{padding}}:{end:{padding}}'.format( + start=hex(int(fr - 1)), + end=hex(int(fr)), + padding=len(PADDING), + ) + if LJ_FR2 + else '{addr:{padding}}'.format( + addr=hex(int(fr)), + padding=len(PADDING), + ) ) -def dump_framelink(L, fr): +def format_lj_debug_getinfo(traceback): + return ' [' + traceback + ']' if traceback else '' + + +def dump_framelink(L, level, fr, nextframe): if fr == frame_sentinel(L): - return '{addr} [S ] FRAME: dummy L'.format( - addr=dump_framelink_slot_address(fr), + return '{addr} [S ] FRAME: dummy L, {thread}'.format( + addr=dump_framelink_slot_address(fr), thread=dump_gcobj(frame_gc(fr)) ) - return '{addr} [ ] FRAME: [{pp}] delta={d}, {f}'.format( + return '{addr} [ ] FRAME #{level}: [{pp:<2}] delta={d}, {f}{traceback}'.format( addr=dump_framelink_slot_address(fr), - pp='PP' if frame_ispcall(fr) else '{frname}{p}'.format( - frname=frametypes(int(frame_type(fr))), - p='P' if frame_typep(fr) & FRAME_P else '' - ), + level=level, + pp=frametypeps(frame_typep(fr)), d=fr - frame_prev(fr), - f=dump_lj_tfunc(fr - LJ_FR2), + f=dump_gcobj(frame_gc(fr)), + traceback=( + format_lj_debug_getinfo(lj_debug_getinfo(L, fr - LJ_FR2, fr, nextframe)) + if nextframe and not frame_isvarg( + nextframe + ) # Not printing for pseudo-frames + else '' + ), ) -def dump_stack_slot(L, slot, base=None, top=None): - base = base or L.base - top = top or L.top - +def dump_stack_slot(L, slot, base, top): return '{addr:{padding}} [ {B}{T}{M}] VALUE: {value}'.format( addr=strx64(slot), padding=2 * len(PADDING) + 1, @@ -828,54 +1101,247 @@ def dump_stack_slot(L, slot, base=None, top=None): ) -def dump_stack(L, base=None, top=None): - base = base or L.base - top = top or L.top +def proto_varinfo(pt): + return mref('uint8_t *', pt.varinfo) + + +def lj_buf_ruleb128(p, p_index): + v = int(p[p_index]) + p_index += 1 + + if v >= 0x80: + sh = 0 + v &= 0x7F + + while True: + sh += 7 + value = int(p[p_index]) + v |= (value & 0x7F) << sh + + p_index += 1 + if value < 0x80: + break + + return v, p_index + + +def debug_varname(pt, pc, slot): + p = lldb.value(proto_varinfo(pt)) + p_index = 0 + + if not p: + return None + + lastpc = 0 + + while True: + startpc, endpc = None, None + name = p[p_index].sbvalue.address_of.summary + vn = int(p[p_index]) + + if vn < VARNAME__MAX: + if vn == VARNAME_END: + break # End of varinfo. + else: + while int(p[p_index]) != 0: # Skip over variable name. + p_index += 1 + + p_index += 1 + uleb128_result, p_index = lj_buf_ruleb128(p, p_index) + lastpc = startpc = lastpc + uleb128_result + + if startpc > pc: + break + + uleb128_result, p_index = lj_buf_ruleb128(p, p_index) + endpc = startpc + uleb128_result + + if pc < endpc: + if slot == 0: + if vn < VARNAME__MAX: + return VARNAMES[vn - 1] + + return name + + slot -= 1 + + return None + + +def debug_localname(L, frame, nextframe, slot1, pc_hint): + if frame == nextframe: + nextframe = None + + fn = cast(GCfuncCPtr, frame_gc(frame)) + pc = debug_framepc(L, fn, nextframe) + is_param = False + + if isluafunc(fn): + pt = funcproto(fn) + + # For the top lua frame we might have a hint about the current pc. Better than nothing + if pc == NO_BCPOS and not nextframe and pc_hint: + pc = proto_bcpos(pt, pc_hint) - 1 + + if 0 > pc > pt.sizebc: + pc = NO_BCPOS + + if pt.numparams > slot1: + is_param = True + + if not nextframe: + if is_param: + # Treat like a function parameter + pc = 0 + + nextframe = L.top + LJ_FR2 + elif frame_isvarg(nextframe): # nextframe exists + pt = funcproto(fn) + + if pt.flags & PROTO_VARARG: + slot1 = pt.numparams + tou32(slot1) + + if (frame + slot1 + LJ_FR2) < nextframe: + return is_param, f'...[{slot1}]' + + return is_param, None + + name = None + + if pc != NO_BCPOS and (name := debug_varname(funcproto(fn), pc, slot1 - 1)): + pass + elif slot1 > 0 and (frame + slot1 + LJ_FR2) < nextframe: + return is_param, None # In original code it's (*temporary), but it's not too useful for us + + return is_param, name + + +def format_localname(localname): + is_param, name = localname + + return f' [{'param' if is_param else 'local'} {name}]' if name else '' + +def dump_stack(L, raw): + if not L: + return 'Main coroutine not found. Provide an address of lua_State*' + + base = L.base + top = L.top + + global PADDING + if not PADDING: + PADDING = ' ' * len(strx64((TValuePtr(L.addr)))) + stack = mref(TValuePtr, L.stack) maxstack = mref(TValuePtr, L.maxstack) + frame_stack_size = int((maxstack - stack) >> 3) red = 5 + 2 * LJ_FR2 + if not stack or not maxstack: + return "Invalid lua_State or uninitialized stack" + + if maxstack - stack > L.stacksize: + return "Invalid lua_State" + + if not base or raw: + return "\n".join([ + dump_stack_slot(L, stack + offset, base, top) + for offset in range(L.stacksize - 1, -1, -1) # noqa: E131 + ]) + "\nNo base information, dumping whole stack" + + pc_hint = None + process = target.GetProcess() + thread = process.GetSelectedThread() + frame = thread.GetSelectedFrame() + + # Sometimes the first frame is not what L->base points to. + # I suppose this is an optimization made in the Lua interpreter because we don't leave the VM. + # All calls to extern C code that requires correct base have a code that updates L->base. + if frame.GetSymbol().name.startswith("lj_BC_"): + rdx = cast(TValuePtr, frame.FindRegister("rdx")) + + if top >= rdx > stack: + print("Base was deduced from the registers") + + pc_hint = cast(BCInsPtr, frame.FindRegister("rbx")) + ins = pc_hint.value.deref.unsigned + pc_a = bc_a(ins) + base_from_pc = cast(TValuePtr, rdx.value.unsigned + 8 * -pc_a - 0x10) + + base = base_from_pc if frame.GetSymbol().name == "lj_BC_RET" and base == rdx else rdx + dump = [ '{padding} Red zone: {nredslots: >2} slots {padding}'.format( padding='-' * len(PADDING), nredslots=red, ), ] - dump.extend([ - dump_stack_slot(L, maxstack + offset, base, top) + dump.extend( + [ + dump_stack_slot(L, maxstack + offset, base, top) for offset in range(red, 0, -1) # noqa: E131 - ]) - dump.extend([ - '{padding} Stack: {nstackslots: >5} slots {padding}'.format( - padding='-' * len(PADDING), - nstackslots=int((maxstack - stack) >> 3), - ), - dump_stack_slot(L, maxstack, base, top), - '{start}:{end} [ ] {nfreeslots} slots: Free stack slots'.format( - start='{address:{padding}}'.format( - address=strx64(top + 1), - padding=len(PADDING), + ] + ) + dump.extend( + [ + '{padding} Stack: {nstackslots: >5} slots {padding}'.format( + padding='-' * len(PADDING), + nstackslots=frame_stack_size, ), - end='{address:{padding}}'.format( - address=strx64(maxstack - 1), - padding=len(PADDING), + dump_stack_slot(L, maxstack, base, top), + '{start}:{end} [ ] {nfreeslots} slots: Free stack slots'.format( + start='{address:{padding}}'.format( + address=strx64(top + 1), + padding=len(PADDING), + ), + end='{address:{padding}}'.format( + address=strx64(maxstack - 1), + padding=len(PADDING), + ), + nfreeslots=int((maxstack - top - 8) >> 3), ), - nfreeslots=int((maxstack - top - 8) >> 3), - ), - ]) + ] + ) + + level = 0 - for framelink, frametop in frames(L): + for framelink, frametop, nextframe in frames(L, base, frame_stack_size): # Dump all data slots in the (framelink, top) interval. - dump.extend([ - dump_stack_slot(L, framelink + offset, base, top) + dump.extend( + [ + dump_stack_slot(L, framelink + offset, base, top) + + format_localname( + debug_localname( + L, + framelink, + nextframe, + offset, + pc_hint + ) + ) for offset in range(frametop - framelink, 0, -1) # noqa: E131 - ]) + ] + ) # Dump frame slot (2 slots in case of GC64). - dump.append(dump_framelink(L, framelink)) + dump.append(dump_framelink(L, level, framelink, nextframe)) + + # Next frame is a pseudo-frame of the current vararg frame, it has the same level. + if not frame_isvarg(framelink): + level += 1 return '\n'.join(dump) +class LJDumpGCobj(Command): + ''' +lj-gcobj + ''' + + def execute(self, debugger, args, result): + gcobj_ptr = cast(GCobjPtr, self.parse(args)) + print(dump_gcobj(gcobj_ptr)) + + class LJDumpTValue(Command): ''' lj-tv @@ -904,9 +1370,10 @@ class LJDumpTValue(Command): Whether the type of the given address differs from the listed above, then error message occurs. ''' + def execute(self, debugger, args, result): - tvptr = TValuePtr(cast('TValue *', self.parse(args))) - print('{}'.format(dump_tvalue(tvptr))) + tvptr = cast(TValuePtr, self.parse(args)) + print(dump_tvalue(tvptr)) class LJState(Command): @@ -919,13 +1386,20 @@ class LJState(Command): ''' def execute(self, debugger, args, result): g = G(L(None)) - print('{}'.format('\n'.join( - map(lambda t: '{} state: {}'.format(*t), { - 'VM': vm_state(g), - 'GC': gc_state(g), - 'JIT': jit_state(g), - }.items()) - ))) + print( + '{}'.format( + '\n'.join( + map( + lambda t: '{} state: {}'.format(*t), + { + 'VM': vm_state(g), + 'GC': gc_state(g), + 'JIT': jit_state(g), + }.items(), + ) + ) + ) + ) class LJDumpArch(Command): @@ -938,11 +1412,8 @@ class LJDumpArch(Command): ''' def execute(self, debugger, args, result): print( - 'LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}, LJ_DUALNUM: {LJ_DUALNUM}' - .format( - LJ_64=LJ_64, - LJ_GC64=LJ_GC64, - LJ_DUALNUM=LJ_DUALNUM + 'LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}, LJ_DUALNUM: {LJ_DUALNUM}'.format( + LJ_64=LJ_64, LJ_GC64=LJ_GC64, LJ_DUALNUM=LJ_DUALNUM ) ) @@ -967,10 +1438,7 @@ class LJGC(Command): ''' def execute(self, debugger, args, result): g = G(L(None)) - print('GC stats: {state}\n{stats}'.format( - state=gc_state(g), - stats=dump_gc(g) - )) + print('GC stats: {state}\n{stats}'.format(state=gc_state(g), stats=dump_gc(g))) class LJDumpString(Command): @@ -984,12 +1452,14 @@ class LJDumpString(Command): is replaced with the corresponding error when decoding fails. ''' def execute(self, debugger, args, result): - string_ptr = GCstrPtr(cast('GCstr *', self.parse(args))) - print("String: {body} [{len} bytes] with hash {hash}".format( - body=strdata(string_ptr), - hash=strx64(string_ptr.hash), - len=string_ptr.len, - )) + string_ptr = cast(GCstrPtr, self.parse(args)) + print( + 'String: {body} [{len} bytes] with hash {hash}'.format( + body=strdata(string_ptr), + hash=strx64(string_ptr.hash), + len=string_ptr.len, + ) + ) class LJDumpTable(Command): @@ -1003,14 +1473,15 @@ class LJDumpTable(Command): * Hash part nodes: : { } => { }; next = ''' + def execute(self, debugger, args, result): - t = GCtabPtr(cast('GCtab *', self.parse(args))) + t = cast(GCtabPtr, self.parse(args)) array = mref(TValuePtr, t.array) nodes = mref(NodePtr, t.node) - mt = gcval(t.metatable) + mt = gcref(t.metatable) capacity = { 'apart': int(t.asize), - 'hpart': int(t.hmask + 1) if t.hmask > 0 else 0 + 'hpart': int(t.hmask + 1) if t.hmask > 0 else 0, } if mt: @@ -1019,22 +1490,24 @@ def execute(self, debugger, args, result): print('Array part: {} slots'.format(capacity['apart'])) for i in range(capacity['apart']): slot = array + i - print('{ptr}: [{index}]: {value}'.format( - ptr=strx64(slot), - index=i, - value=dump_tvalue(slot) - )) + print( + '{ptr}: [{index}]: {value}'.format( + ptr=strx64(slot), index=i, value=dump_tvalue(slot) + ) + ) print('Hash part: {} nodes'.format(capacity['hpart'])) # See hmask comment in lj_obj.h for i in range(capacity['hpart']): node = nodes + i - print('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}'.format( - ptr=strx64(node), - key=dump_tvalue(TValuePtr(node.key.addr)), - val=dump_tvalue(TValuePtr(node.val.addr)), - n=strx64(mref(NodePtr, node.next)) - )) + print( + '{ptr}: {{ {key} }} => {{ {val} }}; next = {n}'.format( + ptr=strx64(node), + key=dump_tvalue(TValuePtr(node.key.addr)), + val=dump_tvalue(TValuePtr(node.val.addr)), + n=strx64(mref(NodePtr, node.next)), + ) + ) class LJDumpStack(Command): @@ -1064,23 +1537,46 @@ class LJDumpStack(Command): execution + V: Variable-length frame for storing arguments of a variadic function + + LP: Lua protected frame (not documented?) + CP: Protected C frame + PP: VM performs a call as a result of executinig pcall or xpcall If L is omitted the main coroutine is used. ''' + def execute(self, debugger, args, result): + raw = args.startswith("--raw") + + if raw: + args = args[len("--raw"):].strip() + lstate = self.parse(args) - lstate_ptr = cast('lua_State *', lstate) if coro is not None else None - print('{}'.format(dump_stack(L(lstate_ptr)))) + lstate_ptr = cast('lua_State *', lstate) if lstate is not None else None + + try: + print('{}'.format(dump_stack(L(lstate_ptr), raw))) + except Exception as e: + import traceback + + # `e` is an exception object that you get from somewhere + print(e, ''.join(traceback.format_tb(e.__traceback__))) + + +class LJDumpBC(Command): + ''' +lj-bc + ''' + def execute(self, debugger, args, result): + bc = cast(BCInsPtr, self.parse(args)).value.deref.unsigned + + print(find_type('BCOp').GetEnumMembers()[bc_op(bc)].name, bc_a(bc), bc_b(bc), bc_c(bc), bc_d(bc)) def register_commands(debugger, commands): for command, cls in commands.items(): cls.command = command debugger.HandleCommand( - 'command script add --overwrite --class luajit_lldb.{cls} {cmd}' - .format( + 'command script add --overwrite --class luajit_lldb.{cls} {cmd}'.format( cls=cls.__name__, cmd=cls.command, ) @@ -1088,37 +1584,66 @@ def register_commands(debugger, commands): print('{cmd} command intialized'.format(cmd=cls.command)) +# Searching for luajit information using luaL_newstate. It's expected to be the only one. +def find_luajit_module(): + for s in target.FindSymbols('luaL_newstate'): + if s.symbol.addr: + return s.module + + return None + + def configure(debugger): - global LJ_64, LJ_GC64, LJ_FR2, LJ_DUALNUM, PADDING, LJ_TISNUM, target + global LJ_64, LJ_GC64, LJ_FR2, LJ_DUALNUM, PADDING, LJ_TISNUM, target, luajit_module target = debugger.GetSelectedTarget() - module = target.modules[0] - LJ_DUALNUM = module.FindSymbol('lj_lib_checknumber') is not None + + if not target: + raise Exception("No target selected. Import this script while running a program.") + + luajit_module = find_luajit_module() + + LJ_DUALNUM = luajit_module.FindSymbol('lj_lib_checknumber') is not None try: - irtype_enum = target.FindFirstType('IRType').enum_members + irtype_enum = find_type('IRType').enum_members + for member in irtype_enum: if member.name == 'IRT_PTR': - LJ_64 = member.unsigned & 0x1f == IRT_P64 + LJ_64 = member.unsigned & 0x1F == IRT_P64 if member.name == 'IRT_PGC': - LJ_FR2 = LJ_GC64 = member.unsigned & 0x1f == IRT_P64 + LJ_FR2 = LJ_GC64 = member.unsigned & 0x1F == IRT_P64 + + if dbg_eval("LJ_VMST_LFUNC").value: + is_tarantool = True except Exception: print('luajit_lldb.py failed to load: ' 'no debugging symbols found for libluajit') return - PADDING = ' ' * len(strx64((TValuePtr(L().addr)))) - LJ_TISNUM = 0xfffeffff if LJ_64 and not LJ_GC64 else LJ_T['NUMX'] + PADDING = ' ' * len(strx64((TValuePtr(L().addr)))) if L() else None + LJ_TISNUM = 0xFFFEFFFF if LJ_64 and not LJ_GC64 else LJ_T['NUMX'] def __lldb_init_module(debugger, internal_dict): - configure(debugger) - register_commands(debugger, { - 'lj-tv': LJDumpTValue, - 'lj-state': LJState, - 'lj-arch': LJDumpArch, - 'lj-gc': LJGC, - 'lj-str': LJDumpString, - 'lj-tab': LJDumpTable, - 'lj-stack': LJDumpStack, - }) - print('luajit_lldb.py is successfully loaded') + try: + configure(debugger) + register_commands( + debugger, + { + 'lj-tv': LJDumpTValue, + 'lj-state': LJState, + 'lj-arch': LJDumpArch, + 'lj-gc': LJGC, + 'lj-gcobj': LJDumpGCobj, + 'lj-str': LJDumpString, + 'lj-tab': LJDumpTable, + 'lj-stack': LJDumpStack, + 'lj-bc': LJDumpBC + }, + ) + print('luajit_lldb.py is successfully loaded') + except Exception as e: + import traceback + + # `e` is an exception object that you get from somewhere + print(e, ''.join(traceback.format_tb(e.__traceback__)))