[Python-checkins] bpo-32962: Backport python-gdb.py and test_gdb.py from master (GH-7726)

Victor Stinner webhook-mailer at python.org
Fri Jun 15 17:59:59 EDT 2018


https://github.com/python/cpython/commit/e36f94f204f3257a206a7766464a94230337fd18
commit: e36f94f204f3257a206a7766464a94230337fd18
branch: 2.7
author: Victor Stinner <vstinner at redhat.com>
committer: GitHub <noreply at github.com>
date: 2018-06-15T23:59:56+02:00
summary:

bpo-32962: Backport python-gdb.py and test_gdb.py from master (GH-7726)

* bpo-32962: python-gdb catchs ValueError on read_var() (GH-7692)

python-gdb now catchs ValueError on read_var(): when Python has no
debug symbols for example.

(cherry picked from commit 019d33b7a447e78057842332fb5d3bad01922122)

* bpo-32962: python-gdb catchs UnicodeDecodeError (GH-7693)

python-gdb now catchs UnicodeDecodeError exceptions when calling
string().

(cherry picked from commit d22fc0bc7de7882da204abe50884bbde2da4f9e7)

bpo-29367: python-gdb.py now supports also method-wrapper
(wrapperobject) objects.

(cherry picked from commit 611083331d534481ca7956a376e38fb0e9ef3854)

files:
A Misc/NEWS.d/next/Tools-Demos/2018-06-14-16-16-53.bpo-32962.2YfdwI.rst
A Misc/NEWS.d/next/Tools-Demos/2018-06-14-16-23-07.bpo-32962.Q3Dwns.rst
A Misc/NEWS.d/next/Tools-Demos/2018-06-15-23-07-50.bpo-29367.52w9Uq.rst
M Lib/test/test_gdb.py
M Tools/gdb/libpython.py

diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py
index dce3c5cdc78c..d49769e44edf 100644
--- a/Lib/test/test_gdb.py
+++ b/Lib/test/test_gdb.py
@@ -3,13 +3,14 @@
 # The code for testing gdb was adapted from similar work in Unladen Swallow's
 # Lib/test/test_jit_gdb.py
 
+import locale
 import os
 import re
 import subprocess
 import sys
 import sysconfig
+import textwrap
 import unittest
-import sysconfig
 
 from test import test_support
 from test.test_support import run_unittest, findfile
@@ -863,7 +864,24 @@ def test_pycfunction(self):
                                           breakpoint='time_gmtime',
                                           cmds_after_breakpoint=['py-bt-full'],
                                           )
-        self.assertIn('#0 <built-in function gmtime', gdb_output)
+        self.assertIn('#1 <built-in function gmtime', gdb_output)
+
+    @unittest.skipIf(python_is_optimized(),
+                     "Python was compiled with optimizations")
+    def test_wrapper_call(self):
+        cmd = textwrap.dedent('''
+            class MyList(list):
+                def __init__(self):
+                    super(MyList, self).__init__()   # wrapper_call()
+
+            print("first break point")
+            l = MyList()
+        ''')
+        # Verify with "py-bt":
+        gdb_output = self.get_stack_trace(cmd,
+                                          cmds_after_breakpoint=['break wrapper_call', 'continue', 'py-bt'])
+        self.assertRegexpMatches(gdb_output,
+                                 r"<method-wrapper u?'__init__' of MyList object at ")
 
 
 class PyPrintTests(DebuggerTests):
diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-06-14-16-16-53.bpo-32962.2YfdwI.rst b/Misc/NEWS.d/next/Tools-Demos/2018-06-14-16-16-53.bpo-32962.2YfdwI.rst
new file mode 100644
index 000000000000..de40070795e9
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2018-06-14-16-16-53.bpo-32962.2YfdwI.rst
@@ -0,0 +1,2 @@
+python-gdb now catchs ValueError on read_var(): when Python has no debug
+symbols for example.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-06-14-16-23-07.bpo-32962.Q3Dwns.rst b/Misc/NEWS.d/next/Tools-Demos/2018-06-14-16-23-07.bpo-32962.Q3Dwns.rst
new file mode 100644
index 000000000000..fc14261019a0
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2018-06-14-16-23-07.bpo-32962.Q3Dwns.rst
@@ -0,0 +1,2 @@
+python-gdb now catchs ``UnicodeDecodeError`` exceptions when calling
+``string()``.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-06-15-23-07-50.bpo-29367.52w9Uq.rst b/Misc/NEWS.d/next/Tools-Demos/2018-06-15-23-07-50.bpo-29367.52w9Uq.rst
new file mode 100644
index 000000000000..48e4124c089a
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2018-06-15-23-07-50.bpo-29367.52w9Uq.rst
@@ -0,0 +1 @@
+python-gdb.py now supports also method-wrapper (wrapperobject) objects.
diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py
index e218a31f59c1..3ae70974cc1c 100755
--- a/Tools/gdb/libpython.py
+++ b/Tools/gdb/libpython.py
@@ -241,12 +241,13 @@ def is_optimized_out(self):
 
     def safe_tp_name(self):
         try:
-            return self.type().field('tp_name').string()
-        except NullPyObjectPtr:
-            # NULL tp_name?
-            return 'unknown'
-        except RuntimeError:
-            # Can't even read the object at all?
+            ob_type = self.type()
+            tp_name = ob_type.field('tp_name')
+            return tp_name.string()
+        # NullPyObjectPtr: NULL tp_name?
+        # RuntimeError: Can't even read the object at all?
+        # UnicodeDecodeError: Failed to decode tp_name bytestring
+        except (NullPyObjectPtr, RuntimeError, UnicodeDecodeError):
             return 'unknown'
 
     def proxyval(self, visited):
@@ -320,7 +321,9 @@ def subclass_from_type(cls, t):
         try:
             tp_name = t.field('tp_name').string()
             tp_flags = int(t.field('tp_flags'))
-        except RuntimeError:
+        # RuntimeError: NULL pointers
+        # UnicodeDecodeError: string() fails to decode the bytestring
+        except (RuntimeError, UnicodeDecodeError):
             # Handle any kind of error e.g. NULL ptrs by simply using the base
             # class
             return cls
@@ -336,6 +339,7 @@ def subclass_from_type(cls, t):
                     'set' : PySetObjectPtr,
                     'frozenset' : PySetObjectPtr,
                     'builtin_function_or_method' : PyCFunctionObjectPtr,
+                    'method-wrapper': wrapperobject,
                     }
         if tp_name in name_map:
             return name_map[tp_name]
@@ -602,7 +606,10 @@ class PyCFunctionObjectPtr(PyObjectPtr):
 
     def proxyval(self, visited):
         m_ml = self.field('m_ml') # m_ml is a (PyMethodDef*)
-        ml_name = m_ml['ml_name'].string()
+        try:
+            ml_name = m_ml['ml_name'].string()
+        except UnicodeDecodeError:
+            ml_name = '<ml_name:UnicodeDecodeError>'
 
         pyop_m_self = self.pyop_field('m_self')
         if pyop_m_self.is_null():
@@ -1131,7 +1138,9 @@ def proxyval(self, visited):
         # Convert the int code points to unicode characters, and generate a
         # local unicode instance.
         # This splits surrogate pairs if sizeof(Py_UNICODE) is 2 here (in gdb).
-        result = u''.join([_unichr(ucs) for ucs in Py_UNICODEs])
+        result = u''.join([
+            (_unichr(ucs) if ucs <= 0x10ffff else '\ufffd')
+            for ucs in Py_UNICODEs])
         return result
 
     def write_repr(self, out, visited):
@@ -1144,6 +1153,41 @@ def write_repr(self, out, visited):
         out.write(val.lstrip('u'))
 
 
+class wrapperobject(PyObjectPtr):
+    _typename = 'wrapperobject'
+
+    def safe_name(self):
+        try:
+            name = self.field('descr')['d_base']['name'].string()
+            return repr(name)
+        except (NullPyObjectPtr, RuntimeError, UnicodeDecodeError):
+            return '<unknown name>'
+
+    def safe_tp_name(self):
+        try:
+            return self.field('self')['ob_type']['tp_name'].string()
+        except (NullPyObjectPtr, RuntimeError, UnicodeDecodeError):
+            return '<unknown tp_name>'
+
+    def safe_self_addresss(self):
+        try:
+            address = long(self.field('self'))
+            return '%#x' % address
+        except (NullPyObjectPtr, RuntimeError):
+            return '<failed to get self address>'
+
+    def proxyval(self, visited):
+        name = self.safe_name()
+        tp_name = self.safe_tp_name()
+        self_address = self.safe_self_addresss()
+        return ("<method-wrapper %s of %s object at %s>"
+                % (name, tp_name, self_address))
+
+    def write_repr(self, out, visited):
+        proxy = self.proxyval(visited)
+        out.write(proxy)
+
+
 def int_from_int(gdbval):
     return int(str(gdbval))
 
@@ -1176,11 +1220,13 @@ def to_string (self):
 
 def pretty_printer_lookup(gdbval):
     type = gdbval.type.unqualified()
-    if type.code == gdb.TYPE_CODE_PTR:
-        type = type.target().unqualified()
-        t = str(type)
-        if t in ("PyObject", "PyFrameObject"):
-            return PyObjectPtrPrinter(gdbval)
+    if type.code != gdb.TYPE_CODE_PTR:
+        return None
+
+    type = type.target().unqualified()
+    t = str(type)
+    if t in ("PyObject", "PyFrameObject", "PyUnicodeObject", "wrapperobject"):
+        return PyObjectPtrPrinter(gdbval)
 
 """
 During development, I've been manually invoking the code in this way:
@@ -1202,7 +1248,7 @@ def pretty_printer_lookup(gdbval):
   /usr/lib/debug/usr/lib/libpython2.6.so.1.0.debug-gdb.py
 """
 def register (obj):
-    if obj == None:
+    if obj is None:
         obj = gdb
 
     # Wire up the pretty-printer
@@ -1304,23 +1350,43 @@ def is_other_python_frame(self):
          '''
         if self.is_waiting_for_gil():
             return 'Waiting for the GIL'
-        elif self.is_gc_collect():
+
+        if self.is_gc_collect():
             return 'Garbage-collecting'
-        else:
-            # Detect invocations of PyCFunction instances:
-            older = self.older()
-            if older and older._gdbframe.name() == 'PyCFunction_Call':
-                # Within that frame:
-                #   "func" is the local containing the PyObject* of the
-                # PyCFunctionObject instance
-                #   "f" is the same value, but cast to (PyCFunctionObject*)
-                #   "self" is the (PyObject*) of the 'self'
-                try:
-                    # Use the prettyprinter for the func:
-                    func = older._gdbframe.read_var('func')
-                    return str(func)
-                except RuntimeError:
-                    return 'PyCFunction invocation (unable to read "func")'
+
+        # Detect invocations of PyCFunction instances:
+        frame = self._gdbframe
+        caller = frame.name()
+        if not caller:
+            return False
+
+        if caller == 'PyCFunction_Call':
+            arg_name = 'func'
+            # Within that frame:
+            #   "func" is the local containing the PyObject* of the
+            # PyCFunctionObject instance
+            #   "f" is the same value, but cast to (PyCFunctionObject*)
+            #   "self" is the (PyObject*) of the 'self'
+            try:
+                # Use the prettyprinter for the func:
+                func = frame.read_var(arg_name)
+                return str(func)
+            except ValueError:
+                return ('PyCFunction invocation (unable to read %s: '
+                        'missing debuginfos?)' % arg_name)
+            except RuntimeError:
+                return 'PyCFunction invocation (unable to read %s)' % arg_name
+
+        if caller == 'wrapper_call':
+            arg_name = 'wp'
+            try:
+                func = frame.read_var(arg_name)
+                return str(func)
+            except ValueError:
+                return ('<wrapper_call invocation (unable to read %s: '
+                        'missing debuginfos?)>' % arg_name)
+            except RuntimeError:
+                return '<wrapper_call invocation (unable to read %s)>' % arg_name
 
         # This frame isn't worth reporting:
         return False
@@ -1368,7 +1434,11 @@ def get_selected_frame(cls):
     def get_selected_python_frame(cls):
         '''Try to obtain the Frame for the python-related code in the selected
         frame, or None'''
-        frame = cls.get_selected_frame()
+        try:
+            frame = cls.get_selected_frame()
+        except gdb.error:
+            # No frame: Python didn't start yet
+            return None
 
         while frame:
             if frame.is_python_frame():
@@ -1509,6 +1579,10 @@ def invoke(self, args, from_tty):
 def move_in_stack(move_up):
     '''Move up or down the stack (for the py-up/py-down command)'''
     frame = Frame.get_selected_python_frame()
+    if not frame:
+        print('Unable to locate python frame')
+        return
+
     while frame:
         if move_up:
             iter_frame = frame.older()
@@ -1571,6 +1645,10 @@ def __init__(self):
 
     def invoke(self, args, from_tty):
         frame = Frame.get_selected_python_frame()
+        if not frame:
+            print('Unable to locate python frame')
+            return
+
         while frame:
             if frame.is_python_frame():
                 frame.print_summary()
@@ -1588,8 +1666,12 @@ def __init__(self):
 
 
     def invoke(self, args, from_tty):
-        sys.stdout.write('Traceback (most recent call first):\n')
         frame = Frame.get_selected_python_frame()
+        if not frame:
+            print('Unable to locate python frame')
+            return
+
+        sys.stdout.write('Traceback (most recent call first):\n')
         while frame:
             if frame.is_python_frame():
                 frame.print_traceback()



More information about the Python-checkins mailing list