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