Tarantool development patches archive
 help / color / mirror / Atom feed
From: Maxim Kokryashkin via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: Sergey Kaplun <skaplun@tarantool.org>
Cc: Maxim Kokryashkin <max.kokryashkin@gmail.com>,
	tarantool-patches@dev.tarantool.org
Subject: Re: [Tarantool-patches] [PATCH luajit v6 1/2] debug: generalized extension
Date: Thu, 18 Apr 2024 01:42:54 +0300	[thread overview]
Message-ID: <wcwxnonudz35mnm7ke74uxnxl7anj5dddq3ksuuo7em5z6qmfa@dwr7csgrrqa6> (raw)
In-Reply-To: <Zh_yAJ4XZW4AdxWi@root>

Hi, Sergey!
Thanks for the review!
See my answers below.
On Wed, Apr 17, 2024 at 07:00:00PM +0300, Sergey Kaplun wrote:
> Hi, Maxim!
> Thanks for the patch!
> Please consider my comments below.
>
> On 04.04.24, Maxim Kokryashkin wrote:
>
> <snipped>
>
> > ---
> >  src/luajit-gdb.py                     | 885 --------------------------
> >  src/{luajit_lldb.py => luajit_dbg.py} | 616 ++++++++++++------
>
> Since luajit_lldb.py is gone, please change the comment in <.flake8rc>.
Fixed.
>
> >  2 files changed, 416 insertions(+), 1085 deletions(-)
> >  delete mode 100644 src/luajit-gdb.py
> >  rename src/{luajit_lldb.py => luajit_dbg.py} (63%)
> >
> > diff --git a/src/luajit-gdb.py b/src/luajit-gdb.py
> > deleted file mode 100644
>
> <snipped>
>
> > diff --git a/src/luajit_lldb.py b/src/luajit_dbg.py
> > similarity index 63%
> > rename from src/luajit_lldb.py
> > rename to src/luajit_dbg.py
> > index 5ac11b65..a42d8f25 100644
> > --- a/src/luajit_lldb.py
> > +++ b/src/luajit_dbg.py
> > @@ -1,10 +1,230 @@
> > -# LLDB extension for LuaJIT post-mortem analysis.
> > -# To use, just put 'command script import <path-to-repo>/src/luajit_lldb.py'
> > -# in lldb.
> > +# Debug extension for LuaJIT post-mortem analysis.
> > +# To use in LLDB: 'command script import <path-to-repo>/src/luajit_dbg.py'
> > +# To use in GDB: 'source <path-to-repo>/src/luajit_dbg.py'
> >
> >  import abc
> >  import re
> > -import lldb
> > +import sys
> > +import types
> > +
> > +from importlib import import_module
> > +
> > +# make script compatible with the ancient Python {{{
>
> Typo: s/make script/Make the script/
>
> <snipped>
Fixed.
>
> > +class Debugger(object):
> > +    def __init__(self):
> > +        self.GDB = False
> > +        self.LLDB = False
> > +
> > +        debuggers = {
> > +            'gdb': lambda lib: True,
> > +            'lldb': lambda lib: lib.debugger is not None,
> > +        }
> > +        for name, healthcheck in debuggers.items():
> > +            lib = None
> > +            try:
> > +                lib = import_module(name)
> > +                if healthcheck(lib):
>
> Why do we need this healthcheck? Why just import of the module isn't
> enough?
> Please add a comment near `debuggers` definition.
Added.
>
> > +                    setattr(self, name.upper(), True)
> > +                    globals()[name] = lib
> > +                    self.name = name
> > +            except Exception:
> > +                continue
> > +
> > +        assert self.LLDB != self.GDB
>
> <snipped>
>
> > +
> > +    def find_type(self, typename):
> > +        if self.LLDB:
> > +            return target.FindFirstType(typename)
> > +        else:
> > +            return gdb.lookup_type(typename)
>
> Why do you drop the cache for types here? It may be critical when
> running scripts for the search of objects on big coredumps or the
> attached process.
Well, I believe I just forgot about that. Added.
Side note: if only we could do the python3-only version of that,
the fix would just be a single decorator in front of the function
definition...
> > +
> > +    def type_to_pointer_type(self, tp):
> > +        if self.LLDB:
> > +            return tp.GetPointerType()
> > +        else:
> > +            return tp.pointer()
>
> <snipped>
>
> > +
> > +    def type_member_offset(self, member):
> > +        if self.LLDB:
> > +            return member.GetOffsetInBytes()
> > +        else:
> > +            return member.bitpos / 8
>
> Should it be `//`?
Doesn't really matter, but ok. Fixed.
>
> <snipped>
>
> > +class Struct(object):
>
> Should we do this part for GDB too? I thought that this class generation
> may be skipped for GDB.
No, the whole idea is to encapsulate the debugger-specific things into
the debugger object and these classes. So we cannot skip them, they have
crucial role in this adapter.
>
> >      def __init__(self, value):
> >          self.value = value
> >
> >      def __getitem__(self, name):
>
> <snipped>
>
> > +def make_property_from_metadata(field, tp):
> > +    builtin = {
> > +        'uint':   dbg.to_unsigned,
> > +        'int':    dbg.to_signed,
> > +        'string': dbg.to_str,
> > +    }
> > +    if tp in builtin.keys():
> > +        return lambda self: builtin[tp](self[field])
> > +    else:
> > +        return lambda self: globals()[tp](self[field])
> > +
> > +
> > +for cls, metainfo in c_structs.items():
> > +    cls_dict = {}
> > +    for field in metainfo:
>
> May you please name field[0], field[1] as local variables for better
> readability?
Fixed.
>
> > +        if not isinstance(field[0], str):
> > +            cls_dict[field[1]] = field[0]
> > +        else:
> > +            cls_dict[field[1]] = property(
> > +                make_property_from_metadata(field[1], field[0])
> > +            )
> > +    globals()[cls] = type(cls, (Struct, ), cls_dict)
> >
> >
> >  for cls in Struct.__subclasses__():
> >      ptr_name = cls.__name__ + 'Ptr'
> >
>
>
> <snipped>
>
> > -
> > -        ret = frame.EvaluateExpression(command)
> > -        return ret
> > +        return dbg.to_unsigned(dbg.eval(command))
>
> Why do we need return unsigned here?
Because all of our commands accept either pointers, or numbers as
argumnents and lldb's eval may return a string instead.
>
> >
> >      @abc.abstractproperty
> >      def command(self):
> > @@ -270,7 +491,7 @@ class Command(object):
>
> <snipped>
>
> > @@ -278,6 +499,11 @@ class Command(object):
> >          properly routed to LLDB frontend. Any unhandled exception will be
> >          automatically transformed into proper errors.
> >          """
> > +    def invoke(self, arg, from_tty):
> > +        try:
> > +            self.execute(arg)
> > +        except Exception as e:
> > +            dbg.write(e)
>
> Why do we need this change?
>
> The error message for such situation is changed and non informative:
>
> | Breakpoint 1, lj_cf_dofile (L=0x2) at /home/burii/reviews/luajit/lj-dbg/src/lib_base.c:429
> | 429     {
> | (gdb) lj-stack L
> | Python Exception <class 'TypeError'>: unsupported operand type(s) for +: 'MemoryError' and 'str'
> | Error occurred in Python: unsupported operand type(s) for +: 'MemoryError' and 'str'
>
> Within the following implementation all works as expected.
> |    def invoke(self, arg, from_tty):
> |        self.execute(arg)
Fixed.
>
> This produces more understandable reason of an error:
> | (gdb) lj-stack L
> | Python Exception <class 'gdb.MemoryError'>: Cannot access memory at address 0x26
> | Error occurred in Python: Cannot access memory at address 0x26
>
Without the explicit exception output LLDB command just fails silently.
Choosing from a less readable error and not understanding what happened
(and whether it even happened at all) I would personally prefer the
first option.

> Also, maybe it is good to add a test for this error.
Kind of a strange idea to test whether the extension throws an unhandled
exception. It's like checking if Tarantool gives a segfault with a
certain backtrace after a NULL dereference.
>
> <snipped

Here is the diff with changes:
===
diff --git a/.flake8rc b/.flake8rc
index 6766ed41..6c1200fc 100644
--- a/.flake8rc
+++ b/.flake8rc
@@ -1,7 +1,7 @@
 [flake8]
 extend-ignore =
   # XXX: Suppress F821, since we have autogenerated names for
-  # 'ptr' type complements in luajit_lldb.py.
+  # 'ptr' type complements in luajit_dbg.py.
   F821
 per-file-ignores =
   # XXX: Flake considers regexp special characters to be
diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index a42d8f25..e800b514 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -9,7 +9,7 @@ import types

 from importlib import import_module

-# make script compatible with the ancient Python {{{
+# Make the script compatible with the ancient Python {{{


 LEGACY = re.match(r'^2\.', sys.version)
@@ -31,7 +31,13 @@ class Debugger(object):
     def __init__(self):
         self.GDB = False
         self.LLDB = False
+        self.type_cache = {}

+        # XXX: While the `gdb` library is only available inside
+        # a debug session, the `lldb` library can be loaded in
+        # any Python script. To address that, we need to perform
+        # an additional check to ensure a debug session is
+        # actually running.
         debuggers = {
             'gdb': lambda lib: True,
             'lldb': lambda lib: lib.debugger is not None,
@@ -116,10 +122,12 @@ class Debugger(object):
         return val.value if self.LLDB else str(val)

     def find_type(self, typename):
-        if self.LLDB:
-            return target.FindFirstType(typename)
-        else:
-            return gdb.lookup_type(typename)
+        if typename not in self.type_cache:
+            if self.LLDB:
+                self.type_cache[typename] = target.FindFirstType(typename)
+            else:
+                self.type_cache[typename] = gdb.lookup_type(typename)
+        return self.type_cache[typename]

     def type_to_pointer_type(self, tp):
         if self.LLDB:
@@ -190,7 +198,7 @@ class Debugger(object):
         if self.LLDB:
             return member.GetOffsetInBytes()
         else:
-            return member.bitpos / 8
+            return member.bitpos // 8

     def get_member(self, value, member_name):
         if self.LLDB:
@@ -437,11 +445,13 @@ def make_property_from_metadata(field, tp):
 for cls, metainfo in c_structs.items():
     cls_dict = {}
     for field in metainfo:
-        if not isinstance(field[0], str):
-            cls_dict[field[1]] = field[0]
+        prop_constructor = field[0]
+        prop_name = field[1]
+        if not isinstance(prop_constructor, str):
+            cls_dict[prop_name] = prop_constructor
         else:
-            cls_dict[field[1]] = property(
-                make_property_from_metadata(field[1], field[0])
+            cls_dict[prop_name] = property(
+                make_property_from_metadata(prop_name, prop_constructor)
             )
     globals()[cls] = type(cls, (Struct, ), cls_dict)

===
>
> --
> Best regards,
> Sergey Kaplun

  reply	other threads:[~2024-04-17 22:42 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-03 22:21 [Tarantool-patches] [PATCH luajit v6 0/2] " Maxim Kokryashkin via Tarantool-patches
2024-04-03 22:21 ` [Tarantool-patches] [PATCH luajit v6 1/2] " Maxim Kokryashkin via Tarantool-patches
2024-04-04 10:14   ` Sergey Bronnikov via Tarantool-patches
2024-04-17 16:00   ` Sergey Kaplun via Tarantool-patches
2024-04-17 22:42     ` Maxim Kokryashkin via Tarantool-patches [this message]
2024-04-18  8:00       ` Sergey Kaplun via Tarantool-patches
2024-08-14 19:34   ` Mikhail Elhimov via Tarantool-patches
2024-04-03 22:21 ` [Tarantool-patches] [PATCH luajit v6 2/2] test: add tests for debugging extensions Maxim Kokryashkin via Tarantool-patches
2024-04-04 10:27   ` Sergey Bronnikov via Tarantool-patches
2024-04-08  9:45     ` Maxim Kokryashkin via Tarantool-patches
2024-04-17 16:00   ` Sergey Kaplun via Tarantool-patches

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=wcwxnonudz35mnm7ke74uxnxl7anj5dddq3ksuuo7em5z6qmfa@dwr7csgrrqa6 \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=m.kokryashkin@tarantool.org \
    --cc=max.kokryashkin@gmail.com \
    --cc=skaplun@tarantool.org \
    --subject='Re: [Tarantool-patches] [PATCH luajit v6 1/2] debug: generalized extension' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

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