Tarantool development patches archive
 help / color / mirror / Atom feed
From: Sergey Bronnikov via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: Sergey Kaplun <skaplun@tarantool.org>,
	Mikhail Elhimov <m.elhimov@vk.team>,
	Evgeniy Temirgaleev <e.temirgaleev@tarantool.org>
Cc: tarantool-patches@dev.tarantool.org
Subject: Re: [Tarantool-patches] [PATCH v2 luajit 5/6] dbg: generalize extension
Date: Wed, 27 May 2026 15:38:56 +0300	[thread overview]
Message-ID: <2f29ca87-ac11-4459-8717-b62f077afdfa@tarantool.org> (raw)
In-Reply-To: <20260519123913.178775-6-skaplun@tarantool.org>

[-- Attachment #1: Type: text/plain, Size: 100605 bytes --]

Hi, Sergey,

thanks for the patch! LGTM with a minor comment.

Sergey

On 5/19/26 15:39, Sergey Kaplun wrote:
> 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 and in its child
> debugger classes with the specific implementations of required methods.
> The extension auto-detects the debugger it was loaded into and selects
> the correct low-level logic implementation.
>
> The F821 ignore is removed from <.flake8rc> since there is no more need
> for it.
> ---
>   .flake8rc                                     |    5 -
>   src/luajit-gdb.py                             |  884 ------------
>   src/luajit_dbg.py                             | 1281 +++++++++++++++++
>   src/luajit_lldb.py                            | 1015 -------------
>   .../debug-extension-tests.py                  |    4 +-
>   5 files changed, 1283 insertions(+), 1906 deletions(-)
>   delete mode 100644 src/luajit-gdb.py
>   create mode 100644 src/luajit_dbg.py

"autopep8 -i" generates the following patch:

--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -651,7 +651,7 @@ def frame_ftsz(framelink):

  def frame_pc(framelink):
      return dbg.cast('BCIns *', frame_ftsz(framelink)) if LJ_FR2 \
-                    else mref('BCIns *', framelink['fr']['tp']['pcr'])
+        else mref('BCIns *', framelink['fr']['tp']['pcr'])


  def frame_prevl(framelink):

Feel free to ignore, but I propose following PEP8 style from the beginning.


>   delete mode 100644 src/luajit_lldb.py
>
> diff --git a/.flake8rc b/.flake8rc
> index 13e6178f..e69de29b 100644
> --- a/.flake8rc
> +++ b/.flake8rc
> @@ -1,5 +0,0 @@
> -[flake8]
> -extend-ignore =
> -  # XXX: Suppress F821, since we have autogenerated names for
> -  # 'ptr' type complements in luajit_lldb.py.
> -  F821
> diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
> deleted file mode 100644
> index be67bf18..00000000
> --- a/src/luajit-gdb.py
> +++ /dev/null
> @@ -1,884 +0,0 @@
> -# GDB extension for LuaJIT post-mortem analysis.
> -# To use, just put 'source <path-to-repo>/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):
> -    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 "<luajit-gdb: error occurred while rendering non-ascii slot>"
> -
> -
> -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 + 3 * 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 <TValue *>
> -
> -The command receives a pointer to <tv> (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 @ <gcr>
> -* LJ_TSTR: string <string payload> @ <gcr>
> -* LJ_TUPVAL: upvalue @ <gcr>
> -* LJ_TTHREAD: thread @ <gcr>
> -* LJ_TPROTO: proto @ <gcr>
> -* LJ_TFUNC: <LFUNC|CFUNC|FFUNC>
> -  <LFUNC>: Lua function @ <gcr>, <nupvals> upvalues,<chunk:line>
> -  <CFUNC>: C function <mcode address>
> -  <FFUNC>: fast function #<ffid>
> -* LJ_TTRACE: trace <traceno> @ <gcr>
> -* LJ_TCDATA: cdata @ <gcr>
> -* LJ_TTAB: table @ <gcr> (asize: <asize>, hmask: <hmask>)
> -* LJ_TUDATA: userdata @ <gcr>
> -* LJ_TNUMX: number <numeric payload>
> -
> -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 <GCstr *>
> -
> -The command receives a <gcr> 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 <GCtab *>
> -
> -The command receives a GCtab address and dumps the table contents:
> -* Metatable address whether the one is set
> -* Array part <asize> slots:
> -  <aslot ptr>: [<index>]: <tv>
> -* Hash part <hsize> nodes:
> -  <hnode ptr>: { <tv> } => { <tv> }; next = <next hnode ptr>
> -    '''
> -
> -    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 [<lua_State *>]
> -
> -The command receives a lua_State address and dumps the given Lua
> -coroutine guest stack:
> -
> -<slot ptr> [<slot attributes>] <VALUE|FRAME>
> -
> -* <slot ptr>: guest stack slot address
> -* <slot attributes>:
> -  - 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)
> -* <VALUE>: see help lj-tv for more info
> -* <FRAME>: 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
> -  [<frame type>] delta=<slots in frame>, <lj-tv for LJ_TFUNC slot>
> -  - <frame type>:
> -    + 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: <INTERP|C|GC|EXIT|RECORD|OPT|ASM|TRACE>
> -* GC state: <PAUSE|PROPAGATE|ATOMIC|SWEEPSTRING|SWEEP|FINALIZE|LAST>
> -* JIT state: <IDLE|ACTIVE|RECORD|START|END|ASM|ERR>
> -    '''
> -
> -    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: <total number of allocated bytes in GC area>
> -* threshold: <limit when gc step is triggered>
> -* debt: <how much GC is behind schedule>
> -* estimate: <estimate of memory actually in use>
> -* stepmul: <incremental GC step granularity>
> -* pause: <pause between successive GC cycles>
> -* sweepstr: <sweep position in string table>
> -* root: <number of all collectable objects>
> -* gray: <number of gray objects>
> -* grayagain: <number of objects for atomic traversal>
> -* weak: <number of weak tables (to be cleared)>
> -* mmudata: <number of udata|cdata to be finalized>
> -    '''
> -
> -    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-gc':    LJGC,
> -        'lj-stack': LJDumpStack,
> -        'lj-state': LJState,
> -        'lj-str':   LJDumpString,
> -        'lj-tab':   LJDumpTable,
> -        'lj-tv':    LJDumpTValue,
> -    })
> -
> -
> -load(None)
> diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
> new file mode 100644
> index 00000000..164f0955
> --- /dev/null
> +++ b/src/luajit_dbg.py
> @@ -0,0 +1,1281 @@
> +# Debug extension for LuaJIT post-mortem analysis.
> +# To use in GDB:
> +# `source <path-to-repo>/src/luajit_dbg.py'
> +# To use in LLDB:
> +# `command script import <path-to-repo>/src/luajit_dbg.py'
> +
> +import abc
> +import re
> +import struct
> +import sys
> +
> +from importlib import import_module
> +
> +# Make the script compatible with ancient Python.
> +LEGACY = re.match(r'^2\.', sys.version)
> +
> +if LEGACY:
> +    int = long  # noqa: F821
> +    range = xrange  # noqa: F821
> +
> +
> +# Debugger. ######################################################
> +
> +
> +lldb = None
> +gdb = None
> +
> +# XXX: While the `gdb` library is only available inside a debug
> +# session, the `lldb` library can be loaded in any Python script.
> +# To address that, we need to perform an additional check to
> +# ensure a debug session is actually running.
> +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)
> +        if healthcheck(lib):
> +            globals()[name] = lib
> +            break
> +    except Exception:
> +        continue
> +
> +assert (not not lldb) != (not not gdb), 'Debugger must be either LLDB or GDB.'
> +
> +
> +class Debugger(object):
> +    def __init__(self):
> +        self.dbgtype_cache = {}
> +
> +    def __new__(self):
> +        if gdb:
> +            self.GDB = True
> +            return super(Debugger, self).__new__(_GDBDebugger)
> +        elif lldb:
> +            self.LLDB = True
> +            return super(Debugger, self).__new__(_LLDBDebugger)
> +
> +    def configure(self):
> +        global PADDING, LJ_TISNUM
> +        if not self.check_libluajit():
> +            return False
> +        try:
> +            self.init_luajit_arch()
> +            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']
> +        except Exception:
> +            self.write('luajit_dbg.py failed to load: '
> +                       'no debugging symbols found for libluajit\n')
> +            return False
> +        return True
> +
> +    def initialize_extension(self, commands):
> +        if self.configure():
> +            for name, command in commands.items():
> +                self.register_command(command, name)
> +                self.write('{} command initialized\n'.format(name))
> +            self.write('LuaJIT debug extension is successfully loaded\n')
> +
> +    @abc.abstractmethod
> +    def cast(self, typestr, val):
> +        '''Cast the value to the required C type.'''
> +        pass
> +
> +    @abc.abstractmethod
> +    def sizeof(self, typestr):
> +        '''Return the size of the given type in bytes.'''
> +        pass
> +
> +    @abc.abstractmethod
> +    def offsetof(self, typestr, fieldstr):
> +        '''Return the offset of the given field in the type in bytes.'''
> +        pass
> +
> +    @abc.abstractmethod
> +    def cstr(self, strptr):
> +        '''Return the content of the string by the given pointer.'''
> +        pass
> +
> +    @abc.abstractmethod
> +    def lookup_global(self, symbol):
> +        '''Look up the global C symbol by the given name.'''
> +        pass
> +
> +    @abc.abstractmethod
> +    def eval(self, command):
> +        '''Parse and evaluate the given debugger command.'''
> +        pass
> +
> +    @abc.abstractmethod
> +    def write(self, msg):
> +        '''Print the message.'''
> +        pass
> +
> +    @abc.abstractmethod
> +    def check_libluajit(self):
> +        '''Check that libluajit is loaded.
> +        Check that the object file with libluajit symbols is loaded.
> +        Postpone loading of the extension if needed.
> +        '''
> +        pass
> +
> +    @abc.abstractmethod
> +    def init_luajit_arch(self):
> +        '''Initialize LuaJIT architecture-specific globals.
> +        Initialize build-dependent global constants.
> +        If no debugging symbols are found raise an error.
> +        '''
> +        pass
> +
> +    @abc.abstractmethod
> +    def register_command(self, command, name):
> +        '''Register the command with the corresponding name.'''
> +        pass
> +
> +    @abc.abstractproperty
> +    def LJBase(self):
> +        '''Base command class.
> +        Provides the base class for the extension commands.
> +        '''
> +
> +
> +class _GDBDebugger(Debugger):
> +    def _dbgtype(self, typestr):
> +        if typestr in self.dbgtype_cache:
> +            return self.dbgtype_cache[typestr]
> +
> +        m = re.match(r'((?:(?:struct|union) )?\S*)\s*[*]', typestr)
> +
> +        dbgtype = gdb.lookup_type(typestr) if m is None \
> +            else gdb.lookup_type(m.group(1)).pointer()
> +
> +        self.dbgtype_cache[typestr] = dbgtype
> +        return dbgtype
> +
> +    def __init__(self):
> +        super(_GDBDebugger, self).__init__()
> +        self.CONNECTED = False
> +
> +    def cast(self, typestr, val):
> +        return gdb.Value(val).cast(self._dbgtype(typestr))
> +
> +    def sizeof(self, typestr):
> +        return self._dbgtype(typestr).sizeof
> +
> +    def offsetof(self, typestr, fieldstr):
> +        return int(self._dbgtype(typestr)[fieldstr].bitpos / 8)
> +
> +    def cstr(self, strptr):
> +        # A string is printed with a pointer to it. Just strip it.
> +        return re.sub(r'^0x[a-f0-9]+\s+(?=")', '', str(strptr))
> +
> +    def lookup_global(self, symbol):
> +        variable, _ = gdb.lookup_symbol(symbol)
> +        return variable.value() if variable else None
> +
> +    def eval(self, command):
> +        if not command:
> +            return None
> +
> +        ret = gdb.parse_and_eval(command)
> +        if not ret:
> +            raise gdb.GdbError('table argument empty')
> +        return ret
> +
> +    def write(self, msg):
> +        gdb.write(msg)
> +
> +    def check_libluajit(self):
> +        # XXX Fragile: Though connecting the callback looks bad,
> +        # it respects both Python 2 and Python 3 (see #4828).
> +        def connect(callback):
> +            if LEGACY:
> +                self.CONNECTED = True
> +            gdb.events.new_objfile.connect(callback)
> +
> +        # XXX Fragile: Though disconnecting the callback looks
> +        # bad, it respects both Python 2 and Python 3 (see #4828).
> +        def disconnect(callback):
> +            if LEGACY:
> +                if not self.CONNECTED:
> +                    return
> +                self.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_dbg.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 False
> +        return True
> +
> +    def init_luajit_arch(self):
> +        global LJ_64, LJ_DUALNUM, LJ_FR2, LJ_GC64
> +        LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
> +        LJ_DUALNUM = gdb.lookup_global_symbol('lj_lib_checknumber') is not None
> +        LJ_FR2 = LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
> +
> +    def register_command(self, command, name):
> +        command(name)
> +
> +    class LJBase(gdb and gdb.Command or object):
> +        def __init__(ljbase, name):
> +            # XXX Fragile: Though the command initialization looks
> +            # bad, it respects both Python 2 and Python 3.
> +            gdb.Command.__init__(ljbase, name, gdb.COMMAND_DATA)
> +
> +        def invoke(ljbase, args, from_tty):
> +            return ljbase.execute(args)
> +
> +        @abc.abstractmethod
> +        def execute(ljbase, args):
> +            '''Implementation of the command.
> +            Subclasses override this method to implement the logic of a given
> +            command, e.g. printing a stack.
> +            '''
> +
> +    LJBase = LJBase
> +
> +
> +class _LLDBDebugger(Debugger):
> +    def _lldb_tp_isfp(self, tp):
> +        return tp.GetBasicType() in [
> +            lldb.eBasicTypeFloat,
> +            lldb.eBasicTypeDouble,
> +            lldb.eBasicTypeLongDouble
> +        ]
> +
> +    def _lldb_value_from_raw(self, raw_value, size, tp):
> +        isfp = self._lldb_tp_isfp(tp)
> +        pack_flag = '<d' if isfp else '<Q'
> +        raw_data = struct.pack(pack_flag, raw_value)
> +        sbdata = lldb.SBData()
> +        sbdata.SetData(
> +            lldb.SBError(),
> +            raw_data,
> +            lldb.eByteOrderLittle,
> +            size
> +        )
> +        sbval_res = self.target.CreateValueFromData(
> +            # XXX: Name is required, let's make it meaningful.
> +            '({tp}){val}'.format(
> +                tp=tp.name,
> +                val=raw_value if isfp else hex(raw_value)
> +            ),
> +            sbdata,
> +            tp
> +        )
> +        return lldb.value(sbval_res)
> +
> +    def __init__(self):
> +        def lldb__add__(lldbval, other):
> +            other = int(other)
> +            sbvalue = lldbval.sbvalue
> +            if sbvalue.TypeIsPointerType():
> +                tp = sbvalue.GetType()
> +                sz = sbvalue.deref.size
> +                addr = sbvalue.GetValueAsUnsigned() + other * sz
> +                return self._lldb_value_from_raw(
> +                    addr, sbvalue.GetByteSize(), tp
> +                )
> +            else:
> +                return int(lldbval) + other
> +
> +        def lldb__bool__(lldbval):
> +            return int(lldbval) != 0
> +
> +        def lldb__ge__(lldbval, other):
> +            return int(lldbval) >= int(other)
> +
> +        def lldb__getitem__(lldbval, key):
> +            if type(key) is lldb.value:
> +                key = int(key)
> +            if type(key) is int:
> +                # Allow array access.
> +                return lldb.value(
> +                    lldbval.sbvalue.GetValueForExpressionPath('[%i]' % key)
> +                )
> +            elif type(key) is str:
> +                return lldb.value(lldbval.sbvalue.GetChildMemberWithName(key))
> +            raise Exception(TypeError('No item of type %s' % str(type(key))))
> +
> +        def lldb__gt__(lldbval, other):
> +            return int(lldbval) > int(other)
> +
> +        def lldb__le__(lldbval, other):
> +            return int(lldbval) <= int(other)
> +
> +        def lldb__lt__(lldbval, other):
> +            return int(lldbval) < int(other)
> +
> +        def lldb__str__(lldbval):
> +            # Instead of default GetSummary.
> +            if not lldbval.sbvalue.TypeIsPointerType():
> +                tp = lldbval.sbvalue.GetType()
> +                is_float = self._lldb_tp_isfp(tp)
> +                if is_float:
> +                    return lldbval.sbvalue.GetValue()
> +                else:
> +                    return str(int(lldbval))
> +
> +            s = lldbval.sbvalue.GetValue()
> +            if s[:2] == '0x':
> +                # Strip useless leading zeros.
> +                res = s[2:].lstrip('0')
> +                return '0x' + (res if res else '0')
> +            return s
> +
> +        def lldb__sub__(lldbval, other):
> +            if type(other) is not lldb.value or \
> +               type(other) is lldb.value and \
> +               not other.sbvalue.TypeIsPointerType():
> +                other = int(other)
> +            if type(other) is int:
> +                return lldb__add__(lldbval, -other)
> +            elif lldbval.sbvalue.TypeIsPointerType():
> +                sbval = lldbval.sbvalue
> +                osbval = other.sbvalue
> +                lldbval_tp = sbval.GetType()
> +                other_tp = osbval.GetType()
> +                # Subtract pointers of the same size only.
> +                elsz = lldbval_tp.GetDereferencedType().size
> +                if other_tp.GetDereferencedType().size != elsz:
> +                    raise Exception(
> +                        'Attempt to substruct {otp} from {stp}'.format(
> +                            stp=lldbval_tp.name,
> +                            otp=other_tp.name
> +                        )
> +                    )
> +                diff = sbval.GetValueAsUnsigned() - osbval.GetValueAsUnsigned()
> +                return int(diff / elsz)
> +            else:
> +                return int(lldbval) - int(other)
> +
> +        super(_LLDBDebugger, self).__init__()
> +        self.target = lldb.debugger.GetSelectedTarget()
> +        # Monkey-patch the lldb.value class.
> +        lldb.value.__add__ = lldb__add__
> +        lldb.value.__bool__ = lldb__bool__
> +        lldb.value.__ge__ = lldb__ge__
> +        lldb.value.__getitem__ = lldb__getitem__
> +        lldb.value.__gt__ = lldb__gt__
> +        lldb.value.__le__ = lldb__le__
> +        lldb.value.__lt__ = lldb__lt__
> +        lldb.value.__str__ = lldb__str__
> +        lldb.value.__sub__ = lldb__sub__
> +
> +        def lldb_major_version():
> +            version_string = lldb.SBDebugger.GetVersionString()
> +            match = re.search(r'(\d+)', version_string)
> +            if match:
> +                return int(match.group(1))
> +            return None
> +
> +        # Needed for features detection.
> +        self.version = lldb_major_version()
> +
> +    def _dbgtype(self, typestr):
> +        if typestr in self.dbgtype_cache:
> +            return self.dbgtype_cache[typestr]
> +
> +        m = re.match(r'((?:(?:struct|union) )?\S*)\s*[*]', typestr)
> +
> +        dbgtype = self.target.FindFirstType(typestr) if m is None \
> +            else self.target.FindFirstType(m.group(1)).GetPointerType()
> +
> +        self.dbgtype_cache[typestr] = dbgtype
> +        return dbgtype
> +
> +    def cast(self, typestr, val):
> +        if isinstance(val, lldb.value):
> +            val = val.sbvalue
> +        elif type(val) is int:
> +            tp = self._dbgtype(typestr)
> +            return self._lldb_value_from_raw(val, tp.GetByteSize(), tp)
> +        elif not isinstance(val, lldb.SBValue):
> +            raise Exception(
> +                'Unexpected cast from type: {t}.'.format(t=type(val))
> +            )
> +
> +        # XXX: Simply SBValue.Cast() works incorrectly since it
> +        # may take the 8 bytes of memory instead of 4, before the
> +        # cast. Construct the value on the fly.
> +        tp = self._dbgtype(typestr)
> +        is_fp = self._lldb_tp_isfp(tp)
> +        rawval = float(val.GetValue()) if is_fp else val.GetValueAsUnsigned()
> +        return self._lldb_value_from_raw(rawval, val.GetByteSize(), tp)
> +
> +    def sizeof(self, typestr):
> +        return self._dbgtype(typestr).GetByteSize()
> +
> +    def offsetof(self, typestr, fieldstr):
> +        def _type_member(type_obj, name):
> +            return next((x for x in type_obj.members if x.name == name), None)
> +
> +        type_obj = self._dbgtype(typestr)
> +        member = _type_member(type_obj, fieldstr)
> +        assert member is not None, 'There is no field {f} in {t}'.format(
> +            f=fieldstr,
> +            t=typestr,
> +        )
> +        return member.GetOffsetInBytes()
> +
> +    def cstr(self, strptr):
> +        return strptr.sbvalue.summary
> +
> +    def lookup_global(self, symbol):
> +        return self.target.FindFirstGlobalVariable(symbol)
> +
> +    def eval(self, command):
> +        if not command:
> +            return None
> +
> +        process = self.target.GetProcess()
> +        thread = process.GetSelectedThread()
> +        frame = thread.GetSelectedFrame()
> +        ret = frame.EvaluateExpression(command)
> +        return ret
> +
> +    def write(self, msg):
> +        sys.stdout.write(msg)
> +
> +    def check_libluajit(self):
> +        # TODO: Implement postpone loading for LLDB too.
> +        return True
> +
> +    def init_luajit_arch(self):
> +        global LJ_64, LJ_DUALNUM, LJ_FR2, LJ_GC64
> +        IRT_P64 = 9
> +        module = self.target.modules[0]
> +        LJ_DUALNUM = module.FindSymbol('lj_lib_checknumber') is not None
> +        irtype_enum = self.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
> +
> +    def register_command(self, command, name):
> +        command.name = name
> +        lldb.debugger.HandleCommand(
> +            'command script add {o} --class luajit_dbg.{cls} {cmd}'.format(
> +                o='--overwrite' if self.version >= 14 else '',
> +                cls=command.__name__,
> +                cmd=name,
> +            )
> +        )
> +
> +    class LJBase(object):
> +        # Ignore given parameters by LLDB.
> +        def __init__(ljbase, debugger, unused):
> +            pass
> +
> +        def get_short_help(ljbase):
> +            return ljbase.__doc__.splitlines()[1]
> +
> +        def get_long_help(ljbase):
> +            return ljbase.__doc__
> +
> +        def __call__(ljbase, debugger, args, exe_ctx, result):
> +            try:
> +                ljbase.execute(args)
> +            except Exception as e:
> +                msg = 'Failed to execute command `{}`: {}'.format(
> +                    ljbase.name,
> +                    e
> +                )
> +                result.SetError(msg)
> +
> +        @abc.abstractmethod
> +        def execute(ljbase, args):
> +            '''Implementation of the command.
> +            Subclasses override this method to implement the logic of a given
> +            command, e.g. printing a stack. Any unhandled exception will be
> +            automatically transformed into proper errors.
> +            '''
> +
> +    LJBase = LJBase
> +
> +
> +dbg = Debugger()
> +
> +
> +# LuaJIT. ########################################################
> +
> +
> +# Constants.
> +
> +
> +LJ_64 = None
> +LJ_DUALNUM = None
> +LJ_FR2 = None
> +LJ_GC64 = 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
> +
> +
> +# Simple converters.
> +
> +
> +def tou64(val):
> +    return dbg.cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF
> +
> +
> +def tou32(val):
> +    return int(val) & 0xFFFFFFFF
> +
> +
> +def i2notu32(val):
> +    return ~int(val) & 0xFFFFFFFF
> +
> +
> +def strx64(val):
> +    return re.sub('L?$', '', hex(int(tou64(val))))
> +
> +
> +# Types and TValues.
> +
> +
> +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')
> +
> +
> +def itype(o):
> +    return tou32(o['it64'] >> 47) if LJ_GC64 else o['it']
> +
> +
> +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 (dbg.cast('int32_t', itype(o)) >> 15) == -2
> +    else:
> +        return itype(o) == LJ_T['LIGHTUD']
> +
> +
> +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)
> +
> +
> +# 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 dbg.cast('ptrdiff_t', framelink['ftsz'] if LJ_FR2
> +                    else framelink['fr']['tp']['ftsz'])
> +
> +
> +def frame_pc(framelink):
> +    return dbg.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 dbg.cast('TValue *',
> +                    dbg.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
> +
> +
> +# 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)
> +
> +
> +# LuaJIT macro implementations and structure access.
> +
> +
> +def mref(typename, obj):
> +    return dbg.cast(typename, obj['ptr64'] if LJ_GC64 else obj['ptr32'])
> +
> +
> +def gcref(obj):
> +    return dbg.cast('GCobj *', obj['gcptr64'] if LJ_GC64
> +                    else dbg.cast('uintptr_t', obj['gcptr32']))
> +
> +
> +def gcval(obj):
> +    return dbg.cast('GCobj *', obj['gcptr64'] & LJ_GCVMASK if LJ_GC64
> +                    else dbg.cast('uintptr_t', obj['gcptr32']))
> +
> +
> +def gcnext(obj):
> +    return gcref(obj)['gch']['nextgc']
> +
> +
> +def L(L=None):
> +    # Look up a symbol for the main coroutine considering the host
> +    # application.
> +    # XXX Fragile: Though the loop initialization looks bad, it
> +    # respects both Python 2 and Python 3.
> +    for lstate in [L] + list(map(lambda main: dbg.lookup_global(main), (
> +        # LuaJIT main coro (see luajit/src/luajit.c).
> +        'globalL',
> +        # Tarantool main coro (see tarantool/src/lua/init.h).
> +        'tarantool_L',
> +        # TODO: Add more.
> +    ))):
> +        if lstate:
> +            return dbg.cast('lua_State *', lstate)
> +
> +
> +def G(L):
> +    return mref('global_State *', L['glref'])
> +
> +
> +def J(g):
> +    g_offset = dbg.offsetof('GG_State', 'g')
> +    J_offset = dbg.offsetof('GG_State', 'J')
> +    return dbg.cast('jit_State *', dbg.cast('char *', g) - g_offset + J_offset)
> +
> +
> +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 strdata(obj):
> +    try:
> +        return dbg.cstr(dbg.cast('char *', dbg.cast('GCstr *', obj) + 1))
> +    except UnicodeEncodeError:
> +        return "<luajit_dbg: error occurred while rendering non-ascii slot>"
> +
> +
> +def funcproto(func):
> +    assert func['ffid'] == 0, 'Attempt to take a prototype of non-Lua function'
> +    return dbg.cast('GCproto *',
> +                    mref('char *', func['pc']) - dbg.sizeof('GCproto'))
> +
> +
> +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,
> +}
> +
> +
> +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 = dbg.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(dbg.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 = dbg.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 = dbg.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(dbg.cast('int32_t', tv['i']))
> +    else:
> +        return 'number {}'.format(dbg.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=dbg.cast('TValue *', fr) - dbg.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 + 3 * 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))
> +
> +
> +# Extension commands. ############################################
> +
> +
> +class LJDumpArch(dbg.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 execute(self, arg):
> +        dbg.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 LJGC(dbg.LJBase):
> +    '''
> +lj-gc
> +
> +The command requires no args and dumps current GC stats:
> +* total: <total number of allocated bytes in GC area>
> +* threshold: <limit when gc step is triggered>
> +* debt: <how much GC is behind schedule>
> +* estimate: <estimate of memory actually in use>
> +* stepmul: <incremental GC step granularity>
> +* pause: <pause between successive GC cycles>
> +* sweepstr: <sweep position in string table>
> +* root: <number of all collectable objects>
> +* gray: <number of gray objects>
> +* grayagain: <number of objects for atomic traversal>
> +* weak: <number of weak tables (to be cleared)>
> +* mmudata: <number of udata|cdata to be finalized>
> +    '''
> +
> +    def execute(self, arg):
> +        g = G(L(None))
> +        dbg.write('GC stats: {state}\n{stats}\n'.format(
> +            state=gc_state(g),
> +            stats=dump_gc(g)
> +        ))
> +
> +
> +class LJDumpStack(dbg.LJBase):
> +    '''
> +lj-stack [<lua_State *>]
> +
> +The command receives a lua_State address and dumps the given Lua
> +coroutine guest stack:
> +
> +<slot ptr> [<slot attributes>] <VALUE|FRAME>
> +
> +* <slot ptr>: guest stack slot address
> +* <slot attributes>:
> +  - 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)
> +* <VALUE>: See help lj-tv for more info
> +* <FRAME>: Framelink slot differs from the value slot: it contains info
> +  related to the function being executed within this guest frame, its
> +  type, and a link to the parent guest frame
> +  [<frame type>] delta=<slots in frame>, <lj-tv for LJ_TFUNC slot>
> +  - <frame type>:
> +    + 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 executing pcall or xpcall
> +
> +If L is omitted, the main coroutine is used.
> +    '''
> +
> +    def execute(self, arg):
> +        dbg.write('{}\n'.format(dump_stack(L(dbg.eval(arg)))))
> +
> +
> +class LJState(dbg.LJBase):
> +    '''
> +lj-state
> +The command requires no args and dumps current VM and GC states:
> +* VM state: <INTERP|C|GC|EXIT|RECORD|OPT|ASM|TRACE>
> +* GC state: <PAUSE|PROPAGATE|ATOMIC|SWEEPSTRING|SWEEP|FINALIZE|LAST>
> +* JIT state: <IDLE|ACTIVE|RECORD|START|END|ASM|ERR>
> +    '''
> +
> +    def execute(self, arg):
> +        g = G(L(None))
> +        dbg.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 LJDumpString(dbg.LJBase):
> +    '''
> +lj-str <GCstr *>
> +
> +The command receives a <gcr> 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 execute(self, arg):
> +        string = dbg.cast('GCstr *', dbg.eval(arg))
> +        dbg.write("String: {body} [{len} bytes] with hash {hash}\n".format(
> +            body=strdata(string),
> +            hash=strx64(string['hash']),
> +            len=string['len'],
> +        ))
> +
> +
> +class LJDumpTable(dbg.LJBase):
> +    '''
> +lj-tab <GCtab *>
> +
> +The command receives a GCtab address and dumps the table contents:
> +* Metatable address whether the one is set
> +* Array part <asize> slots:
> +  <aslot ptr>: [<index>]: <tv>
> +* Hash part <hsize> nodes:
> +  <hnode ptr>: { <tv> } => { <tv> }; next = <next hnode ptr>
> +    '''
> +
> +    def execute(self, arg):
> +        t = dbg.cast('GCtab *', dbg.eval(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:
> +            dbg.write('Metatable detected: {}\n'.format(strx64(mt)))
> +
> +        dbg.write('Array part: {} slots\n'.format(capacity['apart']))
> +        for i in range(capacity['apart']):
> +            slot = array + i
> +            dbg.write('{ptr}: [{index}]: {value}\n'.format(
> +                ptr=slot,
> +                index=i,
> +                value=dump_tvalue(slot)
> +            ))
> +
> +        dbg.write('Hash part: {} nodes\n'.format(capacity['hpart']))
> +        # See hmask comment in lj_obj.h
> +        for i in range(capacity['hpart']):
> +            node = nodes + i
> +            dbg.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 LJDumpTValue(dbg.LJBase):
> +    '''
> +lj-tv <TValue *>
> +
> +The command receives a pointer to <tv> (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 @ <gcr>
> +* LJ_TSTR: string <string payload> @ <gcr>
> +* LJ_TUPVAL: upvalue @ <gcr>
> +* LJ_TTHREAD: thread @ <gcr>
> +* LJ_TPROTO: proto @ <gcr>
> +* LJ_TFUNC: <LFUNC|CFUNC|FFUNC>
> +  <LFUNC>: Lua function @ <gcr>, <nupvals> upvalues,<chunk:line>
> +  <CFUNC>: C function <mcode address>
> +  <FFUNC>: fast function #<ffid>
> +* LJ_TTRACE: trace <traceno> @ <gcr>
> +* LJ_TCDATA: cdata @ <gcr>
> +* LJ_TTAB: table @ <gcr> (asize: <asize>, hmask: <hmask>)
> +* LJ_TUDATA: userdata @ <gcr>
> +* LJ_TNUMX: <number|integer> <numeric payload>
> +
> +Whether the type of the given address differs from the listed above, then
> +error message occurs.
> +    '''
> +
> +    def execute(self, arg):
> +        tv = dbg.cast('TValue *', dbg.eval(arg))
> +        dbg.write('{}\n'.format(dump_tvalue(tv)))
> +
> +
> +def load(event=None):
> +    dbg.initialize_extension({
> +        'lj-arch':  LJDumpArch,
> +        'lj-gc':    LJGC,
> +        'lj-stack': LJDumpStack,
> +        'lj-state': LJState,
> +        'lj-str':   LJDumpString,
> +        'lj-tab':   LJDumpTable,
> +        'lj-tv':    LJDumpTValue,
> +    })
> +
> +
> +if gdb:
> +    load()
> +elif lldb:
> +    def __lldb_init_module(debugger, internal_dictionary):
> +        load()
> diff --git a/src/luajit_lldb.py b/src/luajit_lldb.py
> deleted file mode 100644
> index e3fe82fc..00000000
> --- a/src/luajit_lldb.py
> +++ /dev/null
> @@ -1,1015 +0,0 @@
> -# LLDB extension for LuaJIT post-mortem analysis.
> -# To use, just put 'command script import <path-to-repo>/src/luajit_lldb.py'
> -# in lldb.
> -
> -import abc
> -import re
> -import lldb
> -import struct
> -
> -LJ_64 = None
> -LJ_GC64 = None
> -LJ_FR2 = None
> -LJ_DUALNUM = None
> -PADDING = None
> -
> -# Constants
> -IRT_P64 = 9
> -LJ_GCVMASK = ((1 << 47) - 1)
> -LJ_TISNUM = 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
> -
> -# Debugger specific {{{
> -
> -
> -# Global
> -target = None
> -
> -
> -def lldb_tp_isfp(tp):
> -    return tp.GetBasicType() in [
> -        lldb.eBasicTypeFloat,
> -        lldb.eBasicTypeDouble,
> -        lldb.eBasicTypeLongDouble
> -    ]
> -
> -
> -def lldb_value_from_raw(raw_value, size, tp):
> -    isfp = lldb_tp_isfp(tp)
> -    pack_flag = '<d' if isfp else '<Q'
> -    raw_data = struct.pack(pack_flag, raw_value)
> -    sbdata = lldb.SBData()
> -    sbdata.SetData(
> -        lldb.SBError(),
> -        raw_data,
> -        lldb.eByteOrderLittle,
> -        size
> -    )
> -    sbval_res = target.CreateValueFromData(
> -        # XXX: The name is required. Let's make it meaningful.
> -        '({tp}){val}'.format(
> -            tp=tp.name,
> -            val=raw_value if isfp else hex(raw_value)
> -        ),
> -        sbdata,
> -        tp
> -    )
> -    return lldb.value(sbval_res)
> -
> -
> -def lldb__add__(self, other):
> -    other = int(other)
> -    sbvalue = self.sbvalue
> -    if sbvalue.TypeIsPointerType():
> -        tp = sbvalue.GetType()
> -        sz = sbvalue.deref.size
> -        addr = sbvalue.GetValueAsUnsigned() + other * sz
> -        return lldb_value_from_raw(addr, sbvalue.GetByteSize(), tp)
> -    else:
> -        return int(self) + other
> -
> -
> -def lldb__bool__(self):
> -    return int(self) != 0
> -
> -
> -def lldb__ge__(self, other):
> -    return int(self) >= int(other)
> -
> -
> -def lldb__getitem__(self, key):
> -    if type(key) is lldb.value:
> -        key = int(key)
> -    if type(key) is int:
> -        # Allow array access.
> -        return lldb.value(self.sbvalue.GetValueForExpressionPath('[%i]' % key))
> -    elif type(key) is str:
> -        return lldb.value(self.sbvalue.GetChildMemberWithName(key))
> -    raise Exception(TypeError('No item of type %s' % str(type(key))))
> -
> -
> -def lldb__gt__(self, other):
> -    return int(self) > int(other)
> -
> -
> -def lldb__le__(self, other):
> -    return int(self) <= int(other)
> -
> -
> -def lldb__lt__(self, other):
> -    return int(self) < int(other)
> -
> -
> -def lldb__str__(self):
> -    # Instead of default GetSummary.
> -    if not self.sbvalue.TypeIsPointerType():
> -        tp = self.sbvalue.GetType()
> -        is_float = lldb_tp_isfp(tp)
> -        if is_float:
> -            return self.sbvalue.GetValue()
> -        else:
> -            return str(int(self))
> -
> -    s = self.sbvalue.GetValue()
> -    if s[:2] == '0x':
> -        # Strip useless leading zeros.
> -        res = s[2:].lstrip('0')
> -        return '0x' + (res if res else '0')
> -    return s
> -
> -
> -def lldb__sub__(self, other):
> -    if type(other) is not lldb.value or \
> -       type(other) is lldb.value and not other.sbvalue.TypeIsPointerType():
> -        other = int(other)
> -    if type(other) is int:
> -        return lldb__add__(self, -other)
> -    elif self.sbvalue.TypeIsPointerType():
> -        ssbval = self.sbvalue
> -        osbval = other.sbvalue
> -        self_tp = ssbval.GetType()
> -        other_tp = osbval.GetType()
> -        # Subtract pointers of the same size only.
> -        elsz = self_tp.GetDereferencedType().size
> -        if other_tp.GetDereferencedType().size != elsz:
> -            raise Exception('Attempt to substruct {otp} from {stp}'.format(
> -                stp=self_tp.name,
> -                otp=other_tp.name
> -            ))
> -        diff = ssbval.GetValueAsUnsigned() - osbval.GetValueAsUnsigned()
> -        return int(diff / elsz)
> -    else:
> -        return int(self) - int(other)
> -
> -
> -# Monkey-patch the lldb.value class.
> -lldb.value.__add__ = lldb__add__
> -lldb.value.__bool__ = lldb__bool__
> -lldb.value.__ge__ = lldb__ge__
> -lldb.value.__getitem__ = lldb__getitem__
> -lldb.value.__gt__ = lldb__gt__
> -lldb.value.__le__ = lldb__le__
> -lldb.value.__lt__ = lldb__lt__
> -lldb.value.__str__ = lldb__str__
> -lldb.value.__sub__ = lldb__sub__
> -
> -
> -class Command(object):
> -    def __init__(self, debugger, unused):
> -        pass
> -
> -    def get_short_help(self):
> -        return self.__doc__.splitlines()[0]
> -
> -    def get_long_help(self):
> -        return self.__doc__
> -
> -    def __call__(self, debugger, command, exe_ctx, result):
> -        try:
> -            self.execute(debugger, command, result)
> -        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
> -
> -    @abc.abstractproperty
> -    def command(self):
> -        """Command name.
> -        This name will be used by LLDB in order to unique/ly identify an
> -        implementation that should be executed when a command is run
> -        in the REPL.
> -        """
> -
> -    @abc.abstractmethod
> -    def execute(self, debugger, args, result):
> -        """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
> -        communicated back via the provided result object, so that it's
> -        properly routed to LLDB frontend. Any unhandled exception will be
> -        automatically transformed into proper errors.
> -        """
> -
> -
> -gtype_cache = {}
> -
> -
> -def gtype(typestr):
> -    if typestr in gtype_cache:
> -        return gtype_cache[typestr]
> -
> -    m = re.match(r'((?:(?:struct|union) )?\S*)\s*[*]', typestr)
> -
> -    gtype = target.FindFirstType(typestr) if m is None \
> -        else target.FindFirstType(m.group(1)).GetPointerType()
> -
> -    gtype_cache[typestr] = gtype
> -    return gtype
> -
> -
> -def cast(typestr, val):
> -    if isinstance(val, lldb.value):
> -        val = val.sbvalue
> -    elif type(val) is int:
> -        tp = gtype(typestr)
> -        return lldb_value_from_raw(val, tp.GetByteSize(), tp)
> -    elif not isinstance(val, lldb.SBValue):
> -        raise Exception('unexpected cast from type: {t}'.format(t=type(val)))
> -
> -    # XXX: Simply SBValue.Cast() works incorrectly since it may
> -    # take the 8 bytes of memory instead of 4, before the cast.
> -    # Construct the value on the fly.
> -    tp = gtype(typestr)
> -    is_fp = lldb_tp_isfp(tp)
> -    rawval = float(val.GetValue()) if is_fp else val.GetValueAsUnsigned()
> -    return lldb_value_from_raw(rawval, val.GetByteSize(), tp)
> -
> -
> -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 offsetof(typename, membername):
> -    type_obj = gtype(typename)
> -    member = type_member(type_obj, membername)
> -    assert member is not None
> -    return member.GetOffsetInBytes()
> -
> -
> -def sizeof(typename):
> -    type_obj = gtype(typename)
> -    return type_obj.GetByteSize()
> -
> -
> -def tou64(val):
> -    return cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF
> -
> -
> -def dbg_eval(expr):
> -    process = target.GetProcess()
> -    thread = process.GetSelectedThread()
> -    frame = thread.GetSelectedFrame()
> -    return frame.EvaluateExpression(expr)
> -
> -
> -# }}} Debugger specific
> -
> -
> -def gcval(obj):
> -    return cast('GCobj *', obj['gcptr64'] & LJ_GCVMASK if LJ_GC64
> -                else cast('uintptr_t', obj['gcptr32']))
> -
> -
> -def gcref(obj):
> -    return cast('GCobj *', obj['gcptr64'] if LJ_GC64
> -                else cast('uintptr_t', obj['gcptr32']))
> -
> -
> -def gcnext(obj):
> -    return gcref(obj)['gch']['nextgc']
> -
> -
> -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,
> -}
> -
> -
> -def dump_gc(g):
> -    gc = g['gc']
> -    stats = ['{key}: {value}'.format(key=f, value=getattr(gc, f)) for f in (
> -        'total', 'threshold', 'debt', 'estimate', 'stepmul', 'pause'
> -    )]
> -
> -    stats += ['sweepstr: {sweepstr}/{strmask}'.format(
> -        sweepstr=gc['sweepstr'],
> -        # String hash mask (size of hash table - 1).
> -        strmask=g['strmask'] + 1,
> -    )]
> -
> -    stats += ['{key}: {number} objects'.format(
> -        key=stat,
> -        number=handler(getattr(gc, stat))
> -    ) for stat, handler in gclen.items()]
> -    return '\n'.join(map(lambda s: '\t' + s, stats))
> -
> -
> -def mref(typename, obj):
> -    return cast(typename, obj['ptr64'] if LJ_GC64 else obj['ptr32'])
> -
> -
> -def J(g):
> -    g_offset = offsetof('GG_State', 'g')
> -    J_offset = offsetof('GG_State', 'J')
> -    return cast('jit_State *', (cast('char *', g) - g_offset + J_offset))
> -
> -
> -def G(L):
> -    return mref('global_State *', L['glref'])
> -
> -
> -def L(L=None):
> -    # lookup a symbol for the main coroutine considering the host app
> -    # XXX Fragile: though the loop initialization looks like a crap but it
> -    # respects both Python 2 and Python 3.
> -    for lstate in [L] + list(map(lambda main: lookup_global(main), (
> -        # LuaJIT main coro (see luajit/src/luajit.c)
> -        'globalL',
> -        # Tarantool main coro (see tarantool/src/lua/init.h)
> -        'tarantool_L',
> -        # TODO: Add more
> -    ))):
> -        if lstate:
> -            return cast('lua_State *', lstate)
> -
> -
> -def tou32(val):
> -    return val & 0xFFFFFFFF
> -
> -
> -def i2notu32(val):
> -    return ~int(val) & 0xFFFFFFFF
> -
> -
> -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 strx64(val):
> -    return re.sub('L?$', '',
> -                  hex(int(cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF)))
> -
> -
> -def funcproto(func):
> -    assert func.ffid == 0
> -    return cast('GCproto *', mref('char *', func.pc) - sizeof('GCproto'))
> -
> -
> -def strdata(obj):
> -    try:
> -        ptr = cast('char *', cast('GCstr *', obj) + 1)
> -        return ptr.sbvalue.summary
> -    except UnicodeEncodeError:
> -        return "<luajit-lldb: error occurred while rendering non-ascii slot>"
> -
> -
> -def itype(o):
> -    return tou32(o['it64'] >> 47) if LJ_GC64 else o['it']
> -
> -
> -def tvisint(o):
> -    return LJ_DUALNUM and itype(o) == LJ_TISNUM
> -
> -
> -def tvislightud(o):
> -    if LJ_64 and not LJ_GC64:
> -        return (int(cast('int32_t', itype(o))) >> 15) == -2
> -    else:
> -        return itype(o) == LJ_T['LIGHTUD']
> -
> -
> -def tvisnumber(o):
> -    return itype(o) <= LJ_TISNUM
> -
> -
> -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'])
> -
> -
> -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(cast('GCstr *', 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('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=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(ffid)
> -
> -
> -def dump_lj_ttrace(tv):
> -    trace = cast('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,
> -}
> -
> -
> -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 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 typenames(value):
> -    return {
> -        LJ_T[k]: 'LJ_T' + k for k in LJ_T.keys()
> -    }.get(int(value), 'LJ_TINVALID')
> -
> -
> -def dump_tvalue(tvalue):
> -    return dumpers.get(typenames(itypemap(tvalue)), dump_lj_invalid)(tvalue)
> -
> -
> -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 framelink - int(frame_sized(framelink) / sizeof('TValue'))
> -
> -
> -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(frame_type(framelink)) == 'L' \
> -        and 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
> -
> -
> -# 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 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 + 3 * 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)
> -
> -
> -class LJDumpTValue(Command):
> -    '''
> -lj-tv <TValue *>
> -
> -The command receives a pointer to <tv> (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 @ <gcr>
> -* LJ_TSTR: string <string payload> @ <gcr>
> -* LJ_TUPVAL: upvalue @ <gcr>
> -* LJ_TTHREAD: thread @ <gcr>
> -* LJ_TPROTO: proto @ <gcr>
> -* LJ_TFUNC: <LFUNC|CFUNC|FFUNC>
> -  <LFUNC>: Lua function @ <gcr>, <nupvals> upvalues,<chunk:line>
> -  <CFUNC>: C function <mcode address>
> -  <FFUNC>: fast function #<ffid>
> -* LJ_TTRACE: trace <traceno> @ <gcr>
> -* LJ_TCDATA: cdata @ <gcr>
> -* LJ_TTAB: table @ <gcr> (asize: <asize>, hmask: <hmask>)
> -* LJ_TUDATA: userdata @ <gcr>
> -* LJ_TNUMX: number <numeric payload>
> -
> -Whether the type of the given address differs from the listed above, then
> -error message occurs.
> -    '''
> -    def execute(self, debugger, args, result):
> -        tvptr = cast('TValue *', self.parse(args))
> -        print('{}'.format(dump_tvalue(tvptr)))
> -
> -
> -class LJState(Command):
> -    '''
> -lj-state
> -The command requires no args and dumps current VM and GC states
> -* VM state: <INTERP|C|GC|EXIT|RECORD|OPT|ASM|TRACE>
> -* GC state: <PAUSE|PROPAGATE|ATOMIC|SWEEPSTRING|SWEEP|FINALIZE|LAST>
> -* JIT state: <IDLE|ACTIVE|RECORD|START|END|ASM|ERR>
> -    '''
> -    def execute(self, debugger, args, result):
> -        g = G(L(None))
> -        print('{}'.format('\n'.join(
> -            map(lambda t: '{} state: {}'.format(*t), {
> -                'VM':  vm_state(g),
> -                'GC':  gc_state(g),
> -                'JIT': jit_state(g),
> -            }.items())
> -        )))
> -
> -
> -class LJDumpArch(Command):
> -    '''
> -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 execute(self, debugger, args, result):
> -        print(
> -            'LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}, LJ_DUALNUM: {LJ_DUALNUM}'
> -            .format(
> -                LJ_64=LJ_64,
> -                LJ_GC64=LJ_GC64,
> -                LJ_DUALNUM=LJ_DUALNUM
> -            )
> -        )
> -
> -
> -class LJGC(Command):
> -    '''
> -lj-gc
> -
> -The command requires no args and dumps current GC stats:
> -* total: <total number of allocated bytes in GC area>
> -* threshold: <limit when gc step is triggered>
> -* debt: <how much GC is behind schedule>
> -* estimate: <estimate of memory actually in use>
> -* stepmul: <incremental GC step granularity>
> -* pause: <pause between successive GC cycles>
> -* sweepstr: <sweep position in string table>
> -* root: <number of all collectable objects>
> -* gray: <number of gray objects>
> -* grayagain: <number of objects for atomic traversal>
> -* weak: <number of weak tables (to be cleared)>
> -* mmudata: <number of udata|cdata to be finalized>
> -    '''
> -    def execute(self, debugger, args, result):
> -        g = G(L(None))
> -        print('GC stats: {state}\n{stats}'.format(
> -            state=gc_state(g),
> -            stats=dump_gc(g)
> -        ))
> -
> -
> -class LJDumpString(Command):
> -    '''
> -lj-str <GCstr *>
> -
> -The command receives a <gcr> 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 execute(self, debugger, args, result):
> -        string = cast('GCstr *', self.parse(args))
> -        print("String: {body} [{len} bytes] with hash {hash}".format(
> -            body=strdata(string),
> -            hash=strx64(string['hash']),
> -            len=string['len'],
> -        ))
> -
> -
> -class LJDumpTable(Command):
> -    '''
> -lj-tab <GCtab *>
> -
> -The command receives a GCtab address and dumps the table contents:
> -* Metatable address whether the one is set
> -* Array part <asize> slots:
> -  <aslot ptr>: [<index>]: <tv>
> -* Hash part <hsize> nodes:
> -  <hnode ptr>: { <tv> } => { <tv> }; next = <next hnode ptr>
> -    '''
> -    def execute(self, debugger, args, result):
> -        t = cast('GCtab *', self.parse(args))
> -        array = mref('TValue *', t['array'])
> -        nodes = mref('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:
> -            print('Metatable detected: {}'.format(strx64(mt)))
> -
> -        print('Array part: {} slots'.format(capacity['apart']))
> -        for i in range(capacity['apart']):
> -            slot = array + i
> -            print('{ptr}: [{index}]: {value}'.format(
> -                ptr=strx64(slot),
> -                index=i,
> -                value=dump_tvalue(slot)
> -            ))
> -
> -        print('Hash part: {} nodes'.format(capacity['hpart']))
> -        # See hmask comment in lj_obj.h
> -        for i in range(capacity['hpart']):
> -            node = nodes + i
> -            print('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}'.format(
> -                ptr=strx64(node),
> -                key=dump_tvalue(node['key']),
> -                val=dump_tvalue(node['val']),
> -                n=strx64(mref('Node *', node['next']))
> -            ))
> -
> -
> -class LJDumpStack(Command):
> -    '''
> -lj-stack [<lua_State *>]
> -
> -The command receives a lua_State address and dumps the given Lua
> -coroutine guest stack:
> -
> -<slot ptr> [<slot attributes>] <VALUE|FRAME>
> -
> -* <slot ptr>: guest stack slot address
> -* <slot attributes>:
> -  - 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)
> -* <VALUE>: see help lj-tv for more info
> -* <FRAME>: 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
> -  [<frame type>] delta=<slots in frame>, <lj-tv for LJ_TFUNC slot>
> -  - <frame type>:
> -    + 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 execute(self, debugger, args, result):
> -        print('{}'.format(dump_stack(L(self.parse(args)))))
> -
> -
> -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 initialized'.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
> -
> -    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
> -    except Exception:
> -        print('luajit_lldb.py failed to load: '
> -              'no debugging symbols found for libluajit')
> -        return
> -
> -    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']
> -
> -
> -def __lldb_init_module(debugger, internal_dict):
> -    configure(debugger)
> -    register_commands(debugger, {
> -        'lj-arch':  LJDumpArch,
> -        'lj-gc':    LJGC,
> -        'lj-stack': LJDumpStack,
> -        'lj-state': LJState,
> -        'lj-str':   LJDumpString,
> -        'lj-tab':   LJDumpTable,
> -        'lj-tv':    LJDumpTValue,
> -    })
> -    print('luajit_lldb.py is successfully loaded')
> diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
> index 2b67e151..30a2c478 100644
> --- a/test/tarantool-debugger-tests/debug-extension-tests.py
> +++ b/test/tarantool-debugger-tests/debug-extension-tests.py
> @@ -16,7 +16,7 @@ LUAJIT_BINARY = os.environ['LUAJIT_TEST_BINARY']
>   EXTENSION_PATH = os.environ['DEBUGGER_EXTENSION_PATH']
>   DEBUGGER = os.environ['DEBUGGER_COMMAND']
>   LLDB = 'lldb' in DEBUGGER
> -EXTENSION = EXTENSION_PATH + ('/luajit_lldb.py' if LLDB else '/luajit-gdb.py')
> +EXTENSION = EXTENSION_PATH + '/luajit_dbg.py'
>   TIMEOUT = 10
>   
>   # Don't run any initialization scripts.
> @@ -137,7 +137,7 @@ class TestLoad(TestCaseBase):
>           r'lj-str command initialized\n'
>           r'lj-tab command initialized\n'
>           r'lj-tv command initialized\n'
> -        r'.*is successfully loaded'
> +        r'LuaJIT debug extension is successfully loaded'
>       )
>   
>   

[-- Attachment #2: Type: text/html, Size: 96765 bytes --]

  reply	other threads:[~2026-05-27 12:39 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-19 12:39 [Tarantool-patches] [PATCH v2 luajit 0/6] Unified extension for debuggers Sergey Kaplun via Tarantool-patches
2026-05-19 12:39 ` [Tarantool-patches] [PATCH v2 luajit 1/6] test: introduce tests for debugging extensions Sergey Kaplun via Tarantool-patches
2026-05-20 13:38   ` Sergey Bronnikov via Tarantool-patches
2026-05-25  9:14     ` Sergey Kaplun via Tarantool-patches
2026-05-27  9:54       ` Sergey Bronnikov via Tarantool-patches
2026-05-27 10:52         ` Sergey Kaplun via Tarantool-patches
2026-05-26 13:50   ` Evgeniy Temirgaleev via Tarantool-patches
2026-05-26 14:41     ` Sergey Kaplun via Tarantool-patches
2026-05-26 18:52       ` Evgeniy Temirgaleev via Tarantool-patches
2026-05-27  7:56         ` Sergey Kaplun via Tarantool-patches
2026-05-27 12:41   ` Sergey Bronnikov via Tarantool-patches
2026-05-19 12:39 ` [Tarantool-patches] [PATCH v2 luajit 2/6] lldb: refactor extension Sergey Kaplun via Tarantool-patches
2026-05-27 12:27   ` Sergey Bronnikov via Tarantool-patches
2026-05-19 12:39 ` [Tarantool-patches] [PATCH v2 luajit 3/6] dbg: sort initialization of commands Sergey Kaplun via Tarantool-patches
2026-05-20 13:43   ` Sergey Bronnikov via Tarantool-patches
2026-05-19 12:39 ` [Tarantool-patches] [PATCH v2 luajit 4/6] lldb: support full-range 64-bit lightuserdata Sergey Kaplun via Tarantool-patches
2026-05-27 12:28   ` Sergey Bronnikov via Tarantool-patches
2026-05-19 12:39 ` [Tarantool-patches] [PATCH v2 luajit 5/6] dbg: generalize extension Sergey Kaplun via Tarantool-patches
2026-05-27 12:38   ` Sergey Bronnikov via Tarantool-patches [this message]
2026-05-27 12:55     ` Sergey Kaplun via Tarantool-patches
2026-05-19 12:39 ` [Tarantool-patches] [PATCH v2 luajit 6/6] ci: introduce workflow to test debugger extension Sergey Kaplun via Tarantool-patches
2026-05-20 13:52   ` Sergey Bronnikov via Tarantool-patches
2026-05-25  7:00     ` Sergey Kaplun via Tarantool-patches
2026-05-27 10:57       ` Sergey Bronnikov via Tarantool-patches
2026-05-27 11:58         ` Sergey Kaplun via Tarantool-patches

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=2f29ca87-ac11-4459-8717-b62f077afdfa@tarantool.org \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=e.temirgaleev@tarantool.org \
    --cc=m.elhimov@vk.team \
    --cc=sergeyb@tarantool.org \
    --cc=skaplun@tarantool.org \
    --subject='Re: [Tarantool-patches] [PATCH v2 luajit 5/6] dbg: generalize extension' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox