public inbox for gcc-help@gcc.gnu.org
 help / color / mirror / Atom feed
From: Jonathan Wakely <jwakely.gcc@gmail.com>
To: Paul Smith <paul@mad-scientist.net>
Cc: gcc-help <gcc-help@gcc.gnu.org>
Subject: Re: Help using the GDB C++ STL pretty-printers / xmethods
Date: Mon, 9 May 2022 12:23:47 +0100	[thread overview]
Message-ID: <CAH6eHdRvJ8Vs9M=6QC+G4H=-PA4tyQOsmxz455EPBMomtRZ2eQ@mail.gmail.com> (raw)
In-Reply-To: <CAH6eHdR+X5yPSLdNdaFqFBAWUz7VPSaSG6Nwfw8JfPUVovCyxw@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 2796 bytes --]

On Mon, 9 May 2022 at 10:32, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
>
> On Sun, 8 May 2022 at 20:44, Paul Smith <paul@mad-scientist.net> wrote:
> >
> > On Sun, 2022-05-08 at 09:16 +0100, Jonathan Wakely wrote:
> > > > gdb.set_convenience_variable('mgr', val['mgr'])

This doesn't do what I thought it does, meaning that $mgr ends up
defined to void. I think you can't set a gdb.Value as the value of a
convenience variable.

> > > > init = gdb.parse_and_eval('$mgr->initialized')
> > > >
> > > > This will use the xmethod to evaluate the expression.
> > >
> > > And then:
> > >
> > > if init:
> > >     return gdb.parse_and_eval('*$mgr')
> >
> > Unfortunately, this doesn't work :(.  I can't do it from the GDB
> > command line or python (I have tried both with the same results).
> > Something about convenience variables doesn't play well with xmethods
> > (or maybe this xmethod implementation specifically?)
>
> You're right, sorry. It doesn't work for me with a convenience variable.

And even if I don't set the convenience variable to a gdb.Value, it
still doesn't work with the xmethod:

(gdb) set $mgr = x.mgr
(gdb) whatis $mgr
type = std::unique_ptr<Mgr>
(gdb) p $mgr
$2 = std::unique_ptr<Mgr> = {get() = 0x416eb0}
(gdb) p $mgr->initialized
Attempt to take address of value not located in memory.


>
> But since what you want is something that works in arbitrary Python
> code, not just within GDB, doesn't pybind11 already do everything you
> want?
> https://github.com/pybind/pybind11

If you don't want to (or can't) use pybind11 to create Python
bindings, and you don't want to use the GDB Python API, then you will
have a ton of work to do. The GDB Python API is what provides all the
tools for traversing C++ class hierarchies, examining template
arguments, casting values to related types etc.

The attached diff refactors the std::unique_ptr pretty printer to be
defined in terms of a new types.UniquePtr class that exposes the
std::unique_ptr API to the printer (and the start of a types.Tuple
class for std::tuple, but I didn't make the TuplePrinter use that yet,
and didn't change the xmethods yet). This was mostly just a bit of
copy & paste, and renaming some functions. The hardest part for me was
figuring out how Python module imports work, not refactoring the
actual code. But it all still relies on the GDB API. Doing it in pure
Python would be much more work.

This patch can't be committed though, because it breaks the libstdc++
testsuite. The gdb-test.exp procs expect to be able to source a single
Python script, which breaks if that script tries to import modules in
the same package (because there's no package when GDB loads the Python
file directly via "source blah/blah/printers.py"). That would need to
be fixed to go ahead with changes like this.

[-- Attachment #2: patch.txt --]
[-- Type: text/plain, Size: 10948 bytes --]

commit aedd1591cd8be6ec2bcf1d23d6495fbb7bbb2f9a
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Mon May 9 12:07:26 2022

    libstdc++: Create Python wrappers for std::tuple and std::unique_ptr
    
    libstdc++-v3/ChangeLog:
    
            * python/Makefile.am: Install new scripts.
            * python/Makefile.in: Regenerate.
            * python/libstdcxx/v6/printers.py:
            * python/libstdcxx/v6/types.py: New file.
            * python/libstdcxx/v6/util.py: New file.

diff --git a/libstdc++-v3/python/Makefile.am b/libstdc++-v3/python/Makefile.am
index f523d3a44dc..70b34f74b49 100644
--- a/libstdc++-v3/python/Makefile.am
+++ b/libstdc++-v3/python/Makefile.am
@@ -39,6 +39,8 @@ all-local: gdb.py
 
 nobase_python_DATA = \
     libstdcxx/v6/printers.py \
+    libstdcxx/v6/types.py \
+    libstdcxx/v6/util.py \
     libstdcxx/v6/xmethods.py \
     libstdcxx/v6/__init__.py \
     libstdcxx/__init__.py
diff --git a/libstdc++-v3/python/libstdcxx/v6/printers.py b/libstdc++-v3/python/libstdcxx/v6/printers.py
index 0bd793c0897..f71f12245be 100644
--- a/libstdc++-v3/python/libstdcxx/v6/printers.py
+++ b/libstdc++-v3/python/libstdcxx/v6/printers.py
@@ -19,6 +19,10 @@ import gdb
 import itertools
 import re
 import sys, os, errno
+from libstdcxx.v6 import types
+from libstdcxx.v6.util import _versioned_namespace
+from libstdcxx.v6.util import is_specialization_of
+from libstdcxx.v6.util import get_template_arg_list
 
 ### Python 2 + Python 3 compatibility code
 
@@ -100,8 +104,6 @@ def find_type(orig, name):
         else:
             raise ValueError("Cannot find type %s::%s" % (str(orig), name))
 
-_versioned_namespace = '__8::'
-
 def lookup_templ_spec(templ, *args):
     """
     Lookup template specialization templ<args...>
@@ -165,15 +167,6 @@ def is_member_of_namespace(typ, *namespaces):
             return True
     return False
 
-def is_specialization_of(x, template_name):
-    "Test if a type is a given template instantiation."
-    global _versioned_namespace
-    if type(x) is gdb.Type:
-        x = x.tag
-    if _versioned_namespace:
-        return re.match('^std::(%s)?%s<.*>$' % (_versioned_namespace, template_name), x) is not None
-    return re.match('^std::%s<.*>$' % template_name, x) is not None
-
 def strip_versioned_namespace(typename):
     global _versioned_namespace
     if _versioned_namespace:
@@ -191,17 +184,6 @@ def strip_inline_namespaces(type_str):
     type_str = type_str.replace(fs_ns+'v1::', fs_ns)
     return type_str
 
-def get_template_arg_list(type_obj):
-    "Return a type's template arguments as a list"
-    n = 0
-    template_args = []
-    while True:
-        try:
-            template_args.append(type_obj.template_argument(n))
-        except:
-            return template_args
-        n += 1
-
 class SmartPtrIterator(Iterator):
     "An iterator for smart pointer types with a single 'child' value"
 
@@ -252,55 +234,6 @@ class SharedPointerPrinter:
                 state = 'use count %d, weak count %d' % (usecount, weakcount - 1)
         return '%s<%s> (%s)' % (self.typename, str(targ), state)
 
-def _tuple_impl_get(val):
-    "Return the tuple element stored in a _Tuple_impl<N, T> base class."
-    bases = val.type.fields()
-    if not bases[-1].is_base_class:
-        raise ValueError("Unsupported implementation for std::tuple: %s" % str(val.type))
-    # Get the _Head_base<N, T> base class:
-    head_base = val.cast(bases[-1].type)
-    fields = head_base.type.fields()
-    if len(fields) == 0:
-        raise ValueError("Unsupported implementation for std::tuple: %s" % str(val.type))
-    if fields[0].name == '_M_head_impl':
-        # The tuple element is the _Head_base::_M_head_impl data member.
-        return head_base['_M_head_impl']
-    elif fields[0].is_base_class:
-        # The tuple element is an empty base class of _Head_base.
-        # Cast to that empty base class.
-        return head_base.cast(fields[0].type)
-    else:
-        raise ValueError("Unsupported implementation for std::tuple: %s" % str(val.type))
-
-def tuple_get(n, val):
-    "Return the result of std::get<n>(val) on a std::tuple"
-    tuple_size = len(get_template_arg_list(val.type))
-    if n > tuple_size:
-        raise ValueError("Out of range index for std::get<N> on std::tuple")
-    # Get the first _Tuple_impl<0, T...> base class:
-    node = val.cast(val.type.fields()[0].type)
-    while n > 0:
-        # Descend through the base classes until the Nth one.
-        node = node.cast(node.type.fields()[0].type)
-        n -= 1
-    return _tuple_impl_get(node)
-
-def unique_ptr_get(val):
-    "Return the result of val.get() on a std::unique_ptr"
-    # std::unique_ptr<T, D> contains a std::tuple<D::pointer, D>,
-    # either as a direct data member _M_t (the old implementation)
-    # or within a data member of type __uniq_ptr_data.
-    impl_type = val.type.fields()[0].type.strip_typedefs()
-    # Check for new implementations first:
-    if is_specialization_of(impl_type, '__uniq_ptr_data') \
-        or is_specialization_of(impl_type, '__uniq_ptr_impl'):
-        tuple_member = val['_M_t']['_M_t']
-    elif is_specialization_of(impl_type, 'tuple'):
-        tuple_member = val['_M_t']
-    else:
-        raise ValueError("Unsupported implementation for unique_ptr: %s" % str(impl_type))
-    return tuple_get(0, tuple_member)
-
 class UniquePointerPrinter:
     "Print a unique_ptr"
 
@@ -308,7 +241,7 @@ class UniquePointerPrinter:
         self.val = val
 
     def children (self):
-        return SmartPtrIterator(unique_ptr_get(self.val))
+        return SmartPtrIterator(types.UniquePtr(self.val).get())
 
     def to_string (self):
         return ('std::unique_ptr<%s>' % (str(self.val.type.template_argument(0))))
@@ -1413,7 +1346,7 @@ class StdPathPrinter:
     def __init__ (self, typename, val):
         self.val = val
         self.typename = typename
-        impl = unique_ptr_get(self.val['_M_cmpts']['_M_impl'])
+        impl = types.UniquePtr(self.val['_M_cmpts']['_M_impl']).get()
         self.type = impl.cast(gdb.lookup_type('uintptr_t')) & 3
         if self.type == 0:
             self.impl = impl
diff --git a/libstdc++-v3/python/libstdcxx/v6/types.py b/libstdc++-v3/python/libstdcxx/v6/types.py
new file mode 100644
index 00000000000..65feac0d600
--- /dev/null
+++ b/libstdc++-v3/python/libstdcxx/v6/types.py
@@ -0,0 +1,87 @@
+import gdb
+from libstdcxx.v6.util import is_specialization_of
+from libstdcxx.v6.util import get_template_arg_list
+
+class Tuple:
+    "Python wrapper for std::tuple<T...>"
+
+    def __init__(self, val):
+        self.val = val
+
+    def tuple_size(self):
+        "Return tuple_size_v<tuple<T...>>"
+        return len(get_template_arg_list(self.val.type))
+
+    def element_type(self, n):
+        "Return a gdb.Type for the tuple_element_t<n, tuple<T...>> type"
+        return self._nth_element(n).template_argument(1)
+
+    def get(self, n):
+        "Return the result of std::get<n> on a std::tuple"
+        return self._impl_get(self._nth_element(n))
+
+    def _nth_element(self, n):
+        "Return a gdb.Value for the _Tuple_impl<n, T> base class"
+        if n >= self.tuple_size():
+            raise ValueError("Out of range index for std::get<{}> on std::tuple".format(n))
+        # Get the first _Tuple_impl<0, T...> base class:
+        node = self.val.cast(self.val.type.fields()[0].type)
+        while n > 0:
+            # Descend through the base classes until the Nth one.
+            node = node.cast(node.type.fields()[0].type)
+            n -= 1
+        return node
+
+    def _impl_get(self, node):
+        "Return the tuple element stored in a _Tuple_impl<N, T> base class."
+        bases = node.type.fields()
+        if not bases[-1].is_base_class:
+            raise ValueError("Unsupported implementation for std::tuple: %s" % str(node.type))
+        # Get the _Head_base<N, T> base class:
+        head_base = node.cast(bases[-1].type)
+        fields = head_base.type.fields()
+        if len(fields) == 0:
+            raise ValueError("Unsupported implementation for std::tuple: %s" % str(node.type))
+        if fields[0].name == '_M_head_impl':
+            # The tuple element is the _Head_base::_M_head_impl data member.
+            return head_base['_M_head_impl']
+        elif fields[0].is_base_class:
+            # The tuple element is an empty base class of _Head_base.
+            # Cast to that empty base class.
+            return head_base.cast(fields[0].type)
+        else:
+            raise ValueError("Unsupported implementation for std::tuple: %s" % str(node.type))
+
+class UniquePtr:
+    "Python wrapper for std::unique_ptr<T, D>"
+
+    def __init__ (self, val):
+        self.val = val
+
+    #def pointer_type(self):
+    #    "Return a gdb.Type for pointer typedef"
+    #    return TODO
+
+    def element_type(self):
+        "Return a gdb.Type for the element_type typedef"
+        return self.val.type.template_argument(0)
+
+    def deleter_type(self):
+        "Return a gdb.Type for the deleter_type typedef"
+        return self.val.type.template_argument(1)
+
+    def get(self):
+        "Return a gdb.Value for the get() member function"
+        # std::unique_ptr<T, D> contains a std::tuple<D::pointer, D>,
+        # either as a direct data member _M_t (the old implementation)
+        # or within a data member of type __uniq_ptr_data.
+        impl_type = self.val.type.fields()[0].type.strip_typedefs()
+        # Check for new implementations first:
+        if is_specialization_of(impl_type, '__uniq_ptr_data') \
+            or is_specialization_of(impl_type, '__uniq_ptr_impl'):
+            tuple_member = self.val['_M_t']['_M_t']
+        elif is_specialization_of(impl_type, 'tuple'):
+            tuple_member = self.val['_M_t']
+        else:
+            raise ValueError("Unsupported implementation for unique_ptr: %s" % str(impl_type))
+        return Tuple(tuple_member).get(0)
diff --git a/libstdc++-v3/python/libstdcxx/v6/util.py b/libstdc++-v3/python/libstdcxx/v6/util.py
new file mode 100644
index 00000000000..82e9ce665e0
--- /dev/null
+++ b/libstdc++-v3/python/libstdcxx/v6/util.py
@@ -0,0 +1,24 @@
+import re
+import gdb
+
+_versioned_namespace = '__8::'
+
+def is_specialization_of(x, template_name):
+    "Test if a type is a given template instantiation."
+    global _versioned_namespace
+    if type(x) is gdb.Type:
+        x = x.tag
+    if _versioned_namespace:
+        return re.match('^std::(%s)?%s<.*>$' % (_versioned_namespace, template_name), x) is not None
+    return re.match('^std::%s<.*>$' % template_name, x) is not None
+
+def get_template_arg_list(type_obj):
+    "Return a type's template arguments as a list"
+    n = 0
+    template_args = []
+    while True:
+        try:
+            template_args.append(type_obj.template_argument(n))
+        except:
+            return template_args
+        n += 1

  reply	other threads:[~2022-05-09 11:23 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-07  1:23 Paul Smith
2022-05-07 11:19 ` Hannes Domani
2022-05-07 15:07   ` Paul Smith
2022-05-07 15:35     ` Jonathan Wakely
2022-05-07 19:07       ` Paul Smith
2022-05-07 19:51         ` Jonathan Wakely
2022-05-07 23:08           ` Paul Smith
2022-05-08  8:13             ` Jonathan Wakely
2022-05-08  8:16               ` Jonathan Wakely
2022-05-08 14:09                 ` Paul Smith
2022-05-08 14:36                   ` Jonathan Wakely
2022-05-08 19:44                 ` Paul Smith
2022-05-08 20:26                   ` Paul Smith
2022-05-09 10:47                     ` Hannes Domani
2022-05-09 10:52                       ` Hannes Domani
2022-05-09  9:32                   ` Jonathan Wakely
2022-05-09 11:23                     ` Jonathan Wakely [this message]
2022-05-09 14:05                       ` Paul Smith
2022-05-09 14:40                         ` Paul Smith
2022-05-07 15:44     ` Hannes Domani
2022-05-07 15:25 ` Jonathan Wakely

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='CAH6eHdRvJ8Vs9M=6QC+G4H=-PA4tyQOsmxz455EPBMomtRZ2eQ@mail.gmail.com' \
    --to=jwakely.gcc@gmail.com \
    --cc=gcc-help@gcc.gnu.org \
    --cc=paul@mad-scientist.net \
    /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
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).