Skip to content

Commit

Permalink
[3.6] bpo-30345: Update test_gdb.py and python-gdb.py from master (#1549
Browse files Browse the repository at this point in the history
)

* python-gdb.py supports method-wrapper

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

(cherry picked from commit 6110833)

* Update and enhance python-gdb.py

bpo-29259: Detect PyCFunction is the current frame, not only in the
older frame.
  • Loading branch information
vstinner committed May 12, 2017
1 parent 3dc7c52 commit d05f7fd
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 14 deletions.
22 changes: 20 additions & 2 deletions Lib/test/test_gdb.py
Expand Up @@ -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 locale

# Is this Python configured to support threads?
try:
Expand Down Expand Up @@ -845,7 +846,24 @@ def test_pycfunction(self):
breakpoint='time_gmtime',
cmds_after_breakpoint=['py-bt-full'],
)
self.assertIn('#0 <built-in method gmtime', gdb_output)
self.assertIn('#1 <built-in method 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().__init__() # wrapper_call()
id("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.assertRegex(gdb_output,
r"<method-wrapper u?'__init__' of MyList object at ")


class PyPrintTests(DebuggerTests):
Expand Down
6 changes: 6 additions & 0 deletions Misc/NEWS
Expand Up @@ -151,6 +151,12 @@ Documentation

- bpo-26985: Add missing info of code object in inspect documentation.

Tools/Demos
-----------

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

Tests
-----

Expand Down
64 changes: 52 additions & 12 deletions Tools/gdb/libpython.py
Expand Up @@ -362,6 +362,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]
Expand Down Expand Up @@ -1330,6 +1331,39 @@ def write_repr(self, out, visited):
out.write(quote)


class wrapperobject(PyObjectPtr):
_typename = 'wrapperobject'

def safe_name(self):
try:
name = self.field('descr')['d_base']['name'].string()
return repr(name)
except (NullPyObjectPtr, RuntimeError):
return '<unknown name>'

def safe_tp_name(self):
try:
return self.field('self')['ob_type']['tp_name'].string()
except (NullPyObjectPtr, RuntimeError):
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):
Expand Down Expand Up @@ -1364,11 +1398,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", "PyUnicodeObject"):
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:
Expand Down Expand Up @@ -1497,11 +1533,8 @@ def is_other_python_frame(self):
return 'Garbage-collecting'

# Detect invocations of PyCFunction instances:
older = self.older()
if not older:
return False

caller = older._gdbframe.name()
frame = self._gdbframe
caller = frame.name()
if not caller:
return False

Expand All @@ -1513,18 +1546,25 @@ def is_other_python_frame(self):
# "self" is the (PyObject*) of the 'self'
try:
# Use the prettyprinter for the func:
func = older._gdbframe.read_var('func')
func = frame.read_var('func')
return str(func)
except RuntimeError:
return 'PyCFunction invocation (unable to read "func")'

elif caller == '_PyCFunction_FastCallDict':
try:
func = older._gdbframe.read_var('func_obj')
func = frame.read_var('func_obj')
return str(func)
except RuntimeError:
return 'PyCFunction invocation (unable to read "func_obj")'

if caller == 'wrapper_call':
try:
func = frame.read_var('wp')
return str(func)
except RuntimeError:
return '<wrapper_call invocation>'

# This frame isn't worth reporting:
return False

Expand Down

0 comments on commit d05f7fd

Please sign in to comment.