diff options
-rw-r--r-- | libpython.py | 112 | ||||
-rw-r--r-- | test_gdb.py | 44 |
2 files changed, 127 insertions, 29 deletions
diff --git a/libpython.py b/libpython.py index c574561..e994af2 100644 --- a/libpython.py +++ b/libpython.py @@ -45,6 +45,16 @@ TODO: better handling of "instance" import gdb +# Look up the gdb.Type for some standard types: +_type_char_ptr = gdb.lookup_type('char').pointer() # char* +_type_void_ptr = gdb.lookup_type('void').pointer() # void* +_type_size_t = gdb.lookup_type('size_t') + +SIZEOF_VOID_P = _type_void_ptr.sizeof + + +Py_TPFLAGS_HEAPTYPE = (1L << 9) + Py_TPFLAGS_INT_SUBCLASS = (1L << 23) Py_TPFLAGS_LONG_SUBCLASS = (1L << 24) Py_TPFLAGS_LIST_SUBCLASS = (1L << 25) @@ -132,6 +142,16 @@ class PyObjectPtr(object): def is_null(self): return 0 == long(self._gdbval) + def safe_tp_name(self): + try: + return self.type().field('tp_name').string() + except NullPyObjectPtr: + # NULL tp_name? + return 'unknown' + except RuntimeError: + # Can't even read the object at all? + return 'unknown' + def proxyval(self): ''' Scrape a value from the inferior process, and try to represent it @@ -158,15 +178,7 @@ class PyObjectPtr(object): def __repr__(self): return '<%s at remote 0x%x>' % (self.tp_name, self.address) - try: - tp_name = self.type().field('tp_name').string() - except NullPyObjectPtr: - # NULL tp_name? - tp_name = 'unknown' - except RuntimeError: - # Can't even read the object at all? - tp_name = 'unknown' - return FakeRepr(tp_name, + return FakeRepr(self.safe_tp_name(), long(self._gdbval)) @classmethod @@ -208,6 +220,9 @@ class PyObjectPtr(object): if tp_name in name_map: return name_map[tp_name] + if tp_flags & Py_TPFLAGS_HEAPTYPE: + return HeapTypeObjectPtr + if tp_flags & Py_TPFLAGS_INT_SUBCLASS: return PyIntObjectPtr if tp_flags & Py_TPFLAGS_LONG_SUBCLASS: @@ -251,6 +266,67 @@ class PyObjectPtr(object): return gdb.lookup_type(cls._typename).pointer() +class InstanceProxy(object): + + def __init__(self, cl_name, attrdict, address): + self.cl_name = cl_name + self.attrdict = attrdict + self.address = address + + def __repr__(self): + kwargs = ', '.join(["%s=%r" % (arg, val) + for arg, val in self.attrdict.iteritems()]) + return '<%s(%s) at remote 0x%x>' % (self.cl_name, + kwargs, self.address) + + +def _PyObject_VAR_SIZE(typeobj, nitems): + return ( ( typeobj.field('tp_basicsize') + + nitems * typeobj.field('tp_itemsize') + + (SIZEOF_VOID_P - 1) + ) & ~(SIZEOF_VOID_P - 1) + ).cast(_type_size_t) + +class HeapTypeObjectPtr(PyObjectPtr): + _typename = 'PyObject' + + def proxyval(self): + ''' + Support for new-style classes. + + Currently we just locate the dictionary using _PyObject_GetDictPtr, + ignoring descriptors + ''' + attr_dict = {} + + try: + typeobj = self.type() + dictoffset = int_from_int(typeobj.field('tp_dictoffset')) + if dictoffset != 0: + if dictoffset < 0: + type_PyVarObject_ptr = gdb.lookup_type('PyVarObject').pointer() + tsize = int_from_int(self._gdbval.cast(type_PyVarObject_ptr)['ob_size']) + if tsize < 0: + tsize = -tsize + size = _PyObject_VAR_SIZE(typeobj, tsize) + dictoffset += size + assert dictoffset > 0 + assert dictoffset % SIZEOF_VOID_P == 0 + + dictptr = self._gdbval.cast(_type_char_ptr) + dictoffset + PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer() + dictptr = dictptr.cast(PyObjectPtrPtr) + attr_dict = PyObjectPtr.from_pyobject_ptr(dictptr.dereference()).proxyval() + except RuntimeError: + # Corrupt data somewhere; fail safe + pass + + tp_name = self.safe_tp_name() + + # New-style class: + return InstanceProxy(tp_name, attr_dict, long(self._gdbval)) + + class PyBoolObjectPtr(PyObjectPtr): """ Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two @@ -303,20 +379,6 @@ class PyInstanceObjectPtr(PyObjectPtr): _typename = 'PyInstanceObject' def proxyval(self): - - class InstanceProxy(object): - - def __init__(self, cl_name, attrdict, address): - self.cl_name = cl_name - self.attrdict = attrdict - self.address = address - - def __repr__(self): - kwargs = ', '.join(["%s=%r" % (arg, val) - for arg, val in self.attrdict.iteritems()]) - return '<%s(%s) at remote 0x%x>' % (self.cl_name, - kwargs, self.address) - # Get name of class: in_class = PyObjectPtr.from_pyobject_ptr(self.field('in_class')) cl_name = PyObjectPtr.from_pyobject_ptr(in_class.field('cl_name')).proxyval() @@ -324,6 +386,7 @@ class PyInstanceObjectPtr(PyObjectPtr): # Get dictionary of instance attributes: in_dict = PyObjectPtr.from_pyobject_ptr(self.field('in_dict')).proxyval() + # Old-style class: return InstanceProxy(cl_name, in_dict, long(self._gdbval)) @@ -413,9 +476,6 @@ class PyStringObjectPtr(PyObjectPtr): _typename = 'PyStringObject' def __str__(self): - # Lookup the gdb.Type for "char*" - _type_char_ptr = gdb.lookup_type('char').pointer() - field_ob_sval = self.field('ob_sval') char_ptr = field_ob_sval.address.cast(_type_char_ptr) return char_ptr.string() diff --git a/test_gdb.py b/test_gdb.py index 82cef5d..a75a2b9 100644 --- a/test_gdb.py +++ b/test_gdb.py @@ -169,14 +169,53 @@ class DebuggerTests(unittest.TestCase): gdb_repr, gdb_output = self.get_gdb_repr(''' class Foo: pass -foo = Foo(); -foo.an_int = 42; +foo = Foo() +foo.an_int = 42 print foo''') # FIXME: is there an "assertMatches"; should there be? m = re.match(r'<Foo\(an_int=42\) at remote 0x[0-9a-f]+>', gdb_repr) self.assertTrue(m, msg='Unexpected classic-class rendering %r' % gdb_repr) + def test_modern_class(self): + gdb_repr, gdb_output = self.get_gdb_repr(''' +class Foo(object): + pass +foo = Foo() +foo.an_int = 42 +print foo''') + # FIXME: is there an "assertMatches"; should there be? + m = re.match(r'<Foo\(an_int=42\) at remote 0x[0-9a-f]+>', gdb_repr) + self.assertTrue(m, + msg='Unexpected new-style class rendering %r' % gdb_repr) + + def test_subclassing_list(self): + gdb_repr, gdb_output = self.get_gdb_repr(''' +class Foo(list): + pass +foo = Foo() +foo += [1, 2, 3] +foo.an_int = 42 +print foo''') + # FIXME: is there an "assertMatches"; should there be? + m = re.match(r'<Foo\(an_int=42\) at remote 0x[0-9a-f]+>', gdb_repr) + self.assertTrue(m, + msg='Unexpected new-style class rendering %r' % gdb_repr) + + def test_subclassing_tuple(self): + '''This should exercise the negative tp_dictoffset code in the + new-style class support''' + gdb_repr, gdb_output = self.get_gdb_repr(''' +class Foo(tuple): + pass +foo = Foo((1, 2, 3)) +foo.an_int = 42 +print foo''') + # FIXME: is there an "assertMatches"; should there be? + m = re.match(r'<Foo\(an_int=42\) at remote 0x[0-9a-f]+>', gdb_repr) + self.assertTrue(m, + msg='Unexpected new-style class rendering %r' % gdb_repr) + def assertSane(self, source, corruption, exp_type='unknown'): '''Run Python under gdb, corrupting variables in the inferior process immediately before taking a backtrace. @@ -214,7 +253,6 @@ print foo''') 'set op->ob_type->tp_name=0xDEADBEEF') # TODO: - # new-style classes # frames |