* [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers
@ 2026-06-04 9:30 Sergey Kaplun via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 1/4] dbg: fix lj-stack command for LLDB Sergey Kaplun via Tarantool-patches
` (5 more replies)
0 siblings, 6 replies; 13+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2026-06-04 9:30 UTC (permalink / raw)
To: Sergey Bronnikov, Evgeniy Temirgaleev; +Cc: tarantool-patches
Branch: https://github.com/tarantool/luajit/tree/skaplun/gh-4808-gco-func-proto-bytecode
Issue: https://github.com/tarantool/tarantool/issues/4808
This patch set allows you to inspect bytecodes for a single instruction,
as well as for all bytecodes inside a function or its prototype via GDB
and LLDB.
The first patch is a fixup for the LLDB indexing negative values. It may
affect the lj-stack command. The second patch fixes the DUALNUM mode
detection in LLDB. These fixes are required for the last patch in the
series.
The third auxiliary patch is needed to introduce dumpers for GC objects
similar to TValues dumpers. Also, it may be useful during different
debugging scenarios, so it introduces the lj-gco <GCobj *> command to
dump the GC object info in the same format as for a TValue slot.
The last patch introduces 3 new commands:
* lj-bc <GCIns *> -- dump single bytecode instruction
* lj-func <GCfunc *> -- dump all bytecode instructions for Lua function
or report type of C or F function
* lj-proto <GCproto *> -- dump all bytecode instructions for the
prototype
For example, we have the following Lua script named <tmp.lua>:
| 1 local function mywhile(a)
| 2 local r = 0
| 3 print(a)
| 4 while (a < 30) do
| 5 r = r + a * r/2
| 6 end
| 7 return r
| 8 end
| 9
| 10 local uvname1 = false
| 11 local uvname2 = false
| 12 local function myif(a)
| 13 local s1 = a + 4
| 14 local s2 = s1 + 4
| 15 uvname1 = "s10"
| 16 uvname2 = "s11"
| 17 print(a)
| 18 if a > 10 then
| 19 return a + s2 + s1
| 20 else
| 21 return a - 10 - s2 - s1
| 22 end
| 23 end
| 24
| 25 local f1 = myif
| 26 local f2 = mywhile
| 27 myif(12)
| 28 mywhile(12)
Assume we set a breakpoint at `lj_cf_print` (line 3).
The lj-stack output contains the following lines:
| 0x40001970 [ ] VALUE: Lua function @ 0x400083c0, 0 upvalues, "@../tmp.lua":1
| 0x40001968 [ ] VALUE: Lua function @ 0x40002148, 2 upvalues, "@../tmp.lua":12
| ...
| 0x40001940 [ ] FRAME: [V] delta=1, Lua function @ 0x400084a0, 0 upvalues, "@../tmp.lua":0
The first one is `myif()` function the second is `mywhile()` and the
last one is function loaded via `dofile()`.
The resulting output for the functions is the following:
1)
| (gdb) lj-func 0x400083c0
| "@../tmp.lua":1-8
| 0000 FUNCF rbase: 4
| 0001 KSHORT dst: 1 lits: 0
| 0002 GGET dst: 2 str: 0 ; string "print" @ 0x400037f0
| 0003 MOV dst: 3 var: 0
| 0004 CALL base: 2 lit: 1 lit: 2
| 0005 KSHORT dst: 2 lits: 30
| 0006 ISGE var: 0 var: 2
| 0007 JMP rbase: 2 jump: => 0013
| 0008 LOOP rbase: 2 jump: => 0013
| 0009 MULVV dst: 2 var: 0 var: 1
| 0010 DIVVN dst: 2 var: 2 num: 0 ; number 2
| 0011 ADDVV dst: 1 var: 1 var: 2
| 0012 JMP rbase: 2 jump: => 0005
| 0013 RET1 rbase: 1 lit: 2
The report is the same as for the following command:
| lj-proto (GCproto *)(((char *)(((GCfuncL *)0x400083c0)->pc.ptr32))-sizeof(GCproto))
2)
| (gdb) lj-func 0x40002148
| "@../tmp.lua":12-23
| 0000 FUNCF rbase: 5
| 0001 ADDVN dst: 1 var: 0 num: 0 ; number 4
| 0002 ADDVN dst: 2 var: 1 num: 0 ; number 4
| 0003 USETS uv: 0 str: 0 ; 0x40002527 "uvname1" ; string "s10" @ 0x40002298
| 0004 USETS uv: 1 str: 1 ; 0x4000252f "uvname2" ; string "s11" @ 0x400022b8
| 0005 GGET dst: 3 str: 2 ; string "print" @ 0x400037f0
| 0006 MOV dst: 4 var: 0
| 0007 CALL base: 3 lit: 1 lit: 2
| 0008 KSHORT dst: 3 lits: 10
| 0009 ISGE var: 3 var: 0
| 0010 JMP rbase: 3 jump: => 0015
| 0011 ADDVV dst: 3 var: 0 var: 2
| 0012 ADDVV dst: 3 var: 3 var: 1
| 0013 RET1 rbase: 3 lit: 2
| 0014 JMP rbase: 3 jump: => 0019
| 0015 SUBVN dst: 3 var: 0 num: 1 ; number 10
| 0016 SUBVV dst: 3 var: 3 var: 2
| 0017 SUBVV dst: 3 var: 3 var: 1
| 0018 RET1 rbase: 3 lit: 2
| 0019 RET0 rbase: 0 lit: 1
3)
| (gdb) lj-func 0x400084a0
| "@../tmp.lua":0-30
| 0000 FUNCV rbase: 8
| 0001 FNEW dst: 0 func: 0 ; "@../tmp.lua":1
| 0002 KPRI dst: 1 pri: 1
| 0003 KPRI dst: 2 pri: 1
| 0004 FNEW dst: 3 func: 1 ; "@../tmp.lua":12
| 0005 MOV dst: 4 var: 3
| 0006 MOV dst: 5 var: 0
| 0007 MOV dst: 6 var: 3
| 0008 KSHORT dst: 7 lits: 12
| 0009 CALL base: 6 lit: 1 lit: 2
| 0010 MOV dst: 6 var: 0
| 0011 KSHORT dst: 7 lits: 12
| 0012 CALL base: 6 lit: 1 lit: 2
| 0013 UCLO rbase: 0 jump: => 0014
| 0014 RET0 rbase: 0 lit: 1
The single bytecode instruction may be useful when you debug the VM:
| (gdb) b lj_BC_ISGE
| Breakpoint 2 at 0x5555555f0a08
| (gdb) c
| Continuing.
| Breakpoint 2, 0x00005555555f0a08 in lj_BC_ISGE ()
| (gdb) lj-bc $rbx # PC refers __the next instruction__
| JMP rbase: 3 jump: +5
| (gdb) lj-bc ((BCIns *)$rbx) - 1 # current instruction
| ISGE var: 3 var: 0
Sergey Kaplun (4):
dbg: fix lj-stack command for LLDB
dbg: fix DUALNUM detection for LLDB
dbg: introduce lj-gco command
dbg: introduce lj-bc, lj-func and lj-proto dumpers
src/luajit_dbg.py | 650 ++++++++++++++++--
.../debug-extension-tests.py | 203 +++++-
2 files changed, 757 insertions(+), 96 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [Tarantool-patches] [PATCH luajit 1/4] dbg: fix lj-stack command for LLDB
2026-06-04 9:30 [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Kaplun via Tarantool-patches
@ 2026-06-04 9:30 ` Sergey Kaplun via Tarantool-patches
2026-06-05 14:55 ` Sergey Bronnikov via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 2/4] dbg: fix DUALNUM detection " Sergey Kaplun via Tarantool-patches
` (4 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2026-06-04 9:30 UTC (permalink / raw)
To: Sergey Bronnikov, Evgeniy Temirgaleev; +Cc: tarantool-patches
This commit is the follow-up for the commit
593fef813b7d36060e0c1b1ae2df0fbc0d604d1f ("lldb: refactor extension").
`GetValueForExpressionPath()` may produce the empty value for negative
indexes. This leads to incorrect frame unwinding in the LLDB extension
and errors like:
| Failed to execute command `lj-stack`:
| 'Q' format requires 0 <= number <= 18446744073709551615
This patch adds special handling for the negative indices.
---
src/luajit_dbg.py | 49 ++++++++++++++++---
.../debug-extension-tests.py | 26 +++++++---
2 files changed, 62 insertions(+), 13 deletions(-)
diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index d9196f06..410f0191 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -255,15 +255,31 @@ class _GDBDebugger(Debugger):
class _LLDBDebugger(Debugger):
def _lldb_tp_isfp(self, tp):
- return tp.GetBasicType() in [
+ return tp.GetCanonicalType().GetBasicType() in [
lldb.eBasicTypeFloat,
lldb.eBasicTypeDouble,
lldb.eBasicTypeLongDouble
]
+ def _lldb_tp_issigned(self, tp):
+ return tp.GetCanonicalType().GetBasicType() in [
+ lldb.eBasicTypeChar,
+ lldb.eBasicTypeSignedChar,
+ lldb.eBasicTypeShort,
+ lldb.eBasicTypeInt,
+ lldb.eBasicTypeLong,
+ lldb.eBasicTypeLongLong,
+ lldb.eBasicTypeInt128
+ ]
+
def _lldb_value_from_raw(self, raw_value, size, tp):
isfp = self._lldb_tp_isfp(tp)
- pack_flag = '<d' if isfp else '<Q'
+ if isfp:
+ pack_flag = '<d'
+ elif self._lldb_tp_issigned(tp):
+ pack_flag = '<q'
+ else:
+ pack_flag = '<Q'
raw_data = struct.pack(pack_flag, raw_value)
sbdata = lldb.SBData()
sbdata.SetData(
@@ -308,9 +324,24 @@ class _LLDBDebugger(Debugger):
key = int(key)
if type(key) is int:
# Allow array access.
- return lldb.value(
- lldbval.sbvalue.GetValueForExpressionPath('[%i]' % key)
- )
+ if key >= 0:
+ return lldb.value(
+ lldbval.sbvalue.GetValueForExpressionPath('[%i]' % key)
+ )
+ else:
+ # GetValueForExpressionPath doesn't work for
+ # negative offsets.
+ sbvalue = lldbval.sbvalue
+ assert sbvalue.TypeIsPointerType(), \
+ 'attempt to get index of non-pointer type'
+ tp = sbvalue.GetType().GetPointeeType()
+ sz = sbvalue.deref.size
+ addr = sbvalue.GetValueAsUnsigned() + key * sz
+ return lldb.value(self.target.CreateValueFromAddress(
+ '({tp}){addr}'.format(tp=tp, addr=addr),
+ lldb.SBAddress(addr, self.target),
+ tp,
+ ))
elif type(key) is str:
return lldb.value(lldbval.sbvalue.GetChildMemberWithName(key))
raise Exception(TypeError('No item of type %s' % str(type(key))))
@@ -417,8 +448,12 @@ class _LLDBDebugger(Debugger):
# 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()
+ if self._lldb_tp_isfp(tp):
+ rawval = float(val.GetValue())
+ elif self._lldb_tp_issigned(tp):
+ rawval = val.GetValueAsSigned()
+ else:
+ rawval = val.GetValueAsUnsigned()
return self._lldb_value_from_raw(rawval, val.GetByteSize(), tp)
def sizeof(self, typestr):
diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
index 61529561..7cb60d84 100644
--- a/test/tarantool-debugger-tests/debug-extension-tests.py
+++ b/test/tarantool-debugger-tests/debug-extension-tests.py
@@ -186,16 +186,30 @@ class TestLJGC(TestCaseBase):
)
-class TestLJStack(TestCaseBase):
+STACK_RX = (
+ r'-+ Red zone:\s+\d+ slots -+\n'
+ r'(' + RX_ADDR + r'\s+' + RX_FRAME + r' VALUE: nil\n?)*\n'
+ r'-+ Stack:\s+\d+ slots -+\n'
+ r'(' + RX_ADDR + r'(:' + RX_ADDR + r')?\s+' + RX_FRAME + r'.*\n?)+\n'
+)
+
+
+class TestLJStackBase(TestCaseBase):
extension_cmds = 'lj-stack'
location = 'lj_cf_print'
lua_script = 'print(1)'
- pattern = (
- r'-+ Red zone:\s+\d+ slots -+\n'
- r'(' + RX_ADDR + r'\s+' + RX_FRAME + r' VALUE: nil\n?)*\n'
- r'-+ Stack:\s+\d+ slots -+\n'
- r'(' + RX_ADDR + r'(:' + RX_ADDR + r')?\s+' + RX_FRAME + r'.*\n?)+\n'
+ pattern = STACK_RX
+
+
+# Check LLDB correctness for the specific stack.
+class TestLJStackFunc(TestCaseBase):
+ extension_cmds = 'lj-stack'
+ location = 'lj_cf_print'
+ lua_script = (
+ 'local function nop() end\n'
+ 'print()\n'
)
+ pattern = STACK_RX
class TestLJTV(TestCaseBase):
--
2.54.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [Tarantool-patches] [PATCH luajit 2/4] dbg: fix DUALNUM detection for LLDB
2026-06-04 9:30 [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Kaplun via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 1/4] dbg: fix lj-stack command for LLDB Sergey Kaplun via Tarantool-patches
@ 2026-06-04 9:30 ` Sergey Kaplun via Tarantool-patches
2026-06-05 14:57 ` Sergey Bronnikov via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 3/4] dbg: introduce lj-gco command Sergey Kaplun via Tarantool-patches
` (3 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2026-06-04 9:30 UTC (permalink / raw)
To: Sergey Bronnikov, Evgeniy Temirgaleev; +Cc: tarantool-patches
The `lj-arch` command on LLDB reports 'LJ_DUALNUM: True' for the
single-number build since the `module.FindSymbol()` returns an invalid
`SBSymbol` object [1], which is not `None`. This leads to invalid
DUALNUM mode detection.
This patch fixes this by checking that the returned symbol is valid.
[1]: https://lldb.llvm.org/python_api/lldb.SBModule.html#lldb.SBModule.FindSymbol
---
src/luajit_dbg.py | 3 ++-
.../debug-extension-tests.py | 15 +++++++++++++--
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index 410f0191..300d65e9 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -498,7 +498,8 @@ class _LLDBDebugger(Debugger):
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
+ dualnum_sym = module.FindSymbol('lj_lib_checknumber')
+ LJ_DUALNUM = dualnum_sym is not None and dualnum_sym.IsValid()
irtype_enum = self.target.FindFirstType('IRType').enum_members
for member in irtype_enum:
if member.name == 'IRT_PTR':
diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
index 7cb60d84..06a118ff 100644
--- a/test/tarantool-debugger-tests/debug-extension-tests.py
+++ b/test/tarantool-debugger-tests/debug-extension-tests.py
@@ -92,6 +92,17 @@ def execute_process(cmd, timeout=TIMEOUT):
return process.stdout
+IS_DUALNUM = execute_process([
+ LUAJIT_BINARY, '-e', "print(require('ffi').abi('dualnum'))"
+]).strip() == 'true'
+
+# If it is the guaranteed DUALNUM build (for example, on aarch64),
+# we use this regexp for the guaranteed 'integer' check and
+# 'number' for single-number build.
+RX_INT = r'integer' if IS_DUALNUM else r'number'
+RX_ISDUALNUM = r'True' if IS_DUALNUM else r'False'
+
+
class TestCaseBase(unittest.TestCase):
@classmethod
def construct_cmds(cls):
@@ -150,7 +161,7 @@ class TestLJArch(TestCaseBase):
pattern = (
r'LJ_64: (True|False), '
r'LJ_GC64: (True|False), '
- r'LJ_DUALNUM: (True|False)'
+ r'LJ_DUALNUM: ' + RX_ISDUALNUM
)
@@ -265,7 +276,7 @@ class TestLJTV(TestCaseBase):
r'cdata @ ' + RX_ADDR + r'\n'
r'table @ ' + RX_ADDR + r' \(asize: \d+, hmask: ' + RX_HASH + r'\)\n'
r'userdata @ ' + RX_ADDR + r'\n'
- r'(number|integer) .*1.*\n'
+ RX_INT + r' .*1.*\n'
r'number 1.1\d+\n'
)
--
2.54.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [Tarantool-patches] [PATCH luajit 3/4] dbg: introduce lj-gco command
2026-06-04 9:30 [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Kaplun via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 1/4] dbg: fix lj-stack command for LLDB Sergey Kaplun via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 2/4] dbg: fix DUALNUM detection " Sergey Kaplun via Tarantool-patches
@ 2026-06-04 9:30 ` Sergey Kaplun via Tarantool-patches
2026-06-05 15:02 ` Sergey Bronnikov via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 4/4] dbg: introduce lj-bc, lj-func and lj-proto dumpers Sergey Kaplun via Tarantool-patches
` (2 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2026-06-04 9:30 UTC (permalink / raw)
To: Sergey Bronnikov, Evgeniy Temirgaleev; +Cc: tarantool-patches
Our GDB extension already has dumpers for TValues. But sometimes it
may be useful to dump GC objects (GCobj) without stack context. This
patch adds additional wrappers around dumpers for GC objects to get the
corresponding GC object from a TValue. Also, the lj-gco command is
introduced. It allows dumping GC objects without stack context. The
output format is the same as for the lj-tv command.
Part of tarantool/tarantool#4808
---
src/luajit_dbg.py | 182 ++++++++++++------
.../debug-extension-tests.py | 83 ++++++--
2 files changed, 190 insertions(+), 75 deletions(-)
diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index 300d65e9..f5868e61 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -882,44 +882,29 @@ def lightudV(tv):
# Dumpers.
+# GCobj 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):
+def dump_lj_gco_str(gcobj):
return 'string {body} @ {address}'.format(
- body=strdata(gcval(tv['gcr'])),
- address=strx64(gcval(tv['gcr']))
+ body=strdata(gcobj),
+ address=strx64(gcobj)
)
-def dump_lj_tupval(tv):
- return 'upvalue @ {}'.format(strx64(gcval(tv['gcr'])))
+def dump_lj_gco_upval(gcobj):
+ return 'upvalue @ {}'.format(strx64(gcobj))
-def dump_lj_tthread(tv):
- return 'thread @ {}'.format(strx64(gcval(tv['gcr'])))
+def dump_lj_gco_thread(gcobj):
+ return 'thread @ {}'.format(strx64(gcobj))
-def dump_lj_tproto(tv):
- return 'proto @ {}'.format(strx64(gcval(tv['gcr'])))
+def dump_lj_gco_proto(gcobj):
+ return 'proto @ {}'.format(strx64(gcobj))
-def dump_lj_tfunc(tv):
- func = dbg.cast('struct GCfuncC *', gcval(tv['gcr']))
+def dump_lj_gco_func(gcobj):
+ func = dbg.cast('struct GCfuncC *', gcobj)
ffid = func['ffid']
if ffid == 0:
@@ -936,20 +921,20 @@ def dump_lj_tfunc(tv):
return 'fast function #{}'.format(int(ffid))
-def dump_lj_ttrace(tv):
- trace = dbg.cast('struct GCtrace *', gcval(tv['gcr']))
+def dump_lj_gco_trace(gcobj):
+ trace = dbg.cast('struct GCtrace *', gcobj)
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_gco_cdata(gcobj):
+ return 'cdata @ {}'.format(strx64(gcobj))
-def dump_lj_ttab(tv):
- table = dbg.cast('GCtab *', gcval(tv['gcr']))
+def dump_lj_gco_tab(gcobj):
+ table = dbg.cast('GCtab *', gcobj)
return 'table @ {gcr} (asize: {asize}, hmask: {hmask})'.format(
gcr=strx64(table),
asize=table['asize'],
@@ -957,41 +942,94 @@ def dump_lj_ttab(tv):
)
-def dump_lj_tudata(tv):
- return 'userdata @ {}'.format(strx64(gcval(tv['gcr'])))
+def dump_lj_gco_udata(gcobj):
+ return 'userdata @ {}'.format(strx64(gcobj))
+
+
+def dump_lj_gco_invalid(gcobj):
+ return 'not valid type @ {}'.format(strx64(gcobj))
+
+
+# TValue dumpers
+
+def dump_lj_tv_nil(tv):
+ return 'nil'
+
+
+def dump_lj_tv_false(tv):
+ return 'false'
+
+
+def dump_lj_tv_true(tv):
+ return 'true'
+
+
+def dump_lj_tv_lightud(tv):
+ return 'light userdata @ {}'.format(strx64(lightudV(tv)))
+
+
+# Generate wrappers for TValues containing GCobj.
+gco_fn_dumpers = [
+ fn for fn in globals().keys() if fn.startswith('dump_lj_gco')
+]
+for fn_name in gco_fn_dumpers:
+ wrapped_fn_name = fn_name.replace('gco', 'tv')
+ # Lambda takes `fn_name` as a reference, so the additional
+ # lambda is needed to fixate the correct wrapper.
+ globals()[wrapped_fn_name] = (lambda f: (
+ lambda tv: globals()[f](gcval(tv['gcr']))
+ ))(fn_name)
-def dump_lj_tnumx(tv):
+def dump_lj_tv_numx(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,
+gco_dumpers = {
+ 'LJ_TSTR': dump_lj_gco_str,
+ 'LJ_TUPVAL': dump_lj_gco_upval,
+ 'LJ_TTHREAD': dump_lj_gco_thread,
+ 'LJ_TPROTO': dump_lj_gco_proto,
+ 'LJ_TFUNC': dump_lj_gco_func,
+ 'LJ_TTRACE': dump_lj_gco_trace,
+ 'LJ_TCDATA': dump_lj_gco_cdata,
+ 'LJ_TTAB': dump_lj_gco_tab,
+ 'LJ_TUDATA': dump_lj_gco_udata,
}
+tv_dumpers = {
+ 'LJ_TNIL': dump_lj_tv_nil,
+ 'LJ_TFALSE': dump_lj_tv_false,
+ 'LJ_TTRUE': dump_lj_tv_true,
+ 'LJ_TLIGHTUD': dump_lj_tv_lightud,
+ 'LJ_TSTR': dump_lj_tv_str, # noqa: F821 # Generated.
+ 'LJ_TUPVAL': dump_lj_tv_upval, # noqa: F821 # Generated.
+ 'LJ_TTHREAD': dump_lj_tv_thread, # noqa: F821 # Generated.
+ 'LJ_TPROTO': dump_lj_tv_proto, # noqa: F821 # Generated.
+ 'LJ_TFUNC': dump_lj_tv_func, # noqa: F821 # Generated.
+ 'LJ_TTRACE': dump_lj_tv_trace, # noqa: F821 # Generated.
+ 'LJ_TCDATA': dump_lj_tv_cdata, # noqa: F821 # Generated.
+ 'LJ_TTAB': dump_lj_tv_tab, # noqa: F821 # Generated.
+ 'LJ_TUDATA': dump_lj_tv_udata, # noqa: F821 # Generated.
+ 'LJ_TNUMX': dump_lj_tv_numx,
+}
+
+
+def dump_gcobj(gcobj):
+ return gco_dumpers.get(
+ typenames(i2notu32(gcobj['gch']['gct'])), dump_lj_gco_invalid
+ )(gcobj)
+
+
def dump_tvalue(tvalue):
- return dumpers.get(typenames(itypemap(tvalue)), dump_lj_invalid)(tvalue)
+ return tv_dumpers.get(
+ typenames(itypemap(tvalue)),
+ dump_lj_tv_invalid # noqa: F821 # Generated.
+ )(tvalue)
def dump_framelink_slot_address(fr):
@@ -1011,7 +1049,7 @@ def dump_framelink(L, 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),
+ f=dump_lj_tv_func(fr - LJ_FR2), # noqa: F821 # Generated.
)
@@ -1141,6 +1179,35 @@ The command requires no args and dumps current GC stats:
))
+class LJDumpGCobj(dbg.LJBase):
+ '''
+lj-gco <GCobj *>
+
+The command receives a pointer to <GCobj> (GCobj address) and dumps
+the type and some info related to it.
+
+* 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>
+
+Whether the type of the given address differs from the listed above, then
+error message occurs.
+ '''
+
+ def execute(self, arg):
+ gcobj = dbg.cast('GCobj *', dbg.eval(arg))
+ dbg.write('{}\n'.format(dump_gcobj(gcobj)))
+
+
class LJDumpStack(dbg.LJBase):
'''
lj-stack [<lua_State *>]
@@ -1302,6 +1369,7 @@ def load(event=None):
dbg.initialize_extension({
'lj-arch': LJDumpArch,
'lj-gc': LJGC,
+ 'lj-gco': LJDumpGCobj,
'lj-stack': LJDumpStack,
'lj-state': LJState,
'lj-str': LJDumpString,
diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
index 06a118ff..7e2b5ac4 100644
--- a/test/tarantool-debugger-tests/debug-extension-tests.py
+++ b/test/tarantool-debugger-tests/debug-extension-tests.py
@@ -138,6 +138,17 @@ class TestCaseBase(unittest.TestCase):
self.assertRegex(self.output, self.pattern.strip())
+# LLDB + Clang on macOS can't produce debug info for the C-defined
+# macros. Thus, we hardcoded its value manually.
+def gcval(arg):
+ if sys.platform == 'darwin':
+ # Assume GC64 build only.
+ LJ_GCVMASK = '(((uint64_t)1 << 47) - 1)'
+ return '(((' + arg + ')->gcr).gcptr64 & ' + LJ_GCVMASK + ')'
+ else:
+ return 'gcval(' + arg + ')'
+
+
class TestLoad(TestCaseBase):
extension_cmds = ''
location = 'lj_cf_print'
@@ -145,6 +156,7 @@ class TestLoad(TestCaseBase):
pattern = (
r'lj-arch command initialized\n'
r'lj-gc command initialized\n'
+ r'lj-gco command initialized\n'
r'lj-stack command initialized\n'
r'lj-state command initialized\n'
r'lj-str command initialized\n'
@@ -223,6 +235,31 @@ class TestLJStackFunc(TestCaseBase):
pattern = STACK_RX
+# Sorted in LJT order.
+GCO_ARGS = (
+ '"hello",\n'
+ 'coroutine.create(function() end),\n'
+ 'function() end,\n'
+ 'require,\n'
+ 'print,\n'
+ 'ffi.new("int*"),\n'
+ '{1},\n'
+ 'newproxy(),\n'
+)
+
+
+GCO_RX = (
+ r'string \"hello\" @ ' + RX_ADDR + r'\n'
+ r'thread @ ' + RX_ADDR + r'\n'
+ r'Lua function @ ' + RX_ADDR + r', [0-9]+ upvalues, .+:[0-9]+\n'
+ r'C function @ ' + RX_ADDR + r'\n'
+ r'fast function #[0-9]+\n'
+ r'cdata @ ' + RX_ADDR + r'\n'
+ r'table @ ' + RX_ADDR + r' \(asize: \d+, hmask: ' + RX_HASH + r'\)\n'
+ r'userdata @ ' + RX_ADDR + r'\n'
+)
+
+
class TestLJTV(TestCaseBase):
location = 'lj_cf_print'
extension_cmds = (
@@ -249,15 +286,8 @@ class TestLJTV(TestCaseBase):
' nil,\n'
' false,\n'
' true,\n'
- ' debug.upvalueid(print, 1), \n' # lightuserdata
- ' "hello",\n'
- ' coroutine.create(function() end),\n'
- ' function() end,\n'
- ' require,\n'
- ' print,\n'
- ' ffi.new("int*"),\n'
- ' {1},\n'
- ' newproxy(),\n'
+ ' debug.upvalueid(print, 1), \n' + # lightuserdata
+ GCO_ARGS +
' 1,\n'
' 1.1\n'
')\n'
@@ -267,15 +297,8 @@ class TestLJTV(TestCaseBase):
r'nil\n'
r'false\n'
r'true\n'
- r'light userdata @ ' + RX_ADDR + r'\n'
- r'string \"hello\" @ ' + RX_ADDR + r'\n'
- r'thread @ ' + RX_ADDR + r'\n'
- r'Lua function @ ' + RX_ADDR + r', [0-9]+ upvalues, .+:[0-9]+\n'
- r'C function @ ' + RX_ADDR + r'\n'
- r'fast function #[0-9]+\n'
- r'cdata @ ' + RX_ADDR + r'\n'
- r'table @ ' + RX_ADDR + r' \(asize: \d+, hmask: ' + RX_HASH + r'\)\n'
- r'userdata @ ' + RX_ADDR + r'\n'
+ r'light userdata @ ' + RX_ADDR + r'\n' +
+ GCO_RX +
RX_INT + r' .*1.*\n'
r'number 1.1\d+\n'
)
@@ -312,6 +335,30 @@ class TestLJTab(TestCaseBase):
)
+class TestLJGCo(TestCaseBase):
+ location = 'lj_cf_print'
+ extension_cmds = (
+ 'lj-gco ' + gcval('L->base + 0') + '\n'
+ 'lj-gco ' + gcval('L->base + 1') + '\n'
+ 'lj-gco ' + gcval('L->base + 2') + '\n'
+ 'lj-gco ' + gcval('L->base + 3') + '\n'
+ 'lj-gco ' + gcval('L->base + 4') + '\n'
+ 'lj-gco ' + gcval('L->base + 5') + '\n'
+ 'lj-gco ' + gcval('L->base + 6') + '\n'
+ 'lj-gco ' + gcval('L->base + 7') + '\n'
+ )
+
+ lua_script = (
+ 'local ffi = require("ffi")\n'
+ 'print(\n' +
+ GCO_ARGS +
+ ' 1\n' # Stub for the pattern.
+ ')\n'
+ )
+
+ pattern = GCO_RX
+
+
for test_cls in TestCaseBase.__subclasses__():
test_cls.test = lambda self: self.check()
--
2.54.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [Tarantool-patches] [PATCH luajit 4/4] dbg: introduce lj-bc, lj-func and lj-proto dumpers
2026-06-04 9:30 [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Kaplun via Tarantool-patches
` (2 preceding siblings ...)
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 3/4] dbg: introduce lj-gco command Sergey Kaplun via Tarantool-patches
@ 2026-06-04 9:30 ` Sergey Kaplun via Tarantool-patches
2026-06-05 15:07 ` Sergey Bronnikov via Tarantool-patches
2026-06-05 14:55 ` [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Bronnikov via Tarantool-patches
2026-06-05 16:03 ` [Tarantool-patches] [PATCH luajit 3/5] dbg: update help for the lj-arch command Sergey Kaplun via Tarantool-patches
5 siblings, 1 reply; 13+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2026-06-04 9:30 UTC (permalink / raw)
To: Sergey Bronnikov, Evgeniy Temirgaleev; +Cc: tarantool-patches
This patch adds dumpers for a single bytecode instruction (`lj-bc`), as
well as for all bytecodes inside one function (`lj-func`) or prototype
(`lj-proto`). Its dump is quite similar to the -bl flag but also
reports types of register operands (`jmp`, `dst`, `str`, etc.).
For LLDB, the result from the `lookup_global()` method is wrapped to the
`lldb.value` object to make it more convenient.
Part of tarantool/tarantool#4808
---
src/luajit_dbg.py | 416 +++++++++++++++++-
.../debug-extension-tests.py | 79 ++++
2 files changed, 490 insertions(+), 5 deletions(-)
diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index f5868e61..60308179 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -475,7 +475,7 @@ class _LLDBDebugger(Debugger):
return strptr.sbvalue.summary
def lookup_global(self, symbol):
- return self.target.FindFirstGlobalVariable(symbol)
+ return lldb.value(self.target.FindFirstGlobalVariable(symbol))
def eval(self, command):
if not command:
@@ -648,6 +648,202 @@ def itypemap(o):
return LJ_T['NUMX'] if tvisnumber(o) else itype(o)
+# Bytecode.
+
+def bc_op(ins):
+ return int(ins) & 0xff
+
+
+def bc_a(ins):
+ return (int(ins) >> 8) & 0xff
+
+
+def bc_b(ins):
+ return int(ins) >> 24
+
+
+def bc_c(ins):
+ return (int(ins) >> 16) & 0xff
+
+
+def bc_d(ins):
+ return int(ins) >> 16
+
+
+BCMODE = [
+ 'none', 'dst', 'base', 'var', 'rbase', 'uv',
+ 'lit', 'lits', 'pri', 'num', 'str', 'tab', 'func', 'jump', 'cdata',
+]
+
+
+lj_bc_mode_ = None
+
+
+def lj_bc_mode():
+ global lj_bc_mode_
+ if lj_bc_mode_:
+ return lj_bc_mode_
+ lj_bc_mode_ = dbg.lookup_global('lj_bc_mode')
+ return lj_bc_mode_
+
+
+def bcmode_a(op):
+ return int(lj_bc_mode()[op] & 7)
+
+
+def bcmode_b(op):
+ return int((lj_bc_mode()[op] >> 3) & 15)
+
+
+def bcmode_cd(op):
+ return int((lj_bc_mode()[op] >> 7) & 15)
+
+
+# Unfortunately, there is no place in the VM except the generated
+# Lua table, where the bytecode names are stored. So duplicate
+# them here.
+BYTECODES = [
+ # Comparison ops. ORDER OPR.
+ 'ISLT',
+ 'ISGE',
+ 'ISLE',
+ 'ISGT',
+
+ 'ISEQV',
+ 'ISNEV',
+ 'ISEQS',
+ 'ISNES',
+ 'ISEQN',
+ 'ISNEN',
+ 'ISEQP',
+ 'ISNEP',
+
+ # Unary test and copy ops.
+ 'ISTC',
+ 'ISFC',
+ 'IST',
+ 'ISF',
+ 'ISTYPE',
+ 'ISNUM',
+ 'MOV',
+ 'NOT',
+ 'UNM',
+ 'LEN',
+ 'ADDVN',
+ 'SUBVN',
+ 'MULVN',
+ 'DIVVN',
+ 'MODVN',
+
+ # Binary ops. ORDER OPR.
+ 'ADDNV',
+ 'SUBNV',
+ 'MULNV',
+ 'DIVNV',
+ 'MODNV',
+
+ 'ADDVV',
+ 'SUBVV',
+ 'MULVV',
+ 'DIVVV',
+ 'MODVV',
+
+ 'POW',
+ 'CAT',
+
+ # Constant ops.
+ 'KSTR',
+ 'KCDATA',
+ 'KSHORT',
+ 'KNUM',
+ 'KPRI',
+ 'KNIL',
+
+ # Upvalue and function ops.
+ 'UGET',
+ 'USETV',
+ 'USETS',
+ 'USETN',
+ 'USETP',
+ 'UCLO',
+ 'FNEW',
+
+ # Table ops.
+ 'TNEW',
+ 'TDUP',
+ 'GGET',
+ 'GSET',
+ 'TGETV',
+ 'TGETS',
+ 'TGETB',
+ 'TGETR',
+ 'TSETV',
+ 'TSETS',
+ 'TSETB',
+ 'TSETM',
+ 'TSETR',
+
+ # Calls and vararg handling. T = tail call.
+ 'CALLM',
+ 'CALL',
+ 'CALLMT',
+ 'CALLT',
+ 'ITERC',
+ 'ITERN',
+ 'VARG',
+ 'ISNEXT',
+
+ # Returns.
+ 'RETM',
+ 'RET',
+ 'RET0',
+ 'RET1',
+
+ # Loops and branches. I/J = interp/JIT.
+ # I/C/L = init/call/loop.
+ 'FORI',
+ 'JFORI',
+
+ 'FORL',
+ 'IFORL',
+ 'JFORL',
+
+ 'ITERL',
+ 'IITERL',
+ 'JITERL',
+
+ 'LOOP',
+ 'ILOOP',
+ 'JLOOP',
+
+ 'JMP',
+
+ # Function headers. I/J = interp/JIT.
+ # F/V/C = fixarg/vararg/C func.
+ 'FUNCF',
+ 'IFUNCF',
+ 'JFUNCF',
+ 'FUNCV',
+ 'IFUNCV',
+ 'JFUNCV',
+ 'FUNCC',
+ 'FUNCCW',
+]
+
+
+def proto_bc(proto):
+ return dbg.cast('BCIns *',
+ dbg.cast('char *', proto) + dbg.sizeof('GCproto'))
+
+
+def proto_kgc(pt, idx):
+ return gcref(mref('GCRef *', pt['k'])[idx])
+
+
+def proto_knumtv(pt, idx):
+ return mref('TValue *', pt['k'])[idx]
+
+
# Frames.
@@ -676,10 +872,6 @@ def frametypes(ft):
}.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'])
@@ -1129,6 +1321,137 @@ def dump_gc(g):
return '\n'.join(map(lambda s: '\t' + s, stats))
+def proto_loc(proto):
+ return '{chunk}:{firstline}'.format(
+ chunk=strdata(dbg.cast('GCstr *', gcval(proto['chunkname']))),
+ firstline=proto['firstline'],
+ )
+
+
+def funck(pt, idx):
+ if idx >= 0:
+ assert idx < pt['sizekn'], 'invalid idx for numeric constant in proto'
+ tv = proto_knumtv(pt, idx)
+ return dump_tvalue(tv)
+ else:
+ assert ~idx < pt['sizekgc'], 'invalid idx for GC constant in proto'
+ gcobj = proto_kgc(pt, idx)
+ if typenames(i2notu32(gcobj['gch']['gct'])) == 'LJ_TPROTO':
+ return proto_loc(dbg.cast('GCproto *', gcobj))
+ return dump_gcobj(gcobj)
+
+
+def funcuvname(pt, idx):
+ assert idx < pt['sizeuv'], 'invalid idx for upvalue in proto'
+ uvinfo = mref('uint8_t *', pt['uvinfo'])
+ if not uvinfo:
+ return ''
+
+ # if (idx) while (*uvinfo++ || --idx);
+ while idx > 0:
+ while uvinfo[0]:
+ uvinfo += 1
+ uvinfo += 1
+ idx -= 1
+
+ return 'upvalue {name} @ {addr}'.format(
+ name=dbg.cstr(dbg.cast('char *', uvinfo)),
+ addr=strx64(uvinfo)
+ )
+
+
+def dump_reg(rtype, value, jmp_format=None, jmp_ctx=None):
+ if rtype == 'jump':
+ # Destination of jump instruction encoded as offset from
+ # BCBIAS_J.
+ delta = value - 0x7fff
+ if jmp_format:
+ value = jmp_format(jmp_ctx, delta)
+ else:
+ prefix = '+' if delta >= 0 else ''
+ value = prefix + str(delta)
+ else:
+ value = '{:3d}'.format(value)
+
+ return '{rtype:6} {value}'.format(
+ rtype=rtype + ':',
+ value=value,
+ )
+
+
+def dump_kc(rtype, value, proto):
+ kc = ''
+ if proto:
+ if rtype == 'str' or rtype == 'func':
+ kc = funck(proto, ~value)
+ elif rtype == 'num':
+ kc = funck(proto, value)
+ elif rtype == 'uv':
+ kc = funcuvname(proto, value)
+
+ if kc != '':
+ kc = ' ; ' + kc
+ return kc
+
+
+def dump_bc(ins, jmp_format=None, jmp_ctx=None, proto=None):
+ op = bc_op(ins)
+ if op >= len(BYTECODES):
+ return 'INVALID'
+
+ bcname = BYTECODES[op]
+ bcma = bcmode_a(op)
+ bcmb = bcmode_b(op)
+ bcmcd = bcmode_cd(op)
+
+ kca = dump_kc(BCMODE[bcma], bc_a(ins), proto) if bcma else ''
+ kcc = dump_kc(
+ BCMODE[bcmcd], bc_c(ins) if bcmb else bc_d(ins), proto
+ ) if bcmcd else ''
+
+ return '{name:6} {ra}{rb}{rcd}{kc}'.format(
+ name=bcname,
+ ra=dump_reg(BCMODE[bcma], bc_a(ins)) + ' ' if bcma else '',
+ rb=dump_reg(BCMODE[bcmb], bc_b(ins)) + ' ' if bcmb else '',
+ rcd=dump_reg(
+ BCMODE[bcmcd], bc_c(ins) if bcmb else bc_d(ins),
+ jmp_format=jmp_format, jmp_ctx=jmp_ctx
+ ) if bcmcd else '',
+ kc=kca + kcc
+ )
+
+
+def dump_proto(proto):
+ startbc = proto_bc(proto)
+ func_loc = proto_loc(proto)
+ # Location has the following format: '{chunk}:{firstline}'.
+ dump = '{func_loc}-{lastline}\n'.format(
+ func_loc=func_loc,
+ lastline=proto['firstline'] + proto['numline'],
+ )
+
+ def jmp_format(npc_from, delta):
+ return '=> ' + str(npc_from + delta).zfill(4)
+
+ for bcnum in range(0, int(proto['sizebc'])):
+ dump += (str(bcnum).zfill(4) + ' ' + dump_bc(
+ startbc[bcnum], jmp_format=jmp_format, jmp_ctx=bcnum, proto=proto,
+ ) + '\n')
+ return dump
+
+
+def dump_func(func):
+ ffid = func['ffid']
+
+ if ffid == 0:
+ pt = funcproto(func)
+ return dump_proto(pt)
+ elif ffid == 1:
+ return 'C function @ {}\n'.format(strx64(func['f']))
+ else:
+ return 'fast function #{}\n'.format(int(ffid))
+
+
# Extension commands. ############################################
@@ -1152,6 +1475,59 @@ pointers, respectively.
)
+class LJDumpBC(dbg.LJBase):
+ '''
+lj-bc <BCIns *>
+
+The command receives a pointer to a bytecode instruction and dumps
+the type of the instruction and the values of RA, RB, and RC (or RD)
+virtual registers and their modes (operand types):
+
+<BCNAME> <modeA>: <RA>
+<BCNAME> <modeA>: <RA> <modeB>: <RB> <modeC>: <RC> ; <const> ; <uvname>
+<BCNAME> <modeA>: <RA> <modeD>: <RD>
+
+<BCNAME>: Name of the bytecode instruction
+<R[ABCD]>: The value of the R[ABCD] virtual register operand
+<mode[ABCD]>: The operand type for the R[ABCD] register
+<const>: The value of the constant associated with the operand, if any
+<uvname>: The name of the upvalue, if any
+
+For the list of bytecode names and modes (operand types), see:
+https://github.com/tarantool/tarantool/wiki/LuaJIT-Bytecodes.
+ '''
+
+ def execute(self, arg):
+ dbg.write('{}\n'.format(
+ dump_bc(dbg.cast('BCIns *', dbg.eval(arg))[0])
+ ))
+
+
+class LJDumpFunc(dbg.LJBase):
+ '''
+lj-func <GCfunc *>
+
+The command receives a <gcr> of the corresponding GCfunc object and dumps
+the chunk name, where the corresponding function is defined, the
+corresponding range of lines, and a list of bytecodes related to this
+function:
+
+<file>:<start>-<end>
+<bcnum> <BC>
+...
+<bcnum> <BC>
+
+<file>: The location of the corresponding function definition
+<start>: The number of the line where the function starts
+<end>: The number of the line where the function ends
+<bcnum>: The sequential number of the bytecode instruction
+<BC>: The encoded bytecode instruction. Type "help lj-bc" for details.
+ '''
+
+ def execute(self, arg):
+ dbg.write('{}'.format(dump_func(dbg.cast('GCfuncC *', dbg.eval(arg)))))
+
+
class LJGC(dbg.LJBase):
'''
lj-gc
@@ -1208,6 +1584,33 @@ error message occurs.
dbg.write('{}\n'.format(dump_gcobj(gcobj)))
+class LJDumpProto(dbg.LJBase):
+ '''
+lj-proto <GCproto *>
+
+The command receives a <gcr> of the corresponding GCproto object and dumps
+the chunk name, where the corresponding function is defined, the
+corresponding range of lines, and a list of bytecodes related to this
+function:
+
+<file>:<start>-<end>
+<bcnum> <BC>
+...
+<bcnum> <BC>
+
+<file>: The location of the corresponding function definition
+<start>: The number of the line where the function starts
+<end>: The number of the line where the function ends
+<bcnum>: The sequential number of the bytecode instruction
+<BC>: The encoded bytecode instruction. Type "help lj-bc" for details.
+ '''
+
+ def execute(self, arg):
+ dbg.write('{}'.format(
+ dump_proto(dbg.cast('GCproto *', dbg.eval(arg)))
+ ))
+
+
class LJDumpStack(dbg.LJBase):
'''
lj-stack [<lua_State *>]
@@ -1368,8 +1771,11 @@ error message occurs.
def load(event=None):
dbg.initialize_extension({
'lj-arch': LJDumpArch,
+ 'lj-bc': LJDumpBC,
+ 'lj-func': LJDumpFunc,
'lj-gc': LJGC,
'lj-gco': LJDumpGCobj,
+ 'lj-proto': LJDumpProto,
'lj-stack': LJDumpStack,
'lj-state': LJState,
'lj-str': LJDumpString,
diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
index 7e2b5ac4..b677942c 100644
--- a/test/tarantool-debugger-tests/debug-extension-tests.py
+++ b/test/tarantool-debugger-tests/debug-extension-tests.py
@@ -45,6 +45,7 @@ else:
RX_ADDR = r'0x[a-f0-9]+'
RX_HASH = RX_ADDR # The same pattern for hexademic values.
+RX_BCN = r'00\d\d'
RX_FRAME = r'\[(S|\s)(B|\s)(T|\s)(M|\s)\]'
@@ -149,14 +150,25 @@ def gcval(arg):
return 'gcval(' + arg + ')'
+def mref(arg, tp):
+ if sys.platform == 'darwin':
+ # Assume GC64 build only.
+ return '((' + tp + '*)(' + arg + ').ptr64)'
+ else:
+ return 'mref(' + arg + ', ' + tp + ')'
+
+
class TestLoad(TestCaseBase):
extension_cmds = ''
location = 'lj_cf_print'
lua_script = 'print(1)'
pattern = (
r'lj-arch command initialized\n'
+ r'lj-bc command initialized\n'
+ r'lj-func command initialized\n'
r'lj-gc command initialized\n'
r'lj-gco command initialized\n'
+ r'lj-proto command initialized\n'
r'lj-stack command initialized\n'
r'lj-state command initialized\n'
r'lj-str command initialized\n'
@@ -359,6 +371,73 @@ class TestLJGCo(TestCaseBase):
pattern = GCO_RX
+PROTO_FUNC_SCRIPT = (
+ 'local uvname = false\n'
+ 'local function testf(...)\n'
+ ' local a = ...\n'
+ ' local s1 = a + 42\n'
+ ' uvname = "conststr"\n'
+ ' if a >= 42 then\n'
+ ' return a - s1\n'
+ ' end\n'
+ 'end\n'
+ 'print(testf)\n'
+)
+
+
+PROTO_FUNC_BC_RX = (
+ RX_BCN + r' FUNCV rbase: \d\s*\n' +
+ RX_BCN + r' VARG base: \d lit: \d lit: \d\s*\n' +
+ RX_BCN + r' ADDVN dst: \d var: \d num: +\d' +
+ r' ; ' + RX_INT + r' 42\s*\n' +
+ RX_BCN + r' USETS uv: \d str: \d' +
+ r' ; upvalue "uvname" @ ' + RX_ADDR +
+ r' ; string "conststr" @ ' + RX_ADDR + r'\s*\n' +
+ RX_BCN + r' KSHORT dst: \d lits: 42\s*\n' +
+ RX_BCN + r' ISGT var: \d var: \d\s*\n' +
+ RX_BCN + r' JMP rbase: \d jump: => ' + RX_BCN + r'\s*\n' +
+ RX_BCN + r' SUBVV dst: \d var: \d var: \d\s*\n' +
+ RX_BCN + r' RET1 rbase: \d lit: \d\s*\n' +
+ RX_BCN + r' RET0 rbase: \d lit: \d\s*\n'
+)
+
+
+class TestLJFunc(TestCaseBase):
+ location = 'lj_cf_print'
+ extension_cmds = 'lj-func ' + gcval('L->base')
+ lua_script = PROTO_FUNC_SCRIPT
+ pattern = PROTO_FUNC_BC_RX
+
+
+class TestLJProto(TestCaseBase):
+ location = 'lj_cf_print'
+ extension_cmds = (
+ 'lj-proto '
+ ' ((char *) ' + mref(
+ '((GCfuncL *)' + gcval('L->base') + ')->pc', 'char'
+ ) + ') - sizeof(GCproto)\n'
+ )
+ lua_script = PROTO_FUNC_SCRIPT
+ pattern = PROTO_FUNC_BC_RX
+
+
+class TestLJBC(TestCaseBase):
+ location = 'lj_cf_print'
+ extension_cmds = (
+ 'lj-bc ' + mref(
+ '((GCfuncL *)' + gcval('L->base') + ')->pc', 'BCIns'
+ ) + '\n'
+ 'lj-bc ' + mref(
+ '((GCfuncL *)' + gcval('L->base') + ')->pc', 'BCIns'
+ ) + ' + 6\n'
+ )
+ lua_script = PROTO_FUNC_SCRIPT
+ pattern = (
+ r'FUNCV rbase: \d\s*\n'
+ r'JMP rbase: \d jump: \+\d\n'
+ )
+
+
for test_cls in TestCaseBase.__subclasses__():
test_cls.test = lambda self: self.check()
--
2.54.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers
2026-06-04 9:30 [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Kaplun via Tarantool-patches
` (3 preceding siblings ...)
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 4/4] dbg: introduce lj-bc, lj-func and lj-proto dumpers Sergey Kaplun via Tarantool-patches
@ 2026-06-05 14:55 ` Sergey Bronnikov via Tarantool-patches
2026-06-05 16:03 ` [Tarantool-patches] [PATCH luajit 3/5] dbg: update help for the lj-arch command Sergey Kaplun via Tarantool-patches
5 siblings, 0 replies; 13+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2026-06-05 14:55 UTC (permalink / raw)
To: Sergey Kaplun, Evgeniy Temirgaleev; +Cc: tarantool-patches
[-- Attachment #1: Type: text/plain, Size: 5938 bytes --]
Hi, Sergey,
thanks for your efforts in adding new functionality to dbg extension!
On 6/4/26 12:30, Sergey Kaplun wrote:
> Branch:https://github.com/tarantool/luajit/tree/skaplun/gh-4808-gco-func-proto-bytecode
> Issue:https://github.com/tarantool/tarantool/issues/4808
>
> This patch set allows you to inspect bytecodes for a single instruction,
> as well as for all bytecodes inside a function or its prototype via GDB
> and LLDB.
>
> The first patch is a fixup for the LLDB indexing negative values. It may
> affect the lj-stack command. The second patch fixes the DUALNUM mode
> detection in LLDB. These fixes are required for the last patch in the
> series.
>
> The third auxiliary patch is needed to introduce dumpers for GC objects
> similar to TValues dumpers. Also, it may be useful during different
> debugging scenarios, so it introduces the lj-gco <GCobj *> command to
> dump the GC object info in the same format as for a TValue slot.
>
> The last patch introduces 3 new commands:
> * lj-bc <GCIns *> -- dump single bytecode instruction
> * lj-func <GCfunc *> -- dump all bytecode instructions for Lua function
> or report type of C or F function
> * lj-proto <GCproto *> -- dump all bytecode instructions for the
> prototype
>
> For example, we have the following Lua script named <tmp.lua>:
it is worth adding this example to the appropriate commit message. What
do you think?
> | 1 local function mywhile(a)
> | 2 local r = 0
> | 3 print(a)
> | 4 while (a < 30) do
> | 5 r = r + a * r/2
> | 6 end
> | 7 return r
> | 8 end
> | 9
> | 10 local uvname1 = false
> | 11 local uvname2 = false
> | 12 local function myif(a)
> | 13 local s1 = a + 4
> | 14 local s2 = s1 + 4
> | 15 uvname1 = "s10"
> | 16 uvname2 = "s11"
> | 17 print(a)
> | 18 if a > 10 then
> | 19 return a + s2 + s1
> | 20 else
> | 21 return a - 10 - s2 - s1
> | 22 end
> | 23 end
> | 24
> | 25 local f1 = myif
> | 26 local f2 = mywhile
> | 27 myif(12)
> | 28 mywhile(12)
>
> Assume we set a breakpoint at `lj_cf_print` (line 3).
> The lj-stack output contains the following lines:
>
> | 0x40001970 [ ] VALUE: Lua function @ 0x400083c0, 0 upvalues, "@../tmp.lua":1
> | 0x40001968 [ ] VALUE: Lua function @ 0x40002148, 2 upvalues, "@../tmp.lua":12
> | ...
> | 0x40001940 [ ] FRAME: [V] delta=1, Lua function @ 0x400084a0, 0 upvalues, "@../tmp.lua":0
>
> The first one is `myif()` function the second is `mywhile()` and the
> last one is function loaded via `dofile()`.
>
> The resulting output for the functions is the following:
>
> 1)
> | (gdb) lj-func 0x400083c0
> | "@../tmp.lua":1-8
> | 0000 FUNCF rbase: 4
> | 0001 KSHORT dst: 1 lits: 0
> | 0002 GGET dst: 2 str: 0 ; string "print" @ 0x400037f0
> | 0003 MOV dst: 3 var: 0
> | 0004 CALL base: 2 lit: 1 lit: 2
> | 0005 KSHORT dst: 2 lits: 30
> | 0006 ISGE var: 0 var: 2
> | 0007 JMP rbase: 2 jump: => 0013
> | 0008 LOOP rbase: 2 jump: => 0013
> | 0009 MULVV dst: 2 var: 0 var: 1
> | 0010 DIVVN dst: 2 var: 2 num: 0 ; number 2
> | 0011 ADDVV dst: 1 var: 1 var: 2
> | 0012 JMP rbase: 2 jump: => 0005
> | 0013 RET1 rbase: 1 lit: 2
>
> The report is the same as for the following command:
> | lj-proto (GCproto *)(((char *)(((GCfuncL *)0x400083c0)->pc.ptr32))-sizeof(GCproto))
>
> 2)
> | (gdb) lj-func 0x40002148
> | "@../tmp.lua":12-23
> | 0000 FUNCF rbase: 5
> | 0001 ADDVN dst: 1 var: 0 num: 0 ; number 4
> | 0002 ADDVN dst: 2 var: 1 num: 0 ; number 4
> | 0003 USETS uv: 0 str: 0 ; 0x40002527 "uvname1" ; string "s10" @ 0x40002298
> | 0004 USETS uv: 1 str: 1 ; 0x4000252f "uvname2" ; string "s11" @ 0x400022b8
> | 0005 GGET dst: 3 str: 2 ; string "print" @ 0x400037f0
> | 0006 MOV dst: 4 var: 0
> | 0007 CALL base: 3 lit: 1 lit: 2
> | 0008 KSHORT dst: 3 lits: 10
> | 0009 ISGE var: 3 var: 0
> | 0010 JMP rbase: 3 jump: => 0015
> | 0011 ADDVV dst: 3 var: 0 var: 2
> | 0012 ADDVV dst: 3 var: 3 var: 1
> | 0013 RET1 rbase: 3 lit: 2
> | 0014 JMP rbase: 3 jump: => 0019
> | 0015 SUBVN dst: 3 var: 0 num: 1 ; number 10
> | 0016 SUBVV dst: 3 var: 3 var: 2
> | 0017 SUBVV dst: 3 var: 3 var: 1
> | 0018 RET1 rbase: 3 lit: 2
> | 0019 RET0 rbase: 0 lit: 1
>
> 3)
>
> | (gdb) lj-func 0x400084a0
> | "@../tmp.lua":0-30
> | 0000 FUNCV rbase: 8
> | 0001 FNEW dst: 0 func: 0 ; "@../tmp.lua":1
> | 0002 KPRI dst: 1 pri: 1
> | 0003 KPRI dst: 2 pri: 1
> | 0004 FNEW dst: 3 func: 1 ; "@../tmp.lua":12
> | 0005 MOV dst: 4 var: 3
> | 0006 MOV dst: 5 var: 0
> | 0007 MOV dst: 6 var: 3
> | 0008 KSHORT dst: 7 lits: 12
> | 0009 CALL base: 6 lit: 1 lit: 2
> | 0010 MOV dst: 6 var: 0
> | 0011 KSHORT dst: 7 lits: 12
> | 0012 CALL base: 6 lit: 1 lit: 2
> | 0013 UCLO rbase: 0 jump: => 0014
> | 0014 RET0 rbase: 0 lit: 1
>
> The single bytecode instruction may be useful when you debug the VM:
>
> | (gdb) b lj_BC_ISGE
> | Breakpoint 2 at 0x5555555f0a08
> | (gdb) c
> | Continuing.
> | Breakpoint 2, 0x00005555555f0a08 in lj_BC_ISGE ()
> | (gdb) lj-bc $rbx # PC refers __the next instruction__
> | JMP rbase: 3 jump: +5
> | (gdb) lj-bc ((BCIns *)$rbx) - 1 # current instruction
> | ISGE var: 3 var: 0
>
>
> Sergey Kaplun (4):
> dbg: fix lj-stack command for LLDB
> dbg: fix DUALNUM detection for LLDB
> dbg: introduce lj-gco command
> dbg: introduce lj-bc, lj-func and lj-proto dumpers
>
> src/luajit_dbg.py | 650 ++++++++++++++++--
> .../debug-extension-tests.py | 203 +++++-
> 2 files changed, 757 insertions(+), 96 deletions(-)
>
[-- Attachment #2: Type: text/html, Size: 6546 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [Tarantool-patches] [PATCH luajit 1/4] dbg: fix lj-stack command for LLDB
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 1/4] dbg: fix lj-stack command for LLDB Sergey Kaplun via Tarantool-patches
@ 2026-06-05 14:55 ` Sergey Bronnikov via Tarantool-patches
0 siblings, 0 replies; 13+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2026-06-05 14:55 UTC (permalink / raw)
To: Sergey Kaplun, Evgeniy Temirgaleev; +Cc: tarantool-patches
[-- Attachment #1: Type: text/plain, Size: 5622 bytes --]
Hi, Sergey,
thanks for the patch! LGTM
On 6/4/26 12:30, Sergey Kaplun wrote:
> This commit is the follow-up for the commit
> 593fef813b7d36060e0c1b1ae2df0fbc0d604d1f ("lldb: refactor extension").
> `GetValueForExpressionPath()` may produce the empty value for negative
> indexes. This leads to incorrect frame unwinding in the LLDB extension
> and errors like:
> | Failed to execute command `lj-stack`:
> | 'Q' format requires 0 <= number <= 18446744073709551615
>
> This patch adds special handling for the negative indices.
> ---
> src/luajit_dbg.py | 49 ++++++++++++++++---
> .../debug-extension-tests.py | 26 +++++++---
> 2 files changed, 62 insertions(+), 13 deletions(-)
>
> diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
> index d9196f06..410f0191 100644
> --- a/src/luajit_dbg.py
> +++ b/src/luajit_dbg.py
> @@ -255,15 +255,31 @@ class _GDBDebugger(Debugger):
>
> class _LLDBDebugger(Debugger):
> def _lldb_tp_isfp(self, tp):
> - return tp.GetBasicType() in [
> + return tp.GetCanonicalType().GetBasicType() in [
> lldb.eBasicTypeFloat,
> lldb.eBasicTypeDouble,
> lldb.eBasicTypeLongDouble
> ]
>
> + def _lldb_tp_issigned(self, tp):
> + return tp.GetCanonicalType().GetBasicType() in [
> + lldb.eBasicTypeChar,
> + lldb.eBasicTypeSignedChar,
> + lldb.eBasicTypeShort,
> + lldb.eBasicTypeInt,
> + lldb.eBasicTypeLong,
> + lldb.eBasicTypeLongLong,
> + lldb.eBasicTypeInt128
> + ]
> +
> def _lldb_value_from_raw(self, raw_value, size, tp):
> isfp = self._lldb_tp_isfp(tp)
> - pack_flag = '<d' if isfp else '<Q'
> + if isfp:
> + pack_flag = '<d'
> + elif self._lldb_tp_issigned(tp):
> + pack_flag = '<q'
> + else:
> + pack_flag = '<Q'
> raw_data = struct.pack(pack_flag, raw_value)
> sbdata = lldb.SBData()
> sbdata.SetData(
> @@ -308,9 +324,24 @@ class _LLDBDebugger(Debugger):
> key = int(key)
> if type(key) is int:
> # Allow array access.
> - return lldb.value(
> - lldbval.sbvalue.GetValueForExpressionPath('[%i]' % key)
> - )
> + if key >= 0:
> + return lldb.value(
> + lldbval.sbvalue.GetValueForExpressionPath('[%i]' % key)
> + )
> + else:
> + # GetValueForExpressionPath doesn't work for
> + # negative offsets.
> + sbvalue = lldbval.sbvalue
> + assert sbvalue.TypeIsPointerType(), \
> + 'attempt to get index of non-pointer type'
> + tp = sbvalue.GetType().GetPointeeType()
> + sz = sbvalue.deref.size
> + addr = sbvalue.GetValueAsUnsigned() + key * sz
> + return lldb.value(self.target.CreateValueFromAddress(
> + '({tp}){addr}'.format(tp=tp, addr=addr),
> + lldb.SBAddress(addr, self.target),
> + tp,
> + ))
> elif type(key) is str:
> return lldb.value(lldbval.sbvalue.GetChildMemberWithName(key))
> raise Exception(TypeError('No item of type %s' % str(type(key))))
> @@ -417,8 +448,12 @@ class _LLDBDebugger(Debugger):
> # 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()
> + if self._lldb_tp_isfp(tp):
> + rawval = float(val.GetValue())
> + elif self._lldb_tp_issigned(tp):
> + rawval = val.GetValueAsSigned()
> + else:
> + rawval = val.GetValueAsUnsigned()
> return self._lldb_value_from_raw(rawval, val.GetByteSize(), tp)
>
> def sizeof(self, typestr):
> diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
> index 61529561..7cb60d84 100644
> --- a/test/tarantool-debugger-tests/debug-extension-tests.py
> +++ b/test/tarantool-debugger-tests/debug-extension-tests.py
> @@ -186,16 +186,30 @@ class TestLJGC(TestCaseBase):
> )
>
>
> -class TestLJStack(TestCaseBase):
> +STACK_RX = (
> + r'-+ Redzone:\s+\d+ slots -+\n'
> + r'(' + RX_ADDR + r'\s+' + RX_FRAME + r' VALUE: nil\n?)*\n'
> + r'-+Stack:\s+\d+ slots -+\n'
> + r'(' + RX_ADDR + r'(:' + RX_ADDR + r')?\s+' + RX_FRAME + r'.*\n?)+\n'
> +)
> +
> +
> +class TestLJStackBase(TestCaseBase):
> extension_cmds = 'lj-stack'
> location = 'lj_cf_print'
> lua_script = 'print(1)'
> - pattern = (
> - r'-+ Redzone:\s+\d+ slots -+\n'
> - r'(' + RX_ADDR + r'\s+' + RX_FRAME + r' VALUE: nil\n?)*\n'
> - r'-+Stack:\s+\d+ slots -+\n'
> - r'(' + RX_ADDR + r'(:' + RX_ADDR + r')?\s+' + RX_FRAME + r'.*\n?)+\n'
> + pattern = STACK_RX
> +
> +
> +# Check LLDB correctness for the specific stack.
> +class TestLJStackFunc(TestCaseBase):
> + extension_cmds = 'lj-stack'
> + location = 'lj_cf_print'
> + lua_script = (
> + 'local function nop() end\n'
> + 'print()\n'
> )
> + pattern = STACK_RX
>
>
> class TestLJTV(TestCaseBase):
[-- Attachment #2: Type: text/html, Size: 6023 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [Tarantool-patches] [PATCH luajit 2/4] dbg: fix DUALNUM detection for LLDB
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 2/4] dbg: fix DUALNUM detection " Sergey Kaplun via Tarantool-patches
@ 2026-06-05 14:57 ` Sergey Bronnikov via Tarantool-patches
2026-06-05 16:01 ` Sergey Kaplun via Tarantool-patches
0 siblings, 1 reply; 13+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2026-06-05 14:57 UTC (permalink / raw)
To: Sergey Kaplun, Evgeniy Temirgaleev; +Cc: tarantool-patches
[-- Attachment #1: Type: text/plain, Size: 3196 bytes --]
Hi, Sergey,
thanks for the patch! LGTM with a minor comment below.
Sergey
On 6/4/26 12:30, Sergey Kaplun wrote:
> The `lj-arch` command on LLDB reports 'LJ_DUALNUM: True' for the
> single-number build since the `module.FindSymbol()` returns an invalid
> `SBSymbol` object [1], which is not `None`. This leads to invalid
> DUALNUM mode detection.
>
> This patch fixes this by checking that the returned symbol is valid.
>
> [1]:https://lldb.llvm.org/python_api/lldb.SBModule.html#lldb.SBModule.FindSymbol
> ---
> src/luajit_dbg.py | 3 ++-
> .../debug-extension-tests.py | 15 +++++++++++++--
> 2 files changed, 15 insertions(+), 3 deletions(-)
>
> diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
> index 410f0191..300d65e9 100644
> --- a/src/luajit_dbg.py
> +++ b/src/luajit_dbg.py
I believe DUALNUM should be added to the help for `lj-arch`. Now it
describes only LJ_64, LJ_GC64:
| (gdb) help lj-arch
| 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.
| (gdb)
> @@ -498,7 +498,8 @@ class _LLDBDebugger(Debugger):
> 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
> + dualnum_sym = module.FindSymbol('lj_lib_checknumber')
> + LJ_DUALNUM = dualnum_sym is not None and dualnum_sym.IsValid()
> irtype_enum = self.target.FindFirstType('IRType').enum_members
> for member in irtype_enum:
> if member.name == 'IRT_PTR':
> diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
> index 7cb60d84..06a118ff 100644
> --- a/test/tarantool-debugger-tests/debug-extension-tests.py
> +++ b/test/tarantool-debugger-tests/debug-extension-tests.py
> @@ -92,6 +92,17 @@ def execute_process(cmd, timeout=TIMEOUT):
> return process.stdout
>
>
> +IS_DUALNUM = execute_process([
> + LUAJIT_BINARY, '-e', "print(require('ffi').abi('dualnum'))"
> +]).strip() == 'true'
> +
> +# If it is the guaranteed DUALNUM build (for example, on aarch64),
> +# we use this regexp for the guaranteed 'integer' check and
> +# 'number' for single-number build.
> +RX_INT = r'integer' if IS_DUALNUM else r'number'
> +RX_ISDUALNUM = r'True' if IS_DUALNUM else r'False'
> +
> +
> class TestCaseBase(unittest.TestCase):
> @classmethod
> def construct_cmds(cls):
> @@ -150,7 +161,7 @@ class TestLJArch(TestCaseBase):
> pattern = (
> r'LJ_64: (True|False), '
> r'LJ_GC64: (True|False), '
> - r'LJ_DUALNUM: (True|False)'
> + r'LJ_DUALNUM: ' + RX_ISDUALNUM
> )
>
>
> @@ -265,7 +276,7 @@ class TestLJTV(TestCaseBase):
> r'cdata @ ' + RX_ADDR + r'\n'
> r'table @ ' + RX_ADDR + r' \(asize: \d+, hmask: ' + RX_HASH + r'\)\n'
> r'userdata @ ' + RX_ADDR + r'\n'
> - r'(number|integer) .*1.*\n'
> + RX_INT + r' .*1.*\n'
> r'number 1.1\d+\n'
> )
>
[-- Attachment #2: Type: text/html, Size: 3896 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [Tarantool-patches] [PATCH luajit 3/4] dbg: introduce lj-gco command
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 3/4] dbg: introduce lj-gco command Sergey Kaplun via Tarantool-patches
@ 2026-06-05 15:02 ` Sergey Bronnikov via Tarantool-patches
0 siblings, 0 replies; 13+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2026-06-05 15:02 UTC (permalink / raw)
To: Sergey Kaplun, Evgeniy Temirgaleev; +Cc: tarantool-patches
[-- Attachment #1: Type: text/plain, Size: 12971 bytes --]
Hi, Sergey,
thanks for the patch! LGTM
On 6/4/26 12:30, Sergey Kaplun wrote:
> Our GDB extension already has dumpers for TValues. But sometimes it
> may be useful to dump GC objects (GCobj) without stack context. This
> patch adds additional wrappers around dumpers for GC objects to get the
> corresponding GC object from a TValue. Also, the lj-gco command is
> introduced. It allows dumping GC objects without stack context. The
> output format is the same as for the lj-tv command.
>
> Part of tarantool/tarantool#4808
> ---
> src/luajit_dbg.py | 182 ++++++++++++------
> .../debug-extension-tests.py | 83 ++++++--
> 2 files changed, 190 insertions(+), 75 deletions(-)
>
> diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
> index 300d65e9..f5868e61 100644
> --- a/src/luajit_dbg.py
> +++ b/src/luajit_dbg.py
> @@ -882,44 +882,29 @@ def lightudV(tv):
>
> # Dumpers.
>
> +# GCobj 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):
> +def dump_lj_gco_str(gcobj):
> return 'string {body} @ {address}'.format(
> - body=strdata(gcval(tv['gcr'])),
> - address=strx64(gcval(tv['gcr']))
> + body=strdata(gcobj),
> + address=strx64(gcobj)
> )
>
>
> -def dump_lj_tupval(tv):
> - return 'upvalue @ {}'.format(strx64(gcval(tv['gcr'])))
> +def dump_lj_gco_upval(gcobj):
> + return 'upvalue @ {}'.format(strx64(gcobj))
>
>
> -def dump_lj_tthread(tv):
> - return 'thread @ {}'.format(strx64(gcval(tv['gcr'])))
> +def dump_lj_gco_thread(gcobj):
> + return 'thread @ {}'.format(strx64(gcobj))
>
>
> -def dump_lj_tproto(tv):
> - return 'proto @ {}'.format(strx64(gcval(tv['gcr'])))
> +def dump_lj_gco_proto(gcobj):
> + return 'proto @ {}'.format(strx64(gcobj))
>
>
> -def dump_lj_tfunc(tv):
> - func = dbg.cast('struct GCfuncC *', gcval(tv['gcr']))
> +def dump_lj_gco_func(gcobj):
> + func = dbg.cast('struct GCfuncC *', gcobj)
> ffid = func['ffid']
>
> if ffid == 0:
> @@ -936,20 +921,20 @@ def dump_lj_tfunc(tv):
> return 'fast function #{}'.format(int(ffid))
>
>
> -def dump_lj_ttrace(tv):
> - trace = dbg.cast('struct GCtrace *', gcval(tv['gcr']))
> +def dump_lj_gco_trace(gcobj):
> + trace = dbg.cast('struct GCtrace *', gcobj)
> 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_gco_cdata(gcobj):
> + return 'cdata @ {}'.format(strx64(gcobj))
>
>
> -def dump_lj_ttab(tv):
> - table = dbg.cast('GCtab *', gcval(tv['gcr']))
> +def dump_lj_gco_tab(gcobj):
> + table = dbg.cast('GCtab *', gcobj)
> return 'table @ {gcr} (asize: {asize}, hmask: {hmask})'.format(
> gcr=strx64(table),
> asize=table['asize'],
> @@ -957,41 +942,94 @@ def dump_lj_ttab(tv):
> )
>
>
> -def dump_lj_tudata(tv):
> - return 'userdata @ {}'.format(strx64(gcval(tv['gcr'])))
> +def dump_lj_gco_udata(gcobj):
> + return 'userdata @ {}'.format(strx64(gcobj))
> +
> +
> +def dump_lj_gco_invalid(gcobj):
> + return 'not valid type @ {}'.format(strx64(gcobj))
> +
> +
> +# TValue dumpers
> +
> +def dump_lj_tv_nil(tv):
> + return 'nil'
> +
> +
> +def dump_lj_tv_false(tv):
> + return 'false'
> +
> +
> +def dump_lj_tv_true(tv):
> + return 'true'
> +
> +
> +def dump_lj_tv_lightud(tv):
> + return 'light userdata @ {}'.format(strx64(lightudV(tv)))
> +
> +
> +# Generate wrappers for TValues containing GCobj.
> +gco_fn_dumpers = [
> + fn for fn in globals().keys() if fn.startswith('dump_lj_gco')
> +]
> +for fn_name in gco_fn_dumpers:
> + wrapped_fn_name = fn_name.replace('gco', 'tv')
> + # Lambda takes `fn_name` as a reference, so the additional
> + # lambda is needed to fixate the correct wrapper.
> + globals()[wrapped_fn_name] = (lambda f: (
> + lambda tv: globals()[f](gcval(tv['gcr']))
> + ))(fn_name)
>
>
> -def dump_lj_tnumx(tv):
> +def dump_lj_tv_numx(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,
> +gco_dumpers = {
> + 'LJ_TSTR': dump_lj_gco_str,
> + 'LJ_TUPVAL': dump_lj_gco_upval,
> + 'LJ_TTHREAD': dump_lj_gco_thread,
> + 'LJ_TPROTO': dump_lj_gco_proto,
> + 'LJ_TFUNC': dump_lj_gco_func,
> + 'LJ_TTRACE': dump_lj_gco_trace,
> + 'LJ_TCDATA': dump_lj_gco_cdata,
> + 'LJ_TTAB': dump_lj_gco_tab,
> + 'LJ_TUDATA': dump_lj_gco_udata,
> }
>
>
> +tv_dumpers = {
> + 'LJ_TNIL': dump_lj_tv_nil,
> + 'LJ_TFALSE': dump_lj_tv_false,
> + 'LJ_TTRUE': dump_lj_tv_true,
> + 'LJ_TLIGHTUD': dump_lj_tv_lightud,
> + 'LJ_TSTR': dump_lj_tv_str, # noqa: F821 # Generated.
> + 'LJ_TUPVAL': dump_lj_tv_upval, # noqa: F821 # Generated.
> + 'LJ_TTHREAD': dump_lj_tv_thread, # noqa: F821 # Generated.
> + 'LJ_TPROTO': dump_lj_tv_proto, # noqa: F821 # Generated.
> + 'LJ_TFUNC': dump_lj_tv_func, # noqa: F821 # Generated.
> + 'LJ_TTRACE': dump_lj_tv_trace, # noqa: F821 # Generated.
> + 'LJ_TCDATA': dump_lj_tv_cdata, # noqa: F821 # Generated.
> + 'LJ_TTAB': dump_lj_tv_tab, # noqa: F821 # Generated.
> + 'LJ_TUDATA': dump_lj_tv_udata, # noqa: F821 # Generated.
> + 'LJ_TNUMX': dump_lj_tv_numx,
> +}
> +
> +
> +def dump_gcobj(gcobj):
> + return gco_dumpers.get(
> + typenames(i2notu32(gcobj['gch']['gct'])), dump_lj_gco_invalid
> + )(gcobj)
> +
> +
> def dump_tvalue(tvalue):
> - return dumpers.get(typenames(itypemap(tvalue)), dump_lj_invalid)(tvalue)
> + return tv_dumpers.get(
> + typenames(itypemap(tvalue)),
> + dump_lj_tv_invalid # noqa: F821 # Generated.
> + )(tvalue)
>
>
> def dump_framelink_slot_address(fr):
> @@ -1011,7 +1049,7 @@ def dump_framelink(L, 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),
> + f=dump_lj_tv_func(fr - LJ_FR2), # noqa: F821 # Generated.
> )
>
>
> @@ -1141,6 +1179,35 @@ The command requires no args and dumps current GC stats:
> ))
>
>
> +class LJDumpGCobj(dbg.LJBase):
> + '''
> +lj-gco <GCobj *>
> +
> +The command receives a pointer to <GCobj> (GCobj address) and dumps
> +the type and some info related to it.
> +
> +* 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>
> +
> +Whether the type of the given address differs from the listed above, then
> +error message occurs.
> + '''
> +
> + def execute(self, arg):
> + gcobj = dbg.cast('GCobj *', dbg.eval(arg))
> + dbg.write('{}\n'.format(dump_gcobj(gcobj)))
> +
> +
> class LJDumpStack(dbg.LJBase):
> '''
> lj-stack [<lua_State *>]
> @@ -1302,6 +1369,7 @@ def load(event=None):
> dbg.initialize_extension({
> 'lj-arch': LJDumpArch,
> 'lj-gc': LJGC,
> + 'lj-gco': LJDumpGCobj,
> 'lj-stack': LJDumpStack,
> 'lj-state': LJState,
> 'lj-str': LJDumpString,
> diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
> index 06a118ff..7e2b5ac4 100644
> --- a/test/tarantool-debugger-tests/debug-extension-tests.py
> +++ b/test/tarantool-debugger-tests/debug-extension-tests.py
> @@ -138,6 +138,17 @@ class TestCaseBase(unittest.TestCase):
> self.assertRegex(self.output, self.pattern.strip())
>
>
> +# LLDB + Clang on macOS can't produce debug info for the C-defined
> +# macros. Thus, we hardcoded its value manually.
> +def gcval(arg):
> + if sys.platform == 'darwin':
> + # Assume GC64 build only.
> + LJ_GCVMASK = '(((uint64_t)1 << 47) - 1)'
> + return '(((' + arg + ')->gcr).gcptr64 & ' + LJ_GCVMASK + ')'
> + else:
> + return 'gcval(' + arg + ')'
> +
> +
> class TestLoad(TestCaseBase):
> extension_cmds = ''
> location = 'lj_cf_print'
> @@ -145,6 +156,7 @@ class TestLoad(TestCaseBase):
> pattern = (
> r'lj-arch command initialized\n'
> r'lj-gc command initialized\n'
> + r'lj-gco command initialized\n'
> r'lj-stack command initialized\n'
> r'lj-state command initialized\n'
> r'lj-str command initialized\n'
> @@ -223,6 +235,31 @@ class TestLJStackFunc(TestCaseBase):
> pattern = STACK_RX
>
>
> +# Sorted in LJT order.
> +GCO_ARGS = (
> + '"hello",\n'
> + 'coroutine.create(function() end),\n'
> + 'function() end,\n'
> + 'require,\n'
> + 'print,\n'
> + 'ffi.new("int*"),\n'
> + '{1},\n'
> + 'newproxy(),\n'
> +)
> +
> +
> +GCO_RX = (
> + r'string \"hello\" @ ' + RX_ADDR + r'\n'
> + r'thread @ ' + RX_ADDR + r'\n'
> + r'Lua function @ ' + RX_ADDR + r', [0-9]+ upvalues, .+:[0-9]+\n'
> + r'C function @ ' + RX_ADDR + r'\n'
> + r'fast function #[0-9]+\n'
> + r'cdata @ ' + RX_ADDR + r'\n'
> + r'table @ ' + RX_ADDR + r' \(asize: \d+, hmask: ' + RX_HASH + r'\)\n'
> + r'userdata @ ' + RX_ADDR + r'\n'
> +)
> +
> +
> class TestLJTV(TestCaseBase):
> location = 'lj_cf_print'
> extension_cmds = (
> @@ -249,15 +286,8 @@ class TestLJTV(TestCaseBase):
> ' nil,\n'
> ' false,\n'
> ' true,\n'
> - ' debug.upvalueid(print, 1), \n' # lightuserdata
> - ' "hello",\n'
> - ' coroutine.create(function() end),\n'
> - ' function() end,\n'
> - ' require,\n'
> - ' print,\n'
> - ' ffi.new("int*"),\n'
> - ' {1},\n'
> - ' newproxy(),\n'
> + ' debug.upvalueid(print, 1), \n' + # lightuserdata
> + GCO_ARGS +
> ' 1,\n'
> ' 1.1\n'
> ')\n'
> @@ -267,15 +297,8 @@ class TestLJTV(TestCaseBase):
> r'nil\n'
> r'false\n'
> r'true\n'
> - r'light userdata @ ' + RX_ADDR + r'\n'
> - r'string \"hello\" @ ' + RX_ADDR + r'\n'
> - r'thread @ ' + RX_ADDR + r'\n'
> - r'Lua function @ ' + RX_ADDR + r', [0-9]+ upvalues, .+:[0-9]+\n'
> - r'C function @ ' + RX_ADDR + r'\n'
> - r'fast function #[0-9]+\n'
> - r'cdata @ ' + RX_ADDR + r'\n'
> - r'table @ ' + RX_ADDR + r' \(asize: \d+, hmask: ' + RX_HASH + r'\)\n'
> - r'userdata @ ' + RX_ADDR + r'\n'
> + r'light userdata @ ' + RX_ADDR + r'\n' +
> + GCO_RX +
> RX_INT + r' .*1.*\n'
> r'number 1.1\d+\n'
> )
> @@ -312,6 +335,30 @@ class TestLJTab(TestCaseBase):
> )
>
>
> +class TestLJGCo(TestCaseBase):
> + location = 'lj_cf_print'
> + extension_cmds = (
> + 'lj-gco ' + gcval('L->base + 0') + '\n'
> + 'lj-gco ' + gcval('L->base + 1') + '\n'
> + 'lj-gco ' + gcval('L->base + 2') + '\n'
> + 'lj-gco ' + gcval('L->base + 3') + '\n'
> + 'lj-gco ' + gcval('L->base + 4') + '\n'
> + 'lj-gco ' + gcval('L->base + 5') + '\n'
> + 'lj-gco ' + gcval('L->base + 6') + '\n'
> + 'lj-gco ' + gcval('L->base + 7') + '\n'
> + )
> +
> + lua_script = (
> + 'local ffi = require("ffi")\n'
> + 'print(\n' +
> + GCO_ARGS +
> + ' 1\n' # Stub for the pattern.
> + ')\n'
> + )
> +
> + pattern = GCO_RX
> +
> +
> for test_cls in TestCaseBase.__subclasses__():
> test_cls.test = lambda self: self.check()
>
[-- Attachment #2: Type: text/html, Size: 12761 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [Tarantool-patches] [PATCH luajit 4/4] dbg: introduce lj-bc, lj-func and lj-proto dumpers
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 4/4] dbg: introduce lj-bc, lj-func and lj-proto dumpers Sergey Kaplun via Tarantool-patches
@ 2026-06-05 15:07 ` Sergey Bronnikov via Tarantool-patches
2026-06-05 16:10 ` Sergey Kaplun via Tarantool-patches
0 siblings, 1 reply; 13+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2026-06-05 15:07 UTC (permalink / raw)
To: Sergey Kaplun, Evgeniy Temirgaleev; +Cc: tarantool-patches
[-- Attachment #1: Type: text/plain, Size: 520 bytes --]
Hi, Sergey,
thanks for the patch! LGTM with a minor comment.
Sergey
On 6/4/26 12:30, Sergey Kaplun wrote:
<snipped>
> +
> +
> +def bcmode_cd(op):
> + return int((lj_bc_mode()[op] >> 7) & 15)
> +
> +
> +# Unfortunately, there is no place in the VM except the generated
> +# Lua table, where the bytecode names are stored. So duplicate
> +# them here.
> +BYTECODES = [
> + # Comparison ops. ORDER OPR.
"ORDER OPR" - what does it mean? Here and below.
> + 'ISLT',
> + 'ISGE',
> + 'ISLE',
<snipped>
>
[-- Attachment #2: Type: text/html, Size: 1333 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [Tarantool-patches] [PATCH luajit 2/4] dbg: fix DUALNUM detection for LLDB
2026-06-05 14:57 ` Sergey Bronnikov via Tarantool-patches
@ 2026-06-05 16:01 ` Sergey Kaplun via Tarantool-patches
0 siblings, 0 replies; 13+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2026-06-05 16:01 UTC (permalink / raw)
To: Sergey Bronnikov; +Cc: tarantool-patches
Hi, Sergey!
Thanks for the review!
Added the separate commit as you suggested.
Branch is force-pushed.
On 05.06.26, Sergey Bronnikov wrote:
> Hi, Sergey,
>
> thanks for the patch! LGTM with a minor comment below.
>
> Sergey
>
> On 6/4/26 12:30, Sergey Kaplun wrote:
> > The `lj-arch` command on LLDB reports 'LJ_DUALNUM: True' for the
> > single-number build since the `module.FindSymbol()` returns an invalid
> > `SBSymbol` object [1], which is not `None`. This leads to invalid
> > DUALNUM mode detection.
> >
> > This patch fixes this by checking that the returned symbol is valid.
> >
> > [1]:https://lldb.llvm.org/python_api/lldb.SBModule.html#lldb.SBModule.FindSymbol
> > ---
> > src/luajit_dbg.py | 3 ++-
> > .../debug-extension-tests.py | 15 +++++++++++++--
> > 2 files changed, 15 insertions(+), 3 deletions(-)
> >
> > diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
> > index 410f0191..300d65e9 100644
> > --- a/src/luajit_dbg.py
> > +++ b/src/luajit_dbg.py
>
> I believe DUALNUM should be added to the help for `lj-arch`. Now it
> describes only LJ_64, LJ_GC64:
I've added the separate commit for it. I'll send the patch in the reply
to the main thread.
===================================================================
dbg: update help for the lj-arch command
This patch adds the description of the LJ_DUALNUM dumped flag to the
lj-arch command's help.
diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index 60308179..3a3ca9b8 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -1461,7 +1461,8 @@ 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.
+pointers, respectively. Also, it dumps the value for the LJ_DUALNUM
+compile-time flag to inspect if LuaJIT is built in dual-number mode.
'''
def execute(self, arg):
===================================================================
<snipped>
--
Best regards,
Sergey Kaplun
^ permalink raw reply [flat|nested] 13+ messages in thread
* [Tarantool-patches] [PATCH luajit 3/5] dbg: update help for the lj-arch command
2026-06-04 9:30 [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Kaplun via Tarantool-patches
` (4 preceding siblings ...)
2026-06-05 14:55 ` [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Bronnikov via Tarantool-patches
@ 2026-06-05 16:03 ` Sergey Kaplun via Tarantool-patches
5 siblings, 0 replies; 13+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2026-06-05 16:03 UTC (permalink / raw)
To: Sergey Bronnikov, Evgeniy Temirgaleev; +Cc: tarantool-patches
This patch adds the description of the LJ_DUALNUM dumped flag to the
lj-arch command's help.
---
src/luajit_dbg.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index 300d65e9..9182e012 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -1100,7 +1100,8 @@ 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.
+pointers, respectively. Also, it dumps the value for the LJ_DUALNUM
+compile-time flag to inspect if LuaJIT is built in dual-number mode.
'''
def execute(self, arg):
--
2.54.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [Tarantool-patches] [PATCH luajit 4/4] dbg: introduce lj-bc, lj-func and lj-proto dumpers
2026-06-05 15:07 ` Sergey Bronnikov via Tarantool-patches
@ 2026-06-05 16:10 ` Sergey Kaplun via Tarantool-patches
0 siblings, 0 replies; 13+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2026-06-05 16:10 UTC (permalink / raw)
To: Sergey Bronnikov; +Cc: tarantool-patches
Hi, Sergey!
Thanks for the review!
See my answer below.
On 05.06.26, Sergey Bronnikov wrote:
> Hi, Sergey,
>
> thanks for the patch! LGTM with a minor comment.
>
> Sergey
>
> On 6/4/26 12:30, Sergey Kaplun wrote:
>
>
> <snipped>
>
> > +
> > +
> > +def bcmode_cd(op):
> > + return int((lj_bc_mode()[op] >> 7) & 15)
> > +
> > +
> > +# Unfortunately, there is no place in the VM except the generated
> > +# Lua table, where the bytecode names are stored. So duplicate
> > +# them here.
> > +BYTECODES = [
> > + # Comparison ops. ORDER OPR.
> "ORDER OPR" - what does it mean? Here and below.
Same as in bc.h: order of operators. See lj_parse.c for details.
> > + 'ISLT',
> > + 'ISGE',
> > + 'ISLE',
> <snipped>
> >
--
Best regards,
Sergey Kaplun
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-06-05 16:11 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-04 9:30 [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Kaplun via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 1/4] dbg: fix lj-stack command for LLDB Sergey Kaplun via Tarantool-patches
2026-06-05 14:55 ` Sergey Bronnikov via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 2/4] dbg: fix DUALNUM detection " Sergey Kaplun via Tarantool-patches
2026-06-05 14:57 ` Sergey Bronnikov via Tarantool-patches
2026-06-05 16:01 ` Sergey Kaplun via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 3/4] dbg: introduce lj-gco command Sergey Kaplun via Tarantool-patches
2026-06-05 15:02 ` Sergey Bronnikov via Tarantool-patches
2026-06-04 9:30 ` [Tarantool-patches] [PATCH luajit 4/4] dbg: introduce lj-bc, lj-func and lj-proto dumpers Sergey Kaplun via Tarantool-patches
2026-06-05 15:07 ` Sergey Bronnikov via Tarantool-patches
2026-06-05 16:10 ` Sergey Kaplun via Tarantool-patches
2026-06-05 14:55 ` [Tarantool-patches] [PATCH luajit 0/4] Introduce dumpers for bytecodes in debuggers Sergey Bronnikov via Tarantool-patches
2026-06-05 16:03 ` [Tarantool-patches] [PATCH luajit 3/5] dbg: update help for the lj-arch command Sergey Kaplun via Tarantool-patches
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox