Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
@ 2020-02-05 16:22 Igor Munkin
  2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 1/3] gdb: introduce luajit-gdb extension Igor Munkin
                   ` (7 more replies)
  0 siblings, 8 replies; 23+ messages in thread
From: Igor Munkin @ 2020-02-05 16:22 UTC (permalink / raw)
  To: tarantool-patches

The series provides a gdb extension with commands for inspecting LuaJIT
internals. To use it, just put 'source <path-to-repo>/src/luajit-gdb.py'
in gdb. The extension obliges the one to provide gdbinfo for libluajit,
otherwise loading can fail with the error:

| (gdb) source <path-to-repo>/src/luajit-gdb.py
| luajit-gdb.py failed to load: no debugging symbols found for libluajit

When the loading succeeds the one see the following:

| (gdb) source <path-to-repo>/src/luajit-gdb.py
| lj-arch command initialized
| lj-tv command initialized
| lj-str command initialized
| lj-tab command initialized
| lj-stack command initialized
| lj-state command initialized
| lj-gc command initialized
| luajit-gdb.py is successfully loaded

Below is a description for the set of implemented commands below:

### 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.

NB: Compile-time defines are checked when script is being loaded:
* When LJ_64 is enabled IRT_PTR is an IRT_P64 alias and an IRT_P32 one
  otherwise
* When LJ_64 is enabled IRT_PGC is an IRT_P64 alias and an IRT_P32 one
  otherwise

### lj-tv

The command recieves a <tv address> (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.

### lj-str

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.

### lj-tab

The command recieves a GCtab address and dumps the table contents:
* Metatable address whether the one is set
* Array part <asize> slots:
  <aslot ptr>: [<index>]: <tv>
* Hash part <hsize> nodes:
  <hnode ptr>: { <tv> } => { <tv> }; next = <next hnode ptr>

### lj-stack

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.

### 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>

### 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)>

Signed-off-by: Igor Munkin <imun@tarantool.org>

--

Changes in v2:
* squashed "fixup" commits with the first one
* added loading errors handling

v1: https://lists.tarantool.org/pipermail/tarantool-patches/2020-January/013801.html

Branch: https://github.com/tarantool/luajit/tree/imun/luajit-gdb

Igor Munkin (3):
  gdb: introduce luajit-gdb extension
  gdb: adjust the extension to be used with Python 2
  gdb: enhance the extension loading

 src/luajit-gdb.py | 687 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 687 insertions(+)
 create mode 100644 src/luajit-gdb.py

-- 
2.24.0

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [Tarantool-patches] [PATCH v2 luajit 1/3] gdb: introduce luajit-gdb extension
  2020-02-05 16:22 [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Igor Munkin
@ 2020-02-05 16:22 ` Igor Munkin
  2020-02-13 13:24   ` Igor Munkin
  2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 2/3] gdb: adjust the extension to be used with Python 2 Igor Munkin
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Igor Munkin @ 2020-02-05 16:22 UTC (permalink / raw)
  To: tarantool-patches

The provided luajit-gdb extenstion contains the following additional commands:
* lj-arch -- dumps values of LJ_64 and LJ_GC64 macro definitions
* lj-tv -- dumps the type and some GCobj info related to the given TValue
* lj-str -- dumps the contents of the given GCstr
* lj-tab -- dumps the contents of the given GCtab
* lj-stack -- dumps Lua stack of the given lua_State
* lj-state -- shows current VM, GC and JIT states
* lj-gc -- shows current GC stats

Currently extension supports only x64 builds but respects LJ_GC64 value

Signed-off-by: Igor Munkin <imun@tarantool.org>
---
 src/luajit-gdb.py | 684 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 684 insertions(+)
 create mode 100644 src/luajit-gdb.py

diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
new file mode 100644
index 0000000..77da5e6
--- /dev/null
+++ b/src/luajit-gdb.py
@@ -0,0 +1,684 @@
+import re
+import gdb
+
+gtype_cache = {}
+
+def gtype(typestr):
+    global gtype_cache
+    if typestr in gtype_cache:
+        return gtype_cache[typestr]
+
+    m = re.match(r'((?:(?:struct|union) )?\S*)\s*[*]', typestr)
+
+    gtype = gdb.lookup_type(typestr) if m is None \
+        else gdb.lookup_type(m.group(1)).pointer()
+
+    gtype_cache[typestr] = gtype
+    return gtype
+
+def cast(typestr, val):
+    return gdb.Value(val).cast(gtype(typestr))
+
+def lookup(symbol):
+    variable, _ = gdb.lookup_symbol(symbol)
+    return variable.value() if variable else None
+
+def parse_arg(arg):
+    if not arg:
+        return None
+
+    ret = gdb.parse_and_eval(arg)
+
+    if not ret:
+        raise gdb.GdbError('table argument empty')
+
+    return ret
+
+def tou64(val):
+    return cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF
+
+def tou32(val):
+    return cast('uint32_t', val) & 0xFFFFFFFF
+
+def i2notu64(val):
+    return ~int(val) & 0xFFFFFFFFFFFFFFFF
+
+def i2notu32(val):
+    return ~int(val) & 0xFFFFFFFF
+
+def strx64(val):
+    return hex(cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF)
+
+# Types {{{
+
+LJ_T = {
+    'NIL'     : i2notu32(0),
+    'FALSE'   : i2notu32(1),
+    'TRUE'    : i2notu32(2),
+    'LIGHTUD' : i2notu32(3),
+    'STR'     : i2notu32(4),
+    'UPVAL'   : i2notu32(5),
+    'THREAD'  : i2notu32(6),
+    'PROTO'   : i2notu32(7),
+    'FUNC'    : i2notu32(8),
+    'TRACE'   : i2notu32(9),
+    'CDATA'   : i2notu32(10),
+    'TAB'     : i2notu32(11),
+    'UDATA'   : i2notu32(12),
+    'NUMX'    : i2notu32(13),
+}
+
+def typenames(value):
+    return {
+        LJ_T[k]: 'LJ_T' + k for k in LJ_T.keys()
+    }.get(int(value), 'LJ_TINVALID')
+
+# }}}
+
+# Frames {{{
+
+FRAME_TYPE = 0x3
+FRAME_P = 0x4
+FRAME_TYPEP = FRAME_TYPE | FRAME_P
+
+FRAME = {
+    'LUA': 0x0,
+    'C': 0x1,
+    'CONT': 0x2,
+    'VARG': 0x3,
+    'LUAP': 0x4,
+    'CP': 0x5,
+    'PCALL': 0x6,
+    'PCALLH': 0x7,
+}
+
+def frametypes(ft):
+    return {
+        FRAME['LUA']  : 'L',
+        FRAME['C']    : 'C',
+        FRAME['CONT'] : 'M',
+        FRAME['VARG'] : 'V',
+    }.get(ft, '?')
+
+def bc_a(ins):
+    return (ins >> 8) & 0xff
+
+def frame_ftsz(framelink):
+    return cast('ptrdiff_t', framelink['ftsz'] if LJ_FR2 \
+        else framelink['fr']['tp']['ftsz'])
+
+def frame_pc(framelink):
+    return cast('BCIns *', frame_ftsz(framelink)) if LJ_FR2 \
+        else mref('BCIns *', framelink['fr']['tp']['pcr'])
+
+def frame_prevl(framelink):
+    return framelink - (1 + LJ_FR2 + bc_a(frame_pc(framelink)[-1]))
+
+def frame_ispcall(framelink):
+    return (frame_ftsz(framelink) & FRAME['PCALL']) == FRAME['PCALL']
+
+def frame_sized(framelink):
+    return (frame_ftsz(framelink) & ~FRAME_TYPEP)
+
+def frame_prevd(framelink):
+    return cast('TValue *', cast('char *', framelink) - frame_sized(framelink))
+
+def frame_type(framelink):
+    return frame_ftsz(framelink) & FRAME_TYPE
+
+def frame_typep(framelink):
+    return frame_ftsz(framelink) & FRAME_TYPEP
+
+def frame_islua(framelink):
+    return frametypes(int(frame_type(framelink))) == 'L' \
+        and int(frame_ftsz(framelink)) > 0
+
+def frame_prev(framelink):
+    return frame_prevl(framelink) if frame_islua(framelink) \
+        else frame_prevd(framelink)
+
+# }}}
+
+# Const {{{
+
+LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
+
+LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
+
+LJ_FR2 = LJ_GC64
+
+LJ_GCVMASK = ((1 << 47) - 1)
+
+PADDING = ' ' * len(':' + hex((1 << (47 if LJ_GC64 else 32)) - 1))
+
+# }}}
+
+def itype(o):
+    return cast('uint32_t', o['it64'] >> 47) if LJ_GC64 else o['it']
+
+def mref(typename, obj):
+    return cast(typename, obj['ptr64'] if LJ_GC64 else obj['ptr32'])
+
+def gcref(obj):
+    return cast('GCobj *', obj['gcptr64'] if LJ_GC64
+        else cast('uintptr_t', obj['gcptr32']))
+
+def gcval(obj):
+    return cast('GCobj *', obj['gcptr64'] & LJ_GCVMASK if LJ_GC64
+        else cast('uintptr_t', obj['gcptr32']))
+
+def 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(g):
+    typeGG = gtype('GG_State')
+
+    return cast('jit_State *', int(cast('char *', g))
+        - int(typeGG['g'].bitpos / 8)
+        + int(typeGG['J'].bitpos / 8)
+    )
+
+def vm_state(g):
+    return {
+        i2notu64(0): 'INTERP',
+        i2notu64(1): 'C',
+        i2notu64(2): 'GC',
+        i2notu64(3): 'EXIT',
+        i2notu64(4): 'RECORD',
+        i2notu64(5): 'OPT',
+        i2notu64(6): 'ASM',
+    }.get(int(tou64(g['vmstate'])), 'TRACE')
+
+def gc_state(g):
+    return {
+        0: 'PAUSE',
+        1: 'PROPAGATE',
+        2: 'ATOMIC',
+        3: 'SWEEPSTRING',
+        4: 'SWEEP',
+        5: 'FINALIZE',
+        6: 'LAST',
+    }.get(int(g['gc']['state']), 'INVALID')
+
+def jit_state(g):
+    return {
+        0:    'IDLE',
+        0x10: 'ACTIVE',
+        0x11: 'RECORD',
+        0x12: 'START',
+        0x13: 'END',
+        0x14: 'ASM',
+        0x15: 'ERR',
+    }.get(int(J(g)['state']), 'INVALID')
+
+def tvisnumber(o):
+    return itype(o) <= (0xfffeffff if LJ_64 and not LJ_GC64 else LJ_T['NUMX'])
+
+def tvislightud(o):
+    if LJ_64 and not LJ_GC64:
+        return (cast('int32_t', itype(o)) >> 15) == -2
+    else:
+        return itype(o) == LJ_T['LIGHTUD']
+
+def strdata(obj):
+    # String is printed with pointer to it, thanks to gdb. Just strip it.
+    return str(cast('char *', cast('GCstr *', obj) + 1))[len(PADDING):]
+
+def itypemap(o):
+    if LJ_64 and not LJ_GC64:
+        return LJ_T['NUMX'] if tvisnumber(o)       \
+            else LJ_T['LIGHTUD'] if tvislightud(o) \
+            else itype(o)
+    else:
+        return LJ_T['NUMX'] if tvisnumber(o) else itype(o)
+
+def funcproto(func):
+    assert(func['ffid'] == 0)
+
+    return cast('GCproto *',
+        mref('char *', func['pc']) - gdb.lookup_type('GCproto').sizeof)
+
+def gclistlen(root):
+    count = 0
+    while(gcref(root)):
+        count += 1
+        root = gcref(root)['gch']['nextgc']
+    return count
+
+# 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(gcval(tv['gcr'])))
+
+def dump_lj_tstr(tv):
+    return 'string {body} @ {address}'.format(
+        body = strdata(gcval(tv['gcr'])),
+        address = strx64(gcval(tv['gcr']))
+    )
+
+def dump_lj_tupval(tv):
+    return 'upvalue @ {}'.format(strx64(gcval(tv['gcr'])))
+
+def dump_lj_tthread(tv):
+    return 'thread @ {}'.format(strx64(gcval(tv['gcr'])))
+
+def dump_lj_tproto(tv):
+    return 'proto @ {}'.format(strx64(gcval(tv['gcr'])))
+
+def dump_lj_tfunc(tv):
+    func = cast('struct GCfuncC *', gcval(tv['gcr']))
+    ffid = func['ffid']
+
+    if ffid == 0:
+        pt = funcproto(func)
+        return 'Lua function @ {addr}, {nupvals} upvalues, {chunk}:{line}'.format(
+            addr = strx64(func),
+            nupvals = int(func['nupvalues']),
+            chunk = strdata(cast('GCstr *', gcval(pt['chunkname']))),
+            line = pt['firstline']
+        )
+    elif ffid == 1:
+        return 'C function @ {}'.format(strx64(func['f']))
+    else:
+        return 'fast function #{}'.format(int(ffid))
+
+def dump_lj_ttrace(tv):
+    trace = cast('struct GCtrace *', gcval(tv['gcr']))
+    return 'trace {traceno} @ {addr}'.format(
+        traceno = strx64(trace['traceno']),
+        addr = strx64(trace)
+    )
+
+def dump_lj_tcdata(tv):
+    return 'cdata @ {}'.format(strx64(gcval(tv['gcr'])))
+
+def dump_lj_ttab(tv):
+    table = cast('GCtab *', gcval(tv['gcr']))
+    return 'table @ {gcr} (asize: {asize}, hmask: {hmask})'.format(
+        gcr = strx64(table),
+        asize = table['asize'],
+        hmask = strx64(table['hmask']),
+    )
+
+def dump_lj_tudata(tv):
+    return 'userdata @ {}'.format(strx64(gcval(tv['gcr'])))
+
+def dump_lj_tnumx(tv):
+    return 'number {}'.format(cast('double', tv['n']))
+
+def dump_lj_invalid(tv):
+    return 'not valid type @ {}'.format(strx64(gcval(tv['gcr'])))
+
+# }}}
+
+dumpers = {
+    'LJ_TNIL': dump_lj_tnil,
+    'LJ_TFALSE': dump_lj_tfalse,
+    'LJ_TTRUE': dump_lj_ttrue,
+    'LJ_TLIGHTUD': dump_lj_tlightud,
+    'LJ_TSTR': dump_lj_tstr,
+    'LJ_TUPVAL': dump_lj_tupval,
+    'LJ_TTHREAD': dump_lj_tthread,
+    'LJ_TPROTO': dump_lj_tproto,
+    'LJ_TFUNC': dump_lj_tfunc,
+    'LJ_TTRACE': dump_lj_ttrace,
+    'LJ_TCDATA': dump_lj_tcdata,
+    'LJ_TTAB': dump_lj_ttab,
+    'LJ_TUDATA': dump_lj_tudata,
+    'LJ_TNUMX': dump_lj_tnumx,
+}
+
+def dump_tvalue(tvalue):
+    return dumpers.get(typenames(itypemap(tvalue)), dump_lj_invalid)(tvalue)
+
+def dump_framelink(L, fr):
+    fr2 = fr + LJ_FR2
+
+    return '{fr}{padding} [    ] FRAME: [{pp}] delta={d}, {f}\n'.format(
+        fr = fr,
+        padding = ':{fr2}'.format(fr2 = fr2) if LJ_FR2 else PADDING,
+        pp = 'PP' if frame_ispcall(fr2) else '{frname}{p}'.format(
+            frname = frametypes(int(frame_type(fr2))),
+            p = 'P' if frame_typep(fr2) & FRAME_P else ''
+        ),
+        d = cast('TValue *', fr2) - cast('TValue *', frame_prev(fr2)),
+        f = dump_lj_tfunc(fr),
+    )
+
+def dump_stack_slot(L, slot, base=None, top=None):
+    base = base or L['base']
+    top = top or L['top']
+
+    return '{addr}{padding} [ {B}{T}{M}] VALUE: {value}\n'.format(
+        addr = strx64(slot),
+        padding = PADDING,
+        B = 'B' if slot == base else ' ',
+        T = 'T' if slot == top else ' ',
+        M = 'M' if slot == mref('TValue *', L['maxstack']) else ' ',
+        value = dump_tvalue(slot),
+    )
+
+def dump_stack(L, base=None, top=None):
+    base = base or L['base']
+    top = top or L['top']
+    maxstack = mref('TValue *', L['maxstack'])
+    red = 5 + 2 * LJ_FR2
+
+    dump = '\n'.join([
+        '{start}:{end} [    ] {n} slots: Red zone'.format(
+             start = strx64(maxstack + 1),
+             end = strx64(maxstack + red),
+             n = red,
+        ),
+        '{maxstack}{padding} [   M]'.format(
+            maxstack = strx64(maxstack),
+            padding = PADDING,
+        ),
+        '{start}:{end} [    ] {nfreeslots} slots: Free stack slots'.format(
+            start = strx64(top + 1),
+            end = strx64(maxstack - 1),
+            nfreeslots = int((tou64(maxstack) - tou64(top) - 8) >> 3),
+        ),
+        '{top}{padding} [  T ]'.format(
+            top = strx64(top),
+            padding = PADDING,
+        )
+    ]) + '\n'
+
+    slot = top - 1
+    framelink = base - (1 + LJ_FR2)
+
+    while framelink > mref('TValue *', L['stack']):
+        while slot > framelink + LJ_FR2:
+            dump += dump_stack_slot(L, slot, base, top)
+            slot -= 1
+        dump += dump_framelink(L, framelink)
+        framelink = frame_prev(framelink + LJ_FR2) - LJ_FR2
+        slot -= 1 + LJ_FR2
+
+    dump += '{fr}{padding} [S   ] FRAME: dummy L'.format(
+        fr = slot,
+        padding = ':{nilslot}'.format(nilslot = slot + 1) if LJ_FR2 else PADDING
+    )
+
+    return dump
+
+def dump_gc(g):
+    gc = g['gc']
+    stats = [ '{key}: {value}'.format(key = f, value = gc[f]) for f in (
+        'total', 'threshold', 'debt', 'estimate', 'stepmul', 'pause'
+    ) ]
+
+    stats += [ 'sweepstr: {sweepstr}/{strmask}'.format(
+        sweepstr = gc['sweepstr'],
+        # String hash mask (size of hash table - 1).
+        strmask = g['strmask'] + 1,
+    ) ]
+
+    stats += [ '{key}: {number} objects'.format(
+        key = f,
+        number = gclistlen(gc[f]),
+    ) for f in ('root', 'gray', 'grayagain', 'weak') ]
+
+    # TODO: mmudata
+
+    return '\n'.join(map(lambda s: '\t' + s, stats))
+
+class LJDumpArch(gdb.Command):
+    '''
+lj-arch
+
+The command requires no args and dumps values of LJ_64 and LJ_GC64
+compile-time flags. These values define the sizes of host and GC
+pointers respectively.
+    '''
+
+    def __init__(self):
+        super(LJDumpArch, self).__init__(
+            'lj-arch', gdb.COMMAND_DATA
+        )
+
+    def invoke(self, arg, from_tty):
+        gdb.write('LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}\n'.format(
+            LJ_64 = LJ_64,
+            LJ_GC64 = LJ_GC64
+        ))
+
+LJDumpArch()
+
+class LJDumpTValue(gdb.Command):
+    '''
+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):
+        super(LJDumpTValue, self).__init__(
+            'lj-tv', gdb.COMMAND_DATA
+        )
+
+    def invoke(self, arg, from_tty):
+        tv = cast('TValue *', parse_arg(arg))
+        gdb.write('{}\n'.format(dump_tvalue(tv)))
+
+LJDumpTValue()
+
+class LJDumpString(gdb.Command):
+    '''
+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):
+        super(LJDumpString, self).__init__(
+            'lj-str', gdb.COMMAND_DATA
+        )
+
+    def invoke(self, arg, from_tty):
+        string = cast('GCstr *', parse_arg(arg))
+        gdb.write("String: {body} [{len} bytes] with hash {hash}\n".format(
+            body = strdata(string),
+            hash = strx64(string['hash']),
+            len = string['len'],
+        ))
+
+
+LJDumpString()
+
+class LJDumpTable(gdb.Command):
+    '''
+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):
+        super(LJDumpTable, self).__init__(
+            'lj-tab', gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        t = cast('GCtab *', parse_arg(arg))
+        array = mref('TValue *', t['array'])
+        nodes = mref('struct Node *', t['node'])
+        mt = gcval(t['metatable'])
+        capacity = {
+            'apart': int(t['asize']),
+            'hpart': int(t['hmask'] + 1) if t['hmask'] > 0 else 0
+        }
+
+        if mt != 0:
+            gdb.write('Metatable detected: {}\n'.format(strx64(mt)))
+
+        gdb.write('Array part: {} slots\n'.format(capacity['apart']))
+        for i in range(capacity['apart']):
+            slot = array + i
+            gdb.write('{ptr}: [{index}]: {value}\n'.format(
+                ptr = slot,
+                index = i,
+                value = dump_tvalue(slot)
+            ))
+
+        gdb.write('Hash part: {} nodes\n'.format(capacity['hpart']))
+        # See hmask comment in lj_obj.h
+        for i in range(capacity['hpart']):
+            node = nodes + i
+            gdb.write('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}\n'.format(
+                ptr = node,
+                key = dump_tvalue(node['key']),
+                val= dump_tvalue(node['val']),
+                n = mref('struct Node *', node['next'])
+            ))
+
+LJDumpTable()
+
+class LJDumpStack(gdb.Command):
+    '''
+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):
+        super(LJDumpStack, self).__init__(
+            'lj-stack', gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        gdb.write('{}\n'.format(dump_stack(L(parse_arg(arg)))))
+
+LJDumpStack()
+
+class LJState(gdb.Command):
+    '''
+lj-state
+The command requires no args and dumps current VM and GC states
+* VM state: <INTERP|C|GC|EXIT|RECORD|OPT|ASM|TRACE>
+* GC state: <PAUSE|PROPAGATE|ATOMIC|SWEEPSTRING|SWEEP|FINALIZE|LAST>
+* JIT state: <IDLE|ACTIVE|RECORD|START|END|ASM|ERR>
+    '''
+
+    def __init__(self):
+        super(LJState, self).__init__(
+            'lj-state', gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        g = G(L(None))
+        gdb.write('{}\n'.format('\n'.join(
+            map(lambda t: '{} state: {}'.format(*t), {
+                'VM': vm_state(g),
+                'GC': gc_state(g),
+                'JIT': jit_state(g),
+            }.items())
+        )))
+
+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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [Tarantool-patches] [PATCH v2 luajit 2/3] gdb: adjust the extension to be used with Python 2
  2020-02-05 16:22 [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Igor Munkin
  2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 1/3] gdb: introduce luajit-gdb extension Igor Munkin
@ 2020-02-05 16:22 ` Igor Munkin
  2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 3/3] gdb: enhance the extension loading Igor Munkin
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 23+ messages in thread
From: Igor Munkin @ 2020-02-05 16:22 UTC (permalink / raw)
  To: tarantool-patches

This patch covers all problems faced while using the extension via gdb
compiled with Python 2.

Signed-off-by: Igor Munkin <imun@tarantool.org>
---
 src/luajit-gdb.py | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
index 77da5e6..90df101 100644
--- a/src/luajit-gdb.py
+++ b/src/luajit-gdb.py
@@ -1,5 +1,14 @@
 import re
 import gdb
+import sys
+
+# make script compatible with the ancient Python {{{
+
+if re.match(r'^2\.', sys.version):
+    int = long
+    range = xrange
+
+# }}}
 
 gtype_cache = {}
 
@@ -47,7 +56,8 @@ def i2notu32(val):
     return ~int(val) & 0xFFFFFFFF
 
 def strx64(val):
-    return hex(cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF)
+    return re.sub('L?$', '',
+                  hex(int(cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF)))
 
 # Types {{{
 
@@ -169,7 +179,9 @@ def gcval(obj):
 
 def L(L=None):
     # lookup a symbol for the main coroutine considering the host app
-    for l in (L, *map(lambda l: lookup(l), (
+    # XXX Fragile: though the loop initialization looks like a crap but it
+    # respects both Python 2 and Python 3.
+    for l in [ L ] + list(map(lambda l: lookup(l), (
         # LuaJIT main coro (see luajit/src/luajit.c)
         'globalL',
         # Tarantool main coro (see tarantool/src/lua/init.h)
@@ -234,7 +246,10 @@ def tvislightud(o):
 
 def strdata(obj):
     # String is printed with pointer to it, thanks to gdb. Just strip it.
-    return str(cast('char *', cast('GCstr *', obj) + 1))[len(PADDING):]
+    try:
+        return str(cast('char *', cast('GCstr *', obj) + 1))[len(PADDING):]
+    except UnicodeEncodeError:
+        return "<luajit-gdb: error occured while rendering non-ascii slot>"
 
 def itypemap(o):
     if LJ_64 and not LJ_GC64:
-- 
2.24.0

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [Tarantool-patches] [PATCH v2 luajit 3/3] gdb: enhance the extension loading
  2020-02-05 16:22 [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Igor Munkin
  2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 1/3] gdb: introduce luajit-gdb extension Igor Munkin
  2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 2/3] gdb: adjust the extension to be used with Python 2 Igor Munkin
@ 2020-02-05 16:22 ` Igor Munkin
  2020-02-13 11:48   ` Igor Munkin
  2020-02-26 22:41 ` [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Alexander Turenko
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Igor Munkin @ 2020-02-05 16:22 UTC (permalink / raw)
  To: tarantool-patches

Definition of LJ_64, LJ_GC64 and LJ_FR2 constants in script requires
LuaJIT internal enum values to be presented in the executable. Otherwise
the extension loading fails. The changes handles exception for the
following scenarios:
* luajit-gdb.py is loaded for an arbitrary executable, e.g. /bin/echo
* luajit-gdb.py is loaded for a luajit executable (or the one linked
  against libluajit) but debug info is not found

Signed-off-by: Igor Munkin <imun@tarantool.org>
---
 src/luajit-gdb.py | 102 ++++++++++++++++++++--------------------------
 1 file changed, 45 insertions(+), 57 deletions(-)

diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
index 90df101..bc37674 100644
--- a/src/luajit-gdb.py
+++ b/src/luajit-gdb.py
@@ -1,3 +1,6 @@
+# GDB extension for LuaJIT post-mortem analysis.
+# To use, just put 'source <path-to-repo>/src/luajit-gdb.py' in gdb.
+
 import re
 import gdb
 import sys
@@ -151,11 +154,9 @@ def frame_prev(framelink):
 
 # Const {{{
 
-LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
-
-LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
-
-LJ_FR2 = LJ_GC64
+LJ_64 = None
+LJ_GC64 = None
+LJ_FR2 = None
 
 LJ_GCVMASK = ((1 << 47) - 1)
 
@@ -460,7 +461,14 @@ def dump_gc(g):
 
     return '\n'.join(map(lambda s: '\t' + s, stats))
 
-class LJDumpArch(gdb.Command):
+
+class LJBase(gdb.Command):
+
+    def __init__(self, name):
+        super(__class__, self).__init__(name, gdb.COMMAND_DATA)
+        gdb.write('{} command initialized\n'.format(name))
+
+class LJDumpArch(LJBase):
     '''
 lj-arch
 
@@ -469,20 +477,13 @@ compile-time flags. These values define the sizes of host and GC
 pointers respectively.
     '''
 
-    def __init__(self):
-        super(LJDumpArch, self).__init__(
-            'lj-arch', gdb.COMMAND_DATA
-        )
-
     def invoke(self, arg, from_tty):
         gdb.write('LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}\n'.format(
             LJ_64 = LJ_64,
             LJ_GC64 = LJ_GC64
         ))
 
-LJDumpArch()
-
-class LJDumpTValue(gdb.Command):
+class LJDumpTValue(LJBase):
     '''
 lj-tv <TValue *>
 
@@ -511,18 +512,11 @@ Whether the type of the given address differs from the listed above, then
 error message occurs.
     '''
 
-    def __init__(self):
-        super(LJDumpTValue, self).__init__(
-            'lj-tv', gdb.COMMAND_DATA
-        )
-
     def invoke(self, arg, from_tty):
         tv = cast('TValue *', parse_arg(arg))
         gdb.write('{}\n'.format(dump_tvalue(tv)))
 
-LJDumpTValue()
-
-class LJDumpString(gdb.Command):
+class LJDumpString(LJBase):
     '''
 lj-str <GCstr *>
 
@@ -533,11 +527,6 @@ the payload, size in bytes and hash.
 is replaced with the corresponding error when decoding fails.
     '''
 
-    def __init__(self):
-        super(LJDumpString, self).__init__(
-            'lj-str', gdb.COMMAND_DATA
-        )
-
     def invoke(self, arg, from_tty):
         string = cast('GCstr *', parse_arg(arg))
         gdb.write("String: {body} [{len} bytes] with hash {hash}\n".format(
@@ -546,10 +535,7 @@ is replaced with the corresponding error when decoding fails.
             len = string['len'],
         ))
 
-
-LJDumpString()
-
-class LJDumpTable(gdb.Command):
+class LJDumpTable(LJBase):
     '''
 lj-tab <GCtab *>
 
@@ -561,10 +547,6 @@ The command recieves a GCtab adress and dumps the table contents:
   <hnode ptr>: { <tv> } => { <tv> }; next = <next hnode ptr>
     '''
 
-    def __init__(self):
-        super(LJDumpTable, self).__init__(
-            'lj-tab', gdb.COMMAND_DATA)
-
     def invoke(self, arg, from_tty):
         t = cast('GCtab *', parse_arg(arg))
         array = mref('TValue *', t['array'])
@@ -598,9 +580,7 @@ The command recieves a GCtab adress and dumps the table contents:
                 n = mref('struct Node *', node['next'])
             ))
 
-LJDumpTable()
-
-class LJDumpStack(gdb.Command):
+class LJDumpStack(LJBase):
     '''
 lj-stack [<lua_State *>]
 
@@ -633,16 +613,10 @@ coroutine guest stack:
 If L is ommited the main coroutine is used.
     '''
 
-    def __init__(self):
-        super(LJDumpStack, self).__init__(
-            'lj-stack', gdb.COMMAND_DATA)
-
     def invoke(self, arg, from_tty):
         gdb.write('{}\n'.format(dump_stack(L(parse_arg(arg)))))
 
-LJDumpStack()
-
-class LJState(gdb.Command):
+class LJState(LJBase):
     '''
 lj-state
 The command requires no args and dumps current VM and GC states
@@ -651,10 +625,6 @@ The command requires no args and dumps current VM and GC states
 * JIT state: <IDLE|ACTIVE|RECORD|START|END|ASM|ERR>
     '''
 
-    def __init__(self):
-        super(LJState, self).__init__(
-            'lj-state', gdb.COMMAND_DATA)
-
     def invoke(self, arg, from_tty):
         g = G(L(None))
         gdb.write('{}\n'.format('\n'.join(
@@ -665,9 +635,7 @@ The command requires no args and dumps current VM and GC states
             }.items())
         )))
 
-LJState()
-
-class LJGC(gdb.Command):
+class LJGC(LJBase):
     '''
 lj-gc
 
@@ -685,10 +653,6 @@ The command requires no args and dumps current GC stats:
 * 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(
@@ -696,4 +660,28 @@ The command requires no args and dumps current GC stats:
             stats = dump_gc(g)
         ))
 
-LJGC()
+def load(commands):
+    global LJ_64, LJ_GC64, LJ_FR2
+
+    try:
+        LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
+        LJ_FR2 = LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
+    except:
+        gdb.write('luajit-gdb.py failed to load: '
+                  'no debugging symbols found for libluajit\n')
+        return
+
+    for name, command in commands.items():
+        command(name)
+
+    gdb.write('luajit-gdb.py is successfully loaded\n')
+
+load({
+    'lj-arch': LJDumpArch,
+    'lj-tv': LJDumpTValue,
+    'lj-str': LJDumpString,
+    'lj-tab': LJDumpTable,
+    'lj-stack': LJDumpStack,
+    'lj-state': LJState,
+    'lj-gc': LJGC,
+})
-- 
2.24.0

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 3/3] gdb: enhance the extension loading
  2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 3/3] gdb: enhance the extension loading Igor Munkin
@ 2020-02-13 11:48   ` Igor Munkin
  2020-02-13 12:24     ` Igor Munkin
  0 siblings, 1 reply; 23+ messages in thread
From: Igor Munkin @ 2020-02-13 11:48 UTC (permalink / raw)
  To: tarantool-patches

Damn the old Python:

| (gdb) source ~/luajit-gdb.py
| Traceback (most recent call last):
|   File "~/luajit-gdb.py", line 686, in <module>
|   File "~/luajit-gdb.py", line 675, in load
|   File "~/luajit-gdb.py", line 468, in __init__
| NameError: global name '__class__' is not defined
| (gdb) python
| >import sys
| >print(sys.version)
| >2.7.5 (default, Aug  7 2019, 00:51:29) 
| [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

I'll fix the issue and send either diff or a new patch for this one in a
jiffy.

On 05.02.20, Igor Munkin wrote:
> Definition of LJ_64, LJ_GC64 and LJ_FR2 constants in script requires
> LuaJIT internal enum values to be presented in the executable. Otherwise
> the extension loading fails. The changes handles exception for the
> following scenarios:
> * luajit-gdb.py is loaded for an arbitrary executable, e.g. /bin/echo
> * luajit-gdb.py is loaded for a luajit executable (or the one linked
>   against libluajit) but debug info is not found
> 
> Signed-off-by: Igor Munkin <imun@tarantool.org>
> ---
>  src/luajit-gdb.py | 102 ++++++++++++++++++++--------------------------
>  1 file changed, 45 insertions(+), 57 deletions(-)
> 
> diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
> index 90df101..bc37674 100644
> --- a/src/luajit-gdb.py
> +++ b/src/luajit-gdb.py
> @@ -1,3 +1,6 @@
> +# GDB extension for LuaJIT post-mortem analysis.
> +# To use, just put 'source <path-to-repo>/src/luajit-gdb.py' in gdb.
> +
>  import re
>  import gdb
>  import sys
> @@ -151,11 +154,9 @@ def frame_prev(framelink):
>  
>  # Const {{{
>  
> -LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
> -
> -LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
> -
> -LJ_FR2 = LJ_GC64
> +LJ_64 = None
> +LJ_GC64 = None
> +LJ_FR2 = None
>  
>  LJ_GCVMASK = ((1 << 47) - 1)
>  
> @@ -460,7 +461,14 @@ def dump_gc(g):
>  
>      return '\n'.join(map(lambda s: '\t' + s, stats))
>  
> -class LJDumpArch(gdb.Command):
> +
> +class LJBase(gdb.Command):
> +
> +    def __init__(self, name):
> +        super(__class__, self).__init__(name, gdb.COMMAND_DATA)
> +        gdb.write('{} command initialized\n'.format(name))
> +
> +class LJDumpArch(LJBase):
>      '''
>  lj-arch
>  
> @@ -469,20 +477,13 @@ compile-time flags. These values define the sizes of host and GC
>  pointers respectively.
>      '''
>  
> -    def __init__(self):
> -        super(LJDumpArch, self).__init__(
> -            'lj-arch', gdb.COMMAND_DATA
> -        )
> -
>      def invoke(self, arg, from_tty):
>          gdb.write('LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}\n'.format(
>              LJ_64 = LJ_64,
>              LJ_GC64 = LJ_GC64
>          ))
>  
> -LJDumpArch()
> -
> -class LJDumpTValue(gdb.Command):
> +class LJDumpTValue(LJBase):
>      '''
>  lj-tv <TValue *>
>  
> @@ -511,18 +512,11 @@ Whether the type of the given address differs from the listed above, then
>  error message occurs.
>      '''
>  
> -    def __init__(self):
> -        super(LJDumpTValue, self).__init__(
> -            'lj-tv', gdb.COMMAND_DATA
> -        )
> -
>      def invoke(self, arg, from_tty):
>          tv = cast('TValue *', parse_arg(arg))
>          gdb.write('{}\n'.format(dump_tvalue(tv)))
>  
> -LJDumpTValue()
> -
> -class LJDumpString(gdb.Command):
> +class LJDumpString(LJBase):
>      '''
>  lj-str <GCstr *>
>  
> @@ -533,11 +527,6 @@ the payload, size in bytes and hash.
>  is replaced with the corresponding error when decoding fails.
>      '''
>  
> -    def __init__(self):
> -        super(LJDumpString, self).__init__(
> -            'lj-str', gdb.COMMAND_DATA
> -        )
> -
>      def invoke(self, arg, from_tty):
>          string = cast('GCstr *', parse_arg(arg))
>          gdb.write("String: {body} [{len} bytes] with hash {hash}\n".format(
> @@ -546,10 +535,7 @@ is replaced with the corresponding error when decoding fails.
>              len = string['len'],
>          ))
>  
> -
> -LJDumpString()
> -
> -class LJDumpTable(gdb.Command):
> +class LJDumpTable(LJBase):
>      '''
>  lj-tab <GCtab *>
>  
> @@ -561,10 +547,6 @@ The command recieves a GCtab adress and dumps the table contents:
>    <hnode ptr>: { <tv> } => { <tv> }; next = <next hnode ptr>
>      '''
>  
> -    def __init__(self):
> -        super(LJDumpTable, self).__init__(
> -            'lj-tab', gdb.COMMAND_DATA)
> -
>      def invoke(self, arg, from_tty):
>          t = cast('GCtab *', parse_arg(arg))
>          array = mref('TValue *', t['array'])
> @@ -598,9 +580,7 @@ The command recieves a GCtab adress and dumps the table contents:
>                  n = mref('struct Node *', node['next'])
>              ))
>  
> -LJDumpTable()
> -
> -class LJDumpStack(gdb.Command):
> +class LJDumpStack(LJBase):
>      '''
>  lj-stack [<lua_State *>]
>  
> @@ -633,16 +613,10 @@ coroutine guest stack:
>  If L is ommited the main coroutine is used.
>      '''
>  
> -    def __init__(self):
> -        super(LJDumpStack, self).__init__(
> -            'lj-stack', gdb.COMMAND_DATA)
> -
>      def invoke(self, arg, from_tty):
>          gdb.write('{}\n'.format(dump_stack(L(parse_arg(arg)))))
>  
> -LJDumpStack()
> -
> -class LJState(gdb.Command):
> +class LJState(LJBase):
>      '''
>  lj-state
>  The command requires no args and dumps current VM and GC states
> @@ -651,10 +625,6 @@ The command requires no args and dumps current VM and GC states
>  * JIT state: <IDLE|ACTIVE|RECORD|START|END|ASM|ERR>
>      '''
>  
> -    def __init__(self):
> -        super(LJState, self).__init__(
> -            'lj-state', gdb.COMMAND_DATA)
> -
>      def invoke(self, arg, from_tty):
>          g = G(L(None))
>          gdb.write('{}\n'.format('\n'.join(
> @@ -665,9 +635,7 @@ The command requires no args and dumps current VM and GC states
>              }.items())
>          )))
>  
> -LJState()
> -
> -class LJGC(gdb.Command):
> +class LJGC(LJBase):
>      '''
>  lj-gc
>  
> @@ -685,10 +653,6 @@ The command requires no args and dumps current GC stats:
>  * 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(
> @@ -696,4 +660,28 @@ The command requires no args and dumps current GC stats:
>              stats = dump_gc(g)
>          ))
>  
> -LJGC()
> +def load(commands):
> +    global LJ_64, LJ_GC64, LJ_FR2
> +
> +    try:
> +        LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
> +        LJ_FR2 = LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
> +    except:
> +        gdb.write('luajit-gdb.py failed to load: '
> +                  'no debugging symbols found for libluajit\n')
> +        return
> +
> +    for name, command in commands.items():
> +        command(name)
> +
> +    gdb.write('luajit-gdb.py is successfully loaded\n')
> +
> +load({
> +    'lj-arch': LJDumpArch,
> +    'lj-tv': LJDumpTValue,
> +    'lj-str': LJDumpString,
> +    'lj-tab': LJDumpTable,
> +    'lj-stack': LJDumpStack,
> +    'lj-state': LJState,
> +    'lj-gc': LJGC,
> +})
> -- 
> 2.24.0
> 

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 3/3] gdb: enhance the extension loading
  2020-02-13 11:48   ` Igor Munkin
@ 2020-02-13 12:24     ` Igor Munkin
  0 siblings, 0 replies; 23+ messages in thread
From: Igor Munkin @ 2020-02-13 12:24 UTC (permalink / raw)
  To: tarantool-patches

Fixed, squashed, force-pushed to the branch. Diff is below:

================================================================================

diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
index bc37674..cccec1c 100644
--- a/src/luajit-gdb.py
+++ b/src/luajit-gdb.py
@@ -465,7 +465,9 @@ def dump_gc(g):
 class LJBase(gdb.Command):
 
     def __init__(self, name):
-        super(__class__, self).__init__(name, gdb.COMMAND_DATA)
+        # XXX Fragile: though the command initialization looks like a crap but
+        # it respects both Python 2 and Python 3.
+        gdb.Command.__init__(self, name, gdb.COMMAND_DATA)
         gdb.write('{} command initialized\n'.format(name))
 
 class LJDumpArch(LJBase):

================================================================================

On 13.02.20, Igor Munkin wrote:
> Damn the old Python:
> 
> | (gdb) source ~/luajit-gdb.py
> | Traceback (most recent call last):
> |   File "~/luajit-gdb.py", line 686, in <module>
> |   File "~/luajit-gdb.py", line 675, in load
> |   File "~/luajit-gdb.py", line 468, in __init__
> | NameError: global name '__class__' is not defined
> | (gdb) python
> | >import sys
> | >print(sys.version)
> | >2.7.5 (default, Aug  7 2019, 00:51:29) 
> | [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
> 
> I'll fix the issue and send either diff or a new patch for this one in a
> jiffy.
> 
> On 05.02.20, Igor Munkin wrote:
> > Definition of LJ_64, LJ_GC64 and LJ_FR2 constants in script requires
> > LuaJIT internal enum values to be presented in the executable. Otherwise
> > the extension loading fails. The changes handles exception for the
> > following scenarios:
> > * luajit-gdb.py is loaded for an arbitrary executable, e.g. /bin/echo
> > * luajit-gdb.py is loaded for a luajit executable (or the one linked
> >   against libluajit) but debug info is not found
> > 
> > Signed-off-by: Igor Munkin <imun@tarantool.org>
> > ---
> >  src/luajit-gdb.py | 102 ++++++++++++++++++++--------------------------
> >  1 file changed, 45 insertions(+), 57 deletions(-)
> > 
> > diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
> > index 90df101..bc37674 100644
> > --- a/src/luajit-gdb.py
> > +++ b/src/luajit-gdb.py
> > @@ -1,3 +1,6 @@
> > +# GDB extension for LuaJIT post-mortem analysis.
> > +# To use, just put 'source <path-to-repo>/src/luajit-gdb.py' in gdb.
> > +
> >  import re
> >  import gdb
> >  import sys
> > @@ -151,11 +154,9 @@ def frame_prev(framelink):
> >  
> >  # Const {{{
> >  
> > -LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
> > -
> > -LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
> > -
> > -LJ_FR2 = LJ_GC64
> > +LJ_64 = None
> > +LJ_GC64 = None
> > +LJ_FR2 = None
> >  
> >  LJ_GCVMASK = ((1 << 47) - 1)
> >  
> > @@ -460,7 +461,14 @@ def dump_gc(g):
> >  
> >      return '\n'.join(map(lambda s: '\t' + s, stats))
> >  
> > -class LJDumpArch(gdb.Command):
> > +
> > +class LJBase(gdb.Command):
> > +
> > +    def __init__(self, name):
> > +        super(__class__, self).__init__(name, gdb.COMMAND_DATA)
> > +        gdb.write('{} command initialized\n'.format(name))
> > +
> > +class LJDumpArch(LJBase):
> >      '''
> >  lj-arch
> >  
> > @@ -469,20 +477,13 @@ compile-time flags. These values define the sizes of host and GC
> >  pointers respectively.
> >      '''
> >  
> > -    def __init__(self):
> > -        super(LJDumpArch, self).__init__(
> > -            'lj-arch', gdb.COMMAND_DATA
> > -        )
> > -
> >      def invoke(self, arg, from_tty):
> >          gdb.write('LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}\n'.format(
> >              LJ_64 = LJ_64,
> >              LJ_GC64 = LJ_GC64
> >          ))
> >  
> > -LJDumpArch()
> > -
> > -class LJDumpTValue(gdb.Command):
> > +class LJDumpTValue(LJBase):
> >      '''
> >  lj-tv <TValue *>
> >  
> > @@ -511,18 +512,11 @@ Whether the type of the given address differs from the listed above, then
> >  error message occurs.
> >      '''
> >  
> > -    def __init__(self):
> > -        super(LJDumpTValue, self).__init__(
> > -            'lj-tv', gdb.COMMAND_DATA
> > -        )
> > -
> >      def invoke(self, arg, from_tty):
> >          tv = cast('TValue *', parse_arg(arg))
> >          gdb.write('{}\n'.format(dump_tvalue(tv)))
> >  
> > -LJDumpTValue()
> > -
> > -class LJDumpString(gdb.Command):
> > +class LJDumpString(LJBase):
> >      '''
> >  lj-str <GCstr *>
> >  
> > @@ -533,11 +527,6 @@ the payload, size in bytes and hash.
> >  is replaced with the corresponding error when decoding fails.
> >      '''
> >  
> > -    def __init__(self):
> > -        super(LJDumpString, self).__init__(
> > -            'lj-str', gdb.COMMAND_DATA
> > -        )
> > -
> >      def invoke(self, arg, from_tty):
> >          string = cast('GCstr *', parse_arg(arg))
> >          gdb.write("String: {body} [{len} bytes] with hash {hash}\n".format(
> > @@ -546,10 +535,7 @@ is replaced with the corresponding error when decoding fails.
> >              len = string['len'],
> >          ))
> >  
> > -
> > -LJDumpString()
> > -
> > -class LJDumpTable(gdb.Command):
> > +class LJDumpTable(LJBase):
> >      '''
> >  lj-tab <GCtab *>
> >  
> > @@ -561,10 +547,6 @@ The command recieves a GCtab adress and dumps the table contents:
> >    <hnode ptr>: { <tv> } => { <tv> }; next = <next hnode ptr>
> >      '''
> >  
> > -    def __init__(self):
> > -        super(LJDumpTable, self).__init__(
> > -            'lj-tab', gdb.COMMAND_DATA)
> > -
> >      def invoke(self, arg, from_tty):
> >          t = cast('GCtab *', parse_arg(arg))
> >          array = mref('TValue *', t['array'])
> > @@ -598,9 +580,7 @@ The command recieves a GCtab adress and dumps the table contents:
> >                  n = mref('struct Node *', node['next'])
> >              ))
> >  
> > -LJDumpTable()
> > -
> > -class LJDumpStack(gdb.Command):
> > +class LJDumpStack(LJBase):
> >      '''
> >  lj-stack [<lua_State *>]
> >  
> > @@ -633,16 +613,10 @@ coroutine guest stack:
> >  If L is ommited the main coroutine is used.
> >      '''
> >  
> > -    def __init__(self):
> > -        super(LJDumpStack, self).__init__(
> > -            'lj-stack', gdb.COMMAND_DATA)
> > -
> >      def invoke(self, arg, from_tty):
> >          gdb.write('{}\n'.format(dump_stack(L(parse_arg(arg)))))
> >  
> > -LJDumpStack()
> > -
> > -class LJState(gdb.Command):
> > +class LJState(LJBase):
> >      '''
> >  lj-state
> >  The command requires no args and dumps current VM and GC states
> > @@ -651,10 +625,6 @@ The command requires no args and dumps current VM and GC states
> >  * JIT state: <IDLE|ACTIVE|RECORD|START|END|ASM|ERR>
> >      '''
> >  
> > -    def __init__(self):
> > -        super(LJState, self).__init__(
> > -            'lj-state', gdb.COMMAND_DATA)
> > -
> >      def invoke(self, arg, from_tty):
> >          g = G(L(None))
> >          gdb.write('{}\n'.format('\n'.join(
> > @@ -665,9 +635,7 @@ The command requires no args and dumps current VM and GC states
> >              }.items())
> >          )))
> >  
> > -LJState()
> > -
> > -class LJGC(gdb.Command):
> > +class LJGC(LJBase):
> >      '''
> >  lj-gc
> >  
> > @@ -685,10 +653,6 @@ The command requires no args and dumps current GC stats:
> >  * 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(
> > @@ -696,4 +660,28 @@ The command requires no args and dumps current GC stats:
> >              stats = dump_gc(g)
> >          ))
> >  
> > -LJGC()
> > +def load(commands):
> > +    global LJ_64, LJ_GC64, LJ_FR2
> > +
> > +    try:
> > +        LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
> > +        LJ_FR2 = LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
> > +    except:
> > +        gdb.write('luajit-gdb.py failed to load: '
> > +                  'no debugging symbols found for libluajit\n')
> > +        return
> > +
> > +    for name, command in commands.items():
> > +        command(name)
> > +
> > +    gdb.write('luajit-gdb.py is successfully loaded\n')
> > +
> > +load({
> > +    'lj-arch': LJDumpArch,
> > +    'lj-tv': LJDumpTValue,
> > +    'lj-str': LJDumpString,
> > +    'lj-tab': LJDumpTable,
> > +    'lj-stack': LJDumpStack,
> > +    'lj-state': LJState,
> > +    'lj-gc': LJGC,
> > +})
> > -- 
> > 2.24.0
> > 
> 
> -- 
> Best regards,
> IM

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 1/3] gdb: introduce luajit-gdb extension
  2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 1/3] gdb: introduce luajit-gdb extension Igor Munkin
@ 2020-02-13 13:24   ` Igor Munkin
  0 siblings, 0 replies; 23+ messages in thread
From: Igor Munkin @ 2020-02-13 13:24 UTC (permalink / raw)
  To: tarantool-patches

One more bug found while using the gdb built against Python 2.x. VM
state field is 32-bit length but was treated as 64-bit one. This led to
the following invalid output:

| (gdb) source ~/luajit-gdb.py 
| lj-gc command initialized
| lj-tab command initialized
| lj-str command initialized
| lj-state command initialized
| lj-stack command initialized
| lj-tv command initialized
| lj-arch command initialized
| luajit-gdb.py is successfully loaded
| (gdb) lj-state
| JIT state: IDLE
| GC state: PAUSE
| VM state: TRACE
| (gdb) p ((global_State *)L->glref.ptr32)->vmstate
| $1 = -1   # <-- VM state is INTERP and not valid traceno

Fixed, squashed, force-pushed to the branch. Diff is below:

================================================================================

diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
index cccec1c..b6b3212 100644
--- a/src/luajit-gdb.py
+++ b/src/luajit-gdb.py
@@ -52,9 +52,6 @@ def tou64(val):
 def tou32(val):
     return cast('uint32_t', val) & 0xFFFFFFFF
 
-def i2notu64(val):
-    return ~int(val) & 0xFFFFFFFFFFFFFFFF
-
 def i2notu32(val):
     return ~int(val) & 0xFFFFFFFF
 
@@ -205,14 +202,14 @@ def J(g):
 
 def vm_state(g):
     return {
-        i2notu64(0): 'INTERP',
-        i2notu64(1): 'C',
-        i2notu64(2): 'GC',
-        i2notu64(3): 'EXIT',
-        i2notu64(4): 'RECORD',
-        i2notu64(5): 'OPT',
-        i2notu64(6): 'ASM',
-    }.get(int(tou64(g['vmstate'])), 'TRACE')
+        i2notu32(0): 'INTERP',
+        i2notu32(1): 'C',
+        i2notu32(2): 'GC',
+        i2notu32(3): 'EXIT',
+        i2notu32(4): 'RECORD',
+        i2notu32(5): 'OPT',
+        i2notu32(6): 'ASM',
+    }.get(int(tou32(g['vmstate'])), 'TRACE')
 
 def gc_state(g):
     return {

===============================================================================

On 05.02.20, Igor Munkin wrote:
> The provided luajit-gdb extenstion contains the following additional commands:
> * lj-arch -- dumps values of LJ_64 and LJ_GC64 macro definitions
> * lj-tv -- dumps the type and some GCobj info related to the given TValue
> * lj-str -- dumps the contents of the given GCstr
> * lj-tab -- dumps the contents of the given GCtab
> * lj-stack -- dumps Lua stack of the given lua_State
> * lj-state -- shows current VM, GC and JIT states
> * lj-gc -- shows current GC stats
> 
> Currently extension supports only x64 builds but respects LJ_GC64 value
> 
> Signed-off-by: Igor Munkin <imun@tarantool.org>
> ---
>  src/luajit-gdb.py | 684 ++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 684 insertions(+)
>  create mode 100644 src/luajit-gdb.py
> 
> diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
> new file mode 100644
> index 0000000..77da5e6
> --- /dev/null
> +++ b/src/luajit-gdb.py
> @@ -0,0 +1,684 @@
> +import re
> +import gdb
> +
> +gtype_cache = {}
> +
> +def gtype(typestr):
> +    global gtype_cache
> +    if typestr in gtype_cache:
> +        return gtype_cache[typestr]
> +
> +    m = re.match(r'((?:(?:struct|union) )?\S*)\s*[*]', typestr)
> +
> +    gtype = gdb.lookup_type(typestr) if m is None \
> +        else gdb.lookup_type(m.group(1)).pointer()
> +
> +    gtype_cache[typestr] = gtype
> +    return gtype
> +
> +def cast(typestr, val):
> +    return gdb.Value(val).cast(gtype(typestr))
> +
> +def lookup(symbol):
> +    variable, _ = gdb.lookup_symbol(symbol)
> +    return variable.value() if variable else None
> +
> +def parse_arg(arg):
> +    if not arg:
> +        return None
> +
> +    ret = gdb.parse_and_eval(arg)
> +
> +    if not ret:
> +        raise gdb.GdbError('table argument empty')
> +
> +    return ret
> +
> +def tou64(val):
> +    return cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF
> +
> +def tou32(val):
> +    return cast('uint32_t', val) & 0xFFFFFFFF
> +
> +def i2notu64(val):
> +    return ~int(val) & 0xFFFFFFFFFFFFFFFF
> +
> +def i2notu32(val):
> +    return ~int(val) & 0xFFFFFFFF
> +
> +def strx64(val):
> +    return hex(cast('uint64_t', val) & 0xFFFFFFFFFFFFFFFF)
> +
> +# Types {{{
> +
> +LJ_T = {
> +    'NIL'     : i2notu32(0),
> +    'FALSE'   : i2notu32(1),
> +    'TRUE'    : i2notu32(2),
> +    'LIGHTUD' : i2notu32(3),
> +    'STR'     : i2notu32(4),
> +    'UPVAL'   : i2notu32(5),
> +    'THREAD'  : i2notu32(6),
> +    'PROTO'   : i2notu32(7),
> +    'FUNC'    : i2notu32(8),
> +    'TRACE'   : i2notu32(9),
> +    'CDATA'   : i2notu32(10),
> +    'TAB'     : i2notu32(11),
> +    'UDATA'   : i2notu32(12),
> +    'NUMX'    : i2notu32(13),
> +}
> +
> +def typenames(value):
> +    return {
> +        LJ_T[k]: 'LJ_T' + k for k in LJ_T.keys()
> +    }.get(int(value), 'LJ_TINVALID')
> +
> +# }}}
> +
> +# Frames {{{
> +
> +FRAME_TYPE = 0x3
> +FRAME_P = 0x4
> +FRAME_TYPEP = FRAME_TYPE | FRAME_P
> +
> +FRAME = {
> +    'LUA': 0x0,
> +    'C': 0x1,
> +    'CONT': 0x2,
> +    'VARG': 0x3,
> +    'LUAP': 0x4,
> +    'CP': 0x5,
> +    'PCALL': 0x6,
> +    'PCALLH': 0x7,
> +}
> +
> +def frametypes(ft):
> +    return {
> +        FRAME['LUA']  : 'L',
> +        FRAME['C']    : 'C',
> +        FRAME['CONT'] : 'M',
> +        FRAME['VARG'] : 'V',
> +    }.get(ft, '?')
> +
> +def bc_a(ins):
> +    return (ins >> 8) & 0xff
> +
> +def frame_ftsz(framelink):
> +    return cast('ptrdiff_t', framelink['ftsz'] if LJ_FR2 \
> +        else framelink['fr']['tp']['ftsz'])
> +
> +def frame_pc(framelink):
> +    return cast('BCIns *', frame_ftsz(framelink)) if LJ_FR2 \
> +        else mref('BCIns *', framelink['fr']['tp']['pcr'])
> +
> +def frame_prevl(framelink):
> +    return framelink - (1 + LJ_FR2 + bc_a(frame_pc(framelink)[-1]))
> +
> +def frame_ispcall(framelink):
> +    return (frame_ftsz(framelink) & FRAME['PCALL']) == FRAME['PCALL']
> +
> +def frame_sized(framelink):
> +    return (frame_ftsz(framelink) & ~FRAME_TYPEP)
> +
> +def frame_prevd(framelink):
> +    return cast('TValue *', cast('char *', framelink) - frame_sized(framelink))
> +
> +def frame_type(framelink):
> +    return frame_ftsz(framelink) & FRAME_TYPE
> +
> +def frame_typep(framelink):
> +    return frame_ftsz(framelink) & FRAME_TYPEP
> +
> +def frame_islua(framelink):
> +    return frametypes(int(frame_type(framelink))) == 'L' \
> +        and int(frame_ftsz(framelink)) > 0
> +
> +def frame_prev(framelink):
> +    return frame_prevl(framelink) if frame_islua(framelink) \
> +        else frame_prevd(framelink)
> +
> +# }}}
> +
> +# Const {{{
> +
> +LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
> +
> +LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
> +
> +LJ_FR2 = LJ_GC64
> +
> +LJ_GCVMASK = ((1 << 47) - 1)
> +
> +PADDING = ' ' * len(':' + hex((1 << (47 if LJ_GC64 else 32)) - 1))
> +
> +# }}}
> +
> +def itype(o):
> +    return cast('uint32_t', o['it64'] >> 47) if LJ_GC64 else o['it']
> +
> +def mref(typename, obj):
> +    return cast(typename, obj['ptr64'] if LJ_GC64 else obj['ptr32'])
> +
> +def gcref(obj):
> +    return cast('GCobj *', obj['gcptr64'] if LJ_GC64
> +        else cast('uintptr_t', obj['gcptr32']))
> +
> +def gcval(obj):
> +    return cast('GCobj *', obj['gcptr64'] & LJ_GCVMASK if LJ_GC64
> +        else cast('uintptr_t', obj['gcptr32']))
> +
> +def 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(g):
> +    typeGG = gtype('GG_State')
> +
> +    return cast('jit_State *', int(cast('char *', g))
> +        - int(typeGG['g'].bitpos / 8)
> +        + int(typeGG['J'].bitpos / 8)
> +    )
> +
> +def vm_state(g):
> +    return {
> +        i2notu64(0): 'INTERP',
> +        i2notu64(1): 'C',
> +        i2notu64(2): 'GC',
> +        i2notu64(3): 'EXIT',
> +        i2notu64(4): 'RECORD',
> +        i2notu64(5): 'OPT',
> +        i2notu64(6): 'ASM',
> +    }.get(int(tou64(g['vmstate'])), 'TRACE')
> +
> +def gc_state(g):
> +    return {
> +        0: 'PAUSE',
> +        1: 'PROPAGATE',
> +        2: 'ATOMIC',
> +        3: 'SWEEPSTRING',
> +        4: 'SWEEP',
> +        5: 'FINALIZE',
> +        6: 'LAST',
> +    }.get(int(g['gc']['state']), 'INVALID')
> +
> +def jit_state(g):
> +    return {
> +        0:    'IDLE',
> +        0x10: 'ACTIVE',
> +        0x11: 'RECORD',
> +        0x12: 'START',
> +        0x13: 'END',
> +        0x14: 'ASM',
> +        0x15: 'ERR',
> +    }.get(int(J(g)['state']), 'INVALID')
> +
> +def tvisnumber(o):
> +    return itype(o) <= (0xfffeffff if LJ_64 and not LJ_GC64 else LJ_T['NUMX'])
> +
> +def tvislightud(o):
> +    if LJ_64 and not LJ_GC64:
> +        return (cast('int32_t', itype(o)) >> 15) == -2
> +    else:
> +        return itype(o) == LJ_T['LIGHTUD']
> +
> +def strdata(obj):
> +    # String is printed with pointer to it, thanks to gdb. Just strip it.
> +    return str(cast('char *', cast('GCstr *', obj) + 1))[len(PADDING):]
> +
> +def itypemap(o):
> +    if LJ_64 and not LJ_GC64:
> +        return LJ_T['NUMX'] if tvisnumber(o)       \
> +            else LJ_T['LIGHTUD'] if tvislightud(o) \
> +            else itype(o)
> +    else:
> +        return LJ_T['NUMX'] if tvisnumber(o) else itype(o)
> +
> +def funcproto(func):
> +    assert(func['ffid'] == 0)
> +
> +    return cast('GCproto *',
> +        mref('char *', func['pc']) - gdb.lookup_type('GCproto').sizeof)
> +
> +def gclistlen(root):
> +    count = 0
> +    while(gcref(root)):
> +        count += 1
> +        root = gcref(root)['gch']['nextgc']
> +    return count
> +
> +# 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(gcval(tv['gcr'])))
> +
> +def dump_lj_tstr(tv):
> +    return 'string {body} @ {address}'.format(
> +        body = strdata(gcval(tv['gcr'])),
> +        address = strx64(gcval(tv['gcr']))
> +    )
> +
> +def dump_lj_tupval(tv):
> +    return 'upvalue @ {}'.format(strx64(gcval(tv['gcr'])))
> +
> +def dump_lj_tthread(tv):
> +    return 'thread @ {}'.format(strx64(gcval(tv['gcr'])))
> +
> +def dump_lj_tproto(tv):
> +    return 'proto @ {}'.format(strx64(gcval(tv['gcr'])))
> +
> +def dump_lj_tfunc(tv):
> +    func = cast('struct GCfuncC *', gcval(tv['gcr']))
> +    ffid = func['ffid']
> +
> +    if ffid == 0:
> +        pt = funcproto(func)
> +        return 'Lua function @ {addr}, {nupvals} upvalues, {chunk}:{line}'.format(
> +            addr = strx64(func),
> +            nupvals = int(func['nupvalues']),
> +            chunk = strdata(cast('GCstr *', gcval(pt['chunkname']))),
> +            line = pt['firstline']
> +        )
> +    elif ffid == 1:
> +        return 'C function @ {}'.format(strx64(func['f']))
> +    else:
> +        return 'fast function #{}'.format(int(ffid))
> +
> +def dump_lj_ttrace(tv):
> +    trace = cast('struct GCtrace *', gcval(tv['gcr']))
> +    return 'trace {traceno} @ {addr}'.format(
> +        traceno = strx64(trace['traceno']),
> +        addr = strx64(trace)
> +    )
> +
> +def dump_lj_tcdata(tv):
> +    return 'cdata @ {}'.format(strx64(gcval(tv['gcr'])))
> +
> +def dump_lj_ttab(tv):
> +    table = cast('GCtab *', gcval(tv['gcr']))
> +    return 'table @ {gcr} (asize: {asize}, hmask: {hmask})'.format(
> +        gcr = strx64(table),
> +        asize = table['asize'],
> +        hmask = strx64(table['hmask']),
> +    )
> +
> +def dump_lj_tudata(tv):
> +    return 'userdata @ {}'.format(strx64(gcval(tv['gcr'])))
> +
> +def dump_lj_tnumx(tv):
> +    return 'number {}'.format(cast('double', tv['n']))
> +
> +def dump_lj_invalid(tv):
> +    return 'not valid type @ {}'.format(strx64(gcval(tv['gcr'])))
> +
> +# }}}
> +
> +dumpers = {
> +    'LJ_TNIL': dump_lj_tnil,
> +    'LJ_TFALSE': dump_lj_tfalse,
> +    'LJ_TTRUE': dump_lj_ttrue,
> +    'LJ_TLIGHTUD': dump_lj_tlightud,
> +    'LJ_TSTR': dump_lj_tstr,
> +    'LJ_TUPVAL': dump_lj_tupval,
> +    'LJ_TTHREAD': dump_lj_tthread,
> +    'LJ_TPROTO': dump_lj_tproto,
> +    'LJ_TFUNC': dump_lj_tfunc,
> +    'LJ_TTRACE': dump_lj_ttrace,
> +    'LJ_TCDATA': dump_lj_tcdata,
> +    'LJ_TTAB': dump_lj_ttab,
> +    'LJ_TUDATA': dump_lj_tudata,
> +    'LJ_TNUMX': dump_lj_tnumx,
> +}
> +
> +def dump_tvalue(tvalue):
> +    return dumpers.get(typenames(itypemap(tvalue)), dump_lj_invalid)(tvalue)
> +
> +def dump_framelink(L, fr):
> +    fr2 = fr + LJ_FR2
> +
> +    return '{fr}{padding} [    ] FRAME: [{pp}] delta={d}, {f}\n'.format(
> +        fr = fr,
> +        padding = ':{fr2}'.format(fr2 = fr2) if LJ_FR2 else PADDING,
> +        pp = 'PP' if frame_ispcall(fr2) else '{frname}{p}'.format(
> +            frname = frametypes(int(frame_type(fr2))),
> +            p = 'P' if frame_typep(fr2) & FRAME_P else ''
> +        ),
> +        d = cast('TValue *', fr2) - cast('TValue *', frame_prev(fr2)),
> +        f = dump_lj_tfunc(fr),
> +    )
> +
> +def dump_stack_slot(L, slot, base=None, top=None):
> +    base = base or L['base']
> +    top = top or L['top']
> +
> +    return '{addr}{padding} [ {B}{T}{M}] VALUE: {value}\n'.format(
> +        addr = strx64(slot),
> +        padding = PADDING,
> +        B = 'B' if slot == base else ' ',
> +        T = 'T' if slot == top else ' ',
> +        M = 'M' if slot == mref('TValue *', L['maxstack']) else ' ',
> +        value = dump_tvalue(slot),
> +    )
> +
> +def dump_stack(L, base=None, top=None):
> +    base = base or L['base']
> +    top = top or L['top']
> +    maxstack = mref('TValue *', L['maxstack'])
> +    red = 5 + 2 * LJ_FR2
> +
> +    dump = '\n'.join([
> +        '{start}:{end} [    ] {n} slots: Red zone'.format(
> +             start = strx64(maxstack + 1),
> +             end = strx64(maxstack + red),
> +             n = red,
> +        ),
> +        '{maxstack}{padding} [   M]'.format(
> +            maxstack = strx64(maxstack),
> +            padding = PADDING,
> +        ),
> +        '{start}:{end} [    ] {nfreeslots} slots: Free stack slots'.format(
> +            start = strx64(top + 1),
> +            end = strx64(maxstack - 1),
> +            nfreeslots = int((tou64(maxstack) - tou64(top) - 8) >> 3),
> +        ),
> +        '{top}{padding} [  T ]'.format(
> +            top = strx64(top),
> +            padding = PADDING,
> +        )
> +    ]) + '\n'
> +
> +    slot = top - 1
> +    framelink = base - (1 + LJ_FR2)
> +
> +    while framelink > mref('TValue *', L['stack']):
> +        while slot > framelink + LJ_FR2:
> +            dump += dump_stack_slot(L, slot, base, top)
> +            slot -= 1
> +        dump += dump_framelink(L, framelink)
> +        framelink = frame_prev(framelink + LJ_FR2) - LJ_FR2
> +        slot -= 1 + LJ_FR2
> +
> +    dump += '{fr}{padding} [S   ] FRAME: dummy L'.format(
> +        fr = slot,
> +        padding = ':{nilslot}'.format(nilslot = slot + 1) if LJ_FR2 else PADDING
> +    )
> +
> +    return dump
> +
> +def dump_gc(g):
> +    gc = g['gc']
> +    stats = [ '{key}: {value}'.format(key = f, value = gc[f]) for f in (
> +        'total', 'threshold', 'debt', 'estimate', 'stepmul', 'pause'
> +    ) ]
> +
> +    stats += [ 'sweepstr: {sweepstr}/{strmask}'.format(
> +        sweepstr = gc['sweepstr'],
> +        # String hash mask (size of hash table - 1).
> +        strmask = g['strmask'] + 1,
> +    ) ]
> +
> +    stats += [ '{key}: {number} objects'.format(
> +        key = f,
> +        number = gclistlen(gc[f]),
> +    ) for f in ('root', 'gray', 'grayagain', 'weak') ]
> +
> +    # TODO: mmudata
> +
> +    return '\n'.join(map(lambda s: '\t' + s, stats))
> +
> +class LJDumpArch(gdb.Command):
> +    '''
> +lj-arch
> +
> +The command requires no args and dumps values of LJ_64 and LJ_GC64
> +compile-time flags. These values define the sizes of host and GC
> +pointers respectively.
> +    '''
> +
> +    def __init__(self):
> +        super(LJDumpArch, self).__init__(
> +            'lj-arch', gdb.COMMAND_DATA
> +        )
> +
> +    def invoke(self, arg, from_tty):
> +        gdb.write('LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}\n'.format(
> +            LJ_64 = LJ_64,
> +            LJ_GC64 = LJ_GC64
> +        ))
> +
> +LJDumpArch()
> +
> +class LJDumpTValue(gdb.Command):
> +    '''
> +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):
> +        super(LJDumpTValue, self).__init__(
> +            'lj-tv', gdb.COMMAND_DATA
> +        )
> +
> +    def invoke(self, arg, from_tty):
> +        tv = cast('TValue *', parse_arg(arg))
> +        gdb.write('{}\n'.format(dump_tvalue(tv)))
> +
> +LJDumpTValue()
> +
> +class LJDumpString(gdb.Command):
> +    '''
> +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):
> +        super(LJDumpString, self).__init__(
> +            'lj-str', gdb.COMMAND_DATA
> +        )
> +
> +    def invoke(self, arg, from_tty):
> +        string = cast('GCstr *', parse_arg(arg))
> +        gdb.write("String: {body} [{len} bytes] with hash {hash}\n".format(
> +            body = strdata(string),
> +            hash = strx64(string['hash']),
> +            len = string['len'],
> +        ))
> +
> +
> +LJDumpString()
> +
> +class LJDumpTable(gdb.Command):
> +    '''
> +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):
> +        super(LJDumpTable, self).__init__(
> +            'lj-tab', gdb.COMMAND_DATA)
> +
> +    def invoke(self, arg, from_tty):
> +        t = cast('GCtab *', parse_arg(arg))
> +        array = mref('TValue *', t['array'])
> +        nodes = mref('struct Node *', t['node'])
> +        mt = gcval(t['metatable'])
> +        capacity = {
> +            'apart': int(t['asize']),
> +            'hpart': int(t['hmask'] + 1) if t['hmask'] > 0 else 0
> +        }
> +
> +        if mt != 0:
> +            gdb.write('Metatable detected: {}\n'.format(strx64(mt)))
> +
> +        gdb.write('Array part: {} slots\n'.format(capacity['apart']))
> +        for i in range(capacity['apart']):
> +            slot = array + i
> +            gdb.write('{ptr}: [{index}]: {value}\n'.format(
> +                ptr = slot,
> +                index = i,
> +                value = dump_tvalue(slot)
> +            ))
> +
> +        gdb.write('Hash part: {} nodes\n'.format(capacity['hpart']))
> +        # See hmask comment in lj_obj.h
> +        for i in range(capacity['hpart']):
> +            node = nodes + i
> +            gdb.write('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}\n'.format(
> +                ptr = node,
> +                key = dump_tvalue(node['key']),
> +                val= dump_tvalue(node['val']),
> +                n = mref('struct Node *', node['next'])
> +            ))
> +
> +LJDumpTable()
> +
> +class LJDumpStack(gdb.Command):
> +    '''
> +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):
> +        super(LJDumpStack, self).__init__(
> +            'lj-stack', gdb.COMMAND_DATA)
> +
> +    def invoke(self, arg, from_tty):
> +        gdb.write('{}\n'.format(dump_stack(L(parse_arg(arg)))))
> +
> +LJDumpStack()
> +
> +class LJState(gdb.Command):
> +    '''
> +lj-state
> +The command requires no args and dumps current VM and GC states
> +* VM state: <INTERP|C|GC|EXIT|RECORD|OPT|ASM|TRACE>
> +* GC state: <PAUSE|PROPAGATE|ATOMIC|SWEEPSTRING|SWEEP|FINALIZE|LAST>
> +* JIT state: <IDLE|ACTIVE|RECORD|START|END|ASM|ERR>
> +    '''
> +
> +    def __init__(self):
> +        super(LJState, self).__init__(
> +            'lj-state', gdb.COMMAND_DATA)
> +
> +    def invoke(self, arg, from_tty):
> +        g = G(L(None))
> +        gdb.write('{}\n'.format('\n'.join(
> +            map(lambda t: '{} state: {}'.format(*t), {
> +                'VM': vm_state(g),
> +                'GC': gc_state(g),
> +                'JIT': jit_state(g),
> +            }.items())
> +        )))
> +
> +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
> 

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-05 16:22 [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Igor Munkin
                   ` (2 preceding siblings ...)
  2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 3/3] gdb: enhance the extension loading Igor Munkin
@ 2020-02-26 22:41 ` Alexander Turenko
  2020-02-28 10:46   ` Igor Munkin
  2020-02-26 22:45 ` Alexander Turenko
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2020-02-26 22:41 UTC (permalink / raw)
  To: Igor Munkin; +Cc: tarantool-patches

Bugreport:

When I source the extension from .gdbinit and run gdb to inspect a core
file like so:

gdb tarantool \
    -ex "set solib-search-path $(realpath lib64):$(realpath lib64/lua/5.1):$(realpath lib64/tarantool):$(realpath lib64/tarantool/http):$(realpath lib64/tarantool/cron)" \
    -ex "add-auto-load-safe-path $(realpath .)" \
    -ex "set sysroot $(realpath .)" \
    -ex 'set substitute-path /build/usr/src/debug/tarantool-2.2.1.137 src/tarantool' \
    -ex 'core core'

I got 'luajit-gdb.py failed to load: no debugging symbols found for
libluajit' warning and the extesion is not loaded.

However if I source it afterwards it loded successfully.

I guess it is because `gdb.parse_and_eval('IRT_PTR')` and
`gdb.parse_and_eval('IRT_PGC')` needs values from runtime, which is
available only after `-ex 'core core'` command.

Possible solution: always register lj-* functions, but load those values
inside them (if it is not already done). A kind of lazy loading.

WBR, Alexander Turenko.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-05 16:22 [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Igor Munkin
                   ` (3 preceding siblings ...)
  2020-02-26 22:41 ` [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Alexander Turenko
@ 2020-02-26 22:45 ` Alexander Turenko
  2020-02-27 10:48   ` Igor Munkin
  2020-02-26 23:04 ` Alexander Turenko
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2020-02-26 22:45 UTC (permalink / raw)
  To: Igor Munkin; +Cc: tarantool-patches

Are not it worth to squash those commits? Now the series looks as
'feature + fixup + fixup' and since we don't pushed it yet to a
long-term branch is looks okay to squash fixups into the first commit.

It is up to you, I just asked.

WBR, Alexander Turenko.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-05 16:22 [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Igor Munkin
                   ` (4 preceding siblings ...)
  2020-02-26 22:45 ` Alexander Turenko
@ 2020-02-26 23:04 ` Alexander Turenko
  2020-02-27 10:13   ` Igor Munkin
  2020-02-26 23:10 ` Alexander Turenko
  2020-03-05  7:44 ` Kirill Yukhin
  7 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2020-02-26 23:04 UTC (permalink / raw)
  To: Igor Munkin; +Cc: tarantool-patches

Typo: recieve -> receive.

$ ag recieve tools/tarantool/luajit/luajit-gdb.py 
492:The command recieves a pointer to <tv> (TValue address) and dumps
525:The command recieves a <gcr> of the corresponding GCstr object and dumps
544:The command recieves a GCtab adress and dumps the table contents:
589:The command recieves a lua_State address and dumps the given Lua

WBR, Alexander Turenko.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-05 16:22 [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Igor Munkin
                   ` (5 preceding siblings ...)
  2020-02-26 23:04 ` Alexander Turenko
@ 2020-02-26 23:10 ` Alexander Turenko
  2020-02-27 10:37   ` Igor Munkin
  2020-03-05  7:44 ` Kirill Yukhin
  7 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2020-02-26 23:10 UTC (permalink / raw)
  To: Igor Munkin; +Cc: tarantool-patches

NB: It seems logical to install the script with tarantool-dev /
tarantool-devel package. Is there some place, from which gdb loads
extensions automatically? Maybe `/usr/share/gdb/python/`?

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-26 23:04 ` Alexander Turenko
@ 2020-02-27 10:13   ` Igor Munkin
  0 siblings, 0 replies; 23+ messages in thread
From: Igor Munkin @ 2020-02-27 10:13 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches

Sasha,

Thanks, I hope it was a copy-paste.

On 27.02.20, Alexander Turenko wrote:
> Typo: recieve -> receive.
> 
> $ ag recieve tools/tarantool/luajit/luajit-gdb.py 
> 492:The command recieves a pointer to <tv> (TValue address) and dumps
> 525:The command recieves a <gcr> of the corresponding GCstr object and dumps
> 544:The command recieves a GCtab adress and dumps the table contents:
> 589:The command recieves a lua_State address and dumps the given Lua
> 
> WBR, Alexander Turenko.

Fixed, squashed, force-pushed to the branch. Diff is below:

================================================================================

diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
index b6b3212..0ac0aaa 100644
--- a/src/luajit-gdb.py
+++ b/src/luajit-gdb.py
@@ -486,7 +486,7 @@ class LJDumpTValue(LJBase):
     '''
 lj-tv <TValue *>
 
-The command recieves a pointer to <tv> (TValue address) and dumps
+The command receives a pointer to <tv> (TValue address) and dumps
 the type and some info related to it.
 
 * LJ_TNIL: nil
@@ -519,7 +519,7 @@ class LJDumpString(LJBase):
     '''
 lj-str <GCstr *>
 
-The command recieves a <gcr> of the corresponding GCstr object and dumps
+The command receives a <gcr> of the corresponding GCstr object and dumps
 the payload, size in bytes and hash.
 
 *Caveat*: Since Python 2 provides no native Unicode support, the payload
@@ -538,7 +538,7 @@ class LJDumpTable(LJBase):
     '''
 lj-tab <GCtab *>
 
-The command recieves a GCtab adress and dumps the table contents:
+The command receives a GCtab adress and dumps the table contents:
 * Metatable address whether the one is set
 * Array part <asize> slots:
   <aslot ptr>: [<index>]: <tv>
@@ -583,7 +583,7 @@ class LJDumpStack(LJBase):
     '''
 lj-stack [<lua_State *>]
 
-The command recieves a lua_State address and dumps the given Lua
+The command receives a lua_State address and dumps the given Lua
 coroutine guest stack:
 
 <slot ptr> [<slot attributes>] <VALUE|FRAME>

================================================================================

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-26 23:10 ` Alexander Turenko
@ 2020-02-27 10:37   ` Igor Munkin
  0 siblings, 0 replies; 23+ messages in thread
From: Igor Munkin @ 2020-02-27 10:37 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches

Sasha,

On 27.02.20, Alexander Turenko wrote:
> NB: It seems logical to install the script with tarantool-dev /
> tarantool-devel package. Is there some place, from which gdb loads
> extensions automatically? Maybe `/usr/share/gdb/python/`?

Yes, there is a place where extensions are autoloaded (see the link[1]
for more info). E.g. uJIT package installs its extension to the default
Ubuntu path[2]. Furthemore, one can specify its own path for extension
to be autoloaded on gdb startup.

[1]: https://sourceware.org/gdb/current/onlinedocs/gdb/objfile_002dgdbdotext-file.html#objfile_002dgdbdotext-file
[2]: https://github.com/iponweb/luavela/blob/master/tools/CMakeLists.txt#L133

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-26 22:45 ` Alexander Turenko
@ 2020-02-27 10:48   ` Igor Munkin
  2020-02-27 11:35     ` Alexander Turenko
  0 siblings, 1 reply; 23+ messages in thread
From: Igor Munkin @ 2020-02-27 10:48 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches

Sasha,

On 27.02.20, Alexander Turenko wrote:
> Are not it worth to squash those commits? Now the series looks as
> 'feature + fixup + fixup' and since we don't pushed it yet to a

I see it for now as:
* the feature
* the feature port to Python 2 (I hope it can be dropped in some time)
* several enhancments asked by Sergos (yep, it can be squashed prior to
  be pushed to the long-term branch)

> long-term branch is looks okay to squash fixups into the first commit.
> 
> It is up to you, I just asked.

I'll think about it, thanks.

> 
> WBR, Alexander Turenko.

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-27 10:48   ` Igor Munkin
@ 2020-02-27 11:35     ` Alexander Turenko
  2020-03-03 16:17       ` Igor Munkin
  0 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2020-02-27 11:35 UTC (permalink / raw)
  To: Igor Munkin; +Cc: tarantool-patches

> * the feature port to Python 2 (I hope it can be dropped in some time)

According to [1] not before June, 2024. However I guess that in fact it
may be needed even after 2024.

Not soon.

[1]: https://wiki.centos.org/About/Product

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-26 22:41 ` [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Alexander Turenko
@ 2020-02-28 10:46   ` Igor Munkin
  2020-03-02 16:00     ` Igor Munkin
  0 siblings, 1 reply; 23+ messages in thread
From: Igor Munkin @ 2020-02-28 10:46 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches

Sasha,

Thanks for the report, I've digged a problem a bit and found a solution
for the subj based on the events[1].

On 27.02.20, Alexander Turenko wrote:
> Bugreport:
> 
> When I source the extension from .gdbinit and run gdb to inspect a core
> file like so:
> 
> gdb tarantool \
>     -ex "set solib-search-path $(realpath lib64):$(realpath lib64/lua/5.1):$(realpath lib64/tarantool):$(realpath lib64/tarantool/http):$(realpath lib64/tarantool/cron)" \
>     -ex "add-auto-load-safe-path $(realpath .)" \
>     -ex "set sysroot $(realpath .)" \
>     -ex 'set substitute-path /build/usr/src/debug/tarantool-2.2.1.137 src/tarantool' \
>     -ex 'core core'
> 
> I got 'luajit-gdb.py failed to load: no debugging symbols found for
> libluajit' warning and the extesion is not loaded.

Now you will see the following:
| <snipped>
| luajit-gdb.py loading postponed until libluajit objfile is loaded
| Reading symbols from ./usr/bin/tarantool...
| lj-arch command initialized
| lj-tv command initialized
| lj-str command initialized
| lj-tab command initialized
| lj-stack command initialized
| lj-state command initialized
| lj-gc command initialized
| luajit-gdb.py is successfully loaded
| <snipped>

> 
> However if I source it afterwards it loded successfully.
> 
> I guess it is because `gdb.parse_and_eval('IRT_PTR')` and
> `gdb.parse_and_eval('IRT_PGC')` needs values from runtime, which is
> available only after `-ex 'core core'` command.
> 
> Possible solution: always register lj-* functions, but load those values
> inside them (if it is not already done). A kind of lazy loading.
> 
> WBR, Alexander Turenko.

Fixed, squashed, force-pushed to the branch. Diff is below:

================================================================================

diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
index 0ac0aaa..f23e119 100644
--- a/src/luajit-gdb.py
+++ b/src/luajit-gdb.py
@@ -659,9 +659,20 @@ The command requires no args and dumps current GC stats:
             stats = dump_gc(g)
         ))
 
-def load(commands):
+def init(commands):
     global LJ_64, LJ_GC64, LJ_FR2
 
+    if not gdb.lookup_global_symbol('luaJIT_setmode'):
+        gdb.write('luajit-gdb.py initialization is postponed '
+                  'until libluajit objfile is loaded\n')
+        gdb.events.new_objfile.connect(load)
+        return
+
+    try:
+        gdb.events.new_objfile.disconnect(load)
+    except:
+        pass # was not connected
+
     try:
         LJ_64 = str(gdb.parse_and_eval('IRT_PTR')) == 'IRT_P64'
         LJ_FR2 = LJ_GC64 = str(gdb.parse_and_eval('IRT_PGC')) == 'IRT_P64'
@@ -675,12 +686,15 @@ def load(commands):
 
     gdb.write('luajit-gdb.py is successfully loaded\n')
 
-load({
-    'lj-arch': LJDumpArch,
-    'lj-tv': LJDumpTValue,
-    'lj-str': LJDumpString,
-    'lj-tab': LJDumpTable,
-    'lj-stack': LJDumpStack,
-    'lj-state': LJState,
-    'lj-gc': LJGC,
-})
+def load(event=None):
+    init({
+        'lj-arch': LJDumpArch,
+        'lj-tv': LJDumpTValue,
+        'lj-str': LJDumpString,
+        'lj-tab': LJDumpTable,
+        'lj-stack': LJDumpStack,
+        'lj-state': LJState,
+        'lj-gc': LJGC,
+    })
+
+load(None)

================================================================================

[1]: https://sourceware.org/gdb/onlinedocs/gdb/Events-In-Python.html#Events-In-Python

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-28 10:46   ` Igor Munkin
@ 2020-03-02 16:00     ` Igor Munkin
  2020-03-03 14:16       ` Sergey Ostanevich
  0 siblings, 1 reply; 23+ messages in thread
From: Igor Munkin @ 2020-03-02 16:00 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches

OK, this fix doesn't work e.g. for luajit w/o debuginfo. Consider the
following:

| <snipped>
| luajit-gdb.py initialization is postponed until libluajit objfile is loaded
| Reading symbols from /home/imun/projects/tarantool-luajit/src/luajit...
| (No debugging symbols found in /home/imun/projects/tarantool-luajit/src/luajit)
| luajit-gdb.py initialization is postponed until libluajit objfile is loaded
| <snipped>

The extension initialization goes in loops, since it fails to find
luaJIT_setmode global symbol. I didn't investigate the problem a lot,
just made the extension work.

On 28.02.20, Igor Munkin wrote:

<snipped>

> +    if not gdb.lookup_global_symbol('luaJIT_setmode'):
> +        gdb.write('luajit-gdb.py initialization is postponed '
> +                  'until libluajit objfile is loaded\n')
> +        gdb.events.new_objfile.connect(load)
> +        return
> +

Here is the problem, so I rewrote it the following way:

================================================================================

diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
index 2e20642..f142fc5 100644
--- a/src/luajit-gdb.py
+++ b/src/luajit-gdb.py
@@ -662,7 +662,9 @@ The command requires no args and dumps current GC stats:
 def init(commands):
     global LJ_64, LJ_GC64, LJ_FR2
 
-    if not gdb.lookup_global_symbol('luaJIT_setmode'):
+    try:
+        gdb.parse_and_eval('luaJIT_setmode')
+    except:
         gdb.write('luajit-gdb.py initialization is postponed '
                   'until libluajit objfile is loaded\n')
         gdb.events.new_objfile.connect(load)

================================================================================

Squashed, force-pushed to the branch.

<snipped>

> 
> -- 
> Best regards,
> IM

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-03-02 16:00     ` Igor Munkin
@ 2020-03-03 14:16       ` Sergey Ostanevich
  0 siblings, 0 replies; 23+ messages in thread
From: Sergey Ostanevich @ 2020-03-03 14:16 UTC (permalink / raw)
  To: Igor Munkin; +Cc: tarantool-patches

Hi!

Thanks for the update, LGTM for me.

regards,
Sergos

On 02 мар 19:00, Igor Munkin wrote:
> OK, this fix doesn't work e.g. for luajit w/o debuginfo. Consider the
> following:
> 
> | <snipped>
> | luajit-gdb.py initialization is postponed until libluajit objfile is loaded
> | Reading symbols from /home/imun/projects/tarantool-luajit/src/luajit...
> | (No debugging symbols found in /home/imun/projects/tarantool-luajit/src/luajit)
> | luajit-gdb.py initialization is postponed until libluajit objfile is loaded
> | <snipped>
> 
> The extension initialization goes in loops, since it fails to find
> luaJIT_setmode global symbol. I didn't investigate the problem a lot,
> just made the extension work.
> 
> On 28.02.20, Igor Munkin wrote:
> 
> <snipped>
> 
> > +    if not gdb.lookup_global_symbol('luaJIT_setmode'):
> > +        gdb.write('luajit-gdb.py initialization is postponed '
> > +                  'until libluajit objfile is loaded\n')
> > +        gdb.events.new_objfile.connect(load)
> > +        return
> > +
> 
> Here is the problem, so I rewrote it the following way:
> 
> ================================================================================
> 
> diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
> index 2e20642..f142fc5 100644
> --- a/src/luajit-gdb.py
> +++ b/src/luajit-gdb.py
> @@ -662,7 +662,9 @@ The command requires no args and dumps current GC stats:
>  def init(commands):
>      global LJ_64, LJ_GC64, LJ_FR2
>  
> -    if not gdb.lookup_global_symbol('luaJIT_setmode'):
> +    try:
> +        gdb.parse_and_eval('luaJIT_setmode')
> +    except:
>          gdb.write('luajit-gdb.py initialization is postponed '
>                    'until libluajit objfile is loaded\n')
>          gdb.events.new_objfile.connect(load)
> 
> ================================================================================
> 
> Squashed, force-pushed to the branch.
> 
> <snipped>
> 
> > 
> > -- 
> > Best regards,
> > IM
> 
> -- 
> Best regards,
> IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-27 11:35     ` Alexander Turenko
@ 2020-03-03 16:17       ` Igor Munkin
  2020-03-03 22:39         ` Alexander Turenko
  0 siblings, 1 reply; 23+ messages in thread
From: Igor Munkin @ 2020-03-03 16:17 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches

Sasha,

I squashed the first patch of the series (gdb: introduce luajit-gdb
extension) with the third one (gdb: enhance the extension loading) as
you proposed supra.

As discussed offline, the second patch is left to separate the changes
required for compatibility with Python 2 in git repo history.

Rebased, force-pushed to the branch.

On 27.02.20, Alexander Turenko wrote:
> > * the feature port to Python 2 (I hope it can be dropped in some
> > time)
> 
> According to [1] not before June, 2024. However I guess that in fact
> it may be needed even after 2024.
> 
> Not soon.
> 
> [1]: https://wiki.centos.org/About/Product

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-03-03 16:17       ` Igor Munkin
@ 2020-03-03 22:39         ` Alexander Turenko
  2020-03-17 22:46           ` Igor Munkin
  0 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2020-03-03 22:39 UTC (permalink / raw)
  To: Igor Munkin; +Cc: tarantool-patches

I don't think that it worth to do in-depth review from my side. But I
tried the extension, it works and gives output that I can interpret. It
also has the documentation that is available using gdb's built-in help
command. It is nice, thanks, Igor!

So the patchset LGTM. CCed Kirill.

Igor, please, file an issue for the further activity: we should ship the
tool in our packages (possibly tarantool-dev/devel) in paths, where gdb
will able to catch it automatically.

WBR, Alexander Turenko.

On Tue, Mar 03, 2020 at 07:17:12PM +0300, Igor Munkin wrote:
> Sasha,
> 
> I squashed the first patch of the series (gdb: introduce luajit-gdb
> extension) with the third one (gdb: enhance the extension loading) as
> you proposed supra.
> 
> As discussed offline, the second patch is left to separate the changes
> required for compatibility with Python 2 in git repo history.
> 
> Rebased, force-pushed to the branch.
> 
> On 27.02.20, Alexander Turenko wrote:
> > > * the feature port to Python 2 (I hope it can be dropped in some
> > > time)
> > 
> > According to [1] not before June, 2024. However I guess that in fact
> > it may be needed even after 2024.
> > 
> > Not soon.
> > 
> > [1]: https://wiki.centos.org/About/Product
> 
> -- 
> Best regards,
> IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-02-05 16:22 [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Igor Munkin
                   ` (6 preceding siblings ...)
  2020-02-26 23:10 ` Alexander Turenko
@ 2020-03-05  7:44 ` Kirill Yukhin
  2020-03-05  9:22   ` Igor Munkin
  7 siblings, 1 reply; 23+ messages in thread
From: Kirill Yukhin @ 2020-03-05  7:44 UTC (permalink / raw)
  To: Igor Munkin; +Cc: tarantool-patches

Hello,

On 05 фев 19:22, Igor Munkin wrote:
> 
> Signed-off-by: Igor Munkin <imun@tarantool.org>

I've checked the patchset into tarantool/luajit repo
and bumped a new version in 1.10, 2.2, 2.3 and master.

--
Regards, Kirill Yukhin

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-03-05  7:44 ` Kirill Yukhin
@ 2020-03-05  9:22   ` Igor Munkin
  0 siblings, 0 replies; 23+ messages in thread
From: Igor Munkin @ 2020-03-05  9:22 UTC (permalink / raw)
  To: Kirill Yukhin; +Cc: tarantool-patches

Kirill,

Thanks! As you reminded, here is a ChangeLog entry:

@ChangeLog:
* Introduced luajit-gdb.py extension with commands for inspecting LuaJIT
  internals. The extension obliges the one to provide gdbinfo for
  libluajit, otherwise loading fails. The extension provides the
  following commands:
  - `lj-arch` dumps values of LJ_64 and LJ_GC64 macro definitions
  - `lj-tv` dumps the type and GCobj info related to the given TValue
  - `lj-str` dumps the contents of the given GCstr
  - `lj-tab` dumps the contents of the given GCtab
  - `lj-stack` dumps Lua stack of the given lua_State
  - `lj-state` shows current VM, GC and JIT states
  - `lj-gc` shows current GC stats

On 05.03.20, Kirill Yukhin wrote:
> Hello,
> 
> On 05 фев 19:22, Igor Munkin wrote:
> > 
> > Signed-off-by: Igor Munkin <imun@tarantool.org>
> 
> I've checked the patchset into tarantool/luajit repo
> and bumped a new version in 1.10, 2.2, 2.3 and master.
> 
> --
> Regards, Kirill Yukhin

Added to the corresponding release notes.

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT
  2020-03-03 22:39         ` Alexander Turenko
@ 2020-03-17 22:46           ` Igor Munkin
  0 siblings, 0 replies; 23+ messages in thread
From: Igor Munkin @ 2020-03-17 22:46 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches

Sasha,

On 04.03.20, Alexander Turenko wrote:
> I don't think that it worth to do in-depth review from my side. But I
> tried the extension, it works and gives output that I can interpret. It
> also has the documentation that is available using gdb's built-in help
> command. It is nice, thanks, Igor!
> 
> So the patchset LGTM. CCed Kirill.
> 
> Igor, please, file an issue for the further activity: we should ship the
> tool in our packages (possibly tarantool-dev/devel) in paths, where gdb
> will able to catch it automatically.

See #4808[1] for the further enhancements I proposed for the extension.

> 
> WBR, Alexander Turenko.
> 

<snipped>

[1]: https://github.com/tarantool/tarantool/issues/4808

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2020-03-17 22:52 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-02-05 16:22 [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Igor Munkin
2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 1/3] gdb: introduce luajit-gdb extension Igor Munkin
2020-02-13 13:24   ` Igor Munkin
2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 2/3] gdb: adjust the extension to be used with Python 2 Igor Munkin
2020-02-05 16:22 ` [Tarantool-patches] [PATCH v2 luajit 3/3] gdb: enhance the extension loading Igor Munkin
2020-02-13 11:48   ` Igor Munkin
2020-02-13 12:24     ` Igor Munkin
2020-02-26 22:41 ` [Tarantool-patches] [PATCH v2 luajit 0/3] Introduce gdb extension for LuaJIT Alexander Turenko
2020-02-28 10:46   ` Igor Munkin
2020-03-02 16:00     ` Igor Munkin
2020-03-03 14:16       ` Sergey Ostanevich
2020-02-26 22:45 ` Alexander Turenko
2020-02-27 10:48   ` Igor Munkin
2020-02-27 11:35     ` Alexander Turenko
2020-03-03 16:17       ` Igor Munkin
2020-03-03 22:39         ` Alexander Turenko
2020-03-17 22:46           ` Igor Munkin
2020-02-26 23:04 ` Alexander Turenko
2020-02-27 10:13   ` Igor Munkin
2020-02-26 23:10 ` Alexander Turenko
2020-02-27 10:37   ` Igor Munkin
2020-03-05  7:44 ` Kirill Yukhin
2020-03-05  9:22   ` Igor Munkin

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