Skip to content

Instantly share code, notes, and snippets.

@versusvoid
Created April 9, 2022 14:38
Show Gist options
  • Save versusvoid/76a9538be85f8a74281f482574250a21 to your computer and use it in GitHub Desktop.
Save versusvoid/76a9538be85f8a74281f482574250a21 to your computer and use it in GitHub Desktop.
Show slotted attributes and modules when debuging python with gdb
--- a/libpython.py 2022-03-23 18:38:36.000000000 +0300
+++ b/libpython.py 2022-04-09 17:34:59.590014916 +0300
@@ -370,10 +370,14 @@
'frozenset' : PySetObjectPtr,
'builtin_function_or_method' : PyCFunctionObjectPtr,
'method-wrapper': wrapperobject,
+ 'module': PyModuleObjectPtr,
}
if tp_name in name_map:
return name_map[tp_name]
+ if tp_flags & Py_TPFLAGS_TUPLE_SUBCLASS:
+ return PyTupleObjectPtr
+
if tp_flags & Py_TPFLAGS_HEAPTYPE:
return HeapTypeObjectPtr
@@ -381,8 +385,6 @@
return PyLongObjectPtr
if tp_flags & Py_TPFLAGS_LIST_SUBCLASS:
return PyListObjectPtr
- if tp_flags & Py_TPFLAGS_TUPLE_SUBCLASS:
- return PyTupleObjectPtr
if tp_flags & Py_TPFLAGS_BYTES_SUBCLASS:
return PyBytesObjectPtr
if tp_flags & Py_TPFLAGS_UNICODE_SUBCLASS:
@@ -437,7 +439,7 @@
return self._rep
-def _write_instance_repr(out, visited, name, pyop_attrdict, address):
+def _write_instance_repr(out, visited, name, pyop_attrdict, slots, address):
'''Shared code for use by all classes:
write a representation to file-like object "out"'''
out.write('<')
@@ -455,20 +457,40 @@
out.write('=')
pyop_val.write_repr(out, visited)
out.write(')')
+ elif slots:
+ # Write slotted attributes:
+ out.write('(')
+ first = True
+ for name, val in slots.items():
+ if not first:
+ out.write(', ')
+ first = False
+ out.write(name)
+ out.write('=')
+ val.write_repr(out, visited)
+ out.write(')')
+
out.write(' at remote 0x%x>' % address)
class InstanceProxy(object):
- def __init__(self, cl_name, attrdict, address):
+ def __init__(self, cl_name, attrdict, slots, address):
self.cl_name = cl_name
self.attrdict = attrdict
+ self.slots = slots
self.address = address
def __repr__(self):
+ fields = None
if isinstance(self.attrdict, dict):
+ fields = self.attrdict
+ elif isinstance(self.slots, dict):
+ fields = self.slots
+
+ if fields is not None:
kwargs = ', '.join(["%s=%r" % (arg, val)
- for arg, val in self.attrdict.items()])
+ for arg, val in fields.items()])
return '<%s(%s) at remote 0x%x>' % (self.cl_name,
kwargs, self.address)
else:
@@ -519,6 +541,40 @@
# Not found, or some kind of error:
return None
+ def get_slots(self):
+ try:
+ typeobj = self.type()
+ members = typeobj.field('tp_members')
+ if not members:
+ return None
+
+ type_PyMemberDef_ptr = gdb.lookup_type('PyMemberDef').pointer()
+ member = members.cast(type_PyMemberDef_ptr)
+ PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer()
+ result = {}
+ while member['name']:
+ # T_OBJECT_EXT
+ if int_from_int(member['type']) != 16:
+ member += 1
+ continue
+
+ name = member['name'].string()
+ offset = int_from_int(member['offset'])
+
+ ptr = self._gdbval.cast(_type_char_ptr()) + offset
+ ptr = ptr.cast(PyObjectPtrPtr)
+ result[name] = PyObjectPtr.from_pyobject_ptr(ptr.dereference())
+
+ member += 1
+
+ return result
+ except RuntimeError:
+ # Corrupt data somewhere; fail safe
+ pass
+
+ # Not found, or some kind of error:
+ return None
+
def proxyval(self, visited):
'''
Support for classes.
@@ -535,11 +591,16 @@
if pyop_attr_dict:
attr_dict = pyop_attr_dict.proxyval(visited)
else:
- attr_dict = {}
+ attr_dict = None
+
+ slots = self.get_slots()
+ if slots:
+ slots = {k: v.proxyval(visited) for k, v in slots.items()}
+
tp_name = self.safe_tp_name()
# Class:
- return InstanceProxy(tp_name, attr_dict, long(self._gdbval))
+ return InstanceProxy(tp_name, attr_dict, slots, long(self._gdbval))
def write_repr(self, out, visited):
# Guard against infinite loops:
@@ -549,8 +610,9 @@
visited.add(self.as_address())
pyop_attrdict = self.get_attr_dict()
- _write_instance_repr(out, visited,
- self.safe_tp_name(), pyop_attrdict, self.as_address())
+ slots = self.get_slots()
+ _write_instance_repr(out, visited, self.safe_tp_name(),
+ pyop_attrdict, slots, self.as_address())
class ProxyException(Exception):
def __init__(self, tp_name, args):
@@ -594,6 +656,47 @@
_typename = 'PyClassObject'
+class PyModuleObjectPtr(PyObjectPtr):
+ """
+ Class wrapping a gdb.Value that's a PyModuleObject* i.e. a <module>
+ instance within the process being debugged.
+ """
+ _typename = 'PyModuleObject'
+
+ def proxyval(self, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ return ProxyAlreadyVisited('(...)')
+ visited.add(self.as_address())
+ md_dict = self.pyop_field('md_dict').proxyval(visited)
+ return ProxyException(self.safe_tp_name(), md_dict)
+
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('(...)')
+ return
+ visited.add(self.as_address())
+
+ out.write(self.safe_tp_name())
+ out.write('{')
+ md_dict = self.pyop_field('md_dict')
+ first = True
+ for k, v in md_dict.iteritems():
+ name = k.proxyval(visited)
+ if name.startswith('__'):
+ continue
+
+ if not first:
+ out.write(', ')
+ first = False
+ out.write(name)
+ out.write('=')
+ v.write_repr(out, visited)
+
+ out.write('}')
+
+
class BuiltInFunctionProxy(object):
def __init__(self, ml_name):
self.ml_name = ml_name
@@ -1144,6 +1247,10 @@
return
visited.add(self.as_address())
+ name = self.safe_tp_name()
+ if name != 'tuple':
+ out.write(name)
+
out.write('(')
for i in safe_range(int_from_int(self.field('ob_size'))):
if i > 0:
@@ -1958,3 +2065,17 @@
pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)))
PyLocals()
+
+# When you want to find specific variable
+'''
+f = Frame.get_selected_python_frame()
+pyop_frame = f.get_pyop()
+s = pyop_frame.get_var_by_name('storage')[0]
+while s is None:
+ move_in_stack(move_up=True)
+ f = Frame.get_selected_python_frame()
+ pyop_frame = f.get_pyop()
+ s = pyop_frame.get_var_by_name('storage')[0]
+
+print(s.get_truncated_repr(10000000000))
+'''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment