Skip to content

[2.7] bpo-32962: Backport python-gdb.py and test_gdb.py from master (GH-7710) #7726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions Lib/test/test_gdb.py
Original file line number Diff line number Diff line change
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 sysconfig

from test import test_support
from test.test_support import run_unittest, findfile
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
python-gdb now catchs ValueError on read_var(): when Python has no debug
symbols for example.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
python-gdb now catchs ``UnicodeDecodeError`` exceptions when calling
``string()``.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python-gdb.py now supports also method-wrapper (wrapperobject) objects.
148 changes: 115 additions & 33 deletions Tools/gdb/libpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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):
Expand All @@ -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))

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down