churchyard / rpms / python3

Forked from rpms/python3 6 years ago
Clone

Blame python-gdb.py

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