From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 971E86F3D6; Tue, 30 Jun 2026 17:45:04 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 971E86F3D6 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1782830704; bh=3PcnGq02L9AozFKULDbl8YtyesRlvhGExWcS+2Q3BwY=; h=Date:To:Cc:References:In-Reply-To:Subject:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=gKVfV5Hch1WkcUGf8SlZ3Rpw12Go1j7bsjYYsGO1JidouwL4YsaXsH+pWsyvZweGv 6Ro1+vYrZf4z1PEgKQbyUJzKyUkhHj6YGY5nmSVJRkHGvrIB5A51mZcSHEPpdPmbAe JooFLhHwpp4oA83+lyT1f3gCBjXlnAIjSk0h/uvI= Received: from send60.i.mail.ru (send60.i.mail.ru [89.221.237.155]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 2A7166F3D6 for ; Tue, 30 Jun 2026 17:45:04 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 2A7166F3D6 Received: by exim-smtp-78b8b8c574-k7csr with esmtpa (envelope-from ) id 1weZhu-00000000VLz-2hM0; Tue, 30 Jun 2026 17:45:03 +0300 Content-Type: multipart/alternative; boundary="------------T90u3bPoffQ2oPqAFJ3vcSIy" Message-ID: <74882449-a4cc-4602-a8b3-75bd5e9724eb@tarantool.org> Date: Tue, 30 Jun 2026 17:45:02 +0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Content-Language: en-US To: Sergey Kaplun , Evgeniy Temirgaleev Cc: tarantool-patches@dev.tarantool.org References: <20260625202903.3157425-1-skaplun@tarantool.org> <20260625202903.3157425-2-skaplun@tarantool.org> In-Reply-To: <20260625202903.3157425-2-skaplun@tarantool.org> X-Mailru-Src: smtp X-4EC0790: 10 X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD9F802F6E87846D5F62771AF606873CD438728501C843D40FE00894C459B0CD1B901D6BC57011BB6C9B4227B83FC9362CB85E9B6D326F0B179B54533BAA326DC19160237749B4E196D X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE7D6964C9E324ABA58EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637AC83A81C8FD4AD23D82A6BABE6F325AC2E85FA5F3EDFCBAA7353EFBB553375667C07923B88CEC0ED729A400C5F010055A16BEE88D4073D909C9010251B4CCE15389733CBF5DBD5E913377AFFFEAFD269A417C69337E82CC2CC7F00164DA146DAFE8445B8C89999729449624AB7ADAF37F6B57BC7E64490611E7FA7ABCAF51C92176DF2183F8FC7C0D9442B0B5983000E8941B15DA834481F9449624AB7ADAF37BA3038C0950A5D3613377AFFFEAFD269176DF2183F8FC7C0DDA3EF1F92285B887B076A6E789B0E97A8DF7F3B2552694AD5FFEEA1DED7F25D49FD398EE364050F26055571C92BF10F49AF716F719AB83EB3661434B16C20ACC84D3B47A649675FE827F84554CEF5019E625A9149C048EE33AC447995A7AD182BEBFE083D3B9BA73A03B725D353964B0B7D0EA88DDEDAC722CA9DD8327EE4930A3850AC1BE2E735DD0078234547CCE7C4224003CC83647689D4C264860C145E X-C1DE0DAB: 0D63561A33F958A59145AB7DBE5848C85002B1117B3ED6965DCEDF52D1CA26564869453249F34FA4823CB91A9FED034534781492E4B8EEADC260AEA2E7246735BDAD6C7F3747799A X-C8649E89: 1C3962B70DF3F0AD73CAD6646DEDE191716CD42B3DD1D34CAB70F9BE574AE9C625B6776AC983F447FC0B9F89525902EE6F57B2FD27647F25E66C117BDB76D65969F07D7B9A059E74EA2C6C123EEFCC0A8ED9FE4E88BBE1CDD21F3860CA2AEA78D0C78190CBCB988FB8341EE9D5BE9A0AF188CB1FB0380E23185ECF2ABE099ABCC45474E149BDAE3C6536EB022892E5344C41F94D744909CE2512F26BEC029E55448553D2254B8D95CD72808BE417F3B9E0E7457915DAA85F X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu53w8ahmwBjZKM/YPHZyZHvz5uv+WouB9+ObcCpyrx6l7KImUglyhkEat/+ysWwi0gdhEs0JGjl6ggRWTy1haxBpVdbIX1nthFXMZebaIdHP2ghjoIc/363UZI6Kf1ptIMVRSZSJkMhZtMWqqjuhqagiY= X-Mailru-Sender: C4F68CFF4024C8867DFDF7C7F2588458B3E92BB29DDB312EA7CF1FCFAF80C0E9A14E59AED0C7C52CD833FFD16BC388C3645D15D82EE4B272BD6E4642A116CA93524AA66B5ACBE6721EF430B9A63E2A504198E0F3ECE9B5443453F38A29522196 X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH luajit 1/3] dbg: introduce lj-ir, lj-jslots, lj-trace dumpers X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Sergey Bronnikov via Tarantool-patches Reply-To: Sergey Bronnikov Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" This is a multi-part message in MIME format. --------------T90u3bPoffQ2oPqAFJ3vcSIy Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit Hi, Sergey, thanks for the patch! LGTM with minor comments. Sergey On 6/25/26 23:29, Sergey Kaplun wrote: > This patch adds dumpers for a single IR instruction (`lj-ir`), as well > as for all bytecodes inside one trace (`lj-trace`). Its dump is quite > similar to the -jdump flag but also reports types of register operands > (`ref`, `lit`, `cst`) and operation mode (`N`, `A`, `W`, etc.). > The `lj-trace` command accepts optional /rs flags to dump registers > associated with IR and snapshots for the trace correspondingly. > The `lj-ir` command can be used for dumping IR constants as well. > The `lj-jslots` command dumps the content of `J->slot`. It is useful to > simplify debugging of `rec_check_slots()` assertion failures. > > For LLDB value, the `__getitem__` metamethod now accepts bool keys. > Also, `__index__` is set to allow lldb.value to be used as an index > without explicit conversion to int. Old GDB versions (below 7.12) are > not supported because of the gdb.Value lacks the `__index__` metamethod > and can't be monkey-patched. The support for these versions may be added > by demand. > > Part of tarantool/tarantool#4808 > --- > src/luajit_dbg.py | 1216 ++++++++++++++++- > .../debug-extension-tests.py | 365 +++++ > 2 files changed, 1570 insertions(+), 11 deletions(-) > > diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py > index 2edb199a..fd6ca8a5 100644 > --- a/src/luajit_dbg.py > +++ b/src/luajit_dbg.py > @@ -58,6 +58,26 @@ class Debugger(object): > self.LLDB = True > return super(Debugger, self).__new__(_LLDBDebugger) > > + def parse_flags(self, raw_flags, permitted_flags): > + flags = {} > + for flag in raw_flags: > + if flag not in permitted_flags: > + raise self.error('Unrecongnized option: "{}"'.format(flag)) typo: s/Unrecongnized/Unrecognized/ > + flags[flag] = True > + return flags > + > + def extract_flags(self, arg, permitted_flags): > + if not arg: > + return None, None > + flags = {} > + if arg.startswith('/'): > + match = re.match(r'/(\S*)\s+(.*)$', arg) > + if not match: > + return arg, flags > + raw_flags, arg = match.group(1, 2) > + flags = self.parse_flags(raw_flags, permitted_flags) > + return arg, flags > + > def configure(self): > global PADDING, LJ_TISNUM > if not self.check_libluajit(): > @@ -70,6 +90,17 @@ class Debugger(object): > self.write('luajit_dbg.py failed to load: ' > 'no debugging symbols found for libluajit\n') > return False > + > + # Setup arch. > + try: > + self.arch = str(self.eval('LJ_ARCH_NAME')).split('"')[1] > + except Exception: > + try: > + self.arch = self.detect_arch() > + except Exception: > + # Setup on demand if necessary. > + pass > + > return True > > def initialize_extension(self, commands): > @@ -99,21 +130,42 @@ class Debugger(object): > '''Return the content of the string by the given pointer.''' > pass > > + @abc.abstractmethod > + def address(self, obj): > + '''Return the address in memory of the given object.''' > + pass > + > @abc.abstractmethod > def lookup_global(self, symbol): > '''Look up the global C symbol by the given name.''' > pass > > + @abc.abstractmethod > + def member_by_offset(self, typename, offset, prev_name=None): > + '''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 detect_arch(self): > + '''Detect the CPU architecture and canonicalize it to the LuaJIT > + notation.''' > + pass > + > @abc.abstractmethod > def write(self, msg): > '''Print the message.''' > pass > > + @abc.abstractmethod > + def error(self, msg): > + '''Create the error object with message.''' > + pass > + > @abc.abstractmethod > def check_libluajit(self): > '''Check that libluajit is loaded. > @@ -172,10 +224,50 @@ class _GDBDebugger(Debugger): > # A string is printed with a pointer to it. Just strip it. > return re.sub(r'^0x[a-f0-9]+\s+(?=")', '', str(strptr)) > > + def address(self, obj): > + return obj.address > + > def lookup_global(self, symbol): > variable, _ = gdb.lookup_symbol(symbol) > return variable.value() if variable else None > > + def member_by_offset(self, tp, offset, prev_name=None): > + if isinstance(tp, str): > + tp = self._dbgtype(tp) > + assert offset < tp.sizeof, 'offset is bigger than object size' > + if tp.code == gdb.TYPE_CODE_TYPEDEF: > + tp = tp.strip_typedefs() > + if tp.code == gdb.TYPE_CODE_STRUCT: > + fields = tp.fields() > + for n_field in range(len(fields)): > + islast = n_field == (len(fields) - 1) > + field = fields[n_field] > + start_field = field.bitpos / 8 may be //? > + end_field = fields[n_field + 1].bitpos / 8 if not islast \ > + else tp.sizeof > + if start_field <= offset and offset < end_field: > + next_name = self.member_by_offset( > + field.type, > + offset - start_field, > + prev_name=field.name > + ) > + return '.{field}{suffix}'.format( > + field=field.name, > + suffix=next_name if next_name else '' > + ) > + elif tp.code == gdb.TYPE_CODE_ARRAY: > + # Get array field type. > + target = tp.target() > + tsize = target.sizeof > + idx = int(offset // tsize) > + next_name = self.member_by_offset(target, offset - idx * tsize) > + idxname = idx_name(prev_name) > + if idxname and idx in idxname: > + idx = idxname[idx] > + return '[{}]{}'.format(idx, next_name if next_name else '') > + else: > + return None > + > def eval(self, command): > if not command: > return None > @@ -185,9 +277,23 @@ class _GDBDebugger(Debugger): > raise gdb.GdbError('table argument empty') > return ret > > + def detect_arch(self): > + if hasattr(self, 'arch'): > + return self.arch > + target = str(gdb.execute('info target', False, True)) > + if re.match('.*x86-64.*', target, flags=re.DOTALL): > + return 'x64' > + elif re.match('.*aarch64.*', target, flags=re.DOTALL): > + return 'arm64' > + else: > + return '' > + > def write(self, msg): > gdb.write(msg) > > + def error(self, errmsg): > + return gdb.GdbError(errmsg) > + > def check_libluajit(self): > # XXX Fragile: Though connecting the callback looks bad, > # it respects both Python 2 and Python 3 (see #4828). > @@ -322,8 +428,26 @@ class _LLDBDebugger(Debugger): > def lldb__getitem__(lldbval, key): > if type(key) is lldb.value: > key = int(key) > + if type(key) is bool: > + key = int(key) > if type(key) is int: > # Allow array access. > + ltp = lldbval.sbvalue.GetType() > + # XXX: LLDB in versions 17 - 19 can't use an array > + # object as the initializer for `lldb.value` since > + # `GetValue()` for it returns `None` leading to > + # the invalid result. See > + #https://github.com/llvm/llvm-project/pull/90144. > + if (self.version < 17 or self.version > 19) or \ > + ltp.GetTypeClass() != lldb.eTypeClassArray: > + pass probably it is better to invert condition and remove section with "pass" > + > + > +def ir_kptr(ir): > + irname = IRS[ir['o']] > + assert irname == 'KPTR' or irname == 'KKPTR', 'wrong IR for ir_iptr()' typo: s/ir_iptr()/ir_kptr() or ir_kkptr()/ > + return mref('void *', dbg.cast('IRIns *', dbg.address(ir))[LJ_GC64]['ptr']) > + > + > +def ir_kgc(ir): > + irname = IRS[ir['o']] > + assert irname == 'KGC', 'wrong IR for ir_kgc()' > + return gcref(dbg.cast('IRIns *', dbg.address(ir))[LJ_GC64]['gcr']) > + > + > +def ir_knum(ir): > + irname = IRS[ir['o']] > + assert irname == 'KNUM', 'wrong IR for ir_knum()' > + return dbg.address(dbg.cast('IRIns *', dbg.address(ir))[1]['tv']) > + > + > +def ir_kint64(ir): > + irname = IRS[ir['o']] > + assert irname == 'KINT64', 'wrong IR for ir_knum()' typo: s/ir_knum/ir_kint64/ > + return dbg.address(dbg.cast('IRIns *', dbg.address(ir))[1]['tv']) > + > + > # Dumpers. > > # GCobj dumpers. > @@ -1467,6 +2281,325 @@ def dump_func(func): > return 'fast function #{}\n'.format(int(ffid)) > > > +# JIT dumpers. > + > + > +def dump_call_func(trace, callop): > + ctype = '' > + if callop > 0: > + ir = trace['ir'][REF_BIAS + callop] > + if IRTYPES[irt_type(ir['t'])] == 'nil': # nil == CARG(func, ctype) > + callop = int(ir['op1']) - REF_BIAS > + cdt_idx_irk = trace['ir'][ir['op2']] > + assert IRS[cdt_idx_irk['o']] == 'KINT', \ > + 'unexpected IR for ctype storage' > + ctype_idx = cdt_idx_irk['i'] > + ctype = 'ctype: {}'.format(ctype_idx) > + > + func_str = '' > + if callop < 0: > + irk = trace['ir'][REF_BIAS + callop] > + assert IRS[irk['o']] == 'KINT64', \ > + 'unexpected IR for FFI function storage' > + func_addr = int(ir_kint64(irk)['u64']) > + # TODO: Symbol demangling. > + func_str = '[{:#x}]'.format(func_addr) > + else: > + func_str = '[{:04d}]'.format(callop) > + > + return func_str, ctype > + > + > +def dump_call_args(trace, ins): > + if ins < 0: > + return '{{{}}}'.format(dump_irk(trace, ins)) > + else: > + ir = trace['ir'][REF_BIAS + ins] > + irname = IRS[ir['o']] > + if irname == 'CARG': > + last_arg = '' > + args = dump_call_args(trace, int(ir['op1']) - REF_BIAS) > + op2 = int(ir['op2']) - REF_BIAS > + if op2 < 0: > + last_arg = '{{{}}}'.format(dump_irk(trace, op2)) > + else: > + last_arg = '{{{:04d}}}'.format(op2) > + return args + ', ' + last_arg > + else: > + return '{{{:04d}}}'.format(ins) > + > + > +# Special FP constant. > +CONST_BIAS = 2 ** 52 + 2 ** 51 > + > + > +def dump_irk(trace, idx): > + ref = idx + REF_BIAS > + assert ref >= trace['nk'] and ref < REF_BIAS, 'bad constant in IR dump' > + irins = trace['ir'][ref] > + irname = IRS[irins['o']] > + slot = '' > + if irname == 'KSLOT': > + slot = ' KSLOT: @{}'.format(int(irins['op2'])) > + irins = trace['ir'][irins['op1']] > + irname = IRS[irins['o']] > + > + irtype = irins['t'] > + if irname == 'KPRI': > + typename = typenames(irt_toitype(irtype)) > + # Trivial dump for primitives. > + irk = tv_dumpers.get( > + typename, dump_lj_tv_invalid # noqa: F821 # Generated. > + )(0) > + elif irname == 'KINT': > + irk = 'integer {}'.format(dbg.cast('int32_t', irins['i'])) > + elif irname == 'KGC': > + typename = typenames(irt_toitype(irtype)) > + irk = gco_dumpers.get(typename, dump_lj_gco_invalid)(ir_kgc(irins)) > + elif irname == 'KKPTR': > + addr = ir_kptr(irins) > + if addr == dbg.address(G(L())['nilnode']): > + return '[g->nilnode]' + slot > + irk = '[{}]'.format(strx64(addr)) > + elif irname == 'KPTR': > + irk = '[{}]'.format(strx64(ir_kptr(irins))) > + elif irname == 'KNULL': > + irk = 'NULL' > + elif irname == 'KNUM': > + tv_num = ir_knum(irins) > + if float(tv_num['n']) == CONST_BIAS: > + return 'bias' > + irk = dump_lj_tv_numx(tv_num) > + elif irname == 'KINT64': > + irk = 'int64_t {}'.format(dbg.cast( > + 'int64_t', int(ir_kint64(irins)['u64']) > + )) > + else: > + return 'Unknown IRK: ' + irname > + return irk + slot > + > + > + > +def dump_snap(trace, snapno, snap): > + dump = 'SNAP #{:<3d} ['.format(snapno) > + snap_map = dbg.address(trace['snapmap'][snap['mapofs']]) > + snap_entry_num = 0 > + for slot in range(0, snap['nslots']): > + dump += ' ' > + snap_entry = int(snap_map[snap_entry_num]) > + if snap_entry_num < snap['nent'] and snap_entry >> TREF_SHIFT == slot: > + snap_entry_num += 1 > + ref = int((snap_entry & TREF_REFMASK) - REF_BIAS) > + if ref < 0: > + if int(snap_entry) == 0x1057fff: magic number > +# Assume not cross-platform debugging. > +machine = os.uname().machine > +if machine == 'x86_64': > + RX_GPR = r'r\w\w' > + RX_FPR = r'xmm\d+' > +elif machine == 'arm64' or machine == 'aarch64': > + RX_GPR = r'x\d+' > + RX_FPR = r'd\d+' > +else: > + raise Exception('Unknown archeticture in testing') typo: s/archeticture/architecture/ > + > +class TestLJIRConst(TestCaseBase): > + location = 'trace_stop' > + > + # No narrowing of 42. > + if IS_DUALNUM: > + # KNUM occupies 2 slots. > + _knum_irnum = '6' > + _kgc_irnum = '8' if IS_GC64 else '7' > + _kptr_irnum = '10' if IS_GC64 else '8' > + else: > + # KNUM occupies 2 slots. > + _knum_irnum = '8' > + _kgc_irnum = '10' if IS_GC64 else '9' > + _kptr_irnum = '12' if IS_GC64 else '10' both branches contains the same comment, is it a typo or not? > > --------------T90u3bPoffQ2oPqAFJ3vcSIy Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit

Hi, Sergey,

thanks for the patch! LGTM with minor comments.

Sergey

On 6/25/26 23:29, Sergey Kaplun wrote:
This patch adds dumpers for a single IR instruction (`lj-ir`), as well
as for all bytecodes inside one trace (`lj-trace`). Its dump is quite
similar to the -jdump flag but also reports types of register operands
(`ref`, `lit`, `cst`) and operation mode (`N`, `A`, `W`, etc.).
The `lj-trace` command accepts optional /rs flags to dump registers
associated with IR and snapshots for the trace correspondingly.
The `lj-ir` command can be used for dumping IR constants as well.
The `lj-jslots` command dumps the content of `J->slot`. It is useful to
simplify debugging of `rec_check_slots()` assertion failures.

For LLDB value, the `__getitem__` metamethod now accepts bool keys.
Also, `__index__` is set to allow lldb.value to be used as an index
without explicit conversion to int. Old GDB versions (below 7.12) are
not supported because of the gdb.Value lacks the `__index__` metamethod
and can't be monkey-patched. The support for these versions may be added
by demand.

Part of tarantool/tarantool#4808
---
 src/luajit_dbg.py                             | 1216 ++++++++++++++++-
 .../debug-extension-tests.py                  |  365 +++++
 2 files changed, 1570 insertions(+), 11 deletions(-)

diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index 2edb199a..fd6ca8a5 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -58,6 +58,26 @@ class Debugger(object):
             self.LLDB = True
             return super(Debugger, self).__new__(_LLDBDebugger)
 
+    def parse_flags(self, raw_flags, permitted_flags):
+        flags = {}
+        for flag in raw_flags:
+            if flag not in permitted_flags:
+                raise self.error('Unrecongnized option: "{}"'.format(flag))
typo: s/Unrecongnized/Unrecognized/
+            flags[flag] = True
+        return flags
+
+    def extract_flags(self, arg, permitted_flags):
+        if not arg:
+            return None, None
+        flags = {}
+        if arg.startswith('/'):
+            match = re.match(r'/(\S*)\s+(.*)$', arg)
+            if not match:
+                return arg, flags
+            raw_flags, arg = match.group(1, 2)
+            flags = self.parse_flags(raw_flags, permitted_flags)
+        return arg, flags
+
     def configure(self):
         global PADDING, LJ_TISNUM
         if not self.check_libluajit():
@@ -70,6 +90,17 @@ class Debugger(object):
             self.write('luajit_dbg.py failed to load: '
                        'no debugging symbols found for libluajit\n')
             return False
+
+        # Setup arch.
+        try:
+            self.arch = str(self.eval('LJ_ARCH_NAME')).split('"')[1]
+        except Exception:
+            try:
+                self.arch = self.detect_arch()
+            except Exception:
+                # Setup on demand if necessary.
+                pass
+
         return True
 
     def initialize_extension(self, commands):
@@ -99,21 +130,42 @@ class Debugger(object):
         '''Return the content of the string by the given pointer.'''
         pass
 
+    @abc.abstractmethod
+    def address(self, obj):
+        '''Return the address in memory of the given object.'''
+        pass
+
     @abc.abstractmethod
     def lookup_global(self, symbol):
         '''Look up the global C symbol by the given name.'''
         pass
 
+    @abc.abstractmethod
+    def member_by_offset(self, typename, offset, prev_name=None):
+        '''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 detect_arch(self):
+        '''Detect the CPU architecture and canonicalize it to the LuaJIT
+        notation.'''
+        pass
+
     @abc.abstractmethod
     def write(self, msg):
         '''Print the message.'''
         pass
 
+    @abc.abstractmethod
+    def error(self, msg):
+        '''Create the error object with message.'''
+        pass
+
     @abc.abstractmethod
     def check_libluajit(self):
         '''Check that libluajit is loaded.
@@ -172,10 +224,50 @@ class _GDBDebugger(Debugger):
         # A string is printed with a pointer to it. Just strip it.
         return re.sub(r'^0x[a-f0-9]+\s+(?=")', '', str(strptr))
 
+    def address(self, obj):
+        return obj.address
+
     def lookup_global(self, symbol):
         variable, _ = gdb.lookup_symbol(symbol)
         return variable.value() if variable else None
 
+    def member_by_offset(self, tp, offset, prev_name=None):
+        if isinstance(tp, str):
+            tp = self._dbgtype(tp)
+        assert offset < tp.sizeof, 'offset is bigger than object size'
+        if tp.code == gdb.TYPE_CODE_TYPEDEF:
+            tp = tp.strip_typedefs()
+        if tp.code == gdb.TYPE_CODE_STRUCT:
+            fields = tp.fields()
+            for n_field in range(len(fields)):
+                islast = n_field == (len(fields) - 1)
+                field = fields[n_field]
+                start_field = field.bitpos / 8
may be //?
+                end_field = fields[n_field + 1].bitpos / 8 if not islast \
+                    else tp.sizeof
+                if start_field <= offset and offset < end_field:
+                    next_name = self.member_by_offset(
+                        field.type,
+                        offset - start_field,
+                        prev_name=field.name
+                    )
+                    return '.{field}{suffix}'.format(
+                        field=field.name,
+                        suffix=next_name if next_name else ''
+                    )
+        elif tp.code == gdb.TYPE_CODE_ARRAY:
+            # Get array field type.
+            target = tp.target()
+            tsize = target.sizeof
+            idx = int(offset // tsize)
+            next_name = self.member_by_offset(target, offset - idx * tsize)
+            idxname = idx_name(prev_name)
+            if idxname and idx in idxname:
+                idx = idxname[idx]
+            return '[{}]{}'.format(idx, next_name if next_name else '')
+        else:
+            return None
+
     def eval(self, command):
         if not command:
             return None
@@ -185,9 +277,23 @@ class _GDBDebugger(Debugger):
             raise gdb.GdbError('table argument empty')
         return ret
 
+    def detect_arch(self):
+        if hasattr(self, 'arch'):
+            return self.arch
+        target = str(gdb.execute('info target', False, True))
+        if re.match('.*x86-64.*', target, flags=re.DOTALL):
+            return 'x64'
+        elif re.match('.*aarch64.*', target, flags=re.DOTALL):
+            return 'arm64'
+        else:
+            return ''
+
     def write(self, msg):
         gdb.write(msg)
 
+    def error(self, errmsg):
+        return gdb.GdbError(errmsg)
+
     def check_libluajit(self):
         # XXX Fragile: Though connecting the callback looks bad,
         # it respects both Python 2 and Python 3 (see #4828).
@@ -322,8 +428,26 @@ class _LLDBDebugger(Debugger):
         def lldb__getitem__(lldbval, key):
             if type(key) is lldb.value:
                 key = int(key)
+            if type(key) is bool:
+                key = int(key)
             if type(key) is int:
                 # Allow array access.
+                ltp = lldbval.sbvalue.GetType()
+                # XXX: LLDB in versions 17 - 19 can't use an array
+                # object as the initializer for `lldb.value` since
+                # `GetValue()` for it returns `None` leading to
+                # the invalid result. See
+                # https://github.com/llvm/llvm-project/pull/90144.
+                if (self.version < 17 or self.version > 19) or \
+                   ltp.GetTypeClass() != lldb.eTypeClassArray:
+                    pass
probably it is better to invert condition and remove section with "pass"

    
<snipped>
+
+
+def ir_kptr(ir):
+    irname = IRS[ir['o']]
+    assert irname == 'KPTR' or irname == 'KKPTR', 'wrong IR for ir_iptr()'
typo: s/ir_iptr()/ir_kptr() or ir_kkptr()/
+    return mref('void *', dbg.cast('IRIns *', dbg.address(ir))[LJ_GC64]['ptr'])
+
+
+def ir_kgc(ir):
+    irname = IRS[ir['o']]
+    assert irname == 'KGC', 'wrong IR for ir_kgc()'
+    return gcref(dbg.cast('IRIns *', dbg.address(ir))[LJ_GC64]['gcr'])
+
+
+def ir_knum(ir):
+    irname = IRS[ir['o']]
+    assert irname == 'KNUM', 'wrong IR for ir_knum()'
+    return dbg.address(dbg.cast('IRIns *', dbg.address(ir))[1]['tv'])
+
+
+def ir_kint64(ir):
+    irname = IRS[ir['o']]
+    assert irname == 'KINT64', 'wrong IR for ir_knum()'
typo: s/ir_knum/ir_kint64/
+    return dbg.address(dbg.cast('IRIns *', dbg.address(ir))[1]['tv'])
+
+
 # Dumpers.
 
 # GCobj dumpers.
@@ -1467,6 +2281,325 @@ def dump_func(func):
         return 'fast function #{}\n'.format(int(ffid))
 
 
+# JIT dumpers.
+
+
+def dump_call_func(trace, callop):
+    ctype = ''
+    if callop > 0:
+        ir = trace['ir'][REF_BIAS + callop]
+        if IRTYPES[irt_type(ir['t'])] == 'nil':  # nil == CARG(func, ctype)
+            callop = int(ir['op1']) - REF_BIAS
+            cdt_idx_irk = trace['ir'][ir['op2']]
+            assert IRS[cdt_idx_irk['o']] == 'KINT', \
+                   'unexpected IR for ctype storage'
+            ctype_idx = cdt_idx_irk['i']
+            ctype = 'ctype: {}'.format(ctype_idx)
+
+    func_str = ''
+    if callop < 0:
+        irk = trace['ir'][REF_BIAS + callop]
+        assert IRS[irk['o']] == 'KINT64', \
+               'unexpected IR for FFI function storage'
+        func_addr = int(ir_kint64(irk)['u64'])
+        # TODO: Symbol demangling.
+        func_str = '[{:#x}]'.format(func_addr)
+    else:
+        func_str = '[{:04d}]'.format(callop)
+
+    return func_str, ctype
+
+
+def dump_call_args(trace, ins):
+    if ins < 0:
+        return '{{{}}}'.format(dump_irk(trace, ins))
+    else:
+        ir = trace['ir'][REF_BIAS + ins]
+        irname = IRS[ir['o']]
+        if irname == 'CARG':
+            last_arg = ''
+            args = dump_call_args(trace, int(ir['op1']) - REF_BIAS)
+            op2 = int(ir['op2']) - REF_BIAS
+            if op2 < 0:
+                last_arg = '{{{}}}'.format(dump_irk(trace, op2))
+            else:
+                last_arg = '{{{:04d}}}'.format(op2)
+            return args + ', ' + last_arg
+        else:
+            return '{{{:04d}}}'.format(ins)
+
+
+# Special FP constant.
+CONST_BIAS = 2 ** 52 + 2 ** 51
+
+
+def dump_irk(trace, idx):
+    ref = idx + REF_BIAS
+    assert ref >= trace['nk'] and ref < REF_BIAS, 'bad constant in IR dump'
+    irins = trace['ir'][ref]
+    irname = IRS[irins['o']]
+    slot = ''
+    if irname == 'KSLOT':
+        slot = ' KSLOT: @{}'.format(int(irins['op2']))
+        irins = trace['ir'][irins['op1']]
+        irname = IRS[irins['o']]
+
+    irtype = irins['t']
+    if irname == 'KPRI':
+        typename = typenames(irt_toitype(irtype))
+        # Trivial dump for primitives.
+        irk = tv_dumpers.get(
+            typename, dump_lj_tv_invalid  # noqa: F821 # Generated.
+        )(0)
+    elif irname == 'KINT':
+        irk = 'integer {}'.format(dbg.cast('int32_t', irins['i']))
+    elif irname == 'KGC':
+        typename = typenames(irt_toitype(irtype))
+        irk = gco_dumpers.get(typename, dump_lj_gco_invalid)(ir_kgc(irins))
+    elif irname == 'KKPTR':
+        addr = ir_kptr(irins)
+        if addr == dbg.address(G(L())['nilnode']):
+            return '[g->nilnode]' + slot
+        irk = '[{}]'.format(strx64(addr))
+    elif irname == 'KPTR':
+        irk = '[{}]'.format(strx64(ir_kptr(irins)))
+    elif irname == 'KNULL':
+        irk = 'NULL'
+    elif irname == 'KNUM':
+        tv_num = ir_knum(irins)
+        if float(tv_num['n']) == CONST_BIAS:
+            return 'bias'
+        irk = dump_lj_tv_numx(tv_num)
+    elif irname == 'KINT64':
+        irk = 'int64_t {}'.format(dbg.cast(
+            'int64_t', int(ir_kint64(irins)['u64'])
+        ))
+    else:
+        return 'Unknown IRK: ' + irname
+    return irk + slot
+
+
<snipped>
+
+def dump_snap(trace, snapno, snap):
+    dump = 'SNAP   #{:<3d} ['.format(snapno)
+    snap_map = dbg.address(trace['snapmap'][snap['mapofs']])
+    snap_entry_num = 0
+    for slot in range(0, snap['nslots']):
+        dump += ' '
+        snap_entry = int(snap_map[snap_entry_num])
+        if snap_entry_num < snap['nent'] and snap_entry >> TREF_SHIFT == slot:
+            snap_entry_num += 1
+            ref = int((snap_entry & TREF_REFMASK) - REF_BIAS)
+            if ref < 0:
+                if int(snap_entry) == 0x1057fff:
magic number

    
<snipped>
+# Assume not cross-platform debugging.
+machine = os.uname().machine
+if machine == 'x86_64':
+    RX_GPR = r'r\w\w'
+    RX_FPR = r'xmm\d+'
+elif machine == 'arm64' or machine == 'aarch64':
+    RX_GPR = r'x\d+'
+    RX_FPR = r'd\d+'
+else:
+    raise Exception('Unknown archeticture in testing')
typo: s/archeticture/architecture/

    
<snipped>
+
+class TestLJIRConst(TestCaseBase):
+    location = 'trace_stop'
+
+    # No narrowing of 42.
+    if IS_DUALNUM:
+        # KNUM occupies 2 slots.
+        _knum_irnum = '6'
+        _kgc_irnum = '8' if IS_GC64 else '7'
+        _kptr_irnum = '10' if IS_GC64 else '8'
+    else:
+        # KNUM occupies 2 slots.
+        _knum_irnum = '8'
+        _kgc_irnum = '10' if IS_GC64 else '9'
+        _kptr_irnum = '12' if IS_GC64 else '10'
both branches contains the same comment, is it a typo or not?
<snipped>
 
--------------T90u3bPoffQ2oPqAFJ3vcSIy--