Commit 2648f3f7 authored by Stefan Behnel's avatar Stefan Behnel

Update gdb support in libpython.py from CPython 3.7 (git rev 5fe59f8).

parent a7b253ab
...@@ -38,6 +38,9 @@ Features added ...@@ -38,6 +38,9 @@ Features added
* Signature annotations are now included in the signature docstring generated by * Signature annotations are now included in the signature docstring generated by
the ``embedsignature`` directive. Patch by Lisandro Dalcin (Github issue #1781). the ``embedsignature`` directive. Patch by Lisandro Dalcin (Github issue #1781).
* The gdb support for Python code (``libpython.py``) was updated to the latest
version in CPython 3.7 (git rev 5fe59f8).
* ``len(memoryview)`` can be used in nogil sections to get the size of the * ``len(memoryview)`` can be used in nogil sections to get the size of the
first dimension of a memory view (``shape[0]``). (Github issue #1733) first dimension of a memory view (``shape[0]``). (Github issue #1733)
......
...@@ -25,9 +25,10 @@ giving file/line information and the state of local variables ...@@ -25,9 +25,10 @@ giving file/line information and the state of local variables
In particular, given a gdb.Value corresponding to a PyObject* in the inferior In particular, given a gdb.Value corresponding to a PyObject* in the inferior
process, we can generate a "proxy value" within the gdb process. For example, process, we can generate a "proxy value" within the gdb process. For example,
given a PyObject* in the inferior process that is in fact a PyListObject* given a PyObject* in the inferior process that is in fact a PyListObject*
holding three PyObject* that turn out to be PyStringObject* instances, we can holding three PyObject* that turn out to be PyBytesObject* instances, we can
generate a proxy value within the gdb process that is a list of strings: generate a proxy value within the gdb process that is a list of bytes
["foo", "bar", "baz"] instances:
[b"foo", b"bar", b"baz"]
Doing so can be expensive for complicated graphs of objects, and could take Doing so can be expensive for complicated graphs of objects, and could take
some time, so we also have a "write_repr" method that writes a representation some time, so we also have a "write_repr" method that writes a representation
...@@ -46,70 +47,72 @@ the type names are known to the debugger ...@@ -46,70 +47,72 @@ the type names are known to the debugger
The module also extends gdb with some python-specific commands. The module also extends gdb with some python-specific commands.
''' '''
try: # NOTE: some gdbs are linked with Python 3, so this file should be dual-syntax
input = raw_input # compatible (2.6+ and 3.0+). See #19308.
except NameError:
pass
from __future__ import print_function
import gdb
import os import os
import re
import sys
import struct
import locale import locale
import atexit import sys
import warnings
import tempfile
import textwrap
import itertools
import gdb
try: if sys.version_info[0] >= 3:
xrange unichr = chr
except NameError:
xrange = range xrange = range
long = int
if sys.version_info[0] < 3:
# I think this is the only way to fix this bug :'(
# http://sourceware.org/bugzilla/show_bug.cgi?id=12285
out, err = sys.stdout, sys.stderr
reload(sys).setdefaultencoding('UTF-8')
sys.stdout = out
sys.stderr = err
# Look up the gdb.Type for some standard types: # Look up the gdb.Type for some standard types:
_type_char_ptr = gdb.lookup_type('char').pointer() # char* # Those need to be refreshed as types (pointer sizes) may change when
_type_unsigned_char_ptr = gdb.lookup_type('unsigned char').pointer() # gdb loads different executables
_type_void_ptr = gdb.lookup_type('void').pointer() # void*
SIZEOF_VOID_P = _type_void_ptr.sizeof def _type_char_ptr():
return gdb.lookup_type('char').pointer() # char*
def _type_unsigned_char_ptr():
return gdb.lookup_type('unsigned char').pointer() # unsigned char*
Py_TPFLAGS_HEAPTYPE = (1 << 9)
Py_TPFLAGS_INT_SUBCLASS = (1 << 23) def _type_unsigned_short_ptr():
return gdb.lookup_type('unsigned short').pointer()
def _type_unsigned_int_ptr():
return gdb.lookup_type('unsigned int').pointer()
def _sizeof_void_p():
return gdb.lookup_type('void').pointer().sizeof
# value computed later, see PyUnicodeObjectPtr.proxy()
_is_pep393 = None
Py_TPFLAGS_HEAPTYPE = (1 << 9)
Py_TPFLAGS_LONG_SUBCLASS = (1 << 24) Py_TPFLAGS_LONG_SUBCLASS = (1 << 24)
Py_TPFLAGS_LIST_SUBCLASS = (1 << 25) Py_TPFLAGS_LIST_SUBCLASS = (1 << 25)
Py_TPFLAGS_TUPLE_SUBCLASS = (1 << 26) Py_TPFLAGS_TUPLE_SUBCLASS = (1 << 26)
Py_TPFLAGS_STRING_SUBCLASS = (1 << 27)
Py_TPFLAGS_BYTES_SUBCLASS = (1 << 27) Py_TPFLAGS_BYTES_SUBCLASS = (1 << 27)
Py_TPFLAGS_UNICODE_SUBCLASS = (1 << 28) Py_TPFLAGS_UNICODE_SUBCLASS = (1 << 28)
Py_TPFLAGS_DICT_SUBCLASS = (1 << 29) Py_TPFLAGS_DICT_SUBCLASS = (1 << 29)
Py_TPFLAGS_BASE_EXC_SUBCLASS = (1 << 30) Py_TPFLAGS_BASE_EXC_SUBCLASS = (1 << 30)
Py_TPFLAGS_TYPE_SUBCLASS = (1 << 31) Py_TPFLAGS_TYPE_SUBCLASS = (1 << 31)
MAX_OUTPUT_LEN = 1024
MAX_OUTPUT_LEN=1024
hexdigits = "0123456789abcdef" hexdigits = "0123456789abcdef"
ENCODING = locale.getpreferredencoding() ENCODING = locale.getpreferredencoding()
EVALFRAME = '_PyEval_EvalFrameDefault'
class NullPyObjectPtr(RuntimeError): class NullPyObjectPtr(RuntimeError):
pass pass
def safety_limit(val): def safety_limit(val):
# Given a integer value from the process being debugged, limit it to some # Given an integer value from the process being debugged, limit it to some
# safety threshold so that arbitrary breakage within said process doesn't # safety threshold so that arbitrary breakage within said process doesn't
# break the gdb process too much (e.g. sizes of iterations, sizes of lists) # break the gdb process too much (e.g. sizes of iterations, sizes of lists)
return min(val, 1000) return min(val, 1000)
...@@ -118,20 +121,25 @@ def safety_limit(val): ...@@ -118,20 +121,25 @@ def safety_limit(val):
def safe_range(val): def safe_range(val):
# As per range, but don't trust the value too much: cap it to a safety # As per range, but don't trust the value too much: cap it to a safety
# threshold in case the data was corrupted # threshold in case the data was corrupted
return range(safety_limit(val)) return xrange(safety_limit(int(val)))
def write_unicode(file, text): if sys.version_info[0] >= 3:
def write_unicode(file, text):
file.write(text)
else:
def write_unicode(file, text):
# Write a byte or unicode string to file. Unicode strings are encoded to # Write a byte or unicode string to file. Unicode strings are encoded to
# ENCODING encoding with 'backslashreplace' error handler to avoid # ENCODING encoding with 'backslashreplace' error handler to avoid
# UnicodeEncodeError. # UnicodeEncodeError.
if not isinstance(text, str): if isinstance(text, unicode):
text = text.encode(ENCODING, 'backslashreplace') text = text.encode(ENCODING, 'backslashreplace')
file.write(text) file.write(text)
try:
def os_fsencode(filename): os_fsencode = os.fsencode
if isinstance(filename, str): # only encode in Py2 except AttributeError:
def os_fsencode(filename):
if not isinstance(filename, unicode):
return filename return filename
encoding = sys.getfilesystemencoding() encoding = sys.getfilesystemencoding()
if encoding == 'mbcs': if encoding == 'mbcs':
...@@ -147,13 +155,11 @@ def os_fsencode(filename): ...@@ -147,13 +155,11 @@ def os_fsencode(filename):
encoded.append(byte) encoded.append(byte)
return ''.join(encoded) return ''.join(encoded)
class StringTruncated(RuntimeError): class StringTruncated(RuntimeError):
pass pass
class TruncatedStringIO(object): class TruncatedStringIO(object):
'''Similar to cStringIO, but can truncate the output by raising a '''Similar to io.StringIO, but can truncate the output by raising a
StringTruncated exception''' StringTruncated exception'''
def __init__(self, maxlen=None): def __init__(self, maxlen=None):
self._val = '' self._val = ''
...@@ -171,41 +177,10 @@ class TruncatedStringIO(object): ...@@ -171,41 +177,10 @@ class TruncatedStringIO(object):
def getvalue(self): def getvalue(self):
return self._val return self._val
# pretty printer lookup
all_pretty_typenames = set()
class PrettyPrinterTrackerMeta(type):
def __init__(self, name, bases, dict):
super(PrettyPrinterTrackerMeta, self).__init__(name, bases, dict)
all_pretty_typenames.add(self._typename)
# Class decorator that adds a metaclass and recreates the class with it.
# Copied from 'six'. See Cython/Utils.py.
def _add_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
@_add_metaclass(PrettyPrinterTrackerMeta)
class PyObjectPtr(object): class PyObjectPtr(object):
""" """
Class wrapping a gdb.Value that's a either a (PyObject*) within the Class wrapping a gdb.Value that's either a (PyObject*) within the
inferior process, or some subclass pointer e.g. (PyStringObject*) inferior process, or some subclass pointer e.g. (PyBytesObject*)
There will be a subclass for every refined PyObject type that we care There will be a subclass for every refined PyObject type that we care
about. about.
...@@ -213,7 +188,6 @@ class PyObjectPtr(object): ...@@ -213,7 +188,6 @@ class PyObjectPtr(object):
Note that at every stage the underlying pointer could be NULL, point Note that at every stage the underlying pointer could be NULL, point
to corrupt data, etc; this is the debugger, after all. to corrupt data, etc; this is the debugger, after all.
""" """
_typename = 'PyObject' _typename = 'PyObject'
def __init__(self, gdbval, cast_to=None): def __init__(self, gdbval, cast_to=None):
...@@ -286,7 +260,7 @@ class PyObjectPtr(object): ...@@ -286,7 +260,7 @@ class PyObjectPtr(object):
return PyTypeObjectPtr(self.field('ob_type')) return PyTypeObjectPtr(self.field('ob_type'))
def is_null(self): def is_null(self):
return not self._gdbval return 0 == long(self._gdbval)
def is_optimized_out(self): def is_optimized_out(self):
''' '''
...@@ -347,7 +321,7 @@ class PyObjectPtr(object): ...@@ -347,7 +321,7 @@ class PyObjectPtr(object):
return '<%s at remote 0x%x>' % (self.tp_name, self.address) return '<%s at remote 0x%x>' % (self.tp_name, self.address)
return FakeRepr(self.safe_tp_name(), return FakeRepr(self.safe_tp_name(),
int(self._gdbval)) long(self._gdbval))
def write_repr(self, out, visited): def write_repr(self, out, visited):
''' '''
...@@ -386,44 +360,40 @@ class PyObjectPtr(object): ...@@ -386,44 +360,40 @@ class PyObjectPtr(object):
# class # class
return cls return cls
#print 'tp_flags = 0x%08x' % tp_flags #print('tp_flags = 0x%08x' % tp_flags)
#print 'tp_name = %r' % tp_name #print('tp_name = %r' % tp_name)
name_map = {'bool': PyBoolObjectPtr, name_map = {'bool': PyBoolObjectPtr,
'classobj': PyClassObjectPtr, 'classobj': PyClassObjectPtr,
'instance': PyInstanceObjectPtr,
'NoneType': PyNoneStructPtr, 'NoneType': PyNoneStructPtr,
'frame': PyFrameObjectPtr, 'frame': PyFrameObjectPtr,
'set' : PySetObjectPtr, 'set' : PySetObjectPtr,
'frozenset' : PySetObjectPtr, 'frozenset' : PySetObjectPtr,
'builtin_function_or_method' : PyCFunctionObjectPtr, 'builtin_function_or_method' : PyCFunctionObjectPtr,
'method-wrapper': wrapperobject,
} }
if tp_name in name_map: if tp_name in name_map:
return name_map[tp_name] return name_map[tp_name]
if tp_flags & (Py_TPFLAGS_HEAPTYPE|Py_TPFLAGS_TYPE_SUBCLASS): if tp_flags & Py_TPFLAGS_HEAPTYPE:
return PyTypeObjectPtr return HeapTypeObjectPtr
if tp_flags & Py_TPFLAGS_INT_SUBCLASS:
return PyIntObjectPtr
if tp_flags & Py_TPFLAGS_LONG_SUBCLASS: if tp_flags & Py_TPFLAGS_LONG_SUBCLASS:
return PyLongObjectPtr return PyLongObjectPtr
if tp_flags & Py_TPFLAGS_LIST_SUBCLASS: if tp_flags & Py_TPFLAGS_LIST_SUBCLASS:
return PyListObjectPtr return PyListObjectPtr
if tp_flags & Py_TPFLAGS_TUPLE_SUBCLASS: if tp_flags & Py_TPFLAGS_TUPLE_SUBCLASS:
return PyTupleObjectPtr return PyTupleObjectPtr
if tp_flags & Py_TPFLAGS_STRING_SUBCLASS: if tp_flags & Py_TPFLAGS_BYTES_SUBCLASS:
try:
gdb.lookup_type('PyBytesObject')
return PyBytesObjectPtr return PyBytesObjectPtr
except RuntimeError:
return PyStringObjectPtr
if tp_flags & Py_TPFLAGS_UNICODE_SUBCLASS: if tp_flags & Py_TPFLAGS_UNICODE_SUBCLASS:
return PyUnicodeObjectPtr return PyUnicodeObjectPtr
if tp_flags & Py_TPFLAGS_DICT_SUBCLASS: if tp_flags & Py_TPFLAGS_DICT_SUBCLASS:
return PyDictObjectPtr return PyDictObjectPtr
if tp_flags & Py_TPFLAGS_BASE_EXC_SUBCLASS: if tp_flags & Py_TPFLAGS_BASE_EXC_SUBCLASS:
return PyBaseExceptionObjectPtr return PyBaseExceptionObjectPtr
#if tp_flags & Py_TPFLAGS_TYPE_SUBCLASS:
# return PyTypeObjectPtr
# Use the base class: # Use the base class:
return cls return cls
...@@ -438,7 +408,7 @@ class PyObjectPtr(object): ...@@ -438,7 +408,7 @@ class PyObjectPtr(object):
p = PyObjectPtr(gdbval) p = PyObjectPtr(gdbval)
cls = cls.subclass_from_type(p.type()) cls = cls.subclass_from_type(p.type())
return cls(gdbval, cast_to=cls.get_gdb_type()) return cls(gdbval, cast_to=cls.get_gdb_type())
except RuntimeError as exc: except RuntimeError:
# Handle any kind of error e.g. NULL ptrs by simply using the base # Handle any kind of error e.g. NULL ptrs by simply using the base
# class # class
pass pass
...@@ -449,13 +419,11 @@ class PyObjectPtr(object): ...@@ -449,13 +419,11 @@ class PyObjectPtr(object):
return gdb.lookup_type(cls._typename).pointer() return gdb.lookup_type(cls._typename).pointer()
def as_address(self): def as_address(self):
return int(self._gdbval) return long(self._gdbval)
class PyVarObjectPtr(PyObjectPtr): class PyVarObjectPtr(PyObjectPtr):
_typename = 'PyVarObject' _typename = 'PyVarObject'
class ProxyAlreadyVisited(object): class ProxyAlreadyVisited(object):
''' '''
Placeholder proxy to use when protecting against infinite recursion due to Placeholder proxy to use when protecting against infinite recursion due to
...@@ -471,7 +439,7 @@ class ProxyAlreadyVisited(object): ...@@ -471,7 +439,7 @@ class ProxyAlreadyVisited(object):
def _write_instance_repr(out, visited, name, pyop_attrdict, address): def _write_instance_repr(out, visited, name, pyop_attrdict, address):
'''Shared code for use by old-style and new-style classes: '''Shared code for use by all classes:
write a representation to file-like object "out"''' write a representation to file-like object "out"'''
out.write('<') out.write('<')
out.write(name) out.write(name)
...@@ -480,7 +448,7 @@ def _write_instance_repr(out, visited, name, pyop_attrdict, address): ...@@ -480,7 +448,7 @@ def _write_instance_repr(out, visited, name, pyop_attrdict, address):
if isinstance(pyop_attrdict, PyDictObjectPtr): if isinstance(pyop_attrdict, PyDictObjectPtr):
out.write('(') out.write('(')
first = True first = True
for pyop_arg, pyop_val in pyop_attrdict.items(): for pyop_arg, pyop_val in pyop_attrdict.iteritems():
if not first: if not first:
out.write(', ') out.write(', ')
first = False first = False
...@@ -500,24 +468,27 @@ class InstanceProxy(object): ...@@ -500,24 +468,27 @@ class InstanceProxy(object):
def __repr__(self): def __repr__(self):
if isinstance(self.attrdict, dict): if isinstance(self.attrdict, dict):
kwargs = ', '.join("%s=%r" % (arg, val) for arg, val in self.attrdict.items()) kwargs = ', '.join(["%s=%r" % (arg, val)
return '<%s(%s) at remote 0x%x>' % ( for arg, val in self.attrdict.iteritems()])
self.cl_name, kwargs, self.address) return '<%s(%s) at remote 0x%x>' % (self.cl_name,
kwargs, self.address)
else: else:
return '<%s at remote 0x%x>' % ( return '<%s at remote 0x%x>' % (self.cl_name,
self.cl_name, self.address) self.address)
def _PyObject_VAR_SIZE(typeobj, nitems): def _PyObject_VAR_SIZE(typeobj, nitems):
if _PyObject_VAR_SIZE._type_size_t is None:
_PyObject_VAR_SIZE._type_size_t = gdb.lookup_type('size_t')
return ( ( typeobj.field('tp_basicsize') + return ( ( typeobj.field('tp_basicsize') +
nitems * typeobj.field('tp_itemsize') + nitems * typeobj.field('tp_itemsize') +
(SIZEOF_VOID_P - 1) (_sizeof_void_p() - 1)
) & ~(SIZEOF_VOID_P - 1) ) & ~(_sizeof_void_p() - 1)
).cast(gdb.lookup_type('size_t')) ).cast(_PyObject_VAR_SIZE._type_size_t)
_PyObject_VAR_SIZE._type_size_t = None
class HeapTypeObjectPtr(PyObjectPtr):
class PyTypeObjectPtr(PyObjectPtr): _typename = 'PyObject'
_typename = 'PyTypeObject'
def get_attr_dict(self): def get_attr_dict(self):
''' '''
...@@ -536,9 +507,9 @@ class PyTypeObjectPtr(PyObjectPtr): ...@@ -536,9 +507,9 @@ class PyTypeObjectPtr(PyObjectPtr):
size = _PyObject_VAR_SIZE(typeobj, tsize) size = _PyObject_VAR_SIZE(typeobj, tsize)
dictoffset += size dictoffset += size
assert dictoffset > 0 assert dictoffset > 0
assert dictoffset % SIZEOF_VOID_P == 0 assert dictoffset % _sizeof_void_p() == 0
dictptr = self._gdbval.cast(_type_char_ptr) + dictoffset dictptr = self._gdbval.cast(_type_char_ptr()) + dictoffset
PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer() PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer()
dictptr = dictptr.cast(PyObjectPtrPtr) dictptr = dictptr.cast(PyObjectPtrPtr)
return PyObjectPtr.from_pyobject_ptr(dictptr.dereference()) return PyObjectPtr.from_pyobject_ptr(dictptr.dereference())
...@@ -551,7 +522,7 @@ class PyTypeObjectPtr(PyObjectPtr): ...@@ -551,7 +522,7 @@ class PyTypeObjectPtr(PyObjectPtr):
def proxyval(self, visited): def proxyval(self, visited):
''' '''
Support for new-style classes. Support for classes.
Currently we just locate the dictionary using a transliteration to Currently we just locate the dictionary using a transliteration to
python of _PyObject_GetDictPtr, ignoring descriptors python of _PyObject_GetDictPtr, ignoring descriptors
...@@ -568,8 +539,8 @@ class PyTypeObjectPtr(PyObjectPtr): ...@@ -568,8 +539,8 @@ class PyTypeObjectPtr(PyObjectPtr):
attr_dict = {} attr_dict = {}
tp_name = self.safe_tp_name() tp_name = self.safe_tp_name()
# New-style class: # Class:
return InstanceProxy(tp_name, attr_dict, int(self._gdbval)) return InstanceProxy(tp_name, attr_dict, long(self._gdbval))
def write_repr(self, out, visited): def write_repr(self, out, visited):
# Guard against infinite loops: # Guard against infinite loops:
...@@ -578,16 +549,9 @@ class PyTypeObjectPtr(PyObjectPtr): ...@@ -578,16 +549,9 @@ class PyTypeObjectPtr(PyObjectPtr):
return return
visited.add(self.as_address()) visited.add(self.as_address())
try: pyop_attrdict = self.get_attr_dict()
tp_name = self.field('tp_name').string() _write_instance_repr(out, visited,
except RuntimeError: self.safe_tp_name(), pyop_attrdict, self.as_address())
tp_name = 'unknown'
out.write('<type %s at remote 0x%x>' % (tp_name, self.as_address()))
# pyop_attrdict = self.get_attr_dict()
# _write_instance_repr(out, visited,
# self.safe_tp_name(), pyop_attrdict, self.as_address())
class ProxyException(Exception): class ProxyException(Exception):
def __init__(self, tp_name, args): def __init__(self, tp_name, args):
...@@ -597,7 +561,6 @@ class ProxyException(Exception): ...@@ -597,7 +561,6 @@ class ProxyException(Exception):
def __repr__(self): def __repr__(self):
return '%s%r' % (self.tp_name, self.args) return '%s%r' % (self.tp_name, self.args)
class PyBaseExceptionObjectPtr(PyObjectPtr): class PyBaseExceptionObjectPtr(PyObjectPtr):
""" """
Class wrapping a gdb.Value that's a PyBaseExceptionObject* i.e. an exception Class wrapping a gdb.Value that's a PyBaseExceptionObject* i.e. an exception
...@@ -624,7 +587,6 @@ class PyBaseExceptionObjectPtr(PyObjectPtr): ...@@ -624,7 +587,6 @@ class PyBaseExceptionObjectPtr(PyObjectPtr):
out.write(self.safe_tp_name()) out.write(self.safe_tp_name())
self.write_field_repr('args', out, visited) self.write_field_repr('args', out, visited)
class PyClassObjectPtr(PyObjectPtr): class PyClassObjectPtr(PyObjectPtr):
""" """
Class wrapping a gdb.Value that's a PyClassObject* i.e. a <classobj> Class wrapping a gdb.Value that's a PyClassObject* i.e. a <classobj>
...@@ -640,17 +602,17 @@ class BuiltInFunctionProxy(object): ...@@ -640,17 +602,17 @@ class BuiltInFunctionProxy(object):
def __repr__(self): def __repr__(self):
return "<built-in function %s>" % self.ml_name return "<built-in function %s>" % self.ml_name
class BuiltInMethodProxy(object): class BuiltInMethodProxy(object):
def __init__(self, ml_name, pyop_m_self): def __init__(self, ml_name, pyop_m_self):
self.ml_name = ml_name self.ml_name = ml_name
self.pyop_m_self = pyop_m_self self.pyop_m_self = pyop_m_self
def __repr__(self): def __repr__(self):
return '<built-in method %s of %s object at remote 0x%x>' % ( return ('<built-in method %s of %s object at remote 0x%x>'
self.ml_name, self.pyop_m_self.safe_tp_name(), % (self.ml_name,
self.pyop_m_self.safe_tp_name(),
self.pyop_m_self.as_address()) self.pyop_m_self.as_address())
)
class PyCFunctionObjectPtr(PyObjectPtr): class PyCFunctionObjectPtr(PyObjectPtr):
""" """
...@@ -709,17 +671,21 @@ class PyDictObjectPtr(PyObjectPtr): ...@@ -709,17 +671,21 @@ class PyDictObjectPtr(PyObjectPtr):
def iteritems(self): def iteritems(self):
''' '''
Yields a sequence of (PyObjectPtr key, PyObjectPtr value) pairs, Yields a sequence of (PyObjectPtr key, PyObjectPtr value) pairs,
analagous to dict.items() analogous to dict.iteritems()
''' '''
for i in safe_range(self.field('ma_mask') + 1): keys = self.field('ma_keys')
ep = self.field('ma_table') + i values = self.field('ma_values')
entries, nentries = self._get_entries(keys)
for i in safe_range(nentries):
ep = entries[i]
if long(values):
pyop_value = PyObjectPtr.from_pyobject_ptr(values[i])
else:
pyop_value = PyObjectPtr.from_pyobject_ptr(ep['me_value']) pyop_value = PyObjectPtr.from_pyobject_ptr(ep['me_value'])
if not pyop_value.is_null(): if not pyop_value.is_null():
pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key']) pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key'])
yield (pyop_key, pyop_value) yield (pyop_key, pyop_value)
items = iteritems
def proxyval(self, visited): def proxyval(self, visited):
# Guard against infinite loops: # Guard against infinite loops:
if self.as_address() in visited: if self.as_address() in visited:
...@@ -727,7 +693,7 @@ class PyDictObjectPtr(PyObjectPtr): ...@@ -727,7 +693,7 @@ class PyDictObjectPtr(PyObjectPtr):
visited.add(self.as_address()) visited.add(self.as_address())
result = {} result = {}
for pyop_key, pyop_value in self.items(): for pyop_key, pyop_value in self.iteritems():
proxy_key = pyop_key.proxyval(visited) proxy_key = pyop_key.proxyval(visited)
proxy_value = pyop_value.proxyval(visited) proxy_value = pyop_value.proxyval(visited)
result[proxy_key] = proxy_value result[proxy_key] = proxy_value
...@@ -742,7 +708,7 @@ class PyDictObjectPtr(PyObjectPtr): ...@@ -742,7 +708,7 @@ class PyDictObjectPtr(PyObjectPtr):
out.write('{') out.write('{')
first = True first = True
for pyop_key, pyop_value in self.items(): for pyop_key, pyop_value in self.iteritems():
if not first: if not first:
out.write(', ') out.write(', ')
first = False first = False
...@@ -751,52 +717,31 @@ class PyDictObjectPtr(PyObjectPtr): ...@@ -751,52 +717,31 @@ class PyDictObjectPtr(PyObjectPtr):
pyop_value.write_repr(out, visited) pyop_value.write_repr(out, visited)
out.write('}') out.write('}')
def _get_entries(self, keys):
dk_nentries = int(keys['dk_nentries'])
dk_size = int(keys['dk_size'])
try:
# <= Python 3.5
return keys['dk_entries'], dk_size
except RuntimeError:
# >= Python 3.6
pass
class PyInstanceObjectPtr(PyObjectPtr): if dk_size <= 0xFF:
_typename = 'PyInstanceObject' offset = dk_size
elif dk_size <= 0xFFFF:
def proxyval(self, visited): offset = 2 * dk_size
# Guard against infinite loops: elif dk_size <= 0xFFFFFFFF:
if self.as_address() in visited: offset = 4 * dk_size
return ProxyAlreadyVisited('<...>') else:
visited.add(self.as_address()) offset = 8 * dk_size
# Get name of class:
in_class = self.pyop_field('in_class')
cl_name = in_class.pyop_field('cl_name').proxyval(visited)
# Get dictionary of instance attributes:
in_dict = self.pyop_field('in_dict').proxyval(visited)
# Old-style class:
return InstanceProxy(cl_name, in_dict, int(self._gdbval))
def write_repr(self, out, visited):
# Guard against infinite loops:
if self.as_address() in visited:
out.write('<...>')
return
visited.add(self.as_address())
# Old-style class:
# Get name of class:
in_class = self.pyop_field('in_class')
cl_name = in_class.pyop_field('cl_name').proxyval(visited)
# Get dictionary of instance attributes:
pyop_in_dict = self.pyop_field('in_dict')
_write_instance_repr(out, visited,
cl_name, pyop_in_dict, self.as_address())
class PyIntObjectPtr(PyObjectPtr): ent_addr = keys['dk_indices']['as_1'].address
_typename = 'PyIntObject' ent_addr = ent_addr.cast(_type_unsigned_char_ptr()) + offset
ent_ptr_t = gdb.lookup_type('PyDictKeyEntry').pointer()
ent_addr = ent_addr.cast(ent_ptr_t)
def proxyval(self, visited): return ent_addr, dk_nentries
result = int_from_int(self.field('ob_ival'))
return result
class PyListObjectPtr(PyObjectPtr): class PyListObjectPtr(PyObjectPtr):
...@@ -832,7 +777,6 @@ class PyListObjectPtr(PyObjectPtr): ...@@ -832,7 +777,6 @@ class PyListObjectPtr(PyObjectPtr):
element.write_repr(out, visited) element.write_repr(out, visited)
out.write(']') out.write(']')
class PyLongObjectPtr(PyObjectPtr): class PyLongObjectPtr(PyObjectPtr):
_typename = 'PyLongObject' _typename = 'PyLongObject'
...@@ -854,9 +798,9 @@ class PyLongObjectPtr(PyObjectPtr): ...@@ -854,9 +798,9 @@ class PyLongObjectPtr(PyObjectPtr):
#define PyLong_SHIFT 30 #define PyLong_SHIFT 30
#define PyLong_SHIFT 15 #define PyLong_SHIFT 15
''' '''
ob_size = int(self.field('ob_size')) ob_size = long(self.field('ob_size'))
if ob_size == 0: if ob_size == 0:
return int(0) return 0
ob_digit = self.field('ob_digit') ob_digit = self.field('ob_digit')
...@@ -865,7 +809,7 @@ class PyLongObjectPtr(PyObjectPtr): ...@@ -865,7 +809,7 @@ class PyLongObjectPtr(PyObjectPtr):
else: else:
SHIFT = 30 SHIFT = 30
digits = [ob_digit[i] * (1 << (SHIFT*i)) digits = [long(ob_digit[i]) * 2**(SHIFT*i)
for i in safe_range(abs(ob_size))] for i in safe_range(abs(ob_size))]
result = sum(digits) result = sum(digits)
if ob_size < 0: if ob_size < 0:
...@@ -883,13 +827,11 @@ class PyBoolObjectPtr(PyLongObjectPtr): ...@@ -883,13 +827,11 @@ class PyBoolObjectPtr(PyLongObjectPtr):
Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two
<bool> instances (Py_True/Py_False) within the process being debugged. <bool> instances (Py_True/Py_False) within the process being debugged.
""" """
_typename = 'PyBoolObject'
def proxyval(self, visited): def proxyval(self, visited):
castto = gdb.lookup_type('PyLongObject').pointer() if PyLongObjectPtr.proxyval(self, visited):
self._gdbval = self._gdbval.cast(castto) return True
return bool(PyLongObjectPtr(self._gdbval).proxyval(visited)) else:
return False
class PyNoneStructPtr(PyObjectPtr): class PyNoneStructPtr(PyObjectPtr):
""" """
...@@ -939,10 +881,10 @@ class PyFrameObjectPtr(PyObjectPtr): ...@@ -939,10 +881,10 @@ class PyFrameObjectPtr(PyObjectPtr):
the global variables of this frame the global variables of this frame
''' '''
if self.is_optimized_out(): if self.is_optimized_out():
return return ()
pyop_globals = self.pyop_field('f_globals') pyop_globals = self.pyop_field('f_globals')
return iter(pyop_globals.items()) return pyop_globals.iteritems()
def iter_builtins(self): def iter_builtins(self):
''' '''
...@@ -950,10 +892,10 @@ class PyFrameObjectPtr(PyObjectPtr): ...@@ -950,10 +892,10 @@ class PyFrameObjectPtr(PyObjectPtr):
the builtin variables the builtin variables
''' '''
if self.is_optimized_out(): if self.is_optimized_out():
return return ()
pyop_builtins = self.pyop_field('f_builtins') pyop_builtins = self.pyop_field('f_builtins')
return iter(pyop_builtins.items()) return pyop_builtins.iteritems()
def get_var_by_name(self, name): def get_var_by_name(self, name):
''' '''
...@@ -989,7 +931,7 @@ class PyFrameObjectPtr(PyObjectPtr): ...@@ -989,7 +931,7 @@ class PyFrameObjectPtr(PyObjectPtr):
if self.is_optimized_out(): if self.is_optimized_out():
return None return None
f_trace = self.field('f_trace') f_trace = self.field('f_trace')
if f_trace: if long(f_trace) != 0:
# we have a non-NULL f_trace: # we have a non-NULL f_trace:
return self.f_lineno return self.f_lineno
else: else:
...@@ -1004,7 +946,11 @@ class PyFrameObjectPtr(PyObjectPtr): ...@@ -1004,7 +946,11 @@ class PyFrameObjectPtr(PyObjectPtr):
if self.is_optimized_out(): if self.is_optimized_out():
return '(frame information optimized out)' return '(frame information optimized out)'
filename = self.filename() filename = self.filename()
with open(os_fsencode(filename), 'r') as f: try:
f = open(os_fsencode(filename), 'r')
except IOError:
return None
with f:
all_lines = f.readlines() all_lines = f.readlines()
# Convert from 1-based current_line_num to 0-based list offset: # Convert from 1-based current_line_num to 0-based list offset:
return all_lines[self.current_line_num()-1] return all_lines[self.current_line_num()-1]
...@@ -1030,25 +976,39 @@ class PyFrameObjectPtr(PyObjectPtr): ...@@ -1030,25 +976,39 @@ class PyFrameObjectPtr(PyObjectPtr):
out.write(')') out.write(')')
def print_traceback(self):
if self.is_optimized_out():
sys.stdout.write(' (frame information optimized out)\n')
return
visited = set()
sys.stdout.write(' File "%s", line %i, in %s\n'
% (self.co_filename.proxyval(visited),
self.current_line_num(),
self.co_name.proxyval(visited)))
class PySetObjectPtr(PyObjectPtr): class PySetObjectPtr(PyObjectPtr):
_typename = 'PySetObject' _typename = 'PySetObject'
@classmethod
def _dummy_key(self):
return gdb.lookup_global_symbol('_PySet_Dummy').value()
def __iter__(self):
dummy_ptr = self._dummy_key()
table = self.field('table')
for i in safe_range(self.field('mask') + 1):
setentry = table[i]
key = setentry['key']
if key != 0 and key != dummy_ptr:
yield PyObjectPtr.from_pyobject_ptr(key)
def proxyval(self, visited): def proxyval(self, visited):
# Guard against infinite loops: # Guard against infinite loops:
if self.as_address() in visited: if self.as_address() in visited:
return ProxyAlreadyVisited('%s(...)' % self.safe_tp_name()) return ProxyAlreadyVisited('%s(...)' % self.safe_tp_name())
visited.add(self.as_address()) visited.add(self.as_address())
members = [] members = (key.proxyval(visited) for key in self)
table = self.field('table')
for i in safe_range(self.field('mask')+1):
setentry = table[i]
key = setentry['key']
if key != 0:
key_proxy = PyObjectPtr.from_pyobject_ptr(key).proxyval(visited)
if key_proxy != '<dummy key>':
members.append(key_proxy)
if self.safe_tp_name() == 'frozenset': if self.safe_tp_name() == 'frozenset':
return frozenset(members) return frozenset(members)
else: else:
...@@ -1077,18 +1037,11 @@ class PySetObjectPtr(PyObjectPtr): ...@@ -1077,18 +1037,11 @@ class PySetObjectPtr(PyObjectPtr):
out.write('{') out.write('{')
first = True first = True
table = self.field('table') for key in self:
for i in safe_range(self.field('mask')+1):
setentry = table[i]
key = setentry['key']
if key != 0:
pyop_key = PyObjectPtr.from_pyobject_ptr(key)
key_proxy = pyop_key.proxyval(visited) # FIXME!
if key_proxy != '<dummy key>':
if not first: if not first:
out.write(', ') out.write(', ')
first = False first = False
pyop_key.write_repr(out, visited) key.write_repr(out, visited)
out.write('}') out.write('}')
if tp_name != 'set': if tp_name != 'set':
...@@ -1101,13 +1054,13 @@ class PyBytesObjectPtr(PyObjectPtr): ...@@ -1101,13 +1054,13 @@ class PyBytesObjectPtr(PyObjectPtr):
def __str__(self): def __str__(self):
field_ob_size = self.field('ob_size') field_ob_size = self.field('ob_size')
field_ob_sval = self.field('ob_sval') field_ob_sval = self.field('ob_sval')
return ''.join(struct.pack('b', field_ob_sval[i]) char_ptr = field_ob_sval.address.cast(_type_unsigned_char_ptr())
for i in safe_range(field_ob_size)) return ''.join([chr(char_ptr[i]) for i in safe_range(field_ob_size)])
def proxyval(self, visited): def proxyval(self, visited):
return str(self) return str(self)
def write_repr(self, out, visited, py3=True): def write_repr(self, out, visited):
# Write this out as a Python 3 bytes literal, i.e. with a "b" prefix # Write this out as a Python 3 bytes literal, i.e. with a "b" prefix
# Get a PyStringObject* within the Python 2 gdb process: # Get a PyStringObject* within the Python 2 gdb process:
...@@ -1118,10 +1071,7 @@ class PyBytesObjectPtr(PyObjectPtr): ...@@ -1118,10 +1071,7 @@ class PyBytesObjectPtr(PyObjectPtr):
quote = "'" quote = "'"
if "'" in proxy and not '"' in proxy: if "'" in proxy and not '"' in proxy:
quote = '"' quote = '"'
if py3:
out.write('b') out.write('b')
out.write(quote) out.write(quote)
for byte in proxy: for byte in proxy:
if byte == quote or byte == '\\': if byte == quote or byte == '\\':
...@@ -1145,9 +1095,6 @@ class PyBytesObjectPtr(PyObjectPtr): ...@@ -1145,9 +1095,6 @@ class PyBytesObjectPtr(PyObjectPtr):
class PyStringObjectPtr(PyBytesObjectPtr): class PyStringObjectPtr(PyBytesObjectPtr):
_typename = 'PyStringObject' _typename = 'PyStringObject'
def write_repr(self, out, visited):
return super(PyStringObjectPtr, self).write_repr(out, visited, py3=False)
class PyTupleObjectPtr(PyObjectPtr): class PyTupleObjectPtr(PyObjectPtr):
_typename = 'PyTupleObject' _typename = 'PyTupleObject'
...@@ -1163,8 +1110,8 @@ class PyTupleObjectPtr(PyObjectPtr): ...@@ -1163,8 +1110,8 @@ class PyTupleObjectPtr(PyObjectPtr):
return ProxyAlreadyVisited('(...)') return ProxyAlreadyVisited('(...)')
visited.add(self.as_address()) visited.add(self.as_address())
result = tuple([PyObjectPtr.from_pyobject_ptr(self[i]).proxyval(visited) result = tuple(PyObjectPtr.from_pyobject_ptr(self[i]).proxyval(visited)
for i in safe_range(int_from_int(self.field('ob_size')))]) for i in safe_range(int_from_int(self.field('ob_size'))))
return result return result
def write_repr(self, out, visited): def write_repr(self, out, visited):
...@@ -1185,6 +1132,9 @@ class PyTupleObjectPtr(PyObjectPtr): ...@@ -1185,6 +1132,9 @@ class PyTupleObjectPtr(PyObjectPtr):
else: else:
out.write(')') out.write(')')
class PyTypeObjectPtr(PyObjectPtr):
_typename = 'PyTypeObject'
def _unichr_is_printable(char): def _unichr_is_printable(char):
# Logic adapted from Python 3's Tools/unicode/makeunicodedata.py # Logic adapted from Python 3's Tools/unicode/makeunicodedata.py
...@@ -1193,12 +1143,8 @@ def _unichr_is_printable(char): ...@@ -1193,12 +1143,8 @@ def _unichr_is_printable(char):
import unicodedata import unicodedata
return unicodedata.category(char) not in ("C", "Z") return unicodedata.category(char) not in ("C", "Z")
if sys.maxunicode >= 0x10000: if sys.maxunicode >= 0x10000:
try:
_unichr = unichr _unichr = unichr
except NameError:
_unichr = chr
else: else:
# Needed for proper surrogate support if sizeof(Py_UNICODE) is 2 in gdb # Needed for proper surrogate support if sizeof(Py_UNICODE) is 2 in gdb
def _unichr(x): def _unichr(x):
...@@ -1218,15 +1164,46 @@ class PyUnicodeObjectPtr(PyObjectPtr): ...@@ -1218,15 +1164,46 @@ class PyUnicodeObjectPtr(PyObjectPtr):
return _type_Py_UNICODE.sizeof return _type_Py_UNICODE.sizeof
def proxyval(self, visited): def proxyval(self, visited):
# From unicodeobject.h: global _is_pep393
# Py_ssize_t length; /* Length of raw Unicode data in buffer */ if _is_pep393 is None:
# Py_UNICODE *str; /* Raw Unicode buffer */ fields = gdb.lookup_type('PyUnicodeObject').target().fields()
field_length = int(self.field('length')) _is_pep393 = 'data' in [f.name for f in fields]
if _is_pep393:
# Python 3.3 and newer
may_have_surrogates = False
compact = self.field('_base')
ascii = compact['_base']
state = ascii['state']
is_compact_ascii = (int(state['ascii']) and int(state['compact']))
if not int(state['ready']):
# string is not ready
field_length = long(compact['wstr_length'])
may_have_surrogates = True
field_str = ascii['wstr']
else:
field_length = long(ascii['length'])
if is_compact_ascii:
field_str = ascii.address + 1
elif int(state['compact']):
field_str = compact.address + 1
else:
field_str = self.field('data')['any']
repr_kind = int(state['kind'])
if repr_kind == 1:
field_str = field_str.cast(_type_unsigned_char_ptr())
elif repr_kind == 2:
field_str = field_str.cast(_type_unsigned_short_ptr())
elif repr_kind == 4:
field_str = field_str.cast(_type_unsigned_int_ptr())
else:
# Python 3.2 and earlier
field_length = long(self.field('length'))
field_str = self.field('str') field_str = self.field('str')
may_have_surrogates = self.char_width() == 2
# Gather a list of ints from the Py_UNICODE array; these are either # Gather a list of ints from the Py_UNICODE array; these are either
# UCS-2 or UCS-4 code points: # UCS-1, UCS-2 or UCS-4 code points:
if self.char_width() > 2: if not may_have_surrogates:
Py_UNICODEs = [int(field_str[i]) for i in safe_range(field_length)] Py_UNICODEs = [int(field_str[i]) for i in safe_range(field_length)]
else: else:
# A more elaborate routine if sizeof(Py_UNICODE) is 2 in the # A more elaborate routine if sizeof(Py_UNICODE) is 2 in the
...@@ -1253,24 +1230,19 @@ class PyUnicodeObjectPtr(PyObjectPtr): ...@@ -1253,24 +1230,19 @@ class PyUnicodeObjectPtr(PyObjectPtr):
# Convert the int code points to unicode characters, and generate a # Convert the int code points to unicode characters, and generate a
# local unicode instance. # local unicode instance.
# This splits surrogate pairs if sizeof(Py_UNICODE) is 2 here (in gdb). # 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 return result
def write_repr(self, out, visited): def write_repr(self, out, visited):
# Write this out as a Python 3 str literal, i.e. without a "u" prefix
# Get a PyUnicodeObject* within the Python 2 gdb process: # Get a PyUnicodeObject* within the Python 2 gdb process:
proxy = self.proxyval(visited) proxy = self.proxyval(visited)
# Transliteration of Python 3's Object/unicodeobject.c:unicode_repr # Transliteration of Python 3's Object/unicodeobject.c:unicode_repr
# to Python 2: # to Python 2:
try:
gdb.parse_and_eval('PyString_Type')
except RuntimeError:
# Python 3, don't write 'u' as prefix
pass
else:
# Python 2, write the 'u'
out.write('u')
if "'" in proxy and '"' not in proxy: if "'" in proxy and '"' not in proxy:
quote = '"' quote = '"'
else: else:
...@@ -1371,16 +1343,40 @@ class PyUnicodeObjectPtr(PyObjectPtr): ...@@ -1371,16 +1343,40 @@ class PyUnicodeObjectPtr(PyObjectPtr):
out.write(quote) out.write(quote)
def __unicode__(self):
return self.proxyval(set())
def __str__(self): class wrapperobject(PyObjectPtr):
# In Python 3, everything is unicode (including attributes of e.g. _typename = 'wrapperobject'
# code objects, such as function names). The Python 2 debugger code
# uses PyUnicodePtr objects to format strings etc, whereas with a def safe_name(self):
# Python 2 debuggee we'd get PyStringObjectPtr instances with __str__. try:
# Be compatible with that. name = self.field('descr')['d_base']['name'].string()
return unicode(self).encode('UTF-8') 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): def int_from_int(gdbval):
...@@ -1413,12 +1409,14 @@ class PyObjectPtrPrinter: ...@@ -1413,12 +1409,14 @@ class PyObjectPtrPrinter:
proxyval = pyop.proxyval(set()) proxyval = pyop.proxyval(set())
return stringify(proxyval) return stringify(proxyval)
def pretty_printer_lookup(gdbval): def pretty_printer_lookup(gdbval):
type = gdbval.type.unqualified() type = gdbval.type.unqualified()
if type.code == gdb.TYPE_CODE_PTR: if type.code != gdb.TYPE_CODE_PTR:
return None
type = type.target().unqualified() type = type.target().unqualified()
if str(type) in all_pretty_typenames: t = str(type)
if t in ("PyObject", "PyFrameObject", "PyUnicodeObject", "wrapperobject"):
return PyObjectPtrPrinter(gdbval) return PyObjectPtrPrinter(gdbval)
""" """
...@@ -1440,22 +1438,21 @@ that this python file is installed to the same path as the library (or its ...@@ -1440,22 +1438,21 @@ that this python file is installed to the same path as the library (or its
/usr/lib/libpython2.6.so.1.0-gdb.py /usr/lib/libpython2.6.so.1.0-gdb.py
/usr/lib/debug/usr/lib/libpython2.6.so.1.0.debug-gdb.py /usr/lib/debug/usr/lib/libpython2.6.so.1.0.debug-gdb.py
""" """
def register (obj):
def register(obj):
if obj is None: if obj is None:
obj = gdb obj = gdb
# Wire up the pretty-printer # Wire up the pretty-printer
obj.pretty_printers.append(pretty_printer_lookup) obj.pretty_printers.append(pretty_printer_lookup)
register(gdb.current_objfile()) register (gdb.current_objfile ())
# Unfortunately, the exact API exposed by the gdb module varies somewhat # Unfortunately, the exact API exposed by the gdb module varies somewhat
# from build to build # from build to build
# See http://bugs.python.org/issue8279?#msg102276 # See http://bugs.python.org/issue8279?#msg102276
class Frame(object): class Frame(object):
''' '''
Wrapper for gdb.Frame, adding various methods Wrapper for gdb.Frame, adding various methods
...@@ -1500,9 +1497,26 @@ class Frame(object): ...@@ -1500,9 +1497,26 @@ class Frame(object):
iter_frame = iter_frame.newer() iter_frame = iter_frame.newer()
return index return index
def is_evalframeex(self): # We divide frames into:
'''Is this a PyEval_EvalFrameEx frame?''' # - "python frames":
if self._gdbframe.name() == 'PyEval_EvalFrameEx': # - "bytecode frames" i.e. PyEval_EvalFrameEx
# - "other python frames": things that are of interest from a python
# POV, but aren't bytecode (e.g. GC, GIL)
# - everything else
def is_python_frame(self):
'''Is this a _PyEval_EvalFrameDefault frame, or some other important
frame? (see is_other_python_frame for what "important" means in this
context)'''
if self.is_evalframe():
return True
if self.is_other_python_frame():
return True
return False
def is_evalframe(self):
'''Is this a _PyEval_EvalFrameDefault frame?'''
if self._gdbframe.name() == EVALFRAME:
''' '''
I believe we also need to filter on the inline I believe we also need to filter on the inline
struct frame_id.inline_depth, only regarding frames with struct frame_id.inline_depth, only regarding frames with
...@@ -1511,44 +1525,87 @@ class Frame(object): ...@@ -1511,44 +1525,87 @@ class Frame(object):
So we reject those with type gdb.INLINE_FRAME So we reject those with type gdb.INLINE_FRAME
''' '''
if self._gdbframe.type() == gdb.NORMAL_FRAME: if self._gdbframe.type() == gdb.NORMAL_FRAME:
# We have a PyEval_EvalFrameEx frame: # We have a _PyEval_EvalFrameDefault frame:
return True return True
return False return False
def read_var(self, varname): def is_other_python_frame(self):
""" '''Is this frame worth displaying in python backtraces?
read_var with respect to code blocks (gdbframe.read_var works with Examples:
respect to the most recent block) - waiting on the GIL
- garbage-collecting
- within a CFunction
If it is, return a descriptive string
For other frames, return False
'''
if self.is_waiting_for_gil():
return 'Waiting for the GIL'
Apparently this function doesn't work, though, as it seems to read if self.is_gc_collect():
variables in other frames also sometimes. return 'Garbage-collecting'
"""
block = self._gdbframe.block() # Detect invocations of PyCFunction instances:
var = None frame = self._gdbframe
caller = frame.name()
if not caller:
return False
while block and var is None: if caller in ('_PyCFunction_FastCallDict',
'_PyCFunction_FastCallKeywords'):
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: try:
var = self._gdbframe.read_var(varname, block) # Use the prettyprinter for the func:
except ValueError: func = frame.read_var(arg_name)
pass return str(func)
except RuntimeError:
return 'PyCFunction invocation (unable to read %s)' % arg_name
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
block = block.superblock def is_waiting_for_gil(self):
'''Is this frame waiting on the GIL?'''
# This assumes the _POSIX_THREADS version of Python/ceval_gil.h:
name = self._gdbframe.name()
if name:
return 'pthread_cond_timedwait' in name
return var def is_gc_collect(self):
'''Is this frame "collect" within the garbage-collector?'''
return self._gdbframe.name() == 'collect'
def get_pyop(self): def get_pyop(self):
try: try:
# self.read_var does not always work properly, so select our frame f = self._gdbframe.read_var('f')
# and restore the previously selected frame frame = PyFrameObjectPtr.from_pyobject_ptr(f)
selected_frame = gdb.selected_frame() if not frame.is_optimized_out():
self._gdbframe.select() return frame
f = gdb.parse_and_eval('f') # gdb is unable to get the "f" argument of PyEval_EvalFrameEx()
selected_frame.select() # because it was "optimized out". Try to get "f" from the frame
except RuntimeError: # of the caller, PyEval_EvalCodeEx().
orig_frame = frame
caller = self._gdbframe.older()
if caller:
f = caller.read_var('f')
frame = PyFrameObjectPtr.from_pyobject_ptr(f)
if not frame.is_optimized_out():
return frame
return orig_frame
except ValueError:
return None return None
else:
return PyFrameObjectPtr.from_pyobject_ptr(f)
@classmethod @classmethod
def get_selected_frame(cls): def get_selected_frame(cls):
...@@ -1559,12 +1616,30 @@ class Frame(object): ...@@ -1559,12 +1616,30 @@ class Frame(object):
@classmethod @classmethod
def get_selected_python_frame(cls): def get_selected_python_frame(cls):
'''Try to obtain the Frame for the python code in the selected frame, '''Try to obtain the Frame for the python-related code in the selected
or None''' frame, or None'''
try:
frame = cls.get_selected_frame() frame = cls.get_selected_frame()
except gdb.error:
# No frame: Python didn't start yet
return None
while frame: while frame:
if frame.is_evalframeex(): if frame.is_python_frame():
return frame
frame = frame.older()
# Not found:
return None
@classmethod
def get_selected_bytecode_frame(cls):
'''Try to obtain the Frame for the python bytecode interpreter in the
selected GDB frame, or None'''
frame = cls.get_selected_frame()
while frame:
if frame.is_evalframe():
return frame return frame
frame = frame.older() frame = frame.older()
...@@ -1572,17 +1647,41 @@ class Frame(object): ...@@ -1572,17 +1647,41 @@ class Frame(object):
return None return None
def print_summary(self): def print_summary(self):
if self.is_evalframeex(): if self.is_evalframe():
pyop = self.get_pyop() pyop = self.get_pyop()
if pyop: if pyop:
line = pyop.get_truncated_repr(MAX_OUTPUT_LEN) line = pyop.get_truncated_repr(MAX_OUTPUT_LEN)
write_unicode(sys.stdout, '#%i %s\n' % (self.get_index(), line)) write_unicode(sys.stdout, '#%i %s\n' % (self.get_index(), line))
sys.stdout.write(pyop.current_line()) if not pyop.is_optimized_out():
line = pyop.current_line()
if line is not None:
sys.stdout.write(' %s\n' % line.strip())
else: else:
sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index()) sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
else:
info = self.is_other_python_frame()
if info:
sys.stdout.write('#%i %s\n' % (self.get_index(), info))
else: else:
sys.stdout.write('#%i\n' % self.get_index()) sys.stdout.write('#%i\n' % self.get_index())
def print_traceback(self):
if self.is_evalframe():
pyop = self.get_pyop()
if pyop:
pyop.print_traceback()
if not pyop.is_optimized_out():
line = pyop.current_line()
if line is not None:
sys.stdout.write(' %s\n' % line.strip())
else:
sys.stdout.write(' (unable to read python frame information)\n')
else:
info = self.is_other_python_frame()
if info:
sys.stdout.write(' %s\n' % info)
else:
sys.stdout.write(' (not a python frame)\n')
class PyList(gdb.Command): class PyList(gdb.Command):
'''List the current Python source code, if any '''List the current Python source code, if any
...@@ -1602,6 +1701,7 @@ class PyList(gdb.Command): ...@@ -1602,6 +1701,7 @@ class PyList(gdb.Command):
gdb.COMMAND_FILES, gdb.COMMAND_FILES,
gdb.COMPLETE_NONE) gdb.COMPLETE_NONE)
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
import re import re
...@@ -1617,13 +1717,14 @@ class PyList(gdb.Command): ...@@ -1617,13 +1717,14 @@ class PyList(gdb.Command):
if m: if m:
start, end = map(int, m.groups()) start, end = map(int, m.groups())
frame = Frame.get_selected_python_frame() # py-list requires an actual PyEval_EvalFrameEx frame:
frame = Frame.get_selected_bytecode_frame()
if not frame: if not frame:
print('Unable to locate python frame') print('Unable to locate gdb frame for python bytecode interpreter')
return return
pyop = frame.get_pyop() pyop = frame.get_pyop()
if not pyop: if not pyop or pyop.is_optimized_out():
print('Unable to read information on python frame') print('Unable to read information on python frame')
return return
...@@ -1637,7 +1738,13 @@ class PyList(gdb.Command): ...@@ -1637,7 +1738,13 @@ class PyList(gdb.Command):
if start<1: if start<1:
start = 1 start = 1
with open(os_fsencode(filename), 'r') as f: try:
f = open(os_fsencode(filename), 'r')
except IOError as err:
sys.stdout.write('Unable to open %s: %s\n'
% (filename, err))
return
with f:
all_lines = f.readlines() all_lines = f.readlines()
# start and end are 1-based, all_lines is 0-based; # start and end are 1-based, all_lines is 0-based;
# so [start-1:end] as a python slice gives us [start, end] as a # so [start-1:end] as a python slice gives us [start, end] as a
...@@ -1649,13 +1756,17 @@ class PyList(gdb.Command): ...@@ -1649,13 +1756,17 @@ class PyList(gdb.Command):
linestr = '>' + linestr linestr = '>' + linestr
sys.stdout.write('%4s %s' % (linestr, line)) sys.stdout.write('%4s %s' % (linestr, line))
# ...and register the command: # ...and register the command:
PyList() PyList()
def move_in_stack(move_up): def move_in_stack(move_up):
'''Move up or down the stack (for the py-up/py-down command)''' '''Move up or down the stack (for the py-up/py-down command)'''
frame = Frame.get_selected_python_frame() frame = Frame.get_selected_python_frame()
if not frame:
print('Unable to locate python frame')
return
while frame: while frame:
if move_up: if move_up:
iter_frame = frame.older() iter_frame = frame.older()
...@@ -1665,7 +1776,7 @@ def move_in_stack(move_up): ...@@ -1665,7 +1776,7 @@ def move_in_stack(move_up):
if not iter_frame: if not iter_frame:
break break
if iter_frame.is_evalframeex(): if iter_frame.is_python_frame():
# Result: # Result:
if iter_frame.select(): if iter_frame.select():
iter_frame.print_summary() iter_frame.print_summary()
...@@ -1678,7 +1789,6 @@ def move_in_stack(move_up): ...@@ -1678,7 +1789,6 @@ def move_in_stack(move_up):
else: else:
print('Unable to find a newer python frame') print('Unable to find a newer python frame')
class PyUp(gdb.Command): class PyUp(gdb.Command):
'Select and print the python stack frame that called this one (if any)' 'Select and print the python stack frame that called this one (if any)'
def __init__(self): def __init__(self):
...@@ -1687,10 +1797,10 @@ class PyUp(gdb.Command): ...@@ -1687,10 +1797,10 @@ class PyUp(gdb.Command):
gdb.COMMAND_STACK, gdb.COMMAND_STACK,
gdb.COMPLETE_NONE) gdb.COMPLETE_NONE)
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
move_in_stack(move_up=True) move_in_stack(move_up=True)
class PyDown(gdb.Command): class PyDown(gdb.Command):
'Select and print the python stack frame called by this one (if any)' 'Select and print the python stack frame called by this one (if any)'
def __init__(self): def __init__(self):
...@@ -1699,15 +1809,36 @@ class PyDown(gdb.Command): ...@@ -1699,15 +1809,36 @@ class PyDown(gdb.Command):
gdb.COMMAND_STACK, gdb.COMMAND_STACK,
gdb.COMPLETE_NONE) gdb.COMPLETE_NONE)
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
move_in_stack(move_up=False) move_in_stack(move_up=False)
# Not all builds of gdb have gdb.Frame.select # Not all builds of gdb have gdb.Frame.select
if hasattr(gdb.Frame, 'select'): if hasattr(gdb.Frame, 'select'):
PyUp() PyUp()
PyDown() PyDown()
class PyBacktraceFull(gdb.Command):
'Display the current python frame and all the frames within its call stack (if any)'
def __init__(self):
gdb.Command.__init__ (self,
"py-bt-full",
gdb.COMMAND_STACK,
gdb.COMPLETE_NONE)
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()
frame = frame.older()
PyBacktraceFull()
class PyBacktrace(gdb.Command): class PyBacktrace(gdb.Command):
'Display the current python frame and all the frames within its call stack (if any)' 'Display the current python frame and all the frames within its call stack (if any)'
...@@ -1720,14 +1851,18 @@ class PyBacktrace(gdb.Command): ...@@ -1720,14 +1851,18 @@ class PyBacktrace(gdb.Command):
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
frame = Frame.get_selected_python_frame() 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: while frame:
if frame.is_evalframeex(): if frame.is_python_frame():
frame.print_summary() frame.print_traceback()
frame = frame.older() frame = frame.older()
PyBacktrace() PyBacktrace()
class PyPrint(gdb.Command): class PyPrint(gdb.Command):
'Look up the given python variable name, and print it' 'Look up the given python variable name, and print it'
def __init__(self): def __init__(self):
...@@ -1736,6 +1871,7 @@ class PyPrint(gdb.Command): ...@@ -1736,6 +1871,7 @@ class PyPrint(gdb.Command):
gdb.COMMAND_DATA, gdb.COMMAND_DATA,
gdb.COMPLETE_NONE) gdb.COMPLETE_NONE)
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
name = str(args) name = str(args)
...@@ -1752,16 +1888,23 @@ class PyPrint(gdb.Command): ...@@ -1752,16 +1888,23 @@ class PyPrint(gdb.Command):
pyop_var, scope = pyop_frame.get_var_by_name(name) pyop_var, scope = pyop_frame.get_var_by_name(name)
if pyop_var: if pyop_var:
print('%s %r = %s' % ( print('%s %r = %s'
scope, name, pyop_var.get_truncated_repr(MAX_OUTPUT_LEN))) % (scope,
name,
pyop_var.get_truncated_repr(MAX_OUTPUT_LEN)))
else: else:
print('%r not found' % name) print('%r not found' % name)
PyPrint() PyPrint()
class PyLocals(gdb.Command): class PyLocals(gdb.Command):
'Look up the given python variable name, and print it' 'Look up the given python variable name, and print it'
def __init__(self, command="py-locals"):
gdb.Command.__init__ (self,
command,
gdb.COMMAND_DATA,
gdb.COMPLETE_NONE)
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
name = str(args) name = str(args)
...@@ -1790,6 +1933,18 @@ class PyLocals(gdb.Command): ...@@ -1790,6 +1933,18 @@ class PyLocals(gdb.Command):
def get_namespace(self, pyop_frame): def get_namespace(self, pyop_frame):
return pyop_frame.iter_locals() return pyop_frame.iter_locals()
PyLocals()
##################################################################
## added, not in CPython
##################################################################
import atexit
import warnings
import tempfile
import textwrap
import itertools
class PyGlobals(PyLocals): class PyGlobals(PyLocals):
'List all the globals in the currently select Python frame' 'List all the globals in the currently select Python frame'
...@@ -1798,8 +1953,7 @@ class PyGlobals(PyLocals): ...@@ -1798,8 +1953,7 @@ class PyGlobals(PyLocals):
return pyop_frame.iter_globals() return pyop_frame.iter_globals()
PyLocals("py-locals", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) PyGlobals("py-globals")
PyGlobals("py-globals", gdb.COMMAND_DATA, gdb.COMPLETE_NONE)
class PyNameEquals(gdb.Function): class PyNameEquals(gdb.Function):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment