churchyard / rpms / python2

Forked from rpms/python2 2 years ago
Clone
461494
#!/usr/bin/python
461494
'''
461494
From gdb 7 onwards, gdb's build can be configured --with-python, allowing gdb
461494
to be extended with Python code e.g. for library-specific data visualizations,
461494
such as for the C++ STL types.  Documentation on this API can be seen at:
461494
http://sourceware.org/gdb/current/onlinedocs/gdb/Python-API.html
461494
461494
461494
This python module deals with the case when the process being debugged (the
461494
"inferior process" in gdb parlance) is itself python, or more specifically,
461494
linked against libpython.  In this situation, almost every item of data is a
461494
(PyObject*), and having the debugger merely print their addresses is not very
461494
enlightening.
461494
461494
This module embeds knowledge about the implementation details of libpython so
461494
that we can emit useful visualizations e.g. a string, a list, a dict, a frame
461494
giving file/line information and the state of local variables
461494
461494
In particular, given a gdb.Value corresponding to a PyObject* in the inferior
461494
process, we can generate a "proxy value" within the gdb process.  For example,
461494
given a PyObject* in the inferior process that is in fact a PyListObject*
461494
holding three PyObject* that turn out to be PyStringObject* instances, we can
461494
generate a proxy value within the gdb process that is a list of strings:
461494
  ["foo", "bar", "baz"]
461494
01f3a7
Doing so can be expensive for complicated graphs of objects, and could take
01f3a7
some time, so we also have a "write_repr" method that writes a representation
01f3a7
of the data to a file-like object.  This allows us to stop the traversal by
01f3a7
having the file-like object raise an exception if it gets too much data.
01f3a7
01f3a7
With both "proxyval" and "write_repr" we keep track of the set of all addresses
01f3a7
visited so far in the traversal, to avoid infinite recursion due to cycles in
01f3a7
the graph of object references.
01f3a7
461494
We try to defer gdb.lookup_type() invocations for python types until as late as
461494
possible: for a dynamically linked python binary, when the process starts in
461494
the debugger, the libpython.so hasn't been dynamically loaded yet, so none of
461494
the type names are known to the debugger
461494
461494
The module also extends gdb with some python-specific commands.
461494
'''
461494
461494
import gdb
461494
461494
# Look up the gdb.Type for some standard types:
461494
_type_char_ptr = gdb.lookup_type('char').pointer() # char*
50d7a9
_type_unsigned_char_ptr = gdb.lookup_type('unsigned char').pointer() # unsigned char*
461494
_type_void_ptr = gdb.lookup_type('void').pointer() # void*
461494
_type_size_t = gdb.lookup_type('size_t')
461494
461494
SIZEOF_VOID_P = _type_void_ptr.sizeof
461494
461494
461494
Py_TPFLAGS_HEAPTYPE = (1L << 9)
461494
461494
Py_TPFLAGS_INT_SUBCLASS      = (1L << 23)
461494
Py_TPFLAGS_LONG_SUBCLASS     = (1L << 24)
461494
Py_TPFLAGS_LIST_SUBCLASS     = (1L << 25)
461494
Py_TPFLAGS_TUPLE_SUBCLASS    = (1L << 26)
461494
Py_TPFLAGS_STRING_SUBCLASS   = (1L << 27)
461494
Py_TPFLAGS_UNICODE_SUBCLASS  = (1L << 28)
461494
Py_TPFLAGS_DICT_SUBCLASS     = (1L << 29)
461494
Py_TPFLAGS_BASE_EXC_SUBCLASS = (1L << 30)
461494
Py_TPFLAGS_TYPE_SUBCLASS     = (1L << 31)
461494
461494
01f3a7
MAX_OUTPUT_LEN=1024
01f3a7
461494
class NullPyObjectPtr(RuntimeError):
461494
    pass
461494
461494
461494
def safety_limit(val):
461494
    # Given a integer value from the process being debugged, limit it to some
461494
    # safety threshold so that arbitrary breakage within said process doesn't
461494
    # break the gdb process too much (e.g. sizes of iterations, sizes of lists)
01f3a7
    return min(val, 1000)
461494
461494
461494
def safe_range(val):
461494
    # As per range, but don't trust the value too much: cap it to a safety
461494
    # threshold in case the data was corrupted
461494
    return xrange(safety_limit(val))
461494
461494
01f3a7
class StringTruncated(RuntimeError):
01f3a7
    pass
01f3a7
01f3a7
class TruncatedStringIO(object):
01f3a7
    '''Similar to cStringIO, but can truncate the output by raising a
01f3a7
    StringTruncated exception'''
01f3a7
    def __init__(self, maxlen=None):
01f3a7
        self._val = ''
01f3a7
        self.maxlen = maxlen
01f3a7
01f3a7
    def write(self, data):
01f3a7
        if self.maxlen:
01f3a7
            if len(data) + len(self._val) > self.maxlen:
01f3a7
                # Truncation:
01f3a7
                self._val += data[0:self.maxlen - len(self._val)]
01f3a7
                raise StringTruncated()
01f3a7
            
01f3a7
        self._val += data
01f3a7
01f3a7
    def getvalue(self):
01f3a7
        return self._val
01f3a7
461494
class PyObjectPtr(object):
461494
    """
461494
    Class wrapping a gdb.Value that's a either a (PyObject*) within the
461494
    inferior process, or some subclass pointer e.g. (PyStringObject*)
461494
461494
    There will be a subclass for every refined PyObject type that we care
461494
    about.
461494
461494
    Note that at every stage the underlying pointer could be NULL, point
461494
    to corrupt data, etc; this is the debugger, after all.
461494
    """
461494
    _typename = 'PyObject'
461494
461494
    def __init__(self, gdbval, cast_to=None):
461494
        if cast_to:
461494
                self._gdbval = gdbval.cast(cast_to)
461494
        else:
461494
            self._gdbval = gdbval
461494
461494
    def field(self, name):
461494
        '''
461494
        Get the gdb.Value for the given field within the PyObject, coping with
461494
        some python 2 versus python 3 differences.
461494
461494
        Various libpython types are defined using the "PyObject_HEAD" and
461494
        "PyObject_VAR_HEAD" macros.
461494
461494
        In Python 2, this these are defined so that "ob_type" and (for a var
461494
        object) "ob_size" are fields of the type in question.
461494
461494
        In Python 3, this is defined as an embedded PyVarObject type thus:
461494
           PyVarObject ob_base;
461494
        so that the "ob_size" field is located insize the "ob_base" field, and
461494
        the "ob_type" is most easily accessed by casting back to a (PyObject*).
461494
        '''
461494
        if self.is_null():
461494
            raise NullPyObjectPtr(self)
461494
461494
        if name == 'ob_type':
461494
            pyo_ptr = self._gdbval.cast(PyObjectPtr.get_gdb_type())
461494
            return pyo_ptr.dereference()[name]
461494
461494
        if name == 'ob_size':
461494
            try:
461494
                # Python 2:
461494
                return self._gdbval.dereference()[name]
461494
            except RuntimeError:
461494
                # Python 3:
461494
                return self._gdbval.dereference()['ob_base'][name]
461494
461494
        # General case: look it up inside the object:
461494
        return self._gdbval.dereference()[name]
461494
01f3a7
    def pyop_field(self, name):
01f3a7
        '''
01f3a7
        Get a PyObjectPtr for the given PyObject* field within this PyObject,
01f3a7
        coping with some python 2 versus python 3 differences.
01f3a7
        '''
01f3a7
        return PyObjectPtr.from_pyobject_ptr(self.field(name))
01f3a7
01f3a7
    def write_field_repr(self, name, out, visited):
01f3a7
        '''
01f3a7
        Extract the PyObject* field named "name", and write its representation
01f3a7
        to file-like object "out"
01f3a7
        '''
01f3a7
        field_obj = self.pyop_field(name)
01f3a7
        field_obj.write_repr(out, visited)
01f3a7
01f3a7
    def get_truncated_repr(self, maxlen):
01f3a7
        '''
01f3a7
        Get a repr-like string for the data, but truncate it at "maxlen" bytes
01f3a7
        (ending the object graph traversal as soon as you do)
01f3a7
        '''
01f3a7
        out = TruncatedStringIO(maxlen)
01f3a7
        try:
01f3a7
            self.write_repr(out, set())
01f3a7
        except StringTruncated:
01f3a7
            # Truncation occurred:
01f3a7
            return out.getvalue() + '...(truncated)'
01f3a7
01f3a7
        # No truncation occurred:
01f3a7
        return out.getvalue()
01f3a7
461494
    def type(self):
461494
        return PyTypeObjectPtr(self.field('ob_type'))
461494
461494
    def is_null(self):
461494
        return 0 == long(self._gdbval)
461494
01f3a7
    def is_optimized_out(self):
01f3a7
        '''
01f3a7
        Is the value of the underlying PyObject* visible to the debugger?
01f3a7
01f3a7
        This can vary with the precise version of the compiler used to build
01f3a7
        Python, and the precise version of gdb.
01f3a7
01f3a7
        See e.g. https://bugzilla.redhat.com/show_bug.cgi?id=556975 with
01f3a7
        PyEval_EvalFrameEx's "f"
01f3a7
        '''
01f3a7
        return self._gdbval.is_optimized_out
01f3a7
461494
    def safe_tp_name(self):
461494
        try:
461494
            return self.type().field('tp_name').string()
461494
        except NullPyObjectPtr:
461494
            # NULL tp_name?
461494
            return 'unknown'
461494
        except RuntimeError:
461494
            # Can't even read the object at all?
461494
            return 'unknown'
461494
50d7a9
    def proxyval(self, visited):
461494
        '''
461494
        Scrape a value from the inferior process, and try to represent it
461494
        within the gdb process, whilst (hopefully) avoiding crashes when
461494
        the remote data is corrupt.
461494
461494
        Derived classes will override this.
461494
461494
        For example, a PyIntObject* with ob_ival 42 in the inferior process
461494
        should result in an int(42) in this process.
50d7a9
50d7a9
        visited: a set of all gdb.Value pyobject pointers already visited
50d7a9
        whilst generating this value (to guard against infinite recursion when
50d7a9
        visiting object graphs with loops).  Analogous to Py_ReprEnter and
50d7a9
        Py_ReprLeave
461494
        '''
461494
461494
        class FakeRepr(object):
461494
            """
461494
            Class representing a non-descript PyObject* value in the inferior
461494
            process for when we don't have a custom scraper, intended to have
461494
            a sane repr().
461494
            """
461494
461494
            def __init__(self, tp_name, address):
461494
                self.tp_name = tp_name
461494
                self.address = address
461494
461494
            def __repr__(self):
461494
                # For the NULL pointer, we have no way of knowing a type, so
461494
                # special-case it as per
461494
                # http://bugs.python.org/issue8032#msg100882
461494
                if self.address == 0:
461494
                    return '0x0'
461494
                return '<%s at remote 0x%x>' % (self.tp_name, self.address)
461494
461494
        return FakeRepr(self.safe_tp_name(),
461494
                        long(self._gdbval))
461494
01f3a7
    def write_repr(self, out, visited):
01f3a7
        '''
01f3a7
        Write a string representation of the value scraped from the inferior
01f3a7
        process to "out", a file-like object.
01f3a7
        '''
01f3a7
        # Default implementation: generate a proxy value and write its repr
01f3a7
        # However, this could involve a lot of work for complicated objects,
01f3a7
        # so for derived classes we specialize this
01f3a7
        return out.write(repr(self.proxyval(visited)))
01f3a7
461494
    @classmethod
461494
    def subclass_from_type(cls, t):
461494
        '''
461494
        Given a PyTypeObjectPtr instance wrapping a gdb.Value that's a
461494
        (PyTypeObject*), determine the corresponding subclass of PyObjectPtr
461494
        to use
461494
461494
        Ideally, we would look up the symbols for the global types, but that
461494
        isn't working yet:
461494
          (gdb) python print gdb.lookup_symbol('PyList_Type')[0].value
461494
          Traceback (most recent call last):
461494
            File "<string>", line 1, in <module>
461494
          NotImplementedError: Symbol type not yet supported in Python scripts.
461494
          Error while executing Python code.
461494
461494
        For now, we use tp_flags, after doing some string comparisons on the
461494
        tp_name for some special-cases that don't seem to be visible through
461494
        flags
461494
        '''
461494
        try:
461494
            tp_name = t.field('tp_name').string()
461494
            tp_flags = int(t.field('tp_flags'))
461494
        except RuntimeError:
461494
            # Handle any kind of error e.g. NULL ptrs by simply using the base
461494
            # class
461494
            return cls
461494
461494
        #print 'tp_flags = 0x%08x' % tp_flags
461494
        #print 'tp_name = %r' % tp_name
461494
461494
        name_map = {'bool': PyBoolObjectPtr,
461494
                    'classobj': PyClassObjectPtr,
461494
                    'instance': PyInstanceObjectPtr,
461494
                    'NoneType': PyNoneStructPtr,
461494
                    'frame': PyFrameObjectPtr,
50d7a9
                    'set' : PySetObjectPtr,
50d7a9
                    'frozenset' : PySetObjectPtr,
01f3a7
                    'builtin_function_or_method' : PyCFunctionObjectPtr,
461494
                    }
461494
        if tp_name in name_map:
461494
            return name_map[tp_name]
461494
461494
        if tp_flags & Py_TPFLAGS_HEAPTYPE:
461494
            return HeapTypeObjectPtr
461494
461494
        if tp_flags & Py_TPFLAGS_INT_SUBCLASS:
461494
            return PyIntObjectPtr
461494
        if tp_flags & Py_TPFLAGS_LONG_SUBCLASS:
461494
            return PyLongObjectPtr
461494
        if tp_flags & Py_TPFLAGS_LIST_SUBCLASS:
461494
            return PyListObjectPtr
461494
        if tp_flags & Py_TPFLAGS_TUPLE_SUBCLASS:
461494
            return PyTupleObjectPtr
461494
        if tp_flags & Py_TPFLAGS_STRING_SUBCLASS:
461494
            return PyStringObjectPtr
461494
        if tp_flags & Py_TPFLAGS_UNICODE_SUBCLASS:
461494
            return PyUnicodeObjectPtr
461494
        if tp_flags & Py_TPFLAGS_DICT_SUBCLASS:
461494
            return PyDictObjectPtr
50d7a9
        if tp_flags & Py_TPFLAGS_BASE_EXC_SUBCLASS:
50d7a9
            return PyBaseExceptionObjectPtr
461494
        #if tp_flags & Py_TPFLAGS_TYPE_SUBCLASS:
461494
        #    return PyTypeObjectPtr
461494
461494
        # Use the base class:
461494
        return cls
461494
461494
    @classmethod
461494
    def from_pyobject_ptr(cls, gdbval):
461494
        '''
461494
        Try to locate the appropriate derived class dynamically, and cast
461494
        the pointer accordingly.
461494
        '''
461494
        try:
461494
            p = PyObjectPtr(gdbval)
461494
            cls = cls.subclass_from_type(p.type())
461494
            return cls(gdbval, cast_to=cls.get_gdb_type())
461494
        except RuntimeError:
461494
            # Handle any kind of error e.g. NULL ptrs by simply using the base
461494
            # class
461494
            pass
461494
        return cls(gdbval)
461494
461494
    @classmethod
461494
    def get_gdb_type(cls):
461494
        return gdb.lookup_type(cls._typename).pointer()
461494
50d7a9
    def as_address(self):
50d7a9
        return long(self._gdbval)
50d7a9
50d7a9
50d7a9
class ProxyAlreadyVisited(object):
50d7a9
    '''
50d7a9
    Placeholder proxy to use when protecting against infinite recursion due to
50d7a9
    loops in the object graph.
50d7a9
50d7a9
    Analogous to the values emitted by the users of Py_ReprEnter and Py_ReprLeave
50d7a9
    '''
50d7a9
    def __init__(self, rep):
50d7a9
        self._rep = rep
50d7a9
    
50d7a9
    def __repr__(self):
50d7a9
        return self._rep
461494
01f3a7
01f3a7
def _write_instance_repr(out, visited, name, pyop_attrdict, address):
01f3a7
    '''Shared code for use by old-style and new-style classes:
01f3a7
    write a representation to file-like object "out"'''
01f3a7
    out.write('<')
01f3a7
    out.write(name)
01f3a7
01f3a7
    # Write dictionary of instance attributes:
01f3a7
    if isinstance(pyop_attrdict, PyDictObjectPtr):
01f3a7
        out.write('(')
01f3a7
        first = True
01f3a7
        for pyop_arg, pyop_val in pyop_attrdict.iteritems():
01f3a7
            if not first:
01f3a7
                out.write(', ')
01f3a7
            first = False
01f3a7
            out.write(pyop_arg.proxyval(visited))
01f3a7
            out.write('=')
01f3a7
            pyop_val.write_repr(out, visited)
01f3a7
        out.write(')')
01f3a7
    out.write(' at remote 0x%x>' % address)
01f3a7
01f3a7
461494
class InstanceProxy(object):
461494
461494
    def __init__(self, cl_name, attrdict, address):
461494
        self.cl_name = cl_name
461494
        self.attrdict = attrdict
461494
        self.address = address
461494
461494
    def __repr__(self):
461494
        if isinstance(self.attrdict, dict):
461494
            kwargs = ', '.join(["%s=%r" % (arg, val)
461494
                                for arg, val in self.attrdict.iteritems()])
461494
            return '<%s(%s) at remote 0x%x>' % (self.cl_name,
461494
                                                kwargs, self.address)
461494
        else:
461494
            return '<%s at remote 0x%x>' % (self.cl_name,
461494
                                            self.address)
01f3a7
    
461494
def _PyObject_VAR_SIZE(typeobj, nitems):
461494
    return ( ( typeobj.field('tp_basicsize') +
461494
               nitems * typeobj.field('tp_itemsize') +
461494
               (SIZEOF_VOID_P - 1)
461494
             ) & ~(SIZEOF_VOID_P - 1)
461494
           ).cast(_type_size_t)
461494
461494
class HeapTypeObjectPtr(PyObjectPtr):
461494
    _typename = 'PyObject'
461494
01f3a7
    def get_attr_dict(self):
461494
        '''
01f3a7
        Get the PyDictObject ptr representing the attribute dictionary
01f3a7
        (or None if there's a problem)
461494
        '''
461494
        try:
461494
            typeobj = self.type()
461494
            dictoffset = int_from_int(typeobj.field('tp_dictoffset'))
461494
            if dictoffset != 0:
461494
                if dictoffset < 0:
461494
                    type_PyVarObject_ptr = gdb.lookup_type('PyVarObject').pointer()
461494
                    tsize = int_from_int(self._gdbval.cast(type_PyVarObject_ptr)['ob_size'])
461494
                    if tsize < 0:
461494
                        tsize = -tsize
461494
                    size = _PyObject_VAR_SIZE(typeobj, tsize)
461494
                    dictoffset += size
461494
                    assert dictoffset > 0
461494
                    assert dictoffset % SIZEOF_VOID_P == 0
461494
461494
                dictptr = self._gdbval.cast(_type_char_ptr) + dictoffset
461494
                PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer()
461494
                dictptr = dictptr.cast(PyObjectPtrPtr)
01f3a7
                return PyObjectPtr.from_pyobject_ptr(dictptr.dereference())
461494
        except RuntimeError:
461494
            # Corrupt data somewhere; fail safe
50d7a9
            pass
461494
01f3a7
        # Not found, or some kind of error:
01f3a7
        return None        
01f3a7
01f3a7
    def proxyval(self, visited):
01f3a7
        '''
01f3a7
        Support for new-style classes.
01f3a7
01f3a7
        Currently we just locate the dictionary using a transliteration to
01f3a7
        python of _PyObject_GetDictPtr, ignoring descriptors
01f3a7
        '''
01f3a7
        # Guard against infinite loops:
01f3a7
        if self.as_address() in visited:
01f3a7
            return ProxyAlreadyVisited('<...>')
01f3a7
        visited.add(self.as_address())
01f3a7
01f3a7
        pyop_attr_dict = self.get_attr_dict()
01f3a7
        if pyop_attr_dict:
01f3a7
            attr_dict = pyop_attr_dict.proxyval(visited)
01f3a7
        else:
01f3a7
            attr_dict = {}
461494
        tp_name = self.safe_tp_name()
461494
461494
        # New-style class:
461494
        return InstanceProxy(tp_name, attr_dict, long(self._gdbval))
461494
01f3a7
    def write_repr(self, out, visited):
01f3a7
        # Guard against infinite loops:
01f3a7
        if self.as_address() in visited:
01f3a7
            out.write('<...>')
01f3a7
            return
01f3a7
        visited.add(self.as_address())
01f3a7
01f3a7
        pyop_attrdict = self.get_attr_dict()
01f3a7
        _write_instance_repr(out, visited,
01f3a7
                             self.safe_tp_name(), pyop_attrdict, self.as_address())
01f3a7
50d7a9
class ProxyException(Exception):
50d7a9
    def __init__(self, tp_name, args):
50d7a9
        self.tp_name = tp_name
50d7a9
        self.args = args
50d7a9
50d7a9
    def __repr__(self):
50d7a9
        return '%s%r' % (self.tp_name, self.args)
50d7a9
50d7a9
class PyBaseExceptionObjectPtr(PyObjectPtr):
50d7a9
    """
50d7a9
    Class wrapping a gdb.Value that's a PyBaseExceptionObject* i.e. an exception
50d7a9
    within the process being debugged.
50d7a9
    """
50d7a9
    _typename = 'PyBaseExceptionObject'
50d7a9
50d7a9
    def proxyval(self, visited):
50d7a9
        # Guard against infinite loops:
50d7a9
        if self.as_address() in visited:
50d7a9
            return ProxyAlreadyVisited('(...)')
50d7a9
        visited.add(self.as_address())
01f3a7
        arg_proxy = self.pyop_field('args').proxyval(visited)
50d7a9
        return ProxyException(self.safe_tp_name(),
50d7a9
                              arg_proxy)
461494
01f3a7
    def write_repr(self, out, visited):
01f3a7
        # Guard against infinite loops:
01f3a7
        if self.as_address() in visited:
01f3a7
            out.write('(...)')
01f3a7
            return
01f3a7
        visited.add(self.as_address())
01f3a7
01f3a7
        out.write(self.safe_tp_name())
01f3a7
        self.write_field_repr('args', out, visited)
01f3a7
461494
class PyBoolObjectPtr(PyObjectPtr):
461494
    """
461494
    Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two
461494
    <bool> instances (Py_True/Py_False) within the process being debugged.
461494
    """
461494
    _typename = 'PyBoolObject'
461494
50d7a9
    def proxyval(self, visited):
461494
        if int_from_int(self.field('ob_ival')):
461494
            return True
461494
        else:
461494
            return False
461494
461494
461494
class PyClassObjectPtr(PyObjectPtr):
461494
    """
461494
    Class wrapping a gdb.Value that's a PyClassObject* i.e. a <classobj>
461494
    instance within the process being debugged.
461494
    """
461494
    _typename = 'PyClassObject'
461494
461494
01f3a7
class BuiltInFunctionProxy(object):
01f3a7
    def __init__(self, ml_name):
01f3a7
        self.ml_name = ml_name
01f3a7
01f3a7
    def __repr__(self):
01f3a7
        return "<built-in function="" %s="">" % self.ml_name
01f3a7
01f3a7
class BuiltInMethodProxy(object):
01f3a7
    def __init__(self, ml_name, pyop_m_self):
01f3a7
        self.ml_name = ml_name
01f3a7
        self.pyop_m_self = pyop_m_self
01f3a7
        
01f3a7
    def __repr__(self):
01f3a7
        return ('<built-in method="" %s="" of="" %s="" object="" at="" remote="" 0x%x="">'
01f3a7
                % (self.ml_name,
01f3a7
                   self.pyop_m_self.safe_tp_name(),
01f3a7
                   self.pyop_m_self.as_address())
01f3a7
                )
01f3a7
01f3a7
class PyCFunctionObjectPtr(PyObjectPtr):
01f3a7
    """
01f3a7
    Class wrapping a gdb.Value that's a PyCFunctionObject*
01f3a7
    (see Include/methodobject.h and Objects/methodobject.c)
01f3a7
    """
01f3a7
    _typename = 'PyCFunctionObject'
01f3a7
01f3a7
    def proxyval(self, visited):
01f3a7
        m_ml = self.field('m_ml') # m_ml is a (PyMethodDef*)
01f3a7
        ml_name = m_ml['ml_name'].string()
01f3a7
01f3a7
        pyop_m_self = self.pyop_field('m_self')
01f3a7
        if pyop_m_self.is_null():
01f3a7
            return BuiltInFunctionProxy(ml_name)
01f3a7
        else:
01f3a7
            return BuiltInMethodProxy(ml_name, pyop_m_self)
01f3a7
01f3a7
461494
class PyCodeObjectPtr(PyObjectPtr):
461494
    """
461494
    Class wrapping a gdb.Value that's a PyCodeObject* i.e. a  instance
461494
    within the process being debugged.
461494
    """
461494
    _typename = 'PyCodeObject'
461494
461494
    def addr2line(self, addrq):
461494
        '''
461494
        Get the line number for a given bytecode offset
461494
461494
        Analogous to PyCode_Addr2Line; translated from pseudocode in
461494
        Objects/lnotab_notes.txt
461494
        '''
01f3a7
        co_lnotab = self.pyop_field('co_lnotab').proxyval(set())
461494
461494
        # Initialize lineno to co_firstlineno as per PyCode_Addr2Line
461494
        # not 0, as lnotab_notes.txt has it:
461494
	lineno = int_from_int(self.field('co_firstlineno'))
461494
461494
        addr = 0
461494
        for addr_incr, line_incr in zip(co_lnotab[::2], co_lnotab[1::2]):
461494
            addr += ord(addr_incr)
461494
            if addr > addrq:
461494
                return lineno
461494
            lineno += ord(line_incr)
461494
        return lineno
461494
01f3a7
461494
class PyDictObjectPtr(PyObjectPtr):
461494
    """
461494
    Class wrapping a gdb.Value that's a PyDictObject* i.e. a dict instance
461494
    within the process being debugged.
461494
    """
461494
    _typename = 'PyDictObject'
461494
01f3a7
    def iteritems(self):
01f3a7
        '''
01f3a7
        Yields a sequence of (PyObjectPtr key, PyObjectPtr value) pairs,
01f3a7
        analagous to dict.iteritems()
01f3a7
        '''
01f3a7
        for i in safe_range(self.field('ma_mask') + 1):
01f3a7
            ep = self.field('ma_table') + i
01f3a7
            pyop_value = PyObjectPtr.from_pyobject_ptr(ep['me_value'])
01f3a7
            if not pyop_value.is_null():
01f3a7
                pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key'])
01f3a7
                yield (pyop_key, pyop_value)
01f3a7
50d7a9
    def proxyval(self, visited):
50d7a9
        # Guard against infinite loops:
50d7a9
        if self.as_address() in visited:
50d7a9
            return ProxyAlreadyVisited('{...}')
50d7a9
        visited.add(self.as_address())
50d7a9
461494
        result = {}
01f3a7
        for pyop_key, pyop_value in self.iteritems():
01f3a7
                proxy_key = pyop_key.proxyval(visited)
01f3a7
                proxy_value = pyop_value.proxyval(visited)
01f3a7
                result[proxy_key] = proxy_value
461494
        return result
461494
01f3a7
    def write_repr(self, out, visited):
01f3a7
        # Guard against infinite loops:
01f3a7
        if self.as_address() in visited:
01f3a7
            out.write('{...}')
01f3a7
            return
01f3a7
        visited.add(self.as_address())
01f3a7
01f3a7
        out.write('{')
01f3a7
        first = True
01f3a7
        for pyop_key, pyop_value in self.iteritems():
01f3a7
            if not first:
01f3a7
                out.write(', ')
01f3a7
            first = False
01f3a7
            pyop_key.write_repr(out, visited)
01f3a7
            out.write(': ')
01f3a7
            pyop_value.write_repr(out, visited)
01f3a7
        out.write('}')
461494
461494
class PyInstanceObjectPtr(PyObjectPtr):
461494
    _typename = 'PyInstanceObject'
461494
50d7a9
    def proxyval(self, visited):
50d7a9
        # Guard against infinite loops:
50d7a9
        if self.as_address() in visited:
50d7a9
            return ProxyAlreadyVisited('<...>')
50d7a9
        visited.add(self.as_address())
50d7a9
461494
        # Get name of class:
01f3a7
        in_class = self.pyop_field('in_class')
01f3a7
        cl_name = in_class.pyop_field('cl_name').proxyval(visited)
461494
461494
        # Get dictionary of instance attributes:
01f3a7
        in_dict = self.pyop_field('in_dict').proxyval(visited)
461494
461494
        # Old-style class:
461494
        return InstanceProxy(cl_name, in_dict, long(self._gdbval))
461494
01f3a7
    def write_repr(self, out, visited):
01f3a7
        # Guard against infinite loops:
01f3a7
        if self.as_address() in visited:
01f3a7
            out.write('<...>')
01f3a7
            return
01f3a7
        visited.add(self.as_address())
01f3a7
01f3a7
        # Old-style class:
01f3a7
01f3a7
        # Get name of class:
01f3a7
        in_class = self.pyop_field('in_class')
01f3a7
        cl_name = in_class.pyop_field('cl_name').proxyval(visited)
01f3a7
01f3a7
        # Get dictionary of instance attributes:
01f3a7
        pyop_in_dict = self.pyop_field('in_dict')
461494
01f3a7
        _write_instance_repr(out, visited,
01f3a7
                             cl_name, pyop_in_dict, self.as_address())
01f3a7
        
461494
class PyIntObjectPtr(PyObjectPtr):
461494
    _typename = 'PyIntObject'
461494
50d7a9
    def proxyval(self, visited):
461494
        result = int_from_int(self.field('ob_ival'))
461494
        return result
461494
461494
class PyListObjectPtr(PyObjectPtr):
461494
    _typename = 'PyListObject'
461494
461494
    def __getitem__(self, i):
461494
        # Get the gdb.Value for the (PyObject*) with the given index:
461494
        field_ob_item = self.field('ob_item')
461494
        return field_ob_item[i]
461494
50d7a9
    def proxyval(self, visited):
50d7a9
        # Guard against infinite loops:
50d7a9
        if self.as_address() in visited:
50d7a9
            return ProxyAlreadyVisited('[...]')
50d7a9
        visited.add(self.as_address())
50d7a9
        
50d7a9
        result = [PyObjectPtr.from_pyobject_ptr(self[i]).proxyval(visited)
461494
                  for i in safe_range(int_from_int(self.field('ob_size')))]
461494
        return result
461494
01f3a7
    def write_repr(self, out, visited):
01f3a7
        # Guard against infinite loops:
01f3a7
        if self.as_address() in visited:
01f3a7
            out.write('[...]')
01f3a7
            return
01f3a7
        visited.add(self.as_address())
01f3a7
01f3a7
        out.write('[')
01f3a7
        for i in safe_range(int_from_int(self.field('ob_size'))):
01f3a7
            if i > 0:
01f3a7
                out.write(', ')
01f3a7
            element = PyObjectPtr.from_pyobject_ptr(self[i])
01f3a7
            element.write_repr(out, visited)
01f3a7
        out.write(']')
461494
461494
class PyLongObjectPtr(PyObjectPtr):
461494
    _typename = 'PyLongObject'
461494
50d7a9
    def proxyval(self, visited):
461494
        '''
461494
        Python's Include/longobjrep.h has this declaration:
461494
           struct _longobject {
461494
               PyObject_VAR_HEAD
461494
               digit ob_digit[1];
461494
           };
461494
461494
        with this description:
461494
            The absolute value of a number is equal to
461494
                 SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)
461494
            Negative numbers are represented with ob_size < 0;
461494
            zero is represented by ob_size == 0.
461494
461494
        where SHIFT can be either:
461494
            #define PyLong_SHIFT        30
461494
            #define PyLong_SHIFT        15
461494
        '''
461494
        ob_size = long(self.field('ob_size'))
461494
        if ob_size == 0:
461494
            return 0L
461494
461494
        ob_digit = self.field('ob_digit')
461494
461494
        if gdb.lookup_type('digit').sizeof == 2:
461494
            SHIFT = 15L
461494
        else:
461494
            SHIFT = 30L
461494
461494
        digits = [long(ob_digit[i]) * 2**(SHIFT*i)
461494
                  for i in safe_range(abs(ob_size))]
461494
        result = sum(digits)
461494
        if ob_size < 0:
461494
            result = -result
461494
        return result
461494
461494
461494
class PyNoneStructPtr(PyObjectPtr):
461494
    """
461494
    Class wrapping a gdb.Value that's a PyObject* pointing to the
461494
    singleton (we hope) _Py_NoneStruct with ob_type PyNone_Type
461494
    """
461494
    _typename = 'PyObject'
461494
50d7a9
    def proxyval(self, visited):
461494
        return None
461494
461494
461494
class PyFrameObjectPtr(PyObjectPtr):
461494
    _typename = 'PyFrameObject'
461494
01f3a7
    def __init__(self, gdbval, cast_to):
01f3a7
        PyObjectPtr.__init__(self, gdbval, cast_to)
01f3a7
01f3a7
        if not self.is_optimized_out():
01f3a7
            self.co = PyCodeObjectPtr.from_pyobject_ptr(self.field('f_code'))
01f3a7
            self.co_name = self.co.pyop_field('co_name')
01f3a7
            self.co_filename = self.co.pyop_field('co_filename')
01f3a7
01f3a7
            self.f_lineno = int_from_int(self.field('f_lineno'))
01f3a7
            self.f_lasti = int_from_int(self.field('f_lasti'))
01f3a7
            self.co_nlocals = int_from_int(self.co.field('co_nlocals'))
01f3a7
            self.co_varnames = PyTupleObjectPtr.from_pyobject_ptr(self.co.field('co_varnames'))
01f3a7
01f3a7
    def iter_locals(self):
01f3a7
        '''
01f3a7
        Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
01f3a7
        the local variables of this frame
01f3a7
        '''
01f3a7
        if self.is_optimized_out():
01f3a7
            return
01f3a7
01f3a7
        f_localsplus = self.field('f_localsplus')
01f3a7
        for i in safe_range(self.co_nlocals):
01f3a7
            pyop_value = PyObjectPtr.from_pyobject_ptr(f_localsplus[i])
01f3a7
            if not pyop_value.is_null():
01f3a7
                pyop_name = PyObjectPtr.from_pyobject_ptr(self.co_varnames[i])
01f3a7
                yield (pyop_name, pyop_value)
461494
01f3a7
    def iter_globals(self):
01f3a7
        '''
01f3a7
        Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
01f3a7
        the global variables of this frame
01f3a7
        '''
01f3a7
        if self.is_optimized_out():
01f3a7
            return
01f3a7
01f3a7
        pyop_globals = self.pyop_field('f_globals')
01f3a7
        return pyop_globals.iteritems()
01f3a7
01f3a7
    def iter_builtins(self):
01f3a7
        '''
01f3a7
        Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
01f3a7
        the builtin variables
01f3a7
        '''
01f3a7
        if self.is_optimized_out():
01f3a7
            return
01f3a7
01f3a7
        pyop_builtins = self.pyop_field('f_builtins')
01f3a7
        return pyop_builtins.iteritems()
01f3a7
01f3a7
    def get_var_by_name(self, name):
01f3a7
        '''
01f3a7
        Look for the named local variable, returning a (PyObjectPtr, scope) pair
01f3a7
        where scope is a string 'local', 'global', 'builtin'
01f3a7
        
01f3a7
        If not found, return (None, None)
01f3a7
        '''
01f3a7
        for pyop_name, pyop_value in self.iter_locals():
01f3a7
            if name == pyop_name.proxyval(set()):
01f3a7
                return pyop_value, 'local'
01f3a7
        for pyop_name, pyop_value in self.iter_globals():
01f3a7
            if name == pyop_name.proxyval(set()):
01f3a7
                return pyop_value, 'global'
01f3a7
        for pyop_name, pyop_value in self.iter_builtins():
01f3a7
            if name == pyop_name.proxyval(set()):
01f3a7
                return pyop_value, 'builtin'
01f3a7
        return None, None
01f3a7
01f3a7
    def filename(self):
01f3a7
        '''Get the path of the current Python source file, as a string'''
01f3a7
        if self.is_optimized_out():
01f3a7
            return '(frame information optimized out)'
01f3a7
        return self.co_filename.proxyval(set())
01f3a7
01f3a7
    def current_line_num(self):
01f3a7
        '''Get current line number as an integer (1-based)
01f3a7
        
01f3a7
        Translated from PyFrame_GetLineNumber and PyCode_Addr2Line
01f3a7
        
01f3a7
        See Objects/lnotab_notes.txt
01f3a7
        '''
01f3a7
        if self.is_optimized_out():
01f3a7
            return None
01f3a7
        f_trace = self.field('f_trace')
01f3a7
        if long(f_trace) != 0:
01f3a7
            # we have a non-NULL f_trace:
01f3a7
            return self.f_lineno
01f3a7
        else:
01f3a7
            #try:
01f3a7
            return self.co.addr2line(self.f_lasti)
01f3a7
            #except ValueError:
01f3a7
            #    return self.f_lineno
01f3a7
01f3a7
    def current_line(self):
01f3a7
        '''Get the text of the current source line as a string, with a trailing
01f3a7
        newline character'''
01f3a7
        if self.is_optimized_out():
01f3a7
            return '(frame information optimized out)'
01f3a7
        with open(self.filename(), 'r') as f:
01f3a7
            all_lines = f.readlines()
01f3a7
            # Convert from 1-based current_line_num to 0-based list offset:
01f3a7
            return all_lines[self.current_line_num()-1]
01f3a7
01f3a7
    def write_repr(self, out, visited):
01f3a7
        if self.is_optimized_out():
01f3a7
            out.write('(frame information optimized out)')
01f3a7
            return
01f3a7
        out.write('Frame 0x%x, for file %s, line %i, in %s ('
01f3a7
                  % (self.as_address(),
01f3a7
                     self.co_filename,
01f3a7
                     self.current_line_num(),
01f3a7
                     self.co_name))
01f3a7
        first = True
01f3a7
        for pyop_name, pyop_value in self.iter_locals():
01f3a7
            if not first:
01f3a7
                out.write(', ')
01f3a7
            first = False
01f3a7
01f3a7
            out.write(pyop_name.proxyval(visited))
01f3a7
            out.write('=')
01f3a7
            pyop_value.write_repr(out, visited)
01f3a7
            
01f3a7
        out.write(')')
461494
50d7a9
class PySetObjectPtr(PyObjectPtr):
50d7a9
    _typename = 'PySetObject'
50d7a9
50d7a9
    def proxyval(self, visited):
50d7a9
        # Guard against infinite loops:
50d7a9
        if self.as_address() in visited:
50d7a9
            return ProxyAlreadyVisited('%s(...)' % self.safe_tp_name())
50d7a9
        visited.add(self.as_address())
50d7a9
50d7a9
        members = []
50d7a9
        table = self.field('table')
50d7a9
        for i in safe_range(self.field('mask')+1):
50d7a9
            setentry = table[i]
50d7a9
            key = setentry['key']
50d7a9
            if key != 0:
50d7a9
                key_proxy = PyObjectPtr.from_pyobject_ptr(key).proxyval(visited)
50d7a9
                if key_proxy != '<dummy key="">':
50d7a9
                    members.append(key_proxy)
50d7a9
        if self.safe_tp_name() == 'frozenset':
50d7a9
            return frozenset(members)
50d7a9
        else:
50d7a9
            return set(members)
50d7a9
01f3a7
    def write_repr(self, out, visited):
01f3a7
        out.write(self.safe_tp_name())
01f3a7
01f3a7
        # Guard against infinite loops:
01f3a7
        if self.as_address() in visited:
01f3a7
            out.write('(...)')
01f3a7
            return
01f3a7
        visited.add(self.as_address())
01f3a7
01f3a7
        out.write('([')
01f3a7
        first = True
01f3a7
        table = self.field('table')
01f3a7
        for i in safe_range(self.field('mask')+1):
01f3a7
            setentry = table[i]
01f3a7
            key = setentry['key']
01f3a7
            if key != 0:
01f3a7
                pyop_key = PyObjectPtr.from_pyobject_ptr(key)
01f3a7
                key_proxy = pyop_key.proxyval(visited) # FIXME!
01f3a7
                if key_proxy != '<dummy key="">':
01f3a7
                    if not first:
01f3a7
                        out.write(', ')
01f3a7
                    first = False
01f3a7
                    pyop_key.write_repr(out, visited)
01f3a7
        out.write('])')
01f3a7
01f3a7
461494
class PyStringObjectPtr(PyObjectPtr):
461494
    _typename = 'PyStringObject'
461494
461494
    def __str__(self):
461494
        field_ob_size = self.field('ob_size')
461494
        field_ob_sval = self.field('ob_sval')
50d7a9
        char_ptr = field_ob_sval.address.cast(_type_unsigned_char_ptr)
50d7a9
        return ''.join([chr(char_ptr[i]) for i in safe_range(field_ob_size)])
461494
50d7a9
    def proxyval(self, visited):
461494
        return str(self)
461494
461494
class PyTupleObjectPtr(PyObjectPtr):
461494
    _typename = 'PyTupleObject'
461494
461494
    def __getitem__(self, i):
461494
        # Get the gdb.Value for the (PyObject*) with the given index:
461494
        field_ob_item = self.field('ob_item')
461494
        return field_ob_item[i]
461494
50d7a9
    def proxyval(self, visited):
50d7a9
        # Guard against infinite loops:
50d7a9
        if self.as_address() in visited:
50d7a9
            return ProxyAlreadyVisited('(...)')
50d7a9
        visited.add(self.as_address())
50d7a9
50d7a9
        result = tuple([PyObjectPtr.from_pyobject_ptr(self[i]).proxyval(visited)
461494
                        for i in safe_range(int_from_int(self.field('ob_size')))])
461494
        return result
461494
01f3a7
    def write_repr(self, out, visited):
01f3a7
        # Guard against infinite loops:
01f3a7
        if self.as_address() in visited:
01f3a7
            out.write('(...)')
01f3a7
            return
01f3a7
        visited.add(self.as_address())
01f3a7
01f3a7
        out.write('(')
01f3a7
        for i in safe_range(int_from_int(self.field('ob_size'))):
01f3a7
            if i > 0:
01f3a7
                out.write(', ')
01f3a7
            element = PyObjectPtr.from_pyobject_ptr(self[i])
01f3a7
            element.write_repr(out, visited)
01f3a7
        if self.field('ob_size') == 1:
01f3a7
            out.write(',)')
01f3a7
        else:
01f3a7
            out.write(')')
461494
461494
class PyTypeObjectPtr(PyObjectPtr):
461494
    _typename = 'PyTypeObject'
461494
461494
461494
class PyUnicodeObjectPtr(PyObjectPtr):
461494
    _typename = 'PyUnicodeObject'
461494
50d7a9
    def proxyval(self, visited):
461494
        # From unicodeobject.h:
461494
        #     Py_ssize_t length;  /* Length of raw Unicode data in buffer */
461494
        #     Py_UNICODE *str;    /* Raw Unicode buffer */
461494
        field_length = long(self.field('length'))
461494
        field_str = self.field('str')
461494
461494
        # Gather a list of ints from the Py_UNICODE array; these are either
461494
        # UCS-2 or UCS-4 code points:
461494
        Py_UNICODEs = [int(field_str[i]) for i in safe_range(field_length)]
461494
461494
        # Convert the int code points to unicode characters, and generate a
461494
        # local unicode instance:
461494
        result = u''.join([unichr(ucs) for ucs in Py_UNICODEs])
461494
        return result
461494
461494
461494
def int_from_int(gdbval):
461494
    return int(str(gdbval))
461494
461494
461494
def stringify(val):
461494
    # TODO: repr() puts everything on one line; pformat can be nicer, but
461494
    # can lead to v.long results; this function isolates the choice
461494
    if True:
01f3a7
        return repr(val)            
461494
    else:
461494
        from pprint import pformat
461494
        return pformat(val)
461494
461494
461494
class PyObjectPtrPrinter:
461494
    "Prints a (PyObject*)"
461494
461494
    def __init__ (self, gdbval):
461494
        self.gdbval = gdbval
461494
461494
    def to_string (self):
461494
        pyop = PyObjectPtr.from_pyobject_ptr(self.gdbval)
01f3a7
        if True:
01f3a7
            return pyop.get_truncated_repr(MAX_OUTPUT_LEN)
01f3a7
        else:
01f3a7
            # Generate full proxy value then stringify it.
01f3a7
            # Doing so could be expensive
01f3a7
            proxyval = pyop.proxyval(set())
01f3a7
            return stringify(proxyval)
461494
461494
def pretty_printer_lookup(gdbval):
461494
    type = gdbval.type.unqualified()
461494
    if type.code == gdb.TYPE_CODE_PTR:
461494
        type = type.target().unqualified()
461494
        t = str(type)
01f3a7
        if t in ("PyObject", "PyFrameObject"):
461494
            return PyObjectPtrPrinter(gdbval)
461494
461494
"""
461494
During development, I've been manually invoking the code in this way:
461494
(gdb) python
461494
461494
import sys
461494
sys.path.append('/home/david/coding/python-gdb')
461494
import libpython
461494
end
461494
461494
then reloading it after each edit like this:
461494
(gdb) python reload(libpython)
461494
461494
The following code should ensure that the prettyprinter is registered
461494
if the code is autoloaded by gdb when visiting libpython.so, provided
461494
that this python file is installed to the same path as the library (or its
461494
.debug file) plus a "-gdb.py" suffix, e.g:
461494
  /usr/lib/libpython2.6.so.1.0-gdb.py
461494
  /usr/lib/debug/usr/lib/libpython2.6.so.1.0.debug-gdb.py
461494
"""
461494
def register (obj):
461494
    if obj == None:
461494
        obj = gdb
461494
461494
    # Wire up the pretty-printer
461494
    obj.pretty_printers.append(pretty_printer_lookup)
461494
461494
register (gdb.current_objfile ())
461494
461494
01f3a7
class Frame(object):
01f3a7
    '''
01f3a7
    Wrapper for gdb.Frame, adding various methods 
01f3a7
    '''
01f3a7
    def __init__(self, gdbframe):
01f3a7
        self._gdbframe = gdbframe
01f3a7
01f3a7
    def older(self):
01f3a7
        older = self._gdbframe.older()
01f3a7
        if older:
01f3a7
            return Frame(older)
01f3a7
        else:
01f3a7
            return None
461494
01f3a7
    def newer(self):
01f3a7
        newer = self._gdbframe.newer()
01f3a7
        if newer:
01f3a7
            return Frame(newer)
01f3a7
        else:
01f3a7
            return None
01f3a7
01f3a7
    def select(self):
01f3a7
        self._gdbframe.select()
01f3a7
01f3a7
    def get_index(self):
01f3a7
        '''Calculate index of frame, starting at 0 for the newest frame within
01f3a7
        this thread'''
01f3a7
        index = 0
01f3a7
        # Go down until you reach the newest frame:
01f3a7
        iter_frame = self
01f3a7
        while iter_frame.newer():
01f3a7
            index += 1
01f3a7
            iter_frame = iter_frame.newer()
01f3a7
        return index
01f3a7
01f3a7
    def is_evalframeex(self):
01f3a7
        if self._gdbframe.function():
01f3a7
            if self._gdbframe.function().name == 'PyEval_EvalFrameEx':
01f3a7
                '''
01f3a7
                I believe we also need to filter on the inline
01f3a7
                struct frame_id.inline_depth, only regarding frames with
01f3a7
                an inline depth of 0 as actually being this function
01f3a7
01f3a7
                So we reject those with type gdb.INLINE_FRAME
01f3a7
                '''
01f3a7
                if self._gdbframe.type() == gdb.NORMAL_FRAME:
01f3a7
                    # We have a PyEval_EvalFrameEx frame:
01f3a7
                    return True
01f3a7
01f3a7
        return False
01f3a7
01f3a7
    def get_pyop(self):
461494
        try:
01f3a7
            f = self._gdbframe.read_var('f')
01f3a7
            return PyFrameObjectPtr.from_pyobject_ptr(f)
461494
        except ValueError:
01f3a7
            return None
01f3a7
01f3a7
    @classmethod
01f3a7
    def get_selected_frame(cls):
01f3a7
        _gdbframe = gdb.selected_frame()
01f3a7
        if _gdbframe:
01f3a7
            return Frame(_gdbframe)
01f3a7
        return None
01f3a7
01f3a7
    @classmethod
01f3a7
    def get_selected_python_frame(cls):
01f3a7
        '''Try to obtain the Frame for the python code in the selected frame,
01f3a7
        or None'''
01f3a7
        frame = cls.get_selected_frame()
01f3a7
01f3a7
        while frame:
01f3a7
            if frame.is_evalframeex():
01f3a7
                return frame
01f3a7
            frame = frame.older()
01f3a7
01f3a7
        # Not found:
01f3a7
        return None
01f3a7
01f3a7
    def print_summary(self):
01f3a7
        if self.is_evalframeex():
01f3a7
            pyop = self.get_pyop()
01f3a7
            if pyop:
01f3a7
                sys.stdout.write('#%i %s\n' % (self.get_index(), pyop.get_truncated_repr(MAX_OUTPUT_LEN)))
01f3a7
                sys.stdout.write(pyop.current_line())
01f3a7
            else:
01f3a7
                sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
01f3a7
        else:
01f3a7
            sys.stdout.write('#%i\n' % self.get_index())
461494
461494
class PyList(gdb.Command):
461494
    '''List the current Python source code, if any
461494
461494
    Use
461494
       py-list START
461494
    to list at a different line number within the python source.
461494
    
461494
    Use
461494
       py-list START, END
461494
    to list a specific range of lines within the python source.
461494
    '''
461494
461494
    def __init__(self):
461494
        gdb.Command.__init__ (self,
461494
                              "py-list",
461494
                              gdb.COMMAND_FILES,
461494
                              gdb.COMPLETE_NONE)
461494
461494
461494
    def invoke(self, args, from_tty):
461494
        import re
461494
461494
        start = None
461494
        end = None
461494
461494
        m = re.match(r'\s*(\d+)\s*', args)
461494
        if m:
461494
            start = int(m.group(0))
461494
            end = start + 10
461494
461494
        m = re.match(r'\s*(\d+)\s*,\s*(\d+)\s*', args)
461494
        if m:
461494
            start, end = map(int, m.groups())
461494
01f3a7
        frame = Frame.get_selected_python_frame()
01f3a7
        if not frame:
461494
            print 'Unable to locate python frame'
461494
            return
461494
01f3a7
        pyop = frame.get_pyop()
01f3a7
        if not pyop:
01f3a7
            print 'Unable to read information on python frame'
01f3a7
            return
01f3a7
01f3a7
        filename = pyop.filename()
01f3a7
        lineno = pyop.current_line_num()
461494
461494
        if start is None:
461494
            start = lineno - 5
461494
            end = lineno + 5
461494
461494
        if start<1:
461494
            start = 1
461494
461494
        with open(filename, 'r') as f:
461494
            all_lines = f.readlines()
461494
            # start and end are 1-based, all_lines is 0-based;
461494
            # so [start-1:end] as a python slice gives us [start, end] as a
461494
            # closed interval
461494
            for i, line in enumerate(all_lines[start-1:end]):
01f3a7
                linestr = str(i+start)
01f3a7
                # Highlight current line:
01f3a7
                if i + start == lineno:
01f3a7
                    linestr = '>' + linestr
01f3a7
                sys.stdout.write('%4s    %s' % (linestr, line))
461494
            
461494
        
461494
# ...and register the command:
461494
PyList()
461494
461494
def move_in_stack(move_up):
461494
    '''Move up or down the stack (for the py-up/py-down command)'''
01f3a7
    frame = Frame.get_selected_python_frame()
01f3a7
    while frame:
461494
        if move_up:
01f3a7
            iter_frame = frame.older()
461494
        else:
01f3a7
            iter_frame = frame.newer()
461494
461494
        if not iter_frame:
461494
            break
461494
01f3a7
        if iter_frame.is_evalframeex():
461494
            # Result:
461494
            iter_frame.select()
01f3a7
            iter_frame.print_summary()
461494
            return
461494
01f3a7
        frame = iter_frame
461494
461494
    if move_up:
461494
        print 'Unable to find an older python frame'
461494
    else:
461494
        print 'Unable to find a newer python frame'
461494
461494
class PyUp(gdb.Command):
461494
    'Select and print the python stack frame that called this one (if any)'
461494
    def __init__(self):
461494
        gdb.Command.__init__ (self,
461494
                              "py-up",
461494
                              gdb.COMMAND_STACK,
461494
                              gdb.COMPLETE_NONE)
461494
461494
461494
    def invoke(self, args, from_tty):
461494
        move_in_stack(move_up=True)
461494
461494
PyUp()
461494
461494
class PyDown(gdb.Command):
461494
    'Select and print the python stack frame called by this one (if any)'
461494
    def __init__(self):
461494
        gdb.Command.__init__ (self,
461494
                              "py-down",
461494
                              gdb.COMMAND_STACK,
461494
                              gdb.COMPLETE_NONE)
461494
461494
461494
    def invoke(self, args, from_tty):
461494
        move_in_stack(move_up=False)
461494
461494
PyDown()
461494
461494
class PyBacktrace(gdb.Command):
461494
    'Display the current python frame and all the frames within its call stack (if any)'
461494
    def __init__(self):
461494
        gdb.Command.__init__ (self,
461494
                              "py-bt",
461494
                              gdb.COMMAND_STACK,
461494
                              gdb.COMPLETE_NONE)
461494
461494
461494
    def invoke(self, args, from_tty):
01f3a7
        frame = Frame.get_selected_python_frame()
01f3a7
        while frame:
01f3a7
            if frame.is_evalframeex():
01f3a7
                frame.print_summary()
01f3a7
            frame = frame.older()
461494
01f3a7
PyBacktrace()
461494
01f3a7
class PyPrint(gdb.Command):
01f3a7
    'Look up the given python variable name, and print it'
01f3a7
    def __init__(self):
01f3a7
        gdb.Command.__init__ (self,
01f3a7
                              "py-print",
01f3a7
                              gdb.COMMAND_DATA,
01f3a7
                              gdb.COMPLETE_NONE)
461494
01f3a7
01f3a7
    def invoke(self, args, from_tty):
01f3a7
        name = str(args)
01f3a7
01f3a7
        frame = Frame.get_selected_python_frame()
01f3a7
        if not frame:
01f3a7
            print 'Unable to locate python frame'
01f3a7
            return
01f3a7
01f3a7
        pyop_frame = frame.get_pyop()
01f3a7
        if not pyop_frame:
01f3a7
            print 'Unable to read information on python frame'
01f3a7
            return
01f3a7
01f3a7
        pyop_var, scope = pyop_frame.get_var_by_name(name)
01f3a7
01f3a7
        if pyop_var:
01f3a7
            print ('%s %r = %s'
01f3a7
                   % (scope,
01f3a7
                      name,
01f3a7
                      pyop_var.get_truncated_repr(MAX_OUTPUT_LEN)))
01f3a7
        else:
01f3a7
            print '%r not found' % name
01f3a7
01f3a7
PyPrint()
01f3a7
01f3a7
class PyLocals(gdb.Command):
01f3a7
    'Look up the given python variable name, and print it'
01f3a7
    def __init__(self):
01f3a7
        gdb.Command.__init__ (self,
01f3a7
                              "py-locals",
01f3a7
                              gdb.COMMAND_DATA,
01f3a7
                              gdb.COMPLETE_NONE)
01f3a7
01f3a7
01f3a7
    def invoke(self, args, from_tty):
01f3a7
        name = str(args)
01f3a7
01f3a7
        frame = Frame.get_selected_python_frame()
01f3a7
        if not frame:
01f3a7
            print 'Unable to locate python frame'
01f3a7
            return
01f3a7
01f3a7
        pyop_frame = frame.get_pyop()
01f3a7
        if not pyop_frame:
01f3a7
            print 'Unable to read information on python frame'
01f3a7
            return
01f3a7
01f3a7
        for pyop_name, pyop_value in pyop_frame.iter_locals():
01f3a7
            print ('%s = %s'
01f3a7
                   % (pyop_name.proxyval(set()),
01f3a7
                      pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)))
01f3a7
01f3a7
PyLocals()