Hi! Thanks for the review!     >Среда, 22 ноября 2023, 17:46 +03:00 от Sergey Bronnikov : >  >Hello, Max, > >thanks for the patch! > > >extension cannot be load in GDB: Fixed, thanks! > >(gdb) source src/luajit_dbg.py >Traceback (most recent call last): >   File "src/luajit_dbg.py", line 228, in >     dbg = Debugger() >   File "src/luajit_dbg.py", line 46, in __init__ >     if healthcheck(lib): >   File "src/luajit_dbg.py", line 37, in >     'lldb': lambda lib: lib.debugger is not None, >AttributeError: module 'lldb' has no attribute 'debugger' >(gdb) > >[0] ~/sources/MRG/tarantool/third_party/luajit$ gdb --version >GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1 > >On 11/10/23 23:16, Maksim Kokryashkin wrote: >> From: Maxim Kokryashkin < m.kokryashkin@tarantool.org > >> >> This patch joins the LLDB and GDB LuaJIT debugging extensions >> into one, so now the extension logic can be debugger-agnostic. >> To do that, an adapter class is introduced, and all of the >> debugger-specific behavior is encapsulated there. The extension >> auto-detects the debugger it was loaded into and selects the >> correct low-level logic implementation. >> --- >> src/luajit-gdb.py | 885 -------------------------- >> src/{luajit_lldb.py => luajit_dbg.py} | 617 ++++++++++++------ >> 2 files changed, 417 insertions(+), 1085 deletions(-) >> delete mode 100644 src/luajit-gdb.py >> rename src/{luajit_lldb.py => luajit_dbg.py} (63%) >> >> diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py >> deleted file mode 100644 >> index 5eaf250f..00000000 >> --- a/src/luajit-gdb.py >> +++ /dev/null >> @@ -1,885 +0,0 @@ >> -# GDB extension for LuaJIT post-mortem analysis. >> -# To use, just put 'source /src/luajit-gdb.py' in gdb. >> - >> -import re >> -import gdb >> -import sys >> - >> -# make script compatible with the ancient Python {{{ >> - >> - >> -LEGACY = re.match(r'^2\.', sys.version) >> - >> -if LEGACY: >> - CONNECTED = False >> - int = long >> - range = xrange >> - >> - >> -# }}} >> - >> - >> -gtype_cache = {} >> - >> - >> -def gtype(typestr): >> - global gtype_cache >> - if typestr in gtype_cache: >> - return gtype_cache[typestr] >> - >> - m = re.match(r'((?:(?:struct|union) )?\S*)\s*[*]', typestr) >> - >> - gtype = gdb.lookup_type(typestr) if m is None \ >> - else gdb.lookup_type(m.group(1)).pointer() >> - >> - gtype_cache[typestr] = gtype >> - return gtype >> - >> - >> -def cast(typestr, val): >> - return gdb.Value(val).cast(gtype(typestr)) >> - >> - >> -def lookup(symbol): >> - variable, _ = gdb.lookup_symbol(symbol) >> - return variable.value() if variable else None >> - >> - >> -def parse_arg(arg): >> - if not arg: >> - return None >> - >> - ret = gdb.parse_and_eval(arg) >> - >> - if not ret: >> - raise gdb.GdbError('table argument empty') >> - >> - return ret >> - >> - >> -def tou64(val): >> - return cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF >> - >> - >> -def tou32(val): >> - return cast('uint32_t', val) & 0xFFFFFFFF >> - >> - >> -def i2notu32(val): >> - return ~int(val) & 0xFFFFFFFF >> - >> - >> -def strx64(val): >> - return re.sub('L?$', '', >> - hex(int(cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF))) >> - >> - >> -# Types {{{ >> - >> - >> -LJ_T = { >> - '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), >> -} >> - >> - >> -def typenames(value): >> - return { >> - LJ_T[k]: 'LJ_T' + k for k in LJ_T.keys() >> - }.get(int(value), 'LJ_TINVALID') >> - >> - >> -# }}} >> - >> -# Frames {{{ >> - >> - >> -FRAME_TYPE = 0x3 >> -FRAME_P = 0x4 >> -FRAME_TYPEP = FRAME_TYPE | FRAME_P >> - >> -FRAME = { >> - 'LUA': 0x0, >> - 'C': 0x1, >> - 'CONT': 0x2, >> - 'VARG': 0x3, >> - 'LUAP': 0x4, >> - 'CP': 0x5, >> - 'PCALL': 0x6, >> - 'PCALLH': 0x7, >> -} >> - >> - >> -def frametypes(ft): >> - return { >> - FRAME['LUA']: 'L', >> - FRAME['C']: 'C', >> - FRAME['CONT']: 'M', >> - FRAME['VARG']: 'V', >> - }.get(ft, '?') >> - >> - >> -def bc_a(ins): >> - return (ins >> 8) & 0xff >> - >> - >> -def frame_ftsz(framelink): >> - return cast('ptrdiff_t', framelink['ftsz'] if LJ_FR2 >> - else framelink['fr']['tp']['ftsz']) >> - >> - >> -def frame_pc(framelink): >> - return cast('BCIns *', frame_ftsz(framelink)) if LJ_FR2 \ >> - else mref('BCIns *', framelink['fr']['tp']['pcr']) >> - >> - >> -def frame_prevl(framelink): >> - return framelink - (1 + LJ_FR2 + bc_a(frame_pc(framelink)[-1])) >> - >> - >> -def frame_ispcall(framelink): >> - return (frame_ftsz(framelink) & FRAME['PCALL']) == FRAME['PCALL'] >> - >> - >> -def frame_sized(framelink): >> - return (frame_ftsz(framelink) & ~FRAME_TYPEP) >> - >> - >> -def frame_prevd(framelink): >> - return cast('TValue *', cast('char *', framelink) - frame_sized(framelink)) >> - >> - >> -def frame_type(framelink): >> - return frame_ftsz(framelink) & FRAME_TYPE >> - >> - >> -def frame_typep(framelink): >> - return frame_ftsz(framelink) & FRAME_TYPEP >> - >> - >> -def frame_islua(framelink): >> - return frametypes(int(frame_type(framelink))) == 'L' \ >> - and int(frame_ftsz(framelink)) > 0 >> - >> - >> -def frame_prev(framelink): >> - return frame_prevl(framelink) if frame_islua(framelink) \ >> - else frame_prevd(framelink) >> - >> - >> -def frame_sentinel(L): >> - return mref('TValue *', L['stack']) + LJ_FR2 >> - >> - >> -# }}} >> - >> -# Const {{{ >> - >> - >> -LJ_64 = None >> -LJ_GC64 = None >> -LJ_FR2 = None >> -LJ_DUALNUM = None >> - >> -LJ_GCVMASK = ((1 << 47) - 1) >> -LJ_TISNUM = None >> -PADDING = None >> - >> -# These constants are meaningful only for 'LJ_64' mode. >> -LJ_LIGHTUD_BITS_SEG = 8 >> -LJ_LIGHTUD_BITS_LO = 47 - LJ_LIGHTUD_BITS_SEG >> -LIGHTUD_SEG_MASK = (1 << LJ_LIGHTUD_BITS_SEG) - 1 >> -LIGHTUD_LO_MASK = (1 << LJ_LIGHTUD_BITS_LO) - 1 >> - >> - >> -# }}} >> - >> - >> -def itype(o): >> - return cast('uint32_t', o['it64'] >> 47) if LJ_GC64 else o['it'] >> - >> - >> -def mref(typename, obj): >> - return cast(typename, obj['ptr64'] if LJ_GC64 else obj['ptr32']) >> - >> - >> -def gcref(obj): >> - return cast('GCobj *', obj['gcptr64'] if LJ_GC64 >> - else cast('uintptr_t', obj['gcptr32'])) >> - >> - >> -def gcval(obj): >> - return cast('GCobj *', obj['gcptr64'] & LJ_GCVMASK if LJ_GC64 >> - else cast('uintptr_t', obj['gcptr32'])) >> - >> - >> -def gcnext(obj): >> - return gcref(obj)['gch']['nextgc'] >> - >> - >> -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(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 cast('lua_State *', lstate) >> - >> - >> -def G(L): >> - return mref('global_State *', L['glref']) >> - >> - >> -def J(g): >> - typeGG = gtype('GG_State') >> - >> - return cast('jit_State *', int(cast('char *', g)) >> - - int(typeGG['g'].bitpos / 8) >> - + int(typeGG['J'].bitpos / 8)) >> - >> - >> -def vm_state(g): >> - 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') >> - >> - >> -def gc_state(g): >> - return { >> - 0: 'PAUSE', >> - 1: 'PROPAGATE', >> - 2: 'ATOMIC', >> - 3: 'SWEEPSTRING', >> - 4: 'SWEEP', >> - 5: 'FINALIZE', >> - 6: 'LAST', >> - }.get(int(g['gc']['state']), 'INVALID') >> - >> - >> -def jit_state(g): >> - return { >> - 0: 'IDLE', >> - 0x10: 'ACTIVE', >> - 0x11: 'RECORD', >> - 0x12: 'START', >> - 0x13: 'END', >> - 0x14: 'ASM', >> - 0x15: 'ERR', >> - }.get(int(J(g)['state']), 'INVALID') >> - >> - >> -def tvisint(o): >> - return LJ_DUALNUM and itype(o) == LJ_TISNUM >> - >> - >> -def tvisnumber(o): >> - return itype(o) <= LJ_TISNUM >> - >> - >> -def tvislightud(o): >> - if LJ_64 and not LJ_GC64: >> - return (cast('int32_t', itype(o)) >> 15) == -2 >> - else: >> - return itype(o) == LJ_T['LIGHTUD'] >> - >> - >> -def strdata(obj): >> - # String is printed with pointer to it, thanks to gdb. Just strip it. >> - try: >> - return str(cast('char *', cast('GCstr *', obj) + 1))[len(PADDING):] >> - except UnicodeEncodeError: >> - return "" >> - >> - >> -def itypemap(o): >> - if LJ_64 and not LJ_GC64: >> - 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 funcproto(func): >> - assert func['ffid'] == 0 >> - >> - return cast('GCproto *', >> - mref('char *', func['pc']) - gdb.lookup_type('GCproto').sizeof) >> - >> - >> -def gclistlen(root, end=0x0): >> - count = 0 >> - while (gcref(root) != end): >> - count += 1 >> - root = gcnext(root) >> - return count >> - >> - >> -def gcringlen(root): >> - if not gcref(root): >> - return 0 >> - elif gcref(root) == gcref(gcnext(root)): >> - return 1 >> - else: >> - return 1 + gclistlen(gcnext(root), gcref(root)) >> - >> - >> -gclen = { >> - 'root': gclistlen, >> - 'gray': gclistlen, >> - 'grayagain': gclistlen, >> - 'weak': gclistlen, >> - # XXX: gc.mmudata is a ring-list. >> - 'mmudata': gcringlen, >> -} >> - >> - >> -# The generator that implements frame iterator. >> -# Every frame is represented as a tuple of framelink and frametop. >> -def frames(L): >> - frametop = L['top'] >> - framelink = L['base'] - 1 >> - framelink_sentinel = frame_sentinel(L) >> - while True: >> - yield framelink, frametop >> - frametop = framelink - (1 + LJ_FR2) >> - if framelink <= framelink_sentinel: >> - break >> - framelink = frame_prev(framelink) >> - >> - >> -def lightudV(tv): >> - if LJ_64: >> - u = int(tv['u64']) >> - # lightudseg macro expanded. >> - seg = (u >> LJ_LIGHTUD_BITS_LO) & LIGHTUD_SEG_MASK >> - segmap = mref('uint32_t *', G(L(None))['gc']['lightudseg']) >> - # lightudlo macro expanded. >> - return (int(segmap[seg]) << 32) | (u & LIGHTUD_LO_MASK) >> - else: >> - return gcval(tv['gcr']) >> - >> - >> -# Dumpers {{{ >> - >> - >> -def dump_lj_tnil(tv): >> - return 'nil' >> - >> - >> -def dump_lj_tfalse(tv): >> - return 'false' >> - >> - >> -def dump_lj_ttrue(tv): >> - return 'true' >> - >> - >> -def dump_lj_tlightud(tv): >> - return 'light userdata @ {}'.format(strx64(lightudV(tv))) >> - >> - >> -def dump_lj_tstr(tv): >> - return 'string {body} @ {address}'.format( >> - body=strdata(gcval(tv['gcr'])), >> - address=strx64(gcval(tv['gcr'])) >> - ) >> - >> - >> -def dump_lj_tupval(tv): >> - return 'upvalue @ {}'.format(strx64(gcval(tv['gcr']))) >> - >> - >> -def dump_lj_tthread(tv): >> - return 'thread @ {}'.format(strx64(gcval(tv['gcr']))) >> - >> - >> -def dump_lj_tproto(tv): >> - return 'proto @ {}'.format(strx64(gcval(tv['gcr']))) >> - >> - >> -def dump_lj_tfunc(tv): >> - func = cast('struct GCfuncC *', gcval(tv['gcr'])) >> - ffid = func['ffid'] >> - >> - if ffid == 0: >> - pt = funcproto(func) >> - return 'Lua function @ {addr}, {nups} upvalues, {chunk}:{line}'.format( >> - addr=strx64(func), >> - nups=int(func['nupvalues']), >> - chunk=strdata(cast('GCstr *', gcval(pt['chunkname']))), >> - line=pt['firstline'] >> - ) >> - elif ffid == 1: >> - return 'C function @ {}'.format(strx64(func['f'])) >> - else: >> - return 'fast function #{}'.format(int(ffid)) >> - >> - >> -def dump_lj_ttrace(tv): >> - trace = cast('struct GCtrace *', gcval(tv['gcr'])) >> - return 'trace {traceno} @ {addr}'.format( >> - traceno=strx64(trace['traceno']), >> - addr=strx64(trace) >> - ) >> - >> - >> -def dump_lj_tcdata(tv): >> - return 'cdata @ {}'.format(strx64(gcval(tv['gcr']))) >> - >> - >> -def dump_lj_ttab(tv): >> - table = cast('GCtab *', gcval(tv['gcr'])) >> - return 'table @ {gcr} (asize: {asize}, hmask: {hmask})'.format( >> - gcr=strx64(table), >> - asize=table['asize'], >> - hmask=strx64(table['hmask']), >> - ) >> - >> - >> -def dump_lj_tudata(tv): >> - return 'userdata @ {}'.format(strx64(gcval(tv['gcr']))) >> - >> - >> -def dump_lj_tnumx(tv): >> - if tvisint(tv): >> - return 'integer {}'.format(cast('int32_t', tv['i'])) >> - else: >> - return 'number {}'.format(cast('double', tv['n'])) >> - >> - >> -def dump_lj_invalid(tv): >> - return 'not valid type @ {}'.format(strx64(gcval(tv['gcr']))) >> - >> - >> -# }}} >> - >> - >> -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_tvalue(tvalue): >> - return dumpers.get(typenames(itypemap(tvalue)), dump_lj_invalid)(tvalue) >> - >> - >> -def dump_framelink_slot_address(fr): >> - return '{}:{}'.format(fr - 1, fr) if LJ_FR2 \ >> - else '{}'.format(fr) + PADDING >> - >> - >> -def dump_framelink(L, fr): >> - if fr == frame_sentinel(L): >> - return '{addr} [S ] FRAME: dummy L'.format( >> - addr=dump_framelink_slot_address(fr), >> - ) >> - return '{addr} [ ] FRAME: [{pp}] delta={d}, {f}'.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 '' >> - ), >> - d=cast('TValue *', fr) - cast('TValue *', frame_prev(fr)), >> - f=dump_lj_tfunc(fr - LJ_FR2), >> - ) >> - >> - >> -def dump_stack_slot(L, slot, base=None, top=None): >> - base = base or L['base'] >> - top = top or L['top'] >> - >> - return '{addr}{padding} [ {B}{T}{M}] VALUE: {value}'.format( >> - addr=strx64(slot), >> - padding=PADDING, >> - B='B' if slot == base else ' ', >> - T='T' if slot == top else ' ', >> - M='M' if slot == mref('TValue *', L['maxstack']) else ' ', >> - value=dump_tvalue(slot), >> - ) >> - >> - >> -def dump_stack(L, base=None, top=None): >> - base = base or L['base'] >> - top = top or L['top'] >> - stack = mref('TValue *', L['stack']) >> - maxstack = mref('TValue *', L['maxstack']) >> - red = 5 + 2 * LJ_FR2 >> - >> - dump = [ >> - '{padding} Red zone: {nredslots: >2} slots {padding}'.format( >> - padding='-' * len(PADDING), >> - nredslots=red, >> - ), >> - ] >> - 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((tou64(maxstack) - tou64(stack)) >> 3), >> - ), >> - dump_stack_slot(L, maxstack, base, top), >> - '{start}:{end} [ ] {nfreeslots} slots: Free stack slots'.format( >> - start=strx64(top + 1), >> - end=strx64(maxstack - 1), >> - nfreeslots=int((tou64(maxstack) - tou64(top) - 8) >> 3), >> - ), >> - ]) >> - >> - for framelink, frametop in frames(L): >> - # Dump all data slots in the (framelink, top) interval. >> - dump.extend([ >> - dump_stack_slot(L, framelink + offset, base, top) >> - 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)) >> - >> - return '\n'.join(dump) >> - >> - >> -def dump_gc(g): >> - gc = g['gc'] >> - stats = ['{key}: {value}'.format(key=f, value=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(gc[stat]) >> - ) for stat, handler in gclen.items()] >> - >> - return '\n'.join(map(lambda s: '\t' + s, stats)) >> - >> - >> -class LJBase(gdb.Command): >> - >> - def __init__(self, name): >> - # XXX Fragile: though the command initialization looks like a crap but >> - # it respects both Python 2 and Python 3. >> - gdb.Command.__init__(self, name, gdb.COMMAND_DATA) >> - gdb.write('{} command initialized\n'.format(name)) >> - >> - >> -class LJDumpArch(LJBase): >> - ''' >> -lj-arch >> - >> -The command requires no args and dumps values of LJ_64 and LJ_GC64 >> -compile-time flags. These values define the sizes of host and GC >> -pointers respectively. >> - ''' >> - >> - def invoke(self, arg, from_tty): >> - gdb.write( >> - 'LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}, LJ_DUALNUM: {LJ_DUALNUM}\n' >> - .format( >> - LJ_64=LJ_64, >> - LJ_GC64=LJ_GC64, >> - LJ_DUALNUM=LJ_DUALNUM >> - ) >> - ) >> - >> - >> -class LJDumpTValue(LJBase): >> - ''' >> -lj-tv >> - >> -The command receives a pointer to (TValue address) and dumps >> -the type and some info related to it. >> - >> -* LJ_TNIL: nil >> -* LJ_TFALSE: false >> -* LJ_TTRUE: true >> -* LJ_TLIGHTUD: light userdata @ >> -* LJ_TSTR: string @ >> -* LJ_TUPVAL: upvalue @ >> -* LJ_TTHREAD: thread @ >> -* LJ_TPROTO: proto @ >> -* LJ_TFUNC: >> - : Lua function @ , upvalues, >> - : C function >> - : fast function # >> -* LJ_TTRACE: trace @ >> -* LJ_TCDATA: cdata @ >> -* LJ_TTAB: table @ (asize: , hmask: ) >> -* LJ_TUDATA: userdata @ >> -* LJ_TNUMX: number >> - >> -Whether the type of the given address differs from the listed above, then >> -error message occurs. >> - ''' >> - >> - def invoke(self, arg, from_tty): >> - tv = cast('TValue *', parse_arg(arg)) >> - gdb.write('{}\n'.format(dump_tvalue(tv))) >> - >> - >> -class LJDumpString(LJBase): >> - ''' >> -lj-str >> - >> -The command receives a of the corresponding GCstr object and dumps >> -the payload, size in bytes and hash. >> - >> -*Caveat*: Since Python 2 provides no native Unicode support, the payload >> -is replaced with the corresponding error when decoding fails. >> - ''' >> - >> - def invoke(self, arg, from_tty): >> - string = cast('GCstr *', parse_arg(arg)) >> - gdb.write("String: {body} [{len} bytes] with hash {hash}\n".format( >> - body=strdata(string), >> - hash=strx64(string['hash']), >> - len=string['len'], >> - )) >> - >> - >> -class LJDumpTable(LJBase): >> - ''' >> -lj-tab >> - >> -The command receives a GCtab adress and dumps the table contents: >> -* Metatable address whether the one is set >> -* Array part slots: >> - : []: >> -* Hash part nodes: >> - : { } => { }; next = >> - ''' >> - >> - def invoke(self, arg, from_tty): >> - t = cast('GCtab *', parse_arg(arg)) >> - array = mref('TValue *', t['array']) >> - nodes = mref('struct Node *', t['node']) >> - mt = gcval(t['metatable']) >> - capacity = { >> - 'apart': int(t['asize']), >> - 'hpart': int(t['hmask'] + 1) if t['hmask'] > 0 else 0 >> - } >> - >> - if mt != 0: >> - gdb.write('Metatable detected: {}\n'.format(strx64(mt))) >> - >> - gdb.write('Array part: {} slots\n'.format(capacity['apart'])) >> - for i in range(capacity['apart']): >> - slot = array + i >> - gdb.write('{ptr}: [{index}]: {value}\n'.format( >> - ptr=slot, >> - index=i, >> - value=dump_tvalue(slot) >> - )) >> - >> - gdb.write('Hash part: {} nodes\n'.format(capacity['hpart'])) >> - # See hmask comment in lj_obj.h >> - for i in range(capacity['hpart']): >> - node = nodes + i >> - gdb.write('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}\n'.format( >> - ptr=node, >> - key=dump_tvalue(node['key']), >> - val=dump_tvalue(node['val']), >> - n=mref('struct Node *', node['next']) >> - )) >> - >> - >> -class LJDumpStack(LJBase): >> - ''' >> -lj-stack [] >> - >> -The command receives a lua_State address and dumps the given Lua >> -coroutine guest stack: >> - >> - [] >> - >> -* : guest stack slot address >> -* : >> - - S: Bottom of the stack (the slot L->stack points to) >> - - B: Base of the current guest frame (the slot L->base points to) >> - - T: Top of the current guest frame (the slot L->top points to) >> - - M: Last slot of the stack (the slot L->maxstack points to) >> -* : see help lj-tv for more info >> -* : framelink slot differs from the value slot: it contains info >> - related to the function being executed within this guest frame, its >> - type and link to the parent guest frame >> - [] delta=, >> - - : >> - + L: VM performs a call as a result of bytecode execution >> - + C: VM performs a call as a result of lj_vm_call >> - + M: VM performs a call to a metamethod as a result of bytecode >> - execution >> - + V: Variable-length frame for storing arguments of a variadic >> - function >> - + CP: Protected C frame >> - + PP: VM performs a call as a result of executinig pcall or xpcall >> - >> -If L is ommited the main coroutine is used. >> - ''' >> - >> - def invoke(self, arg, from_tty): >> - gdb.write('{}\n'.format(dump_stack(L(parse_arg(arg))))) >> - >> - >> -class LJState(LJBase): >> - ''' >> -lj-state >> -The command requires no args and dumps current VM and GC states >> -* VM state: >> -* GC state: >> -* JIT state: >> - ''' >> - >> - def invoke(self, arg, from_tty): >> - g = G(L(None)) >> - gdb.write('{}\n'.format('\n'.join( >> - map(lambda t: '{} state: {}'.format(*t), { >> - 'VM': vm_state(g), >> - 'GC': gc_state(g), >> - 'JIT': jit_state(g), >> - }.items()) >> - ))) >> - >> - >> -class LJGC(LJBase): >> - ''' >> -lj-gc >> - >> -The command requires no args and dumps current GC stats: >> -* total: >> -* threshold: >> -* debt: >> -* estimate: >> -* stepmul: >> -* pause: >> -* sweepstr: >> -* root: >> -* gray: >> -* grayagain: >> -* weak: >> -* mmudata: >> - ''' >> - >> - def invoke(self, arg, from_tty): >> - g = G(L(None)) >> - gdb.write('GC stats: {state}\n{stats}\n'.format( >> - state=gc_state(g), >> - stats=dump_gc(g) >> - )) >> - >> - >> -def init(commands): >> - global LJ_64, LJ_GC64, LJ_FR2, LJ_DUALNUM, LJ_TISNUM, PADDING >> - >> - # XXX Fragile: though connecting the callback looks like a crap but it >> - # respects both Python 2 and Python 3 (see #4828). >> - def connect(callback): >> - if LEGACY: >> - global CONNECTED >> - CONNECTED = True >> - gdb.events.new_objfile.connect(callback) >> - >> - # XXX Fragile: though disconnecting the callback looks like a crap but it >> - # respects both Python 2 and Python 3 (see #4828). >> - def disconnect(callback): >> - if LEGACY: >> - global CONNECTED >> - if not CONNECTED: >> - return >> - CONNECTED = False >> - gdb.events.new_objfile.disconnect(callback) >> - >> - try: >> - # Try to remove the callback at first to not append duplicates to >> - # gdb.events.new_objfile internal list. >> - disconnect(load) >> - except Exception: >> - # Callback is not connected. >> - pass >> - >> - try: >> - # Detect whether libluajit objfile is loaded. >> - gdb.parse_and_eval('luaJIT_setmode') >> - except Exception: >> - gdb.write('luajit-gdb.py initialization is postponed ' >> - 'until libluajit objfile is loaded\n') >> - # Add a callback to be executed when the next objfile is loaded. >> - connect(load) >> - return >> - >> - try: >> - LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64' >> - LJ_FR2 = LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64' >> - LJ_DUALNUM = gdb.lookup_global_symbol('lj_lib_checknumber') is not None >> - except Exception: >> - gdb.write('luajit-gdb.py failed to load: ' >> - 'no debugging symbols found for libluajit\n') >> - return >> - >> - for name, command in commands.items(): >> - command(name) >> - >> - PADDING = ' ' * len(':' + hex((1 << (47 if LJ_GC64 else 32)) - 1)) >> - LJ_TISNUM = 0xfffeffff if LJ_64 and not LJ_GC64 else LJ_T['NUMX'] >> - >> - gdb.write('luajit-gdb.py is successfully loaded\n') >> - >> - >> -def load(event=None): >> - init({ >> - 'lj-arch': LJDumpArch, >> - 'lj-tv': LJDumpTValue, >> - 'lj-str': LJDumpString, >> - 'lj-tab': LJDumpTable, >> - 'lj-stack': LJDumpStack, >> - 'lj-state': LJState, >> - 'lj-gc': LJGC, >> - }) >> - >> - >> -load(None) >> diff --git a/src/luajit_lldb.py b/src/luajit_dbg.py >> similarity index 63% >> rename from src/luajit_lldb.py >> rename to src/luajit_dbg.py >> index ef0986cc..cb46b473 100644 >> --- a/src/luajit_lldb.py >> +++ b/src/luajit_dbg.py >> @@ -1,10 +1,231 @@ >> -# LLDB extension for LuaJIT post-mortem analysis. >> -# To use, just put 'command script import /src/luajit_lldb.py' >> -# in lldb. >> +# Debug extension for LuaJIT post-mortem analysis. >> +# To use in LLDB: 'command script import /src/luajit_dbg.py' >> +# To use in GDB: 'source /src/luajit_dbg.py' >> >> import abc >> import re >> -import lldb >> +import sys >> +import types >> + >> +from importlib import import_module >> + >> +# make script compatible with the ancient Python {{{ >> + >> + >> +LEGACY = re.match(r'^2\.', sys.version) >> + >> +if LEGACY: >> + CONNECTED = False >> + int = long >> + range = xrange >> + >> + >> +def is_integer_type(val): >> + return isinstance(val, int) or (LEGACY and isinstance(val, types.IntType)) >> + >> + >> +# }}} >> + >> + >> +class Debugger(object): >> + def __init__(self): >> + self.GDB = False >> + self.LLDB = False >> + >> + debuggers = { >> + 'gdb': lambda lib: True, >> + 'lldb': lambda lib: lib.debugger is not None, >> + } >> + for name, healthcheck in debuggers.items(): >> + lib = None >> + try: >> + lib = import_module(name) >> + except ImportError: >> + continue >> + >> + if healthcheck(lib): >> + setattr(self, name.upper(), True) >> + globals()[name] = lib >> + self.name = name >> + >> + assert self.LLDB != self.GDB >> + >> + def setup_target(self, debugger): >> + global target >> + if self.LLDB: >> + target = debugger.GetSelectedTarget() >> + >> + def write(self, msg): >> + if self.LLDB: >> + print(msg) >> + else: >> + gdb.write(msg + '\n') >> + >> + def cmd_init(self, cmd_cls, debugger=None): >> + if self.LLDB: >> + debugger.HandleCommand( >> + 'command script add --overwrite --class ' >> + 'luajit_dbg.{cls} {cmd}' >> + .format( >> + cls=cmd_cls.__name__, >> + cmd=cmd_cls.command, >> + ) >> + ) >> + else: >> + cmd_cls() >> + >> + def event_connect(self, callback): >> + if not self.LLDB: >> + # XXX Fragile: though connecting the callback looks like a crap but >> + # it respects both Python 2 and Python 3 (see #4828). >> + if LEGACY: >> + global CONNECTED >> + CONNECTED = True >> + gdb.events.new_objfile.connect(callback) >> + >> + def event_disconnect(self, callback): >> + if not self.LLDB: >> + # XXX Fragile: though disconnecting the callback looks like a crap >> + # but it respects both Python 2 and Python 3 (see #4828). >> + if LEGACY: >> + global CONNECTED >> + if not CONNECTED: >> + return >> + CONNECTED = False >> + gdb.events.new_objfile.disconnect(callback) >> + >> + def lookup_variable(self, name): >> + if self.LLDB: >> + return target.FindFirstGlobalVariable(name) >> + else: >> + variable, _ = gdb.lookup_symbol(name) >> + return variable.value() if variable else None >> + >> + def lookup_symbol(self, sym): >> + if self.LLDB: >> + return target.modules[0].FindSymbol(sym) >> + else: >> + return gdb.lookup_global_symbol(sym) >> + >> + def to_unsigned(self, val): >> + return val.unsigned if self.LLDB else int(val) >> + >> + def to_signed(self, val): >> + return val.signed if self.LLDB else int(val) >> + >> + def to_str(self, val): >> + return val.value if self.LLDB else str(val) >> + >> + def find_type(self, typename): >> + if self.LLDB: >> + return target.FindFirstType(typename) >> + else: >> + return gdb.lookup_type(typename) >> + >> + def type_to_pointer_type(self, tp): >> + if self.LLDB: >> + return tp.GetPointerType() >> + else: >> + return tp.pointer() >> + >> + def cast_impl(self, value, t, pointer_type): >> + if self.LLDB: >> + if is_integer_type(value): >> + # Integer casts require some black magic >> + # for lldb to behave properly. >> + if pointer_type: >> + return target.CreateValueFromAddress( >> + 'value', >> + lldb.SBAddress(value, target), >> + t.GetPointeeType(), >> + ).address_of >> + else: >> + return target.CreateValueFromData( >> + name='value', >> + data=lldb.SBData.CreateDataFromInt(value, size=8), >> + type=t, >> + ) >> + else: >> + return value.Cast(t) >> + else: >> + return gdb.Value(value).cast(t) >> + >> + def dereference(self, val): >> + if self.LLDB: >> + return val.Dereference() >> + else: >> + return val.dereference() >> + >> + def eval(self, expression): >> + if self.LLDB: >> + process = target.GetProcess() >> + thread = process.GetSelectedThread() >> + frame = thread.GetSelectedFrame() >> + >> + if not expression: >> + return None >> + >> + return frame.EvaluateExpression(expression) >> + else: >> + return gdb.parse_and_eval(expression) >> + >> + def type_sizeof_impl(self, tp): >> + if self.LLDB: >> + return tp.GetByteSize() >> + else: >> + return tp.sizeof >> + >> + def summary(self, val): >> + if self.LLDB: >> + return val.summary >> + else: >> + return str(val)[len(PADDING):].strip() >> + >> + def type_member(self, type_obj, name): >> + if self.LLDB: >> + return next((x for x in type_obj.members if x.name == name), None) >> + else: >> + return type_obj[name] >> + >> + def type_member_offset(self, member): >> + if self.LLDB: >> + return member.GetOffsetInBytes() >> + else: >> + return member.bitpos / 8 >> + >> + def get_member(self, value, member_name): >> + if self.LLDB: >> + return value.GetChildMemberWithName(member_name) >> + else: >> + return value[member_name] >> + >> + def address_of(self, value): >> + if self.LLDB: >> + return value.address_of >> + else: >> + return value.address >> + >> + def arch_init(self): >> + global LJ_64, LJ_GC64, LJ_FR2, LJ_DUALNUM, PADDING, LJ_TISNUM, target >> + if self.LLDB: >> + irtype_enum = dbg.find_type('IRType').enum_members >> + for member in irtype_enum: >> + if member.name == 'IRT_PTR': >> + LJ_64 = dbg.to_unsigned(member) & 0x1f == IRT_P64 >> + if member.name == 'IRT_PGC': >> + LJ_GC64 = dbg.to_unsigned(member) & 0x1f == IRT_P64 >> + else: >> + LJ_64 = str(dbg.eval('IRT_PTR')) == 'IRT_P64' >> + LJ_GC64 = str(dbg.eval('IRT_PGC')) == 'IRT_P64' >> + >> + LJ_FR2 = LJ_GC64 >> + LJ_DUALNUM = dbg.lookup_symbol('lj_lib_checknumber') is not None >> + # Two extra characters are required to fit in the `0x` part. >> + PADDING = ' ' * len(strx64(L())) >> + LJ_TISNUM = 0xfffeffff if LJ_64 and not LJ_GC64 else LJ_T['NUMX'] >> + >> + >> +dbg = Debugger() >> >> LJ_64 = None >> LJ_GC64 = None >> @@ -17,68 +238,73 @@ IRT_P64 = 9 >> LJ_GCVMASK = ((1 << 47) - 1) >> LJ_TISNUM = None >> >> -# Debugger specific {{{ >> - >> - >> # Global >> target = None >> >> >> -class Ptr: >> +class Ptr(object): >> def __init__(self, value, normal_type): >> self.value = value >> self.normal_type = normal_type >> >> @property >> def __deref(self): >> - return self.normal_type(self.value.Dereference()) >> + return self.normal_type(dbg.dereference(self.value)) >> >> def __add__(self, other): >> - assert isinstance(other, int) >> + assert is_integer_type(other) >> return self.__class__( >> cast( >> self.normal_type.__name__ + ' *', >> cast( >> 'uintptr_t', >> - self.value.unsigned + other * self.value.deref.size, >> + dbg.to_unsigned(self.value) + other * sizeof( >> + self.normal_type.__name__ >> + ), >> ), >> ), >> ) >> >> def __sub__(self, other): >> - assert isinstance(other, int) or isinstance(other, Ptr) >> - if isinstance(other, int): >> + assert is_integer_type(other) or isinstance(other, Ptr) >> + if is_integer_type(other): >> return self.__add__(-other) >> else: >> - return int((self.value.unsigned - other.value.unsigned) >> - / sizeof(self.normal_type.__name__)) >> + return int( >> + ( >> + dbg.to_unsigned(self.value) - dbg.to_unsigned(other.value) >> + ) / sizeof(self.normal_type.__name__) >> + ) >> >> def __eq__(self, other): >> - assert isinstance(other, Ptr) or isinstance(other, int) and other >= 0 >> + assert isinstance(other, Ptr) or is_integer_type(other) >> if isinstance(other, Ptr): >> - return self.value.unsigned == other.value.unsigned >> + return dbg.to_unsigned(self.value) == dbg.to_unsigned(other.value) >> else: >> - return self.value.unsigned == other >> + return dbg.to_unsigned(self.value) == other >> >> def __ne__(self, other): >> return not self == other >> >> def __gt__(self, other): >> assert isinstance(other, Ptr) >> - return self.value.unsigned > other.value.unsigned >> + return dbg.to_unsigned(self.value) > dbg.to_unsigned(other.value) >> >> def __ge__(self, other): >> assert isinstance(other, Ptr) >> - return self.value.unsigned >= other.value.unsigned >> + return dbg.to_unsigned(self.value) >= dbg.to_unsigned(other.value) >> >> def __bool__(self): >> - return self.value.unsigned != 0 >> + return dbg.to_unsigned(self.value) != 0 >> >> def __int__(self): >> - return self.value.unsigned >> + return dbg.to_unsigned(self.value) >> + >> + def __long__(self): >> + return dbg.to_unsigned(self.value) >> >> def __str__(self): >> - return self.value.value >> + return dbg.to_str(self.value) >> >> def __getattr__(self, name): >> if name != '__deref': >> @@ -86,53 +312,26 @@ class Ptr: >> return self.__deref >> >> >> -class MetaStruct(type): >> - def __init__(cls, name, bases, nmspc): >> - super(MetaStruct, cls).__init__(name, bases, nmspc) >> - >> - def make_general(field, tp): >> - builtin = { >> - '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: >> - if not isinstance(field[0], str): >> - setattr(cls, field[1], field[0]) >> - else: >> - setattr( >> - cls, >> - field[1], >> - property(make_general(field[1], field[0])), >> - ) >> - >> - >> -class Struct(metaclass=MetaStruct): >> +class Struct(object): >> def __init__(self, value): >> self.value = value >> >> def __getitem__(self, name): >> - return self.value.GetChildMemberWithName(name) >> + return dbg.get_member(self.value, name) >> >> @property >> def addr(self): >> - return self.value.address_of >> + return dbg.address_of(self.value) >> >> >> c_structs = { >> 'MRef': [ >> - (property(lambda self: self['ptr64'].unsigned if LJ_GC64 >> - else self['ptr32'].unsigned), 'ptr') >> + (property(lambda self: dbg.to_unsigned(self['ptr64']) if LJ_GC64 >> + else dbg.to_unsigned(self['ptr32'])), 'ptr') >> ], >> 'GCRef': [ >> - (property(lambda self: self['gcptr64'].unsigned if LJ_GC64 >> - else self['gcptr32'].unsigned), 'gcptr') >> + (property(lambda self: dbg.to_unsigned(self['gcptr64']) if LJ_GC64 >> + else dbg.to_unsigned(self['gcptr32'])), 'gcptr') >> ], >> 'TValue': [ >> ('GCRef', 'gcr'), >> @@ -141,8 +340,12 @@ c_structs = { >> ('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: dbg.to_signed(self['ftsz']) if LJ_GC64 else None >> + ), >> + 'ftsz' >> + ) >> ], >> 'GCState': [ >> ('GCRef', 'root'), >> @@ -216,26 +419,51 @@ c_structs = { >> ('TValue', 'val'), >> ('MRef', 'next') >> ], >> - 'BCIns': [] >> + 'BCIns': [], >> } >> >> >> -for cls in c_structs.keys(): >> - globals()[cls] = type(cls, (Struct, ), {'metainfo': c_structs[cls]}) >> +def make_property_from_metadata(field, tp): >> + builtin = { >> + 'uint': dbg.to_unsigned, >> + 'int': dbg.to_signed, >> + 'string': dbg.to_str, >> + } >> + if tp in builtin.keys(): >> + return lambda self: builtin[tp](self[field]) >> + else: >> + return lambda self: globals()[tp](self[field]) >> + >> + >> +for cls, metainfo in c_structs.items(): >> + cls_dict = {} >> + for field in metainfo: >> + if not isinstance(field[0], str): >> + cls_dict[field[1]] = field[0] >> + else: >> + cls_dict[field[1]] = property( >> + make_property_from_metadata(field[1], field[0]) >> + ) >> + globals()[cls] = type(cls, (Struct, ), cls_dict) >> >> >> for cls in Struct.__subclasses__(): >> ptr_name = cls.__name__ + 'Ptr' >> >> + def make_init(cls): >> + return lambda self, value: super(type(self), self).__init__(value, cls) >> + >> globals()[ptr_name] = type(ptr_name, (Ptr,), { >> - '__init__': >> - lambda self, value: super(type(self), self).__init__(value, cls) >> + '__init__': make_init(cls) >> }) >> >> >> -class Command(object): >> - def __init__(self, debugger, unused): >> - pass >> +class Command(object if dbg.LLDB else gdb.Command): >> + def __init__(self, debugger=None, unused=None): >> + if dbg.GDB: >> + # XXX Fragile: though initialization looks like a crap but it >> + # respects both Python 2 and Python 3 (see #4828). >> + gdb.Command.__init__(self, self.command, gdb.COMMAND_DATA) >> >> def get_short_help(self): >> return self.__doc__.splitlines()[0] >> @@ -245,21 +473,15 @@ class Command(object): >> >> def __call__(self, debugger, command, exe_ctx, result): >> try: >> - self.execute(debugger, command, result) >> + self.execute(command) >> except Exception as e: >> msg = 'Failed to execute command `{}`: {}'.format(self.command, e) >> result.SetError(msg) >> >> def parse(self, command): >> - process = target.GetProcess() >> - thread = process.GetSelectedThread() >> - frame = thread.GetSelectedFrame() >> - >> if not command: >> return None >> - >> - ret = frame.EvaluateExpression(command) >> - return ret >> + return dbg.to_unsigned(dbg.eval(command)) >> >> @abc.abstractproperty >> def command(self): >> @@ -270,7 +492,7 @@ class Command(object): >> """ >> >> @abc.abstractmethod >> - def execute(self, debugger, args, result): >> + def execute(self, args): >> """Implementation of the command. >> Subclasses override this method to implement the logic of a given >> command, e.g. printing a stacktrace. The command output should be >> @@ -278,6 +500,11 @@ class Command(object): >> properly routed to LLDB frontend. Any unhandled exception will be >> automatically transformed into proper errors. >> """ >> + def invoke(self, arg, from_tty): >> + try: >> + self.execute(arg) >> + except Exception as e: >> + dbg.write(e) >> >> >> def cast(typename, value): >> @@ -299,75 +526,38 @@ def cast(typename, value): >> name = name[:-1].strip() >> pointer_type = True >> >> - # Get the lldb type representation. >> - t = target.FindFirstType(name) >> + # Get the inferior type representation. >> + t = dbg.find_type(name) >> if pointer_type: >> - 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, >> - ) >> - else: >> - casted = value.Cast(t) >> + t = dbg.type_to_pointer_type(t) >> + >> + casted = dbg.cast_impl(value, t, pointer_type) >> >> if isinstance(typename, type): >> - # Wrap lldb object, if possible >> + # Wrap inferior object, if possible >> return typename(casted) >> else: >> return casted >> >> >> -def lookup_global(name): >> - return target.FindFirstGlobalVariable(name) >> - >> - >> -def type_member(type_obj, name): >> - return next((x for x in type_obj.members if x.name == name), None) >> - >> - >> -def find_type(typename): >> - return target.FindFirstType(typename) >> - >> - >> def offsetof(typename, membername): >> - type_obj = find_type(typename) >> - member = type_member(type_obj, membername) >> + type_obj = dbg.find_type(typename) >> + member = dbg.type_member(type_obj, membername) >> assert member is not None >> - return member.GetOffsetInBytes() >> + return dbg.type_member_offset(member) >> >> >> def sizeof(typename): >> - type_obj = find_type(typename) >> - return type_obj.GetByteSize() >> + type_obj = dbg.find_type(typename) >> + return dbg.type_sizeof_impl(type_obj) >> >> >> def vtou64(value): >> - return value.unsigned & 0xFFFFFFFFFFFFFFFF >> + return dbg.to_unsigned(value) & 0xFFFFFFFFFFFFFFFF >> >> >> def vtoi(value): >> - return value.signed >> - >> - >> -def dbg_eval(expr): >> - process = target.GetProcess() >> - thread = process.GetSelectedThread() >> - frame = thread.GetSelectedFrame() >> - return frame.EvaluateExpression(expr) >> - >> - >> -# }}} Debugger specific >> + return dbg.to_signed(value) >> >> >> def gcval(obj): >> @@ -393,7 +583,7 @@ def gclistlen(root, end=0x0): >> >> >> def gcringlen(root): >> - if not gcref(root): >> + if gcref(root) == 0: >> return 0 >> elif gcref(root) == gcref(gcnext(root)): >> return 1 >> @@ -439,7 +629,7 @@ def J(g): >> J_offset = offsetof('GG_State', 'J') >> return cast( >> jit_StatePtr, >> - vtou64(cast('char *', g)) - g_offset + J_offset, >> + int(vtou64(cast('char *', g)) - g_offset + J_offset), >> ) >> >> >> @@ -451,7 +641,7 @@ 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), ( >> + for lstate in [L] + list(map(lambda main: dbg.lookup_variable(main), ( >> # LuaJIT main coro (see luajit/src/luajit.c) >> 'globalL', >> # Tarantool main coro (see tarantool/src/lua/init.h) >> @@ -459,7 +649,7 @@ def L(L=None): >> # TODO: Add more >> ))): >> if lstate: >> - return lua_State(lstate) >> + return lua_StatePtr(lstate) >> >> >> def tou32(val): >> @@ -523,9 +713,9 @@ def funcproto(func): >> def strdata(obj): >> try: >> ptr = cast('char *', obj + 1) >> - return ptr.summary >> + return dbg.summary(ptr) >> except UnicodeEncodeError: >> - return "" >> + return "" >> >> >> def itype(o): >> @@ -730,12 +920,12 @@ def frame_pc(framelink): >> >> >> def frame_prevl(framelink): >> - # We are evaluating the `frame_pc(framelink)[-1])` with lldb's >> + # We are evaluating the `frame_pc(framelink)[-1])` with >> # REPL, because the lldb API is faulty and it's not possible to cast >> # 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]')) >> + bcins = vtou64(dbg.eval('((BCIns *)' + str(frame_pc(framelink)) + ')[-1]')) >> return framelink - (1 + LJ_FR2 + bc_a(bcins)) >> >> >> @@ -789,12 +979,12 @@ def frames(L): >> >> def dump_framelink_slot_address(fr): >> return '{start:{padding}}:{end:{padding}}'.format( >> - start=hex(int(fr - 1)), >> - end=hex(int(fr)), >> + start=strx64(fr - 1), >> + end=strx64(fr), >> padding=len(PADDING), >> ) if LJ_FR2 else '{addr:{padding}}'.format( >> - addr=hex(int(fr)), >> - padding=len(PADDING), >> + addr=strx64(fr), >> + padding=2 * len(PADDING) + 1, >> ) >> >> >> @@ -863,7 +1053,6 @@ def dump_stack(L, base=None, top=None): >> nfreeslots=int((maxstack - top - 8) >> 3), >> ), >> ]) >> - >> for framelink, frametop in frames(L): >> # Dump all data slots in the (framelink, top) interval. >> dump.extend([ >> @@ -904,9 +1093,11 @@ the type and some info related to it. >> Whether the type of the given address differs from the listed above, then >> error message occurs. >> ''' >> - def execute(self, debugger, args, result): >> + command = 'lj-tv' >> + >> + def execute(self, args): >> tvptr = TValuePtr(cast('TValue *', self.parse(args))) >> - print('{}'.format(dump_tvalue(tvptr))) >> + dbg.write('{}'.format(dump_tvalue(tvptr))) >> >> >> class LJState(Command): >> @@ -917,9 +1108,11 @@ The command requires no args and dumps current VM and GC states >> * GC state: >> * JIT state: >> ''' >> - def execute(self, debugger, args, result): >> + command = 'lj-state' >> + >> + def execute(self, args): >> g = G(L(None)) >> - print('{}'.format('\n'.join( >> + dbg.write('{}'.format('\n'.join( >> map(lambda t: '{} state: {}'.format(*t), { >> 'VM': vm_state(g), >> 'GC': gc_state(g), >> @@ -936,8 +1129,10 @@ The command requires no args and dumps values of LJ_64 and LJ_GC64 >> compile-time flags. These values define the sizes of host and GC >> pointers respectively. >> ''' >> - def execute(self, debugger, args, result): >> - print( >> + command = 'lj-arch' >> + >> + def execute(self, args): >> + dbg.write( >> 'LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}, LJ_DUALNUM: {LJ_DUALNUM}' >> .format( >> LJ_64=LJ_64, >> @@ -965,9 +1160,11 @@ The command requires no args and dumps current GC stats: >> * weak: >> * mmudata: >> ''' >> - def execute(self, debugger, args, result): >> + command = 'lj-gc' >> + >> + def execute(self, args): >> g = G(L(None)) >> - print('GC stats: {state}\n{stats}'.format( >> + dbg.write('GC stats: {state}\n{stats}'.format( >> state=gc_state(g), >> stats=dump_gc(g) >> )) >> @@ -983,9 +1180,11 @@ the payload, size in bytes and hash. >> *Caveat*: Since Python 2 provides no native Unicode support, the payload >> is replaced with the corresponding error when decoding fails. >> ''' >> - def execute(self, debugger, args, result): >> + command = 'lj-str' >> + >> + def execute(self, args): >> string_ptr = GCstrPtr(cast('GCstr *', self.parse(args))) >> - print("String: {body} [{len} bytes] with hash {hash}".format( >> + dbg.write("String: {body} [{len} bytes] with hash {hash}".format( >> body=strdata(string_ptr), >> hash=strx64(string_ptr.hash), >> len=string_ptr.len, >> @@ -1003,7 +1202,9 @@ The command receives a GCtab adress and dumps the table contents: >> * Hash part nodes: >> : { } => { }; next = >> ''' >> - def execute(self, debugger, args, result): >> + command = 'lj-tab' >> + >> + def execute(self, args): >> t = GCtabPtr(cast('GCtab *', self.parse(args))) >> array = mref(TValuePtr, t.array) >> nodes = mref(NodePtr, t.node) >> @@ -1014,22 +1215,22 @@ The command receives a GCtab adress and dumps the table contents: >> } >> >> if mt: >> - print('Metatable detected: {}'.format(strx64(mt))) >> + dbg.write('Metatable detected: {}'.format(strx64(mt))) >> >> - print('Array part: {} slots'.format(capacity['apart'])) >> + dbg.write('Array part: {} slots'.format(capacity['apart'])) >> for i in range(capacity['apart']): >> slot = array + i >> - print('{ptr}: [{index}]: {value}'.format( >> + dbg.write('{ptr}: [{index}]: {value}'.format( >> ptr=strx64(slot), >> index=i, >> value=dump_tvalue(slot) >> )) >> >> - print('Hash part: {} nodes'.format(capacity['hpart'])) >> + dbg.write('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( >> + dbg.write('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}'.format( >> ptr=strx64(node), >> key=dump_tvalue(TValuePtr(node.key.addr)), >> val=dump_tvalue(TValuePtr(node.val.addr)), >> @@ -1069,56 +1270,72 @@ coroutine guest stack: >> >> If L is ommited the main coroutine is used. >> ''' >> - def execute(self, debugger, args, result): >> + command = 'lj-stack' >> + >> + def execute(self, args): >> 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 else None >> + dbg.write('{}'.format(dump_stack(L(lstate_ptr)))) >> >> >> -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( >> - cls=cls.__name__, >> - cmd=cls.command, >> - ) >> - ) >> - print('{cmd} command intialized'.format(cmd=cls.command)) >> +LJ_COMMANDS = [ >> + LJDumpTValue, >> + LJState, >> + LJDumpArch, >> + LJGC, >> + LJDumpString, >> + LJDumpTable, >> + LJDumpStack, >> +] >> + >> >> +def register_commands(commands, debugger=None): >> + for cls in commands: >> + dbg.cmd_init(cls, debugger) >> + dbg.write('{cmd} command intialized'.format(cmd=cls.command)) >> >> -def configure(debugger): >> - global LJ_64, LJ_GC64, LJ_FR2, LJ_DUALNUM, PADDING, LJ_TISNUM, target >> - target = debugger.GetSelectedTarget() >> - module = target.modules[0] >> - LJ_DUALNUM = module.FindSymbol('lj_lib_checknumber') is not None >> >> +def configure(debugger=None): >> + global PADDING, LJ_TISNUM, LJ_DUALNUM >> + dbg.setup_target(debugger) >> try: >> - irtype_enum = target.FindFirstType('IRType').enum_members >> - for member in irtype_enum: >> - if member.name == 'IRT_PTR': >> - LJ_64 = member.unsigned & 0x1f == IRT_P64 >> - if member.name == 'IRT_PGC': >> - LJ_FR2 = LJ_GC64 = member.unsigned & 0x1f == IRT_P64 >> + # Try to remove the callback at first to not append duplicates to >> + # gdb.events.new_objfile internal list. >> + dbg.event_disconnect(load) >> 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'] >> - >> - >> -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') >> + # Callback is not connected. >> + pass >> + >> + try: >> + # Detect whether libluajit objfile is loaded. >> + dbg.eval('luaJIT_setmode') >> + except Exception: >> + dbg.write('luajit_dbg.py initialization is postponed ' >> + 'until libluajit objfile is loaded\n') >> + # Add a callback to be executed when the next objfile is loaded. >> + dbg.event_connect(load) >> + return False >> + >> + try: >> + dbg.arch_init() >> + except Exception: >> + dbg.write('LuaJIT debug extension failed to load: ' >> + 'no debugging symbols found for libluajit') >> + return False >> + return True >> + >> + >> +# XXX: The dummy parameter is needed for this function to >> +# work as a gdb callback. >> +def load(_=None, debugger=None): >> + if configure(debugger): >> + register_commands(LJ_COMMANDS, debugger) >> + dbg.write('LuaJIT debug extension is successfully loaded') >> + >> + >> +def __lldb_init_module(debugger, _=None): >> + load(None, debugger) >> + >> + >> +if dbg.GDB: >> + load()