def is_pyobject_ptr(addr): try: _type_pyop = caching_lookup_type('PyObject').pointer() _type_pyvarop = caching_lookup_type('PyVarObject').pointer() except RuntimeError: # not linked against python return None pyop = gdb.Value(addr).cast(_type_pyop) try: ob_refcnt = pyop['ob_refcnt'] if ob_refcnt >=0 and ob_refcnt < 0xffff: obtype = pyop['ob_type'] if obtype != 0: type_refcnt = obtype.cast(_type_pyop)['ob_refcnt'] if type_refcnt > 0 and type_refcnt < 0xffff: type_ob_size = obtype.cast(_type_pyvarop)['ob_size'] if type_ob_size > 0xffff: return 0 for fieldname in ('tp_del', 'tp_mro', 'tp_init', 'tp_getset'): if not looks_like_ptr(obtype[fieldname]): return 0 # Then this looks like a Python object: return PyObjectPtr.from_pyobject_ptr(pyop) except (RuntimeError, UnicodeDecodeError): pass # Not a python object (or corrupt)
def get_attr_dict(self): ''' Get the PyDictObject ptr representing the attribute dictionary (or None if there's a problem) ''' from heap import type_char_ptr try: typeobj = self.type() dictoffset = int_from_int(typeobj.field('tp_dictoffset')) if dictoffset != 0: if dictoffset < 0: type_PyVarObject_ptr = caching_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 if dictoffset % SIZEOF_VOID_P != 0: # Corrupt somehow? return None dictptr = self._gdbval.cast(type_char_ptr) + dictoffset PyObjectPtrPtr = caching_lookup_type('PyObject').pointer().pointer() dictptr = dictptr.cast(PyObjectPtrPtr) return PyObjectPtr.from_pyobject_ptr(dictptr.dereference()) except RuntimeError: # Corrupt data somewhere; fail safe pass # Not found, or some kind of error: return None
def as_python_object(addr): '''Given an address of an allocation, determine if it holds a PyObject, or a PyGC_Head Return a WrappedPointer for the PyObject* if it does (which might have a different location c.f. when PyGC_Head was allocated) Return None if it doesn't look like a PyObject*''' # Try casting to PyObject* ? # FIXME: what about the debug allocator? try: _type_pyop = caching_lookup_type('PyObject').pointer() _type_PyGC_Head = caching_lookup_type('PyGC_Head') except RuntimeError: # not linked against python return None pyop = is_pyobject_ptr(addr) if pyop: return pyop else: # maybe a GC type: _type_PyGC_Head_ptr = _type_PyGC_Head.pointer() gc_ptr = gdb.Value(addr).cast(_type_PyGC_Head_ptr) # print gc_ptr.dereference() PYGC_REFS_REACHABLE = -3 if gc_ptr['gc']['gc_refs'] == PYGC_REFS_REACHABLE: # FIXME: need to cover other values pyop = is_pyobject_ptr(gdb.Value(addr + _type_PyGC_Head.sizeof)) if pyop: return pyop
def as_python_object(addr): '''Given an address of an allocation, determine if it holds a PyObject, or a PyGC_Head Return a WrappedPointer for the PyObject* if it does (which might have a different location c.f. when PyGC_Head was allocated) Return None if it doesn't look like a PyObject*''' # Try casting to PyObject* ? # FIXME: what about the debug allocator? try: _type_pyop = caching_lookup_type('PyObject').pointer() _type_PyGC_Head = caching_lookup_type('PyGC_Head') except RuntimeError: # not linked against python return None pyop = is_pyobject_ptr(addr) if pyop: return pyop else: # maybe a GC type: _type_PyGC_Head_ptr = _type_PyGC_Head.pointer() gc_ptr = gdb.Value(addr).cast(_type_PyGC_Head_ptr) # print gc_ptr.dereference() # PYGC_REFS_REACHABLE = -3 if gc_ptr['gc']['gc_refs'] in ( -2, -3, -4): # FIXME: need to cover other values pyop = is_pyobject_ptr(gdb.Value(addr + _type_PyGC_Head.sizeof)) if pyop: return pyop
def __str__(self): corruptFlag = False result = ('<%s chunk=0x%x mem=0x%x' % (self.__class__.__name__, self.as_address(), self.as_mem())) if self.has_PREV_INUSE(): result += ' PREV_INUSE' else: try: result += ' prev_size=%i' % self.field('prev_size') except gdb.MemoryError: corruptFlag = True result += ' prev_size=???' if self.has_NON_MAIN_ARENA(): result += ' NON_MAIN_ARENA' if self.has_IS_MMAPPED(): result += ' IS_MMAPPED' else: if self.is_inuse(): result += ' inuse' else: result += ' free' SIZE_SZ = caching_lookup_type('size_t').sizeof try: result += ' chunksize=%i memsize=%i>' % (self.chunksize(), self.memsize()) except gdb.MemoryError: result += ' chunksize=??? memsize=???>' corruptFlag = True if corruptFlag: result += "<CORRUPT CHUNK>" return result
def as_gtype_instance(addr, size): #type_GObject_ptr = caching_lookup_type('GObject').pointer() try: type_GTypeInstance_ptr = caching_lookup_type('GTypeInstance').pointer() except RuntimeError: # Not linked against GLib? return None gobj = gdb.Value(addr).cast(type_GTypeInstance_ptr) try: gtype = gobj['g_class']['g_type'] #print 'gtype', gtype typenode = get_typenode_for_gtype(gtype) # If I remove the next line, we get errors like: # Cannot access memory at address 0xd1a712caa5b6e5c0 # Does this line give us an early chance to raise an exception? #print 'typenode', typenode # It appears to be in the coercion to boolean here: # if typenode: if typenode is not None: #print 'typenode.dereference()', typenode.dereference() return GTypeInstancePtr.from_gtypeinstance_ptr(addr, typenode) except RuntimeError: # Any random buffer that we point this at that isn't a GTypeInstance (or # GObject) is likely to raise a RuntimeError at some point in the above pass return None
def _PyObject_VAR_SIZE(typeobj, nitems): type_size_t = caching_lookup_type('size_t') return ( ( typeobj.field('tp_basicsize') + nitems * typeobj.field('tp_itemsize') + (SIZEOF_VOID_P - 1) ) & ~(SIZEOF_VOID_P - 1) ).cast(type_size_t)
def categorize_refs(self, usage_set, level=0, detail=None): cl_name = self.cl_name() # print 'cl_name', cl_name # Visit the in_dict: in_dict = self.field('in_dict') # print 'in_dict', in_dict dict_detail = '%s.__dict__' % cl_name # Mark the ptr as being a dictionary, adding detail usage_set.set_addr_category(obj_addr_to_gc_addr(in_dict), Category('cpython', 'PyDictObject', dict_detail), level=1) # Visit ma_table: _type_PyDictObject_ptr = caching_lookup_type('PyDictObject').pointer() in_dict = in_dict.cast(_type_PyDictObject_ptr) ma_table = long(in_dict['ma_table']) # Record details: usage_set.set_addr_category(ma_table, Category('cpython', 'PyDictEntry table', dict_detail), level=2) return True
def categorize_refs(self, usage_set, level=0, detail=None): return True # FIXME cl_name = self.cl_name() # print 'cl_name', cl_name # Visit the in_dict: in_dict = self.field('in_dict') # print 'in_dict', in_dict dict_detail = '%s.__dict__' % cl_name # Mark the ptr as being a dictionary, adding detail usage_set.set_addr_category(obj_addr_to_gc_addr(in_dict), Category('cpython', 'PyDictObject', dict_detail), level=1) # Visit ma_table: _type_PyDictObject_ptr = caching_lookup_type('PyDictObject').pointer() in_dict = in_dict.cast(_type_PyDictObject_ptr) ma_table = int(in_dict['ma_table']) # Record details: usage_set.set_addr_category(ma_table, Category('cpython', 'PyDictEntry table', dict_detail), level=2) return True
def from_pyobject_ptr(cls, addr): ob_type = addr['ob_type'] tp_flags = ob_type['tp_flags'] if tp_flags & Py_TPFLAGS_HEAPTYPE: return HeapTypeObjectPtr(addr) if tp_flags & Py_TPFLAGS_UNICODE_SUBCLASS: return PyUnicodeObjectPtr(addr.cast(caching_lookup_type('PyUnicodeObject').pointer())) if tp_flags & Py_TPFLAGS_DICT_SUBCLASS: return PyDictObjectPtr(addr.cast(caching_lookup_type('PyDictObject').pointer())) tp_name = ob_type['tp_name'].string() if tp_name == 'instance': __type_PyInstanceObjectPtr = caching_lookup_type('PyInstanceObject').pointer() return PyInstanceObjectPtr(addr.cast(__type_PyInstanceObjectPtr)) return PyObjectPtr(addr)
def categorize_sqlite3(addr, usage_set, visited): # "struct sqlite3" is defined in src/sqliteInt.h, which is an internal header ptr_type = caching_lookup_type('sqlite3').pointer() obj_ptr = gdb.Value(addr).cast(ptr_type) # print obj_ptr.dereference() aDb = obj_ptr['aDb'] Db_addr = long(aDb) Db_malloc_addr = Db_addr - 8 if usage_set.set_addr_category(Db_malloc_addr, Category('sqlite3', 'struct Db', None), visited): print aDb['pBt'].dereference()
def categorize_sqlite3(addr, usage_set, visited): # "struct sqlite3" is defined in src/sqliteInt.h, which is an internal header ptr_type = caching_lookup_type('sqlite3').pointer() obj_ptr = gdb.Value(addr).cast(ptr_type) # print obj_ptr.dereference() aDb = obj_ptr['aDb'] Db_addr = int(aDb) Db_malloc_addr = Db_addr - 8 if usage_set.set_addr_category(Db_malloc_addr, Category('sqlite3', 'struct Db', None), visited): print(aDb['pBt'].dereference())
def iter_free_blocks(self): '''Yield the sequence of free blocks within this pool. Doesn't include the areas after nextoffset that have never been allocated''' # print self._gdbval.dereference() size = self.block_size() freeblock = self.field('freeblock') _type_block_ptr_ptr = caching_lookup_type('unsigned char').pointer().pointer() # Walk the singly-linked list of free blocks for this chunk while long(freeblock) != 0: # print 'freeblock:', (fmt_addr(long(freeblock)), long(size)) yield (long(freeblock), long(size)) freeblock = freeblock.cast(_type_block_ptr_ptr).dereference()
def iter_free_blocks(self): '''Yield the sequence of free blocks within this pool. Doesn't include the areas after nextoffset that have never been allocated''' # print self._gdbval.dereference() size = self.block_size() freeblock = self.field('freeblock') _type_block_ptr_ptr = caching_lookup_type('unsigned char').pointer().pointer() # Walk the singly-linked list of free blocks for this chunk while to_int(freeblock) != 0: # print 'freeblock:', (fmt_addr(int(freeblock)), int(size)) yield (to_int(freeblock), to_int(size)) freeblock = freeblock.cast(_type_block_ptr_ptr).dereference()
def __init__(self, addr, typenode, typename): # Try to cast the ptr to the named type: addr = gdb.Value(addr) try: if is_typename_castable(typename): # This requires, say, gtk2-debuginfo: ptr_type = caching_lookup_type(typename).pointer() addr = addr.cast(ptr_type) #print typename, addr.dereference() #if typename == 'GdkPixbuf': # print 'GOT PIXELS', addr['pixels'] except RuntimeError, e: pass
def categorize_refs(self, usage_set, level=0, detail=None): priv_type = caching_lookup_type('GdkImagePrivateX11').pointer() priv_data = WrappedPointer(self._gdbval['windowing_data'].cast(priv_type)) usage_set.set_addr_category(priv_data.as_address(), Category('GType', 'GdkImagePrivateX11', ''), level=level+1, debug=True) ximage = WrappedPointer(priv_data.field('ximage')) dims = '%sw x %sh x %sbpp' % (ximage.field('width'), ximage.field('height'), ximage.field('depth')) usage_set.set_addr_category(ximage.as_address(), Category('X11', 'Image', dims), level=level+2, debug=True) usage_set.set_addr_category(int(ximage.field('data')), Category('X11', 'Image data', dims), level=level+2, debug=True)
def __str__(self): result = ('<%s chunk=0x%x mem=0x%x' % (self.__class__.__name__, self.as_address(), self.as_mem())) if self.has_PREV_INUSE(): result += ' PREV_INUSE' else: result += ' prev_size=%i' % self.field('prev_size') if self.has_NON_MAIN_ARENA(): result += ' NON_MAIN_ARENA' if self.has_IS_MMAPPED(): result += ' IS_MMAPPED' else: if self.is_inuse(): result += ' inuse' else: result += ' free' SIZE_SZ = caching_lookup_type('size_t').sizeof result += ' chunksize=%i memsize=%i>' % (self.chunksize(), self.chunksize() - (2 * SIZE_SZ)) return result
def categorize_refs(self, usage_set, level=0, detail=None): priv_type = caching_lookup_type('GdkImagePrivateX11').pointer() priv_data = WrappedPointer( self._gdbval['windowing_data'].cast(priv_type)) usage_set.set_addr_category(priv_data.as_address(), Category('GType', 'GdkImagePrivateX11', ''), level=level + 1, debug=True) ximage = WrappedPointer(priv_data.field('ximage')) dims = '%sw x %sh x %sbpp' % (ximage.field('width'), ximage.field('height'), ximage.field('depth')) usage_set.set_addr_category(ximage.as_address(), Category('X11', 'Image', dims), level=level + 2, debug=True) usage_set.set_addr_category(int(ximage.field('data')), Category('X11', 'Image data', dims), level=level + 2, debug=True)
def obj_addr_to_gc_addr(addr): '''Given a PyObject* address, convert to a PyGC_Head* address (i.e. the allocator's view of the same)''' #print 'obj_addr_to_gc_addr(%s)' % fmt_addr(long(addr)) _type_PyGC_Head = caching_lookup_type('PyGC_Head') return long(addr) - _type_PyGC_Head.sizeof
def gdb_type(cls): # Deferred lookup of the "mchunkptr" type: return caching_lookup_type('mchunkptr')
def as_mem(self): # Analog of chunk2mem: the address as seen by the program (e.g. malloc) SIZE_SZ = caching_lookup_type('size_t').sizeof return self.as_address() + (2 * SIZE_SZ)
def memsize(self): '''Returns size of the allocated block''' SIZE_SZ = caching_lookup_type('size_t').sizeof return self.chunksize() - (2 * SIZE_SZ)
def POOL_OVERHEAD(): return ROUNDUP(caching_lookup_type('struct pool_header').sizeof)
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # C++ support import re import gdb from heap import caching_lookup_type, looks_like_ptr from heap.compat import execute void_ptr_ptr = caching_lookup_type('void').pointer().pointer() def get_class_name(addr, size): # Try to detect a vtable ptr at the top of this object: vtable = gdb.Value(addr).cast(void_ptr_ptr).dereference() if not looks_like_ptr(vtable): return None info = execute('info sym (void *)0x%x' % long(vtable)) # "vtable for Foo + 8 in section .rodata of /home/david/heap/test_cplusplus" m = re.match('vtable for (.*) \+ (.*)', info) if m: return m.group(1) # Not matched: return None
def obj_addr_to_gc_addr(addr): '''Given a PyObject* address, convert to a PyGC_Head* address (i.e. the allocator's view of the same)''' #print 'obj_addr_to_gc_addr(%s)' % fmt_addr(int(addr)) _type_PyGC_Head = caching_lookup_type('PyGC_Head') return int(addr) - _type_PyGC_Head.sizeof
def is_pyobject_ptr(addr): try: _type_pyop = caching_lookup_type('PyObject').pointer() _type_pyvarop = caching_lookup_type('PyVarObject').pointer() except RuntimeError: # not linked against python return None try: typeop = pyop = gdb.Value(addr).cast(_type_pyop) # If we follow type chain on a PyObject long enough, we should arrive # at 'type' and type(type) should be 'type'. # The levels are: a <- b means a = type(b) # 0 - type # 1 - type <- class A # type <- class M(type) # 2 - type <- class A <- A() # type <- class M(type) <- class B(metaclass=M) # 3 - type <- class M(type) <- class B(metaclass=M) <- B() for i in range(4): if typeop['ob_type'] == typeop: return PyObjectPtr.from_pyobject_ptr(pyop) typeop = typeop['ob_type'].cast(_type_pyop) return 0 # gdb.write('PYOP {}\n'.format(pyop)) ob_refcnt = pyop['ob_refcnt'] if ob_refcnt >= 0 and ob_refcnt < 0xffff: # gdb.write('refcnt ok {}\n'.format(int(ob_refcnt))) obtype = pyop['ob_type'] if looks_like_ptr(obtype): # gdb.write('obtype ok {}\n'.format(obtype)) type_refcnt = obtype.cast(_type_pyop)['ob_refcnt'] if type_refcnt > 0 and type_refcnt < 0xffff: # gdb.write('type refcnt ok\n') # type_ob_size = obtype.cast(_type_pyvarop)['ob_size'] # if type_ob_size > 0xffff: # return 0 # gdb.write('ob size ok\n') for fieldname in ('tp_base', 'tp_free', 'tp_repr', 'tp_new'): if not looks_like_ptr(obtype[fieldname]): return 0 # gdb.write('methods ok\n') # Then this looks like a Python object: return PyObjectPtr.from_pyobject_ptr(pyop) except UnicodeDecodeError as e: # print('is_pyobject_ptr 0x{:x}'.format(addr)) # gdb.write(str(e) + '\n') pass except RuntimeError: pass # Not a python object (or corrupt)
def invoke(self, args, from_tty): from heap.glibc import iter_mappings,lookup_symbol from heap import WrappedPointer,caching_lookup_type SIZE_SZ = caching_lookup_type('size_t').sizeof type_size_t = gdb.lookup_type('size_t') type_size_t_ptr = type_size_t.pointer() arg_list = gdb.string_to_argv(args) parser = argparse.ArgumentParser(add_help=True, usage="objdump [-v][-s SIZE] <ADDR>") parser.add_argument('addr', metavar='ADDR', type=str, nargs=1, help="Target address") parser.add_argument('-s', dest='size', default=None, help='Total dump size') parser.add_argument('-v', dest='verbose', action="store_true", default=False, help='Verbose') try: args_dict = parser.parse_args(args=arg_list) except: return addr_arg = args_dict.addr[0] if addr_arg.startswith('0x'): addr = int(addr_arg, 16) else: addr = int(addr_arg) if args_dict.size: if args_dict.size.startswith('0x'): total_size = int(args_dict.size, 16) else: total_size = int(args_dict.size) else: total_size = 10 * SIZE_SZ if args_dict.verbose: print('Searching in the following object ranges') print('-------------------------------------------------') text = [] #list of tuples (start, end, pathname, ...) of a valid memory map #XXX why pid would be 0? for pid in [ o.pid for o in gdb.inferiors() if o.pid !=0 ]: for r in iter_mappings(pid): if args_dict.verbose: print("%s - %s : %s" % (hex(r[0]), hex(r[1]), r[2])) text.append((r[0], r[1], r[2])) print('\nDumping Object at address %s' % fmt_addr(addr)) print('-------------------------------------------------') for a in range(addr, addr + (total_size * SIZE_SZ), SIZE_SZ): ptr = WrappedPointer(gdb.Value(a).cast(type_size_t_ptr)) #dereference first, at the first access denied bail out, dont go further try: val = ptr.dereference() found = False val_int = int(str(val.cast(type_size_t))) #XXX Error occurred in Python command: invalid literal for int() with base 10: '0x418' pathname = "" for t in text: if val_int >= t[0] and val_int < t[1]: found = True pathname = t[2] if found: sym = lookup_symbol(val_int) if sym: out_line = "%s => %s (%s in %s)" % (fmt_addr(ptr.as_address()), fmt_addr(val_int), sym, pathname ) else: out_line = "%s => %s (%s)" % (fmt_addr(ptr.as_address()), fmt_addr(val_int), pathname ) else: out_line = "%s => %s" % (fmt_addr(ptr.as_address()), fmt_addr(val_int) ) #if it's not a symbol, try a string # try just a few chars #if not found: print(out_line) except gdb.MemoryError: print("Error accessing memory at %s" % fmt_addr(ptr.as_address())) return
def gdb_type(cls): # Deferred lookup of the "poolp" type: return caching_lookup_type('poolp')
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # C++ support import re import gdb from heap import caching_lookup_type, looks_like_ptr from heap.compat import execute void_ptr_ptr = caching_lookup_type("void").pointer().pointer() def get_class_name(addr, size): # Try to detect a vtable ptr at the top of this object: vtable = gdb.Value(addr).cast(void_ptr_ptr).dereference() if not looks_like_ptr(vtable): return None info = execute("info sym (void *)0x%x" % int(vtable)) # "vtable for Foo + 8 in section .rodata of /home/david/heap/test_cplusplus" m = re.match("vtable for (.*) \+ (.*)", info) if m: return m.group(1) # Not matched: return None