From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 27879722123; Wed, 6 Dec 2023 17:15:56 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 27879722123 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1701872156; bh=WWmYjpG6GNef4bkUpprnYDVsrInmrhNDPoOEXXkPwJo=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=OwEaGYGqWwkA5Pzotihc48la02dukehD1yDTiJeh2vmxenfp5rFI09r7YVxOoGgVi CgHkp/TaGjhTbxBawfeRsuybLkRnIVsIvGKYz9U4x+3TOG5OJSAmUlNnMd+1Xbdrm0 Ta+iX6EM/dNE/nJtbI35tjw1r23hk8INUl0px6Zk= Received: from mail-lj1-f172.google.com (mail-lj1-f172.google.com [209.85.208.172]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 52DB9722116 for ; Wed, 6 Dec 2023 17:15:27 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 52DB9722116 Received: by mail-lj1-f172.google.com with SMTP id 38308e7fff4ca-2c9efa1ab7fso55510001fa.0 for ; Wed, 06 Dec 2023 06:15:27 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1701872126; x=1702476926; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Yze3/XkV/FXvsLG2g+ykHprho5BirOeEGYNdqQqZ1Fs=; b=vJnZfpyqdcduyWLb385N1UczKAhTyKqkCr2R8r5oj7fgiPW3gi9RrO4HmD32jQAHtG hWtOK0RAe2KK+gTRHvSts6hgo38rjg/phLSB2AaV6dhvHyokpCj1jZVCqv96X/W2tOTn quclssodWN68p1+8UIvC7Xy+gFza1veyLXtrGtGCzLm6mRGO4UONKmhRGPhyUgu+9ddL NnjMq74b9bp24+6zTddeTuCJMMwWiVCogtIMSnyCBh+tXHS5JgIybo2dri62nPP/mO1+ csrU3Os0n3FeuhLi8cm2pL44cvmeFEFp8g0oJ7Y8dJYKhjjL/inv9FZXivfQDDI23Gdq AW1w== X-Gm-Message-State: AOJu0Ywwo8MpVw8Zz0KTTP53qzWzN5C1Y0ArdhEgq1tDS4iq+XDreY0Q 1BP0GfjV/Gt5B/uALobz9F+DD/ntixF4gg== X-Google-Smtp-Source: AGHT+IGDe/3BkLroGQ4rwdSdzFnVhZENifTP2mF6KH9b1Lsxf/y4Ci5yt+mFq/j8EbaoQr4WXJZexw== X-Received: by 2002:a2e:9190:0:b0:2c9:ffbd:3cb0 with SMTP id f16-20020a2e9190000000b002c9ffbd3cb0mr335565ljg.183.1701872125588; Wed, 06 Dec 2023 06:15:25 -0800 (PST) Received: from fckxorg.mail.msk ([2a00:1148:b0ba:16:a3e8:bdc1:dbed:dbc8]) by smtp.gmail.com with ESMTPSA id u7-20020a2ea167000000b002c9fc51f34csm1158048ljl.32.2023.12.06.06.15.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 06 Dec 2023 06:15:25 -0800 (PST) X-Google-Original-From: Maxim Kokryashkin To: tarantool-patches@dev.tarantool.org, skaplun@tarantool.org, sergeyb@tarantool.org, imun@tarantool.org Date: Wed, 6 Dec 2023 17:14:55 +0300 Message-ID: X-Mailer: git-send-email 2.43.0 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH luajit v5 1/2] debug: generalized extension X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Maxim Kokryashkin via Tarantool-patches Reply-To: Maxim Kokryashkin Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" 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 d2070e9b..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 address 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 omitted 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 5ac11b65..e79b7c32 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 address 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 address 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 omitted 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() -- 2.43.0