Author: dmalcolm
Update of /cvs/pkgs/rpms/python3/devel
In directory cvs01.phx2.fedoraproject.org:/tmp/cvs-serv20525
Modified Files:
python-gdb.py python3.spec
Log Message:
update python-gdb.py from v4 to v5 (improving performance and stability, adding commands)
Index: python-gdb.py
===================================================================
RCS file: /cvs/pkgs/rpms/python3/devel/python-gdb.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -p -r1.2 -r1.3
--- python-gdb.py 25 Mar 2010 20:38:13 -0000 1.2
+++ python-gdb.py 1 Apr 2010 02:53:39 -0000 1.3
@@ -23,6 +23,15 @@ holding three PyObject* that turn out to
generate a proxy value within the gdb process that is a list of strings:
["foo", "bar", "baz"]
+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
+of the data to a file-like object. This allows us to stop the traversal by
+having the file-like object raise an exception if it gets too much data.
+
+With both "proxyval" and "write_repr" we keep track of the set of all
addresses
+visited so far in the traversal, to avoid infinite recursion due to cycles in
+the graph of object references.
+
We try to defer gdb.lookup_type() invocations for python types until as late as
possible: for a dynamically linked python binary, when the process starts in
the debugger, the libpython.so hasn't been dynamically loaded yet, so none of
@@ -55,6 +64,8 @@ Py_TPFLAGS_BASE_EXC_SUBCLASS = (1L << 30
Py_TPFLAGS_TYPE_SUBCLASS = (1L << 31)
+MAX_OUTPUT_LEN=1024
+
class NullPyObjectPtr(RuntimeError):
pass
@@ -63,7 +74,7 @@ def safety_limit(val):
# Given a integer value from the process being debugged, limit it to some
# 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)
- return min(val, 100)
+ return min(val, 1000)
def safe_range(val):
@@ -72,6 +83,28 @@ def safe_range(val):
return xrange(safety_limit(val))
+class StringTruncated(RuntimeError):
+ pass
+
+class TruncatedStringIO(object):
+ '''Similar to cStringIO, but can truncate the output by raising a
+ StringTruncated exception'''
+ def __init__(self, maxlen=None):
+ self._val = ''
+ self.maxlen = maxlen
+
+ def write(self, data):
+ if self.maxlen:
+ if len(data) + len(self._val) > self.maxlen:
+ # Truncation:
+ self._val += data[0:self.maxlen - len(self._val)]
+ raise StringTruncated()
+
+ self._val += data
+
+ def getvalue(self):
+ return self._val
+
class PyObjectPtr(object):
"""
Class wrapping a gdb.Value that's a either a (PyObject*) within the
@@ -125,12 +158,54 @@ class PyObjectPtr(object):
# General case: look it up inside the object:
return self._gdbval.dereference()[name]
+ def pyop_field(self, name):
+ '''
+ Get a PyObjectPtr for the given PyObject* field within this PyObject,
+ coping with some python 2 versus python 3 differences.
+ '''
+ return PyObjectPtr.from_pyobject_ptr(self.field(name))
+
+ def write_field_repr(self, name, out, visited):
+ '''
+ Extract the PyObject* field named "name", and write its representation
+ to file-like object "out"
+ '''
+ field_obj = self.pyop_field(name)
+ field_obj.write_repr(out, visited)
+
+ def get_truncated_repr(self, maxlen):
+ '''
+ Get a repr-like string for the data, but truncate it at "maxlen" bytes
+ (ending the object graph traversal as soon as you do)
+ '''
+ out = TruncatedStringIO(maxlen)
+ try:
+ self.write_repr(out, set())
+ except StringTruncated:
+ # Truncation occurred:
+ return out.getvalue() + '...(truncated)'
+
+ # No truncation occurred:
+ return out.getvalue()
+
def type(self):
return PyTypeObjectPtr(self.field('ob_type'))
def is_null(self):
return 0 == long(self._gdbval)
+ def is_optimized_out(self):
+ '''
+ Is the value of the underlying PyObject* visible to the debugger?
+
+ This can vary with the precise version of the compiler used to build
+ Python, and the precise version of gdb.
+
+ See e.g.
https://bugzilla.redhat.com/show_bug.cgi?id=556975 with
+ PyEval_EvalFrameEx's "f"
+ '''
+ return self._gdbval.is_optimized_out
+
def safe_tp_name(self):
try:
return self.type().field('tp_name').string()
@@ -180,6 +255,16 @@ class PyObjectPtr(object):
return FakeRepr(self.safe_tp_name(),
long(self._gdbval))
+ def write_repr(self, out, visited):
+ '''
+ Write a string representation of the value scraped from the inferior
+ process to "out", a file-like object.
+ '''
+ # Default implementation: generate a proxy value and write its repr
+ # However, this could involve a lot of work for complicated objects,
+ # so for derived classes we specialize this
+ return out.write(repr(self.proxyval(visited)))
+
@classmethod
def subclass_from_type(cls, t):
'''
@@ -217,6 +302,7 @@ class PyObjectPtr(object):
'frame': PyFrameObjectPtr,
'set' : PySetObjectPtr,
'frozenset' : PySetObjectPtr,
+ 'builtin_function_or_method' : PyCFunctionObjectPtr,
}
if tp_name in name_map:
return name_map[tp_name]
@@ -283,6 +369,28 @@ class ProxyAlreadyVisited(object):
def __repr__(self):
return self._rep
+
+def _write_instance_repr(out, visited, name, pyop_attrdict, address):
+ '''Shared code for use by old-style and new-style classes:
+ write a representation to file-like object "out"'''
+ out.write('<')
+ out.write(name)
+
+ # Write dictionary of instance attributes:
+ if isinstance(pyop_attrdict, PyDictObjectPtr):
+ out.write('(')
+ first = True
+ for pyop_arg, pyop_val in pyop_attrdict.iteritems():
+ if not first:
+ out.write(', ')
+ first = False
+ out.write(pyop_arg.proxyval(visited))
+ out.write('=')
+ pyop_val.write_repr(out, visited)
+ out.write(')')
+ out.write(' at remote 0x%x>' % address)
+
+
class InstanceProxy(object):
def __init__(self, cl_name, attrdict, address):
@@ -299,8 +407,7 @@ class InstanceProxy(object):
else:
return '<%s at remote 0x%x>' % (self.cl_name,
self.address)
-
-
+
def _PyObject_VAR_SIZE(typeobj, nitems):
return ( ( typeobj.field('tp_basicsize') +
nitems * typeobj.field('tp_itemsize') +
@@ -311,19 +418,11 @@ def _PyObject_VAR_SIZE(typeobj, nitems):
class HeapTypeObjectPtr(PyObjectPtr):
_typename = 'PyObject'
- def proxyval(self, visited):
+ def get_attr_dict(self):
'''
- Support for new-style classes.
-
- Currently we just locate the dictionary using a transliteration to
- python of _PyObject_GetDictPtr, ignoring descriptors
+ Get the PyDictObject ptr representing the attribute dictionary
+ (or None if there's a problem)
'''
- # Guard against infinite loops:
- if self.as_address() in visited:
- return ProxyAlreadyVisited('<...>')
- visited.add(self.as_address())
-
- attr_dict = {}
try:
typeobj = self.type()
dictoffset = int_from_int(typeobj.field('tp_dictoffset'))
@@ -341,16 +440,47 @@ class HeapTypeObjectPtr(PyObjectPtr):
dictptr = self._gdbval.cast(_type_char_ptr) + dictoffset
PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer()
dictptr = dictptr.cast(PyObjectPtrPtr)
- attr_dict =
PyObjectPtr.from_pyobject_ptr(dictptr.dereference()).proxyval(visited)
+ return PyObjectPtr.from_pyobject_ptr(dictptr.dereference())
except RuntimeError:
# Corrupt data somewhere; fail safe
pass
+ # Not found, or some kind of error:
+ return None
+
+ def proxyval(self, visited):
+ '''
+ Support for new-style classes.
+
+ Currently we just locate the dictionary using a transliteration to
+ python of _PyObject_GetDictPtr, ignoring descriptors
+ '''
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ return ProxyAlreadyVisited('<...>')
+ visited.add(self.as_address())
+
+ pyop_attr_dict = self.get_attr_dict()
+ if pyop_attr_dict:
+ attr_dict = pyop_attr_dict.proxyval(visited)
+ else:
+ attr_dict = {}
tp_name = self.safe_tp_name()
# New-style class:
return InstanceProxy(tp_name, attr_dict, long(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())
+
+ pyop_attrdict = self.get_attr_dict()
+ _write_instance_repr(out, visited,
+ self.safe_tp_name(), pyop_attrdict, self.as_address())
+
class ProxyException(Exception):
def __init__(self, tp_name, args):
self.tp_name = tp_name
@@ -371,10 +501,20 @@ class PyBaseExceptionObjectPtr(PyObjectP
if self.as_address() in visited:
return ProxyAlreadyVisited('(...)')
visited.add(self.as_address())
- arg_proxy =
PyObjectPtr.from_pyobject_ptr(self.field('args')).proxyval(visited)
+ arg_proxy = self.pyop_field('args').proxyval(visited)
return ProxyException(self.safe_tp_name(),
arg_proxy)
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('(...)')
+ return
+ visited.add(self.as_address())
+
+ out.write(self.safe_tp_name())
+ self.write_field_repr('args', out, visited)
+
class PyBoolObjectPtr(PyObjectPtr):
"""
Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two
@@ -397,6 +537,43 @@ class PyClassObjectPtr(PyObjectPtr):
_typename = 'PyClassObject'
+class BuiltInFunctionProxy(object):
+ def __init__(self, ml_name):
+ self.ml_name = ml_name
+
+ def __repr__(self):
+ return "<built-in function %s>" % self.ml_name
+
+class BuiltInMethodProxy(object):
+ def __init__(self, ml_name, pyop_m_self):
+ self.ml_name = ml_name
+ self.pyop_m_self = pyop_m_self
+
+ def __repr__(self):
+ return ('<built-in method %s of %s object at remote 0x%x>'
+ % (self.ml_name,
+ self.pyop_m_self.safe_tp_name(),
+ self.pyop_m_self.as_address())
+ )
+
+class PyCFunctionObjectPtr(PyObjectPtr):
+ """
+ Class wrapping a gdb.Value that's a PyCFunctionObject*
+ (see Include/methodobject.h and Objects/methodobject.c)
+ """
+ _typename = 'PyCFunctionObject'
+
+ def proxyval(self, visited):
+ m_ml = self.field('m_ml') # m_ml is a (PyMethodDef*)
+ ml_name = m_ml['ml_name'].string()
+
+ pyop_m_self = self.pyop_field('m_self')
+ if pyop_m_self.is_null():
+ return BuiltInFunctionProxy(ml_name)
+ else:
+ return BuiltInMethodProxy(ml_name, pyop_m_self)
+
+
class PyCodeObjectPtr(PyObjectPtr):
"""
Class wrapping a gdb.Value that's a PyCodeObject* i.e. a <code> instance
@@ -411,7 +588,7 @@ class PyCodeObjectPtr(PyObjectPtr):
Analogous to PyCode_Addr2Line; translated from pseudocode in
Objects/lnotab_notes.txt
'''
- co_lnotab =
PyObjectPtr.from_pyobject_ptr(self.field('co_lnotab')).proxyval(set())
+ co_lnotab = self.pyop_field('co_lnotab').proxyval(set())
# Initialize lineno to co_firstlineno as per PyCode_Addr2Line
# not 0, as lnotab_notes.txt has it:
@@ -425,6 +602,7 @@ class PyCodeObjectPtr(PyObjectPtr):
lineno += ord(line_incr)
return lineno
+
class PyDictObjectPtr(PyObjectPtr):
"""
Class wrapping a gdb.Value that's a PyDictObject* i.e. a dict instance
@@ -432,6 +610,18 @@ class PyDictObjectPtr(PyObjectPtr):
"""
_typename = 'PyDictObject'
+ def iteritems(self):
+ '''
+ Yields a sequence of (PyObjectPtr key, PyObjectPtr value) pairs,
+ analagous to dict.iteritems()
+ '''
+ for i in safe_range(self.field('ma_mask') + 1):
+ ep = self.field('ma_table') + i
+ pyop_value = PyObjectPtr.from_pyobject_ptr(ep['me_value'])
+ if not pyop_value.is_null():
+ pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key'])
+ yield (pyop_key, pyop_value)
+
def proxyval(self, visited):
# Guard against infinite loops:
if self.as_address() in visited:
@@ -439,14 +629,29 @@ class PyDictObjectPtr(PyObjectPtr):
visited.add(self.as_address())
result = {}
- for i in safe_range(self.field('ma_mask') + 1):
- ep = self.field('ma_table') + i
- pvalue = PyObjectPtr.from_pyobject_ptr(ep['me_value'])
- if not pvalue.is_null():
- pkey = PyObjectPtr.from_pyobject_ptr(ep['me_key'])
- result[pkey.proxyval(visited)] = pvalue.proxyval(visited)
+ for pyop_key, pyop_value in self.iteritems():
+ proxy_key = pyop_key.proxyval(visited)
+ proxy_value = pyop_value.proxyval(visited)
+ result[proxy_key] = proxy_value
return result
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('{...}')
+ return
+ visited.add(self.as_address())
+
+ out.write('{')
+ first = True
+ for pyop_key, pyop_value in self.iteritems():
+ if not first:
+ out.write(', ')
+ first = False
+ pyop_key.write_repr(out, visited)
+ out.write(': ')
+ pyop_value.write_repr(out, visited)
+ out.write('}')
class PyInstanceObjectPtr(PyObjectPtr):
_typename = 'PyInstanceObject'
@@ -458,16 +663,34 @@ class PyInstanceObjectPtr(PyObjectPtr):
visited.add(self.as_address())
# Get name of class:
- in_class = PyObjectPtr.from_pyobject_ptr(self.field('in_class'))
- cl_name =
PyObjectPtr.from_pyobject_ptr(in_class.field('cl_name')).proxyval(visited)
+ in_class = self.pyop_field('in_class')
+ cl_name = in_class.pyop_field('cl_name').proxyval(visited)
# Get dictionary of instance attributes:
- in_dict =
PyObjectPtr.from_pyobject_ptr(self.field('in_dict')).proxyval(visited)
+ in_dict = self.pyop_field('in_dict').proxyval(visited)
# Old-style class:
return InstanceProxy(cl_name, in_dict, long(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):
_typename = 'PyIntObject'
@@ -493,6 +716,20 @@ class PyListObjectPtr(PyObjectPtr):
for i in safe_range(int_from_int(self.field('ob_size')))]
return result
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('[...]')
+ return
+ visited.add(self.as_address())
+
+ out.write('[')
+ for i in safe_range(int_from_int(self.field('ob_size'))):
+ if i > 0:
+ out.write(', ')
+ element = PyObjectPtr.from_pyobject_ptr(self[i])
+ element.write_repr(out, visited)
+ out.write(']')
class PyLongObjectPtr(PyObjectPtr):
_typename = 'PyLongObject'
@@ -524,7 +761,6 @@ class PyLongObjectPtr(PyObjectPtr):
if gdb.lookup_type('digit').sizeof == 2:
SHIFT = 15L
else:
- # FIXME: I haven't yet tested this case
SHIFT = 30L
digits = [long(ob_digit[i]) * 2**(SHIFT*i)
@@ -549,10 +785,129 @@ class PyNoneStructPtr(PyObjectPtr):
class PyFrameObjectPtr(PyObjectPtr):
_typename = 'PyFrameObject'
- def __str__(self):
- fi = FrameInfo(self)
- return str(fi)
+ def __init__(self, gdbval, cast_to):
+ PyObjectPtr.__init__(self, gdbval, cast_to)
+
+ if not self.is_optimized_out():
+ self.co = PyCodeObjectPtr.from_pyobject_ptr(self.field('f_code'))
+ self.co_name = self.co.pyop_field('co_name')
+ self.co_filename = self.co.pyop_field('co_filename')
+
+ self.f_lineno = int_from_int(self.field('f_lineno'))
+ self.f_lasti = int_from_int(self.field('f_lasti'))
+ self.co_nlocals = int_from_int(self.co.field('co_nlocals'))
+ self.co_varnames =
PyTupleObjectPtr.from_pyobject_ptr(self.co.field('co_varnames'))
+ def iter_locals(self):
+ '''
+ Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
+ the local variables of this frame
+ '''
+ if self.is_optimized_out():
+ return
+
+ f_localsplus = self.field('f_localsplus')
+ for i in safe_range(self.co_nlocals):
+ pyop_value = PyObjectPtr.from_pyobject_ptr(f_localsplus[i])
+ if not pyop_value.is_null():
+ pyop_name = PyObjectPtr.from_pyobject_ptr(self.co_varnames[i])
+ yield (pyop_name, pyop_value)
+
+ def iter_globals(self):
+ '''
+ Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
+ the global variables of this frame
+ '''
+ if self.is_optimized_out():
+ return
+
+ pyop_globals = self.pyop_field('f_globals')
+ return pyop_globals.iteritems()
+
+ def iter_builtins(self):
+ '''
+ Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
+ the builtin variables
+ '''
+ if self.is_optimized_out():
+ return
+
+ pyop_builtins = self.pyop_field('f_builtins')
+ return pyop_builtins.iteritems()
+
+ def get_var_by_name(self, name):
+ '''
+ Look for the named local variable, returning a (PyObjectPtr, scope) pair
+ where scope is a string 'local', 'global', 'builtin'
+
+ If not found, return (None, None)
+ '''
+ for pyop_name, pyop_value in self.iter_locals():
+ if name == pyop_name.proxyval(set()):
+ return pyop_value, 'local'
+ for pyop_name, pyop_value in self.iter_globals():
+ if name == pyop_name.proxyval(set()):
+ return pyop_value, 'global'
+ for pyop_name, pyop_value in self.iter_builtins():
+ if name == pyop_name.proxyval(set()):
+ return pyop_value, 'builtin'
+ return None, None
+
+ def filename(self):
+ '''Get the path of the current Python source file, as a
string'''
+ if self.is_optimized_out():
+ return '(frame information optimized out)'
+ return self.co_filename.proxyval(set())
+
+ def current_line_num(self):
+ '''Get current line number as an integer (1-based)
+
+ Translated from PyFrame_GetLineNumber and PyCode_Addr2Line
+
+ See Objects/lnotab_notes.txt
+ '''
+ if self.is_optimized_out():
+ return None
+ f_trace = self.field('f_trace')
+ if long(f_trace) != 0:
+ # we have a non-NULL f_trace:
+ return self.f_lineno
+ else:
+ #try:
+ return self.co.addr2line(self.f_lasti)
+ #except ValueError:
+ # return self.f_lineno
+
+ def current_line(self):
+ '''Get the text of the current source line as a string, with a
trailing
+ newline character'''
+ if self.is_optimized_out():
+ return '(frame information optimized out)'
+ with open(self.filename(), 'r') as f:
+ all_lines = f.readlines()
+ # Convert from 1-based current_line_num to 0-based list offset:
+ return all_lines[self.current_line_num()-1]
+
+ def write_repr(self, out, visited):
+ if self.is_optimized_out():
+ out.write('(frame information optimized out)')
+ return
+ out.write('Frame 0x%x, for file %s, line %i, in %s ('
+ % (self.as_address(),
+ self.co_filename,
+ self.current_line_num(),
+ self.co_name))
+ first = True
+ for pyop_name, pyop_value in self.iter_locals():
+ if not first:
+ out.write(', ')
+ first = False
+
+ out.write(pyop_name.proxyval(visited))
+ out.write('=')
+ pyop_value.write_repr(out, visited)
+
+ out.write(')')
class PySetObjectPtr(PyObjectPtr):
_typename = 'PySetObject'
@@ -577,6 +932,32 @@ class PySetObjectPtr(PyObjectPtr):
else:
return set(members)
+ def write_repr(self, out, visited):
+ out.write(self.safe_tp_name())
+
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('(...)')
+ return
+ visited.add(self.as_address())
+
+ out.write('([')
+ first = True
+ table = self.field('table')
+ 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:
+ out.write(', ')
+ first = False
+ pyop_key.write_repr(out, visited)
+ out.write('])')
+
+
class PyStringObjectPtr(PyObjectPtr):
_typename = 'PyStringObject'
@@ -589,7 +970,6 @@ class PyStringObjectPtr(PyObjectPtr):
def proxyval(self, visited):
return str(self)
-
class PyTupleObjectPtr(PyObjectPtr):
_typename = 'PyTupleObject'
@@ -608,6 +988,23 @@ class PyTupleObjectPtr(PyObjectPtr):
for i in
safe_range(int_from_int(self.field('ob_size')))])
return result
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('(...)')
+ return
+ visited.add(self.as_address())
+
+ out.write('(')
+ for i in safe_range(int_from_int(self.field('ob_size'))):
+ if i > 0:
+ out.write(', ')
+ element = PyObjectPtr.from_pyobject_ptr(self[i])
+ element.write_repr(out, visited)
+ if self.field('ob_size') == 1:
+ out.write(',)')
+ else:
+ out.write(')')
class PyTypeObjectPtr(PyObjectPtr):
_typename = 'PyTypeObject'
@@ -641,77 +1038,12 @@ def stringify(val):
# TODO: repr() puts everything on one line; pformat can be nicer, but
# can lead to v.long results; this function isolates the choice
if True:
- return repr(val)
+ return repr(val)
else:
from pprint import pformat
return pformat(val)
-class FrameInfo:
- '''
- Class representing all of the information we can scrape about a
- PyFrameObject*
- '''
- def __init__(self, fval):
- self.fval = fval
- self.co = PyCodeObjectPtr.from_pyobject_ptr(fval.field('f_code'))
- self.co_name = PyObjectPtr.from_pyobject_ptr(self.co.field('co_name'))
- self.co_filename =
PyObjectPtr.from_pyobject_ptr(self.co.field('co_filename'))
- self.f_lineno = int_from_int(fval.field('f_lineno'))
- self.f_lasti = int_from_int(fval.field('f_lasti'))
- self.co_nlocals = int_from_int(self.co.field('co_nlocals'))
- self.co_varnames =
PyTupleObjectPtr.from_pyobject_ptr(self.co.field('co_varnames'))
- self.locals = [] # list of kv pairs
- f_localsplus = self.fval.field('f_localsplus')
- for i in safe_range(self.co_nlocals):
- #print 'i=%i' % i
- value = PyObjectPtr.from_pyobject_ptr(f_localsplus[i])
- if not value.is_null():
- name = PyObjectPtr.from_pyobject_ptr(self.co_varnames[i])
- #print 'name=%s' % name
- value = value.proxyval(set())
- #print 'value=%s' % value
- self.locals.append((str(name), value))
-
- def filename(self):
- '''Get the path of the current Python source file, as a
string'''
- return self.co_filename.proxyval(set())
-
- def current_line_num(self):
- '''Get current line number as an integer (1-based)
-
- Translated from PyFrame_GetLineNumber and PyCode_Addr2Line
-
- See Objects/lnotab_notes.txt
- '''
- f_trace = self.fval.field('f_trace')
- if long(f_trace) != 0:
- # we have a non-NULL f_trace:
- return self.f_lineno
- else:
- #try:
- return self.co.addr2line(self.f_lasti)
- #except ValueError:
- # return self.f_lineno
-
- def current_line(self):
- '''Get the text of the current source line as a string, with a
trailing
- newline character'''
- with open(self.filename(), 'r') as f:
- all_lines = f.readlines()
- # Convert from 1-based current_line_num to 0-based list offset:
- return all_lines[self.current_line_num()-1]
-
- def __str__(self):
- return ('Frame 0x%x, for file %s, line %i, in %s (%s)'
- % (long(self.fval._gdbval),
- self.co_filename,
- self.current_line_num(),
- self.co_name,
- ', '.join(['%s=%s' % (k, stringify(v)) for k, v in
self.locals]))
- )
-
-
class PyObjectPtrPrinter:
"Prints a (PyObject*)"
@@ -719,29 +1051,22 @@ class PyObjectPtrPrinter:
self.gdbval = gdbval
def to_string (self):
- proxyval = PyObjectPtr.from_pyobject_ptr(self.gdbval).proxyval(set())
- return stringify(proxyval)
-
-
-class PyFrameObjectPtrPrinter(PyObjectPtrPrinter):
- "Prints a (PyFrameObject*)"
-
- def to_string (self):
pyop = PyObjectPtr.from_pyobject_ptr(self.gdbval)
- fi = FrameInfo(pyop)
- return str(fi)
-
+ if True:
+ return pyop.get_truncated_repr(MAX_OUTPUT_LEN)
+ else:
+ # Generate full proxy value then stringify it.
+ # Doing so could be expensive
+ proxyval = pyop.proxyval(set())
+ return stringify(proxyval)
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 == "PyObject":
+ if t in ("PyObject", "PyFrameObject"):
return PyObjectPtrPrinter(gdbval)
- elif t == "PyFrameObject":
- return PyFrameObjectPtrPrinter(gdbval)
-
"""
During development, I've been manually invoking the code in this way:
@@ -771,29 +1096,96 @@ def register (obj):
register (gdb.current_objfile ())
-def get_python_frame(gdb_frame):
- try:
- f = gdb_frame.read_var('f')
- return PyFrameObjectPtr.from_pyobject_ptr(f)
- except ValueError:
- return None
-def get_selected_python_frame():
- '''Try to obtain a (gdbframe, PyFrameObjectPtr) pair for the
- currently-running python code, or (None, None)'''
- gdb_frame = gdb.selected_frame()
- while gdb_frame:
- if (gdb_frame.function() is None or
- gdb_frame.function().name != 'PyEval_EvalFrameEx'):
- gdb_frame = gdb_frame.older()
- continue
+class Frame(object):
+ '''
+ Wrapper for gdb.Frame, adding various methods
+ '''
+ def __init__(self, gdbframe):
+ self._gdbframe = gdbframe
+
+ def older(self):
+ older = self._gdbframe.older()
+ if older:
+ return Frame(older)
+ else:
+ return None
+
+ def newer(self):
+ newer = self._gdbframe.newer()
+ if newer:
+ return Frame(newer)
+ else:
+ return None
+
+ def select(self):
+ self._gdbframe.select()
+
+ def get_index(self):
+ '''Calculate index of frame, starting at 0 for the newest frame
within
+ this thread'''
+ index = 0
+ # Go down until you reach the newest frame:
+ iter_frame = self
+ while iter_frame.newer():
+ index += 1
+ iter_frame = iter_frame.newer()
+ return index
+
+ def is_evalframeex(self):
+ if self._gdbframe.function():
+ if self._gdbframe.function().name == 'PyEval_EvalFrameEx':
+ '''
+ I believe we also need to filter on the inline
+ struct frame_id.inline_depth, only regarding frames with
+ an inline depth of 0 as actually being this function
+
+ So we reject those with type gdb.INLINE_FRAME
+ '''
+ if self._gdbframe.type() == gdb.NORMAL_FRAME:
+ # We have a PyEval_EvalFrameEx frame:
+ return True
+ return False
+
+ def get_pyop(self):
try:
- f = gdb_frame.read_var('f')
- return gdb_frame, PyFrameObjectPtr.from_pyobject_ptr(f)
+ f = self._gdbframe.read_var('f')
+ return PyFrameObjectPtr.from_pyobject_ptr(f)
except ValueError:
- gdb_frame = gdb_frame.older()
- return None, None
+ return None
+
+ @classmethod
+ def get_selected_frame(cls):
+ _gdbframe = gdb.selected_frame()
+ if _gdbframe:
+ return Frame(_gdbframe)
+ return None
+
+ @classmethod
+ def get_selected_python_frame(cls):
+ '''Try to obtain the Frame for the python code in the selected
frame,
+ or None'''
+ frame = cls.get_selected_frame()
+
+ while frame:
+ if frame.is_evalframeex():
+ return frame
+ frame = frame.older()
+
+ # Not found:
+ return None
+
+ def print_summary(self):
+ if self.is_evalframeex():
+ pyop = self.get_pyop()
+ if pyop:
+ sys.stdout.write('#%i %s\n' % (self.get_index(),
pyop.get_truncated_repr(MAX_OUTPUT_LEN)))
+ sys.stdout.write(pyop.current_line())
+ else:
+ sys.stdout.write('#%i (unable to read python frame
information)\n' % self.get_index())
+ else:
+ sys.stdout.write('#%i\n' % self.get_index())
class PyList(gdb.Command):
'''List the current Python source code, if any
@@ -829,14 +1221,18 @@ class PyList(gdb.Command):
if m:
start, end = map(int, m.groups())
- gdb_frame, py_frame = get_selected_python_frame()
- if not py_frame:
+ frame = Frame.get_selected_python_frame()
+ if not frame:
print 'Unable to locate python frame'
return
- fi = FrameInfo(py_frame)
- filename = fi.filename()
- lineno = fi.current_line_num()
+ pyop = frame.get_pyop()
+ if not pyop:
+ print 'Unable to read information on python frame'
+ return
+
+ filename = pyop.filename()
+ lineno = pyop.current_line_num()
if start is None:
start = lineno - 5
@@ -851,7 +1247,11 @@ class PyList(gdb.Command):
# so [start-1:end] as a python slice gives us [start, end] as a
# closed interval
for i, line in enumerate(all_lines[start-1:end]):
- sys.stdout.write('%4s %s' % (i+start, line))
+ linestr = str(i+start)
+ # Highlight current line:
+ if i + start == lineno:
+ linestr = '>' + linestr
+ sys.stdout.write('%4s %s' % (linestr, line))
# ...and register the command:
@@ -859,27 +1259,23 @@ PyList()
def move_in_stack(move_up):
'''Move up or down the stack (for the py-up/py-down
command)'''
- gdb_frame, py_frame = get_selected_python_frame()
- while gdb_frame:
+ frame = Frame.get_selected_python_frame()
+ while frame:
if move_up:
- iter_frame = gdb_frame.older()
+ iter_frame = frame.older()
else:
- iter_frame = gdb_frame.newer()
+ iter_frame = frame.newer()
if not iter_frame:
break
- if (iter_frame.function() and
- iter_frame.function().name == 'PyEval_EvalFrameEx'):
+ if iter_frame.is_evalframeex():
# Result:
iter_frame.select()
- py_frame = get_python_frame(iter_frame)
- fi = FrameInfo(py_frame)
- print fi
- sys.stdout.write(fi.current_line())
+ iter_frame.print_summary()
return
- gdb_frame = iter_frame
+ frame = iter_frame
if move_up:
print 'Unable to find an older python frame'
@@ -924,18 +1320,73 @@ class PyBacktrace(gdb.Command):
def invoke(self, args, from_tty):
- gdb_frame, py_frame = get_selected_python_frame()
- while gdb_frame:
- gdb_frame = gdb_frame.older()
-
- if not gdb_frame:
- break
-
- if (gdb_frame.function() and
- gdb_frame.function().name == 'PyEval_EvalFrameEx'):
- py_frame = get_python_frame(gdb_frame)
- fi = FrameInfo(py_frame)
- print ' ', fi
- sys.stdout.write(fi.current_line())
+ frame = Frame.get_selected_python_frame()
+ while frame:
+ if frame.is_evalframeex():
+ frame.print_summary()
+ frame = frame.older()
PyBacktrace()
+
+class PyPrint(gdb.Command):
+ 'Look up the given python variable name, and print it'
+ def __init__(self):
+ gdb.Command.__init__ (self,
+ "py-print",
+ gdb.COMMAND_DATA,
+ gdb.COMPLETE_NONE)
+
+
+ def invoke(self, args, from_tty):
+ name = str(args)
+
+ frame = Frame.get_selected_python_frame()
+ if not frame:
+ print 'Unable to locate python frame'
+ return
+
+ pyop_frame = frame.get_pyop()
+ if not pyop_frame:
+ print 'Unable to read information on python frame'
+ return
+
+ pyop_var, scope = pyop_frame.get_var_by_name(name)
+
+ if pyop_var:
+ print ('%s %r = %s'
+ % (scope,
+ name,
+ pyop_var.get_truncated_repr(MAX_OUTPUT_LEN)))
+ else:
+ print '%r not found' % name
+
+PyPrint()
+
+class PyLocals(gdb.Command):
+ 'Look up the given python variable name, and print it'
+ def __init__(self):
+ gdb.Command.__init__ (self,
+ "py-locals",
+ gdb.COMMAND_DATA,
+ gdb.COMPLETE_NONE)
+
+
+ def invoke(self, args, from_tty):
+ name = str(args)
+
+ frame = Frame.get_selected_python_frame()
+ if not frame:
+ print 'Unable to locate python frame'
+ return
+
+ pyop_frame = frame.get_pyop()
+ if not pyop_frame:
+ print 'Unable to read information on python frame'
+ return
+
+ for pyop_name, pyop_value in pyop_frame.iter_locals():
+ print ('%s = %s'
+ % (pyop_name.proxyval(set()),
+ pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)))
+
+PyLocals()
Index: python3.spec
===================================================================
RCS file: /cvs/pkgs/rpms/python3/devel/python3.spec,v
retrieving revision 1.19
retrieving revision 1.20
diff -u -p -r1.19 -r1.20
--- python3.spec 25 Mar 2010 20:38:13 -0000 1.19
+++ python3.spec 1 Apr 2010 02:53:39 -0000 1.20
@@ -39,7 +39,7 @@
Summary: Version 3 of the Python programming language aka Python 3000
Name: python3
Version: %{pybasever}.2
-Release: 3%{?dist}
+Release: 4%{?dist}
License: Python
Group: Development/Languages
Source:
http://python.org/ftp/python/%{version}/Python-%{version}.tar.bz2
@@ -74,7 +74,7 @@ Source3: macros.pybytecompile
#
# Downloaded from:
#
http://bugs.python.org/issue8032
-# This is Tools/gdb/libpython.py from v4 of the patch
+# This is Tools/gdb/libpython.py from v5 of the patch
Source4: python-gdb.py
# Systemtap tapset to make it easier to use the systemtap static probes
@@ -695,6 +695,10 @@ rm -fr %{buildroot}
%changelog
+* Wed Mar 31 2010 David Malcolm <dmalcolm(a)redhat.com> - 3.1.2-4
+- update python-gdb.py from v4 to v5 (improving performance and stability,
+adding commands)
+
* Thu Mar 25 2010 David Malcolm <dmalcolm(a)redhat.com> - 3.1.2-3
- update python-gdb.py from v3 to v4 (fixing infinite recursion on reference
cycles and tracebacks on bytes 0x80-0xff in strings, adding handlers for sets