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