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 2/6] lldb: refactor extension
Date: Wed, 27 May 2026 15:27:16 +0300	[thread overview]
Message-ID: <e7707d21-9192-4469-89ef-fee56964ef67@tarantool.org> (raw)
In-Reply-To: <20260519123913.178775-3-skaplun@tarantool.org>

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

Hi, Sergey,

thanks for the patch! LGTM

Sergey

On 5/19/26 15:39, Sergey Kaplun wrote:
> This patch refactors lldb python extension C structure casts and type
> handling. Now it uses a monkey-patched lldb.value class instead of a
> handwritten one. Hence, there is no need for hardcoded C type structure
> fields. All LuaJIT-related functions use the same semantics as in the
> gdb extension. Also, this patch changes all fields' access to item
> access instead of attribute access.
>
> This commit fixes all tests except the initialization check since the
> order of loaded commands is different from gdb. This will be fixed in
> the next commit.
> ---
>   src/luajit_lldb.py                            | 605 +++++++-----------
>   .../debug-extension-tests.py                  |  33 +-
>   2 files changed, 256 insertions(+), 382 deletions(-)
>
> diff --git a/src/luajit_lldb.py b/src/luajit_lldb.py
> index 6e66954f..218cc8d1 100644
> --- a/src/luajit_lldb.py
> +++ b/src/luajit_lldb.py
> @@ -5,6 +5,7 @@
>   import abc
>   import re
>   import lldb
> +import struct
>   
>   LJ_64 = None
>   LJ_GC64 = None
> @@ -24,213 +25,132 @@ LJ_TISNUM = None
>   target = None
>   
>   
> -class Ptr:
> -    def __init__(self, value, normal_type):
> -        self.value = value
> -        self.normal_type = normal_type
> -
> -    @property
> -    def __deref(self):
> -        return self.normal_type(self.value.Dereference())
> -
> -    def __add__(self, other):
> -        assert isinstance(other, int)
> -        return self.__class__(
> -            cast(
> -                self.normal_type.__name__ + ' *',
> -                cast(
> -                    'uintptr_t',
> -                    self.value.unsigned + other * self.value.deref.size,
> -                ),
> -            ),
> -        )
> +def lldb_tp_isfp(tp):
> +    return tp.GetBasicType() in [
> +        lldb.eBasicTypeFloat,
> +        lldb.eBasicTypeDouble,
> +        lldb.eBasicTypeLongDouble
> +    ]
>   
> -    def __sub__(self, other):
> -        assert isinstance(other, int) or isinstance(other, Ptr)
> -        if isinstance(other, int):
> -            return self.__add__(-other)
> -        else:
> -            return int((self.value.unsigned - other.value.unsigned)
> -                       / sizeof(self.normal_type.__name__))
>   
> -    def __eq__(self, other):
> -        assert isinstance(other, Ptr) or isinstance(other, int) and other >= 0
> -        if isinstance(other, Ptr):
> -            return self.value.unsigned == other.value.unsigned
> -        else:
> -            return self.value.unsigned == other
> -
> -    def __ne__(self, other):
> -        return not self == other
> -
> -    def __gt__(self, other):
> -        assert isinstance(other, Ptr)
> -        return self.value.unsigned > other.value.unsigned
> -
> -    def __ge__(self, other):
> -        assert isinstance(other, Ptr)
> -        return self.value.unsigned >= other.value.unsigned
> -
> -    def __bool__(self):
> -        return self.value.unsigned != 0
> -
> -    def __int__(self):
> -        return self.value.unsigned
> -
> -    def __str__(self):
> -        return self.value.value
> -
> -    def __getattr__(self, name):
> -        if name != '__deref':
> -            return getattr(self.__deref, name)
> -        return self.__deref
> -
> -
> -class MetaStruct(type):
> -    def __init__(cls, name, bases, nmspc):
> -        super(MetaStruct, cls).__init__(name, bases, nmspc)
> -
> -        def make_general(field, tp):
> -            builtin = {
> -                        'uint':   'unsigned',
> -                        'int':    'signed',
> -                        'string': 'value',
> -                    }
> -            if tp in builtin.keys():
> -                return lambda self: getattr(self[field], builtin[tp])
> -            else:
> -                return lambda self: globals()[tp](self[field])
> -
> -        if hasattr(cls, 'metainfo'):
> -            for field in cls.metainfo:
> -                if not isinstance(field[0], str):
> -                    setattr(cls, field[1], field[0])
> -                else:
> -                    setattr(
> -                        cls,
> -                        field[1],
> -                        property(make_general(field[1], field[0])),
> -                    )
> -
> -
> -class Struct(metaclass=MetaStruct):
> -    def __init__(self, value):
> -        self.value = value
> -
> -    def __getitem__(self, name):
> -        return self.value.GetChildMemberWithName(name)
> -
> -    @property
> -    def addr(self):
> -        return self.value.address_of
> -
> -
> -c_structs = {
> -    'MRef': [
> -        (property(lambda self: self['ptr64'].unsigned if LJ_GC64
> -                  else self['ptr32'].unsigned), 'ptr')
> -    ],
> -    'GCRef': [
> -        (property(lambda self: self['gcptr64'].unsigned if LJ_GC64
> -                  else self['gcptr32'].unsigned), 'gcptr')
> -    ],
> -    'TValue': [
> -        ('GCRef', 'gcr'),
> -        ('uint', 'it'),
> -        ('uint', 'i'),
> -        ('int', 'it64'),
> -        ('string', 'n'),
> -        (property(lambda self: FR(self['fr']) if not LJ_GC64 else None), 'fr'),
> -        (property(lambda self: self['ftsz'].signed if LJ_GC64 else None),
> -         'ftsz')
> -    ],
> -    'GCState': [
> -        ('GCRef', 'root'),
> -        ('GCRef', 'gray'),
> -        ('GCRef', 'grayagain'),
> -        ('GCRef', 'weak'),
> -        ('GCRef', 'mmudata'),
> -        ('uint', 'state'),
> -        ('uint', 'total'),
> -        ('uint', 'threshold'),
> -        ('uint', 'debt'),
> -        ('uint', 'estimate'),
> -        ('uint', 'stepmul'),
> -        ('uint', 'pause'),
> -        ('uint', 'sweepstr')
> -    ],
> -    'lua_State': [
> -        ('MRef', 'glref'),
> -        ('MRef', 'stack'),
> -        ('MRef', 'maxstack'),
> -        ('TValuePtr', 'top'),
> -        ('TValuePtr', 'base')
> -    ],
> -    'global_State': [
> -        ('GCState', 'gc'),
> -        ('uint', 'vmstate'),
> -        ('uint', 'strmask')
> -    ],
> -    'jit_State': [
> -        ('uint', 'state')
> -    ],
> -    'GChead': [
> -        ('GCRef', 'nextgc')
> -    ],
> -    'GCobj': [
> -        ('GChead', 'gch')
> -    ],
> -    'GCstr': [
> -        ('uint', 'hash'),
> -        ('uint', 'len')
> -    ],
> -    'FrameLink': [
> -        ('MRef', 'pcr'),
> -        ('int', 'ftsz')
> -    ],
> -    'FR': [
> -        ('FrameLink', 'tp')
> -    ],
> -    'GCfuncC': [
> -        ('MRef', 'pc'),
> -        ('uint', 'ffid'),
> -        ('uint', 'nupvalues'),
> -        ('uint', 'f')
> -    ],
> -    'GCtab': [
> -        ('MRef', 'array'),
> -        ('MRef', 'node'),
> -        ('GCRef', 'metatable'),
> -        ('uint', 'asize'),
> -        ('uint', 'hmask')
> -    ],
> -    'GCproto': [
> -        ('GCRef', 'chunkname'),
> -        ('int', 'firstline')
> -    ],
> -    'GCtrace': [
> -        ('uint', 'traceno')
> -    ],
> -    'Node': [
> -        ('TValue', 'key'),
> -        ('TValue', 'val'),
> -        ('MRef', 'next')
> -    ],
> -    'BCIns': []
> -}
> +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)
>   
>   
> -for cls in c_structs.keys():
> -    globals()[cls] = type(cls, (Struct, ), {'metainfo': c_structs[cls]})
> +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
>   
>   
> -for cls in Struct.__subclasses__():
> -    ptr_name = cls.__name__ + 'Ptr'
> +def lldb__bool__(self):
> +    return int(self) != 0
>   
> -    globals()[ptr_name] = type(ptr_name, (Ptr,), {
> -        '__init__':
> -            lambda self, value: super(type(self), self).__init__(value, cls)
> -    })
> +
> +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):
> @@ -280,52 +200,38 @@ class Command(object):
>           """
>   
>   
> -def cast(typename, value):
> -    pointer_type = False
> -    name = None
> -    if isinstance(value, Struct) or isinstance(value, Ptr):
> -        # Get underlying value, if passed object is a wrapper.
> -        value = value.value
> +gtype_cache = {}
>   
> -    # Obtain base type name, decide whether it's a pointer.
> -    if isinstance(typename, type):
> -        name = typename.__name__
> -        if name.endswith('Ptr'):
> -            pointer_type = True
> -            name = name[:-3]
> -    else:
> -        name = typename
> -        if name[-1] == '*':
> -            name = name[:-1].strip()
> -            pointer_type = True
> -
> -    # Get the lldb type representation.
> -    t = target.FindFirstType(name)
> -    if pointer_type:
> -        t = t.GetPointerType()
> -
> -    if isinstance(value, int):
> -        # Integer casts require some black magic for lldb to behave properly.
> -        if pointer_type:
> -            casted = target.CreateValueFromAddress(
> -                'value',
> -                lldb.SBAddress(value, target),
> -                t.GetPointeeType(),
> -            ).address_of
> -        else:
> -            casted = target.CreateValueFromData(
> -                name='value',
> -                data=lldb.SBData.CreateDataFromInt(value, size=8),
> -                type=t,
> -            )
> -    else:
> -        casted = value.Cast(t)
>   
> -    if isinstance(typename, type):
> -        # Wrap lldb object, if possible
> -        return typename(casted)
> -    else:
> -        return casted
> +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):
> @@ -336,28 +242,20 @@ def type_member(type_obj, name):
>       return next((x for x in type_obj.members if x.name == name), None)
>   
>   
> -def find_type(typename):
> -    return target.FindFirstType(typename)
> -
> -
>   def offsetof(typename, membername):
> -    type_obj = find_type(typename)
> +    type_obj = gtype(typename)
>       member = type_member(type_obj, membername)
>       assert member is not None
>       return member.GetOffsetInBytes()
>   
>   
>   def sizeof(typename):
> -    type_obj = find_type(typename)
> +    type_obj = gtype(typename)
>       return type_obj.GetByteSize()
>   
>   
> -def vtou64(value):
> -    return value.unsigned & 0xFFFFFFFFFFFFFFFF
> -
> -
> -def vtoi(value):
> -    return value.signed
> +def tou64(val):
> +    return cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF
>   
>   
>   def dbg_eval(expr):
> @@ -371,17 +269,17 @@ def dbg_eval(expr):
>   
>   
>   def gcval(obj):
> -    return cast(GCobjPtr, cast('uintptr_t', obj.gcptr & LJ_GCVMASK) if LJ_GC64
> -                else cast('uintptr_t', obj.gcptr))
> +    return cast('GCobj *', obj['gcptr64'] & LJ_GCVMASK if LJ_GC64
> +                else cast('uintptr_t', obj['gcptr32']))
>   
>   
>   def gcref(obj):
> -    return cast(GCobjPtr, obj.gcptr if LJ_GC64
> -                else cast('uintptr_t', obj.gcptr))
> +    return cast('GCobj *', obj['gcptr64'] if LJ_GC64
> +                else cast('uintptr_t', obj['gcptr32']))
>   
>   
>   def gcnext(obj):
> -    return gcref(obj).gch.nextgc
> +    return gcref(obj)['gch']['nextgc']
>   
>   
>   def gclistlen(root, end=0x0):
> @@ -412,15 +310,15 @@ gclen = {
>   
>   
>   def dump_gc(g):
> -    gc = g.gc
> +    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,
> +        sweepstr=gc['sweepstr'],
>           # String hash mask (size of hash table - 1).
> -        strmask=g.strmask + 1,
> +        strmask=g['strmask'] + 1,
>       )]
>   
>       stats += ['{key}: {number} objects'.format(
> @@ -431,20 +329,17 @@ def dump_gc(g):
>   
>   
>   def mref(typename, obj):
> -    return cast(typename, obj.ptr)
> +    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_StatePtr,
> -        vtou64(cast('char *', g)) - g_offset + J_offset,
> -    )
> +    return cast('jit_State *', (cast('char *', g) - g_offset + J_offset))
>   
>   
>   def G(L):
> -    return mref(global_StatePtr, L.glref)
> +    return mref('global_State *', L['glref'])
>   
>   
>   def L(L=None):
> @@ -459,7 +354,7 @@ def L(L=None):
>           # TODO: Add more
>       ))):
>           if lstate:
> -            return lua_State(lstate)
> +            return cast('lua_State *', lstate)
>   
>   
>   def tou32(val):
> @@ -481,7 +376,7 @@ def vm_state(g):
>           i2notu32(6): 'RECORD',
>           i2notu32(7): 'OPT',
>           i2notu32(8): 'ASM',
> -    }.get(int(tou32(g.vmstate)), 'TRACE')
> +    }.get(int(tou32(g['vmstate'])), 'TRACE')
>   
>   
>   def gc_state(g):
> @@ -493,7 +388,7 @@ def gc_state(g):
>           4: 'SWEEP',
>           5: 'FINALIZE',
>           6: 'LAST',
> -    }.get(g.gc.state, 'INVALID')
> +    }.get(int(g['gc']['state']), 'INVALID')
>   
>   
>   def jit_state(g):
> @@ -505,31 +400,29 @@ def jit_state(g):
>           0x13: 'END',
>           0x14: 'ASM',
>           0x15: 'ERR',
> -    }.get(J(g).state, 'INVALID')
> +    }.get(int(J(g).state), 'INVALID')
>   
>   
>   def strx64(val):
>       return re.sub('L?$', '',
> -                  hex(int(val) & 0xFFFFFFFFFFFFFFFF))
> +                  hex(int(cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF)))
>   
>   
>   def funcproto(func):
>       assert func.ffid == 0
> -    proto_size = sizeof('GCproto')
> -    value = cast('uintptr_t', vtou64(mref('char *', func.pc)) - proto_size)
> -    return cast(GCprotoPtr, value)
> +    return cast('GCproto *', mref('char *', func.pc) - sizeof('GCproto'))
>   
>   
>   def strdata(obj):
>       try:
> -        ptr = cast('char *', obj + 1)
> -        return ptr.summary
> +        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
> +    return tou32(o['it64'] >> 47) if LJ_GC64 else o['it']
>   
>   
>   def tvisint(o):
> @@ -538,7 +431,7 @@ def tvisint(o):
>   
>   def tvislightud(o):
>       if LJ_64 and not LJ_GC64:
> -        return (vtoi(cast('int32_t', itype(o))) >> 15) == -2
> +        return (int(cast('int32_t', itype(o))) >> 15) == -2
>       else:
>           return itype(o) == LJ_T['LIGHTUD']
>   
> @@ -560,80 +453,80 @@ def dump_lj_ttrue(tv):
>   
>   
>   def dump_lj_tlightud(tv):
> -    return 'light userdata @ {}'.format(strx64(gcval(tv.gcr)))
> +    return 'light userdata @ {}'.format(strx64(gcval(tv['gcr'])))
>   
>   
>   def dump_lj_tstr(tv):
>       return 'string {body} @ {address}'.format(
> -        body=strdata(cast(GCstrPtr, gcval(tv.gcr))),
> -        address=strx64(gcval(tv.gcr))
> +        body=strdata(cast('GCstr *', gcval(tv['gcr']))),
> +        address=strx64(gcval(tv['gcr']))
>       )
>   
>   
>   def dump_lj_tupval(tv):
> -    return 'upvalue @ {}'.format(strx64(gcval(tv.gcr)))
> +    return 'upvalue @ {}'.format(strx64(gcval(tv['gcr'])))
>   
>   
>   def dump_lj_tthread(tv):
> -    return 'thread @ {}'.format(strx64(gcval(tv.gcr)))
> +    return 'thread @ {}'.format(strx64(gcval(tv['gcr'])))
>   
>   
>   def dump_lj_tproto(tv):
> -    return 'proto @ {}'.format(strx64(gcval(tv.gcr)))
> +    return 'proto @ {}'.format(strx64(gcval(tv['gcr'])))
>   
>   
>   def dump_lj_tfunc(tv):
> -    func = cast(GCfuncCPtr, gcval(tv.gcr))
> -    ffid = func.ffid
> +    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(GCstrPtr, gcval(pt.chunkname))),
> -            line=pt.firstline
> +            nups=func['nupvalues'],
> +            chunk=strdata(cast('GCstr *', gcval(pt['chunkname']))),
> +            line=pt['firstline']
>           )
>       elif ffid == 1:
> -        return 'C function @ {}'.format(strx64(func.f))
> +        return 'C function @ {}'.format(strx64(func['f']))
>       else:
>           return 'fast function #{}'.format(ffid)
>   
>   
>   def dump_lj_ttrace(tv):
> -    trace = cast(GCtracePtr, gcval(tv.gcr))
> +    trace = cast('GCtrace *', gcval(tv['gcr']))
>       return 'trace {traceno} @ {addr}'.format(
> -        traceno=strx64(trace.traceno),
> +        traceno=strx64(trace['traceno']),
>           addr=strx64(trace)
>       )
>   
>   
>   def dump_lj_tcdata(tv):
> -    return 'cdata @ {}'.format(strx64(gcval(tv.gcr)))
> +    return 'cdata @ {}'.format(strx64(gcval(tv['gcr'])))
>   
>   
>   def dump_lj_ttab(tv):
> -    table = cast(GCtabPtr, gcval(tv.gcr))
> +    table = cast('GCtab *', gcval(tv['gcr']))
>       return 'table @ {gcr} (asize: {asize}, hmask: {hmask})'.format(
>           gcr=strx64(table),
> -        asize=table.asize,
> -        hmask=strx64(table.hmask),
> +        asize=table['asize'],
> +        hmask=strx64(table['hmask']),
>       )
>   
>   
>   def dump_lj_tudata(tv):
> -    return 'userdata @ {}'.format(strx64(gcval(tv.gcr)))
> +    return 'userdata @ {}'.format(strx64(gcval(tv['gcr'])))
>   
>   
>   def dump_lj_tnumx(tv):
>       if tvisint(tv):
> -        return 'integer {}'.format(cast('int32_t', tv.i))
> +        return 'integer {}'.format(cast('int32_t', tv['i']))
>       else:
> -        return 'number {}'.format(tv.n)
> +        return 'number {}'.format(cast('double', tv['n']))
>   
>   
>   def dump_lj_invalid(tv):
> -    return 'not valid type @ {}'.format(strx64(gcval(tv.gcr)))
> +    return 'not valid type @ {}'.format(strx64(gcval(tv['gcr'])))
>   
>   
>   dumpers = {
> @@ -686,8 +579,8 @@ def typenames(value):
>       }.get(int(value), 'LJ_TINVALID')
>   
>   
> -def dump_tvalue(tvptr):
> -    return dumpers.get(typenames(itypemap(tvptr)), dump_lj_invalid)(tvptr)
> +def dump_tvalue(tvalue):
> +    return dumpers.get(typenames(itypemap(tvalue)), dump_lj_invalid)(tvalue)
>   
>   
>   FRAME_TYPE = 0x3
> @@ -720,23 +613,17 @@ def bc_a(ins):
>   
>   
>   def frame_ftsz(framelink):
> -    return vtou64(cast('ptrdiff_t', framelink.ftsz if LJ_FR2
> -                       else framelink.fr.tp.ftsz))
> +    return cast('ptrdiff_t', framelink['ftsz'] if LJ_FR2
> +                else framelink['fr']['tp']['ftsz'])
>   
>   
>   def frame_pc(framelink):
> -    return cast(BCInsPtr, frame_ftsz(framelink)) if LJ_FR2 \
> -        else mref(BCInsPtr, framelink.fr.tp.pcr)
> +    return cast('BCIns *', frame_ftsz(framelink)) if LJ_FR2 \
> +        else mref('BCIns *', framelink['fr']['tp']['pcr'])
>   
>   
>   def frame_prevl(framelink):
> -    # We are evaluating the `frame_pc(framelink)[-1])` with lldb's
> -    # REPL, because the lldb API is faulty and it's not possible to cast
> -    # a struct member of 32-bit type to 64-bit type without getting onto
> -    # the next property bits, despite the fact that it's an actual value, not
> -    # a pointer to it.
> -    bcins = vtou64(dbg_eval('((BCIns *)' + str(frame_pc(framelink)) + ')[-1]'))
> -    return framelink - (1 + LJ_FR2 + bc_a(bcins))
> +    return framelink - (1 + LJ_FR2 + bc_a(frame_pc(framelink)[-1]))
>   
>   
>   def frame_ispcall(framelink):
> @@ -770,14 +657,14 @@ def frame_prev(framelink):
>   
>   
>   def frame_sentinel(L):
> -    return mref(TValuePtr, L.stack) + LJ_FR2
> +    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
> +    frametop = L['top']
> +    framelink = L['base'] - 1
>       framelink_sentinel = frame_sentinel(L)
>       while True:
>           yield framelink, frametop
> @@ -788,14 +675,8 @@ def frames(L):
>   
>   
>   def dump_framelink_slot_address(fr):
> -    return '{start:{padding}}:{end:{padding}}'.format(
> -        start=hex(int(fr - 1)),
> -        end=hex(int(fr)),
> -        padding=len(PADDING),
> -    ) if LJ_FR2 else '{addr:{padding}}'.format(
> -        addr=hex(int(fr)),
> -        padding=len(PADDING),
> -    )
> +    return '{}:{}'.format(fr - 1, fr) if LJ_FR2 \
> +        else '{}'.format(fr) + PADDING
>   
>   
>   def dump_framelink(L, fr):
> @@ -809,30 +690,30 @@ def dump_framelink(L, fr):
>               frname=frametypes(int(frame_type(fr))),
>               p='P' if frame_typep(fr) & FRAME_P else ''
>           ),
> -        d=fr - frame_prev(fr),
> +        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
> +    base = base or L['base']
> +    top = top or L['top']
>   
> -    return '{addr:{padding}} [ {B}{T}{M}] VALUE: {value}'.format(
> +    return '{addr}{padding} [ {B}{T}{M}] VALUE: {value}'.format(
>           addr=strx64(slot),
> -        padding=2 * len(PADDING) + 1,
> +        padding=PADDING,
>           B='B' if slot == base else ' ',
>           T='T' if slot == top else ' ',
> -        M='M' if slot == mref(TValuePtr, L.maxstack) 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(TValuePtr, L.stack)
> -    maxstack = mref(TValuePtr, L.maxstack)
> +    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 = [
> @@ -848,19 +729,13 @@ def dump_stack(L, base=None, top=None):
>       dump.extend([
>           '{padding} Stack: {nstackslots: >5} slots {padding}'.format(
>               padding='-' * len(PADDING),
> -            nstackslots=int((maxstack - stack) >> 3),
> +            nstackslots=int((tou64(maxstack) - tou64(stack)) >> 3),
>           ),
>           dump_stack_slot(L, maxstack, base, top),
>           '{start}:{end} [    ] {nfreeslots} slots: Free stack slots'.format(
> -            start='{address:{padding}}'.format(
> -                address=strx64(top + 1),
> -                padding=len(PADDING),
> -            ),
> -            end='{address:{padding}}'.format(
> -                address=strx64(maxstack - 1),
> -                padding=len(PADDING),
> -            ),
> -            nfreeslots=int((maxstack - top - 8) >> 3),
> +            start=strx64(top + 1),
> +            end=strx64(maxstack - 1),
> +            nfreeslots=int((tou64(maxstack) - tou64(top) - 8) >> 3),
>           ),
>       ])
>   
> @@ -905,7 +780,7 @@ Whether the type of the given address differs from the listed above, then
>   error message occurs.
>       '''
>       def execute(self, debugger, args, result):
> -        tvptr = TValuePtr(cast('TValue *', self.parse(args)))
> +        tvptr = cast('TValue *', self.parse(args))
>           print('{}'.format(dump_tvalue(tvptr)))
>   
>   
> @@ -984,11 +859,11 @@ the payload, size in bytes and hash.
>   is replaced with the corresponding error when decoding fails.
>       '''
>       def execute(self, debugger, args, result):
> -        string_ptr = GCstrPtr(cast('GCstr *', self.parse(args)))
> +        string = cast('GCstr *', self.parse(args))
>           print("String: {body} [{len} bytes] with hash {hash}".format(
> -            body=strdata(string_ptr),
> -            hash=strx64(string_ptr.hash),
> -            len=string_ptr.len,
> +            body=strdata(string),
> +            hash=strx64(string['hash']),
> +            len=string['len'],
>           ))
>   
>   
> @@ -1004,13 +879,13 @@ The command receives a GCtab address and dumps the table contents:
>     <hnode ptr>: { <tv> } => { <tv> }; next = <next hnode ptr>
>       '''
>       def execute(self, debugger, args, result):
> -        t = GCtabPtr(cast('GCtab *', self.parse(args)))
> -        array = mref(TValuePtr, t.array)
> -        nodes = mref(NodePtr, t.node)
> -        mt = gcval(t.metatable)
> +        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
> +            'apart': int(t['asize']),
> +            'hpart': int(t['hmask'] + 1) if t['hmask'] > 0 else 0
>           }
>   
>           if mt:
> @@ -1031,9 +906,9 @@ The command receives a GCtab address and dumps the table contents:
>               node = nodes + i
>               print('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}'.format(
>                   ptr=strx64(node),
> -                key=dump_tvalue(TValuePtr(node.key.addr)),
> -                val=dump_tvalue(TValuePtr(node.val.addr)),
> -                n=strx64(mref(NodePtr, node.next))
> +                key=dump_tvalue(node['key']),
> +                val=dump_tvalue(node['val']),
> +                n=strx64(mref('Node *', node['next']))
>               ))
>   
>   
> @@ -1070,9 +945,7 @@ coroutine guest stack:
>   If L is omitted the main coroutine is used.
>       '''
>       def execute(self, debugger, args, result):
> -        lstate = self.parse(args)
> -        lstate_ptr = cast('lua_State *', lstate) if coro is not None else None
> -        print('{}'.format(dump_stack(L(lstate_ptr))))
> +        print('{}'.format(dump_stack(L(self.parse(args)))))
>   
>   
>   def register_commands(debugger, commands):
> @@ -1106,7 +979,7 @@ def configure(debugger):
>                 'no debugging symbols found for libluajit')
>           return
>   
> -    PADDING = ' ' * len(strx64((TValuePtr(L().addr))))
> +    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']
>   
>   
> diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
> index 6094c535..11c2492b 100644
> --- a/test/tarantool-debugger-tests/debug-extension-tests.py
> +++ b/test/tarantool-debugger-tests/debug-extension-tests.py
> @@ -125,20 +125,22 @@ class TestCaseBase(unittest.TestCase):
>               self.assertRegex(self.output, self.pattern.strip())
>   
>   
> -class TestLoad(TestCaseBase):
> -    extension_cmds = ''
> -    location = 'lj_cf_print'
> -    lua_script = 'print(1)'
> -    pattern = (
> -        r'lj-arch command initialized\n'
> -        r'lj-tv command initialized\n'
> -        r'lj-str command initialized\n'
> -        r'lj-tab command initialized\n'
> -        r'lj-stack command initialized\n'
> -        r'lj-state command initialized\n'
> -        r'lj-gc command initialized\n'
> -        r'.*is successfully loaded'
> -    )
> +# FIXME: Skip for LLDB since it has different order.
> +if not LLDB:
> +    class TestLoad(TestCaseBase):
> +        extension_cmds = ''
> +        location = 'lj_cf_print'
> +        lua_script = 'print(1)'
> +        pattern = (
> +            r'lj-arch command initialized\n'
> +            r'lj-tv command initialized\n'
> +            r'lj-str command initialized\n'
> +            r'lj-tab command initialized\n'
> +            r'lj-stack command initialized\n'
> +            r'lj-state command initialized\n'
> +            r'lj-gc command initialized\n'
> +            r'.*is successfully loaded'
> +        )
>   
>   
>   class TestLJArch(TestCaseBase):
> @@ -281,6 +283,5 @@ class TestLJTab(TestCaseBase):
>   for test_cls in TestCaseBase.__subclasses__():
>       test_cls.test = lambda self: self.check()
>   
> -# FIXME: skip for LLDB since most commands are not working anyway.
> -if __name__ == '__main__' and not LLDB:
> +if __name__ == '__main__':
>       unittest.main(verbosity=2)

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

  reply	other threads:[~2026-05-27 12:27 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 [this message]
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
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=e7707d21-9192-4469-89ef-fee56964ef67@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 2/6] lldb: refactor 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