churchyard / rpms / python38

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