[Tarantool-patches] [PATCH luajit 3/4] gdb: make several enhancements

Igor Munkin imun at tarantool.org
Mon Jan 27 23:41:07 MSK 2020


* Introduce lj-gc command that shows current GC stats
* Remove mandatory L argument for lj-state and make it optional for
  lj-stack (main coroutine is used if argument is ommited)
* Adjust doc strings

Signed-off-by: Igor Munkin <imun at tarantool.org>
---
 src/luajit-gdb.py | 204 ++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 171 insertions(+), 33 deletions(-)

diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
index 4f69152..77da5e6 100644
--- a/src/luajit-gdb.py
+++ b/src/luajit-gdb.py
@@ -19,6 +19,21 @@ def gtype(typestr):
 def cast(typestr, val):
     return gdb.Value(val).cast(gtype(typestr))
 
+def lookup(symbol):
+    variable, _ = gdb.lookup_symbol(symbol)
+    return variable.value() if variable else None
+
+def parse_arg(arg):
+    if not arg:
+        return None
+
+    ret = gdb.parse_and_eval(arg)
+
+    if not ret:
+        raise gdb.GdbError('table argument empty')
+
+    return ret
+
 def tou64(val):
     return cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF
 
@@ -152,18 +167,30 @@ def gcval(obj):
     return cast('GCobj *', obj['gcptr64'] & LJ_GCVMASK if LJ_GC64
         else cast('uintptr_t', obj['gcptr32']))
 
+def L(L=None):
+    # lookup a symbol for the main coroutine considering the host app
+    for l in (L, *map(lambda l: lookup(l), (
+        # LuaJIT main coro (see luajit/src/luajit.c)
+        'globalL',
+        # Tarantool main coro (see tarantool/src/lua/init.h)
+        'tarantool_L',
+        # TODO: Add more
+    ))):
+        if l:
+            return cast('lua_State *', l)
+
 def G(L):
     return mref('global_State *', L['glref'])
 
-def J(L):
+def J(g):
     typeGG = gtype('GG_State')
 
-    return cast('jit_State *', int(cast('char *', G(L)))
+    return cast('jit_State *', int(cast('char *', g))
         - int(typeGG['g'].bitpos / 8)
         + int(typeGG['J'].bitpos / 8)
     )
 
-def vm_state(L):
+def vm_state(g):
     return {
         i2notu64(0): 'INTERP',
         i2notu64(1): 'C',
@@ -174,7 +201,7 @@ def vm_state(L):
         i2notu64(6): 'ASM',
     }.get(int(tou64(g['vmstate'])), 'TRACE')
 
-def gc_state(L):
+def gc_state(g):
     return {
         0: 'PAUSE',
         1: 'PROPAGATE',
@@ -183,9 +210,9 @@ def gc_state(L):
         4: 'SWEEP',
         5: 'FINALIZE',
         6: 'LAST',
-    }.get(int(G(L)['gc']['state']), 'INVALID')
+    }.get(int(g['gc']['state']), 'INVALID')
 
-def jit_state(L):
+def jit_state(g):
     return {
         0:    'IDLE',
         0x10: 'ACTIVE',
@@ -194,7 +221,7 @@ def jit_state(L):
         0x13: 'END',
         0x14: 'ASM',
         0x15: 'ERR',
-    }.get(int(J(L)['state']), 'INVALID')
+    }.get(int(J(g)['state']), 'INVALID')
 
 def tvisnumber(o):
     return itype(o) <= (0xfffeffff if LJ_64 and not LJ_GC64 else LJ_T['NUMX'])
@@ -223,6 +250,13 @@ def funcproto(func):
     return cast('GCproto *',
         mref('char *', func['pc']) - gdb.lookup_type('GCproto').sizeof)
 
+def gclistlen(root):
+    count = 0
+    while(gcref(root)):
+        count += 1
+        root = gcref(root)['gch']['nextgc']
+    return count
+
 # Dumpers {{{
 
 def dump_lj_tnil(tv):
@@ -390,24 +424,34 @@ def dump_stack(L, base=None, top=None):
 
     return dump
 
-def parse_arg(arg):
-    argv = gdb.string_to_argv(arg)
+def dump_gc(g):
+    gc = g['gc']
+    stats = [ '{key}: {value}'.format(key = f, value = gc[f]) for f in (
+        'total', 'threshold', 'debt', 'estimate', 'stepmul', 'pause'
+    ) ]
 
-    if len(argv) == 0:
-        raise gdb.GdbError("Wrong number of arguments."
-            "Use 'help <command>' to get more info.")
+    stats += [ 'sweepstr: {sweepstr}/{strmask}'.format(
+        sweepstr = gc['sweepstr'],
+        # String hash mask (size of hash table - 1).
+        strmask = g['strmask'] + 1,
+    ) ]
 
-    ret = gdb.parse_and_eval(arg)
+    stats += [ '{key}: {number} objects'.format(
+        key = f,
+        number = gclistlen(gc[f]),
+    ) for f in ('root', 'gray', 'grayagain', 'weak') ]
 
-    if not ret:
-        raise gdb.GdbError('table argument empty')
+    # TODO: mmudata
 
-    return ret
+    return '\n'.join(map(lambda s: '\t' + s, stats))
 
 class LJDumpArch(gdb.Command):
     '''
 lj-arch
-Dumps compile-time information
+
+The command requires no args and dumps values of LJ_64 and LJ_GC64
+compile-time flags. These values define the sizes of host and GC
+pointers respectively.
     '''
 
     def __init__(self):
@@ -425,8 +469,31 @@ LJDumpArch()
 
 class LJDumpTValue(gdb.Command):
     '''
-lj-tv address
-Dumps the contents of the TValue at address.
+lj-tv <TValue *>
+
+The command recieves a pointer to <tv> (TValue address) and dumps
+the type and some info related to it.
+
+* LJ_TNIL: nil
+* LJ_TFALSE: false
+* LJ_TTRUE: true
+* LJ_TLIGHTUD: light userdata @ <gcr>
+* LJ_TSTR: string <string payload> @ <gcr>
+* LJ_TUPVAL: upvalue @ <gcr>
+* LJ_TTHREAD: thread @ <gcr>
+* LJ_TPROTO: proto @ <gcr>
+* LJ_TFUNC: <LFUNC|CFUNC|FFUNC>
+  <LFUNC>: Lua function @ <gcr>, <nupvals> upvalues, <chunk:line>
+  <CFUNC>: C function <mcode address>
+  <FFUNC>: fast function #<ffid>
+* LJ_TTRACE: trace <traceno> @ <gcr>
+* LJ_TCDATA: cdata @ <gcr>
+* LJ_TTAB: table @ <gcr> (asize: <asize>, hmask: <hmask>)
+* LJ_TUDATA: userdata @ <gcr>
+* LJ_TNUMX: number <numeric payload>
+
+Whether the type of the given address differs from the listed above, then
+error message occurs.
     '''
 
     def __init__(self):
@@ -442,8 +509,13 @@ LJDumpTValue()
 
 class LJDumpString(gdb.Command):
     '''
-lj-str address
-Dumps the contents of the GCstr at address.
+lj-str <GCstr *>
+
+The command recieves a <gcr> of the corresponding GCstr object and dumps
+the payload, size in bytes and hash.
+
+*Caveat*: Since Python 2 provides no native Unicode support, the payload
+is replaced with the corresponding error when decoding fails.
     '''
 
     def __init__(self):
@@ -464,8 +536,14 @@ LJDumpString()
 
 class LJDumpTable(gdb.Command):
     '''
-lj-tab address
-Dumps the contents of the GCtab at address.
+lj-tab <GCtab *>
+
+The command recieves a GCtab adress and dumps the table contents:
+* Metatable address whether the one is set
+* Array part <asize> slots:
+  <aslot ptr>: [<index>]: <tv>
+* Hash part <hsize> nodes:
+  <hnode ptr>: { <tv> } => { <tv> }; next = <next hnode ptr>
     '''
 
     def __init__(self):
@@ -509,8 +587,35 @@ LJDumpTable()
 
 class LJDumpStack(gdb.Command):
     '''
-lj-stack L
-Dumps Lua stack of the given coroutine L.
+lj-stack [<lua_State *>]
+
+The command recieves a lua_State address and dumps the given Lua
+coroutine guest stack:
+
+<slot ptr> [<slot attributes>] <VALUE|FRAME>
+
+* <slot ptr>: guest stack slot address
+* <slot attributes>:
+  - S: Bottom of the stack (the slot L->stack points to)
+  - B: Base of the current guest frame (the slot L->base points to)
+  - T: Top of the current guest frame (the slot L->top points to)
+  - M: Last slot of the stack (the slot L->maxstack points to)
+* <VALUE>: see help lj-tv for more info
+* <FRAME>: framelink slot differs from the value slot: it contains info
+  related to the function being executed within this guest frame, its
+  type and link to the parent guest frame
+  [<frame type>] delta=<slots in frame>, <lj-tv for LJ_TFUNC slot>
+  - <frame type>:
+    + L:  VM performs a call as a result of bytecode execution
+    + C:  VM performs a call as a result of lj_vm_call
+    + M:  VM performs a call to a metamethod as a result of bytecode
+          execution
+    + V:  Variable-length frame for storing arguments of a variadic
+          function
+    + CP: Protected C frame
+    + PP: VM performs a call as a result of executinig pcall or xpcall
+
+If L is ommited the main coroutine is used.
     '''
 
     def __init__(self):
@@ -518,15 +623,17 @@ Dumps Lua stack of the given coroutine L.
             'lj-stack', gdb.COMMAND_DATA)
 
     def invoke(self, arg, from_tty):
-        L = cast('lua_State *', parse_arg(arg))
-        gdb.write('{}\n'.format(dump_stack(L)))
+        gdb.write('{}\n'.format(dump_stack(L(parse_arg(arg)))))
 
 LJDumpStack()
 
 class LJState(gdb.Command):
     '''
-lj-state L
-Show current VM and GC state.
+lj-state
+The command requires no args and dumps current VM and GC states
+* VM state: <INTERP|C|GC|EXIT|RECORD|OPT|ASM|TRACE>
+* GC state: <PAUSE|PROPAGATE|ATOMIC|SWEEPSTRING|SWEEP|FINALIZE|LAST>
+* JIT state: <IDLE|ACTIVE|RECORD|START|END|ASM|ERR>
     '''
 
     def __init__(self):
@@ -534,13 +641,44 @@ Show current VM and GC state.
             'lj-state', gdb.COMMAND_DATA)
 
     def invoke(self, arg, from_tty):
-        L = cast('lua_State *', parse_arg(arg))
+        g = G(L(None))
         gdb.write('{}\n'.format('\n'.join(
             map(lambda t: '{} state: {}'.format(*t), {
-                'VM': vm_state(L),
-                'GC': gc_state(L),
-                'JIT': jit_state(L),
+                'VM': vm_state(g),
+                'GC': gc_state(g),
+                'JIT': jit_state(g),
             }.items())
         )))
 
 LJState()
+
+class LJGC(gdb.Command):
+    '''
+lj-gc
+
+The command requires no args and dumps current GC stats:
+* total: <total number of allocated bytes in GC area>
+* threshold: <limit when gc step is triggered>
+* debt: <how much GC is behind schedule>
+* estimate: <estimate of memory actually in use>
+* stepmul: <incremental GC step granularity>
+* pause: <pause between successive GC cycles>
+* sweepstr: <sweep position in string table>
+* root: <number of all collectable objects>
+* gray: <number of gray objects>
+* grayagain: <number of objects for atomic traversal>
+* weak: <number of weak tables (to be cleared)>
+    '''
+
+    def __init__(self):
+        super(LJGC, self).__init__(
+            'lj-gc', gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        g = G(L(None))
+        gdb.write('GC stats: {state}\n{stats}\n'.format(
+            state = gc_state(g),
+            stats = dump_gc(g)
+        ))
+
+LJGC()
-- 
2.24.0



More information about the Tarantool-patches mailing list