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