selected_frame.select()
- def get_cython_globals_dict(self):
- """
- Get the Cython globals dict where the remote names are turned into
- local strings.
- """
+ def get_remote_cython_globals_dict(self):
m = gdb.parse_and_eval('__pyx_m')
try:
with debugging support (-g)?"""))
m = m.cast(PyModuleObject.pointer())
- pyobject_dict = libpython.PyObjectPtr.from_pyobject_ptr(m['md_dict'])
+ return m['md_dict']
+
+
+ def get_cython_globals_dict(self):
+ """
+ Get the Cython globals dict where the remote names are turned into
+ local strings.
+ """
+ remote_dict = self.get_remote_cython_globals_dict()
+ pyobject_dict = libpython.PyObjectPtr.from_pyobject_ptr(remote_dict)
result = {}
seen = set()
print_ = CyPrint.register(),
locals = CyLocals.register(),
globals = CyGlobals.register(),
+ exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'),
+ _exec = CyExec.register(),
cy_cname = CyCName('cy_cname'),
cy_cvalue = CyCValue('cy_cvalue'),
cy_lineno = CyLine('cy_lineno'),
prefix=' ')
+class CyExec(CythonCommand):
+ name = '-cy-exec'
+ command_class = gdb.COMMAND_STACK
+ completer_class = gdb.COMPLETE_NONE
+
+ def _fill_locals_dict(self, executor, local_dict_pointer):
+ "Fill a remotely allocated dict with values from the Cython C stack"
+ cython_func = self.get_cython_function()
+
+ for name, cyvar in cython_func.locals.iteritems():
+ if cyvar.type == PythonObject:
+ # skip unitialized Cython variables
+ try:
+ val = gdb.parse_and_eval(cyvar.cname)
+ except RuntimeError:
+ continue
+ else:
+ # Fortunately, Cython initializes all local (automatic)
+ # variables to NULL
+ if libpython.pointervalue(val) == 0:
+ continue
+
+ pystringp = executor.alloc_pystring(name)
+ code = '''
+ PyDict_SetItem(
+ (PyObject *) %d,
+ (PyObject *) %d,
+ (PyObject *) %s)
+ ''' % (local_dict_pointer, pystringp, cyvar.cname)
+
+ # PyDict_SetItem doesn't steal our reference
+ executor.decref(pystringp)
+
+ if gdb.parse_and_eval(code) < 0:
+ gdb.parse_and_eval('PyErr_Print()')
+ raise gdb.GdbError("Unable to execute Python code.")
+
+ def _find_first_cython_or_python_frame(self):
+ frame = gdb.selected_frame()
+ while frame:
+ if (self.is_cython_function(frame) or
+ self.is_python_function(frame)):
+ return frame
+
+ frame = frame.older()
+
+ raise gdb.GdbError("There is no Cython or Python frame on the stack.")
+
+ def invoke(self, expr, from_tty):
+ frame = self._find_first_cython_or_python_frame()
+ if self.is_python_function(frame):
+ libpython.py_exec.invoke(expr, from_tty)
+ return
+
+ executor = libpython.PythonCodeExecutor()
+
+ # get the dict of Cython globals and construct a dict in the inferior
+ # with Cython locals
+ global_dict = gdb.parse_and_eval(
+ '(PyObject *) PyModule_GetDict(__pyx_m)')
+ local_dict = gdb.parse_and_eval('(PyObject *) PyDict_New()')
+
+ try:
+ self._fill_locals_dict(executor, libpython.pointervalue(local_dict))
+ executor.evalcode(expr, global_dict, local_dict)
+ finally:
+ executor.decref(libpython.pointervalue(local_dict))
+
+
# Functions
class CyCName(gdb.Function, CythonBase):
import re
import sys
import atexit
+import warnings
import tempfile
import itertools
_logging_state = _LoggingState()
+def get_selected_inferior():
+ """
+ Return the selected inferior in gdb.
+ """
+ # Woooh, another bug in gdb! Is there an end in sight?
+ # http://sourceware.org/bugzilla/show_bug.cgi?id=12212
+ return gdb.inferiors()[0]
+
+ selected_thread = gdb.selected_thread()
+
+ for inferior in gdb.inferiors():
+ for thread in inferior.threads():
+ if thread == selected_thread:
+ return inferior
+
+
class GenericCodeStepper(gdb.Command):
"""
Superclass for code stepping. Subclasses must implement the following
"""
def stopped(self):
- return gdb.inferiors()[0].pid == 0
+ return get_selected_inferior().pid == 0
def _stackdepth(self, frame):
depth = 0
py_run = PyRun('py-run')
py_cont = PyCont('py-cont')
-py_step.init_breakpoints()
\ No newline at end of file
+py_step.init_breakpoints()
+
+Py_single_input = 256
+Py_eval_input = 258
+
+def pointervalue(gdbval):
+ """
+ Return the value of the pionter as a Python int.
+
+ gdbval.type must be a pointer type
+ """
+ # don't convert with int() as it will raise a RuntimeError
+ if gdbval.address is not None:
+ return long(gdbval.address)
+ else:
+ # the address attribute is None sometimes, in which case we can
+ # still convert the pointer to an int
+ return long(gdbval)
+
+
+class PythonCodeExecutor(object):
+
+ def malloc(self, size):
+ chunk = (gdb.parse_and_eval("(void *) malloc(%d)" % size))
+
+ pointer = pointervalue(chunk)
+ if pointer == 0:
+ err("No memory could be allocated in the inferior.")
+
+ return pointer
+
+ def alloc_string(self, string):
+ pointer = self.malloc(len(string))
+ get_selected_inferior().write_memory(pointer, string)
+
+ return pointer
+
+ def alloc_pystring(self, string):
+ stringp = self.alloc_string(string)
+ try:
+ result = gdb.parse_and_eval(
+ 'PyString_FromStringAndSize((char *) %d, (size_t) %d)' %
+ (stringp, len(string)))
+ finally:
+ self.free(stringp)
+
+ pointer = pointervalue(result)
+ if pointer == 0:
+ err("Unable to allocate Python string in "
+ "the inferior.")
+
+ return pointer
+
+ def free(self, pointer):
+ gdb.parse_and_eval("free((void *) %d)" % pointer)
+
+ def decref(self, pointer):
+ "Decrement the reference count of a Python object in the inferior."
+ # Py_DecRef is like Py_XDECREF, but a function. So we don't have
+ # to check for NULL. This should also decref all our allocated
+ # Python strings.
+ gdb.parse_and_eval('Py_DecRef((PyObject *) %d)' % pointer)
+
+ def evalcode(self, code, global_dict=None, local_dict=None):
+ """
+ Evaluate python code `code` given as a string in the inferior and
+ return the result as a gdb.Value. Returns a new reference in the
+ inferior.
+
+ Of course, executing any code in the inferior may be dangerous and may
+ leave the debuggee in an unsafe state or terminate it alltogether.
+ """
+ if '\0' in code:
+ err("String contains NUL byte.")
+
+ code += '\0'
+
+ pointer = self.alloc_string(code)
+
+ globalsp = pointervalue(global_dict)
+ localsp = pointervalue(local_dict)
+
+ if globalp == 0 or localp == 0:
+ raise gdb.GdbError("Unable to obtain or create locals or globals.")
+
+ code = """
+ PyRun_String(
+ (PyObject *) %(code)d,
+ (int) %(start)d,
+ (PyObject *) %(globals)s,
+ (PyObject *) %(locals)d)
+ """ % dict(code=pointer, start=Py_single_input,
+ globals=globalsp, locals=localsp)
+
+ with FetchAndRestoreError():
+ try:
+ self.decref(gdb.parse_and_eval(code))
+ finally:
+ self.free(pointer)
+
+
+class FetchAndRestoreError(PythonCodeExecutor):
+ """
+ Context manager that fetches the error indicator in the inferior and
+ restores it on exit.
+ """
+
+ def __init__(self):
+ self.sizeof_PyObjectPtr = gdb.lookup_type('PyObject').pointer().sizeof
+ self.pointer = self.malloc(self.sizeof_PyObjectPtr * 3)
+
+ type = self.pointer
+ value = self.pointer + self.sizeof_PyObjectPtr
+ traceback = self.pointer + self.sizeof_PyObjectPtr * 2
+
+ self.errstate = type, value, traceback
+
+ def __enter__(self):
+ gdb.parse_and_eval("PyErr_Fetch(%d, %d, %d)" % self.errstate)
+
+ def __exit__(self, *args):
+ if gdb.parse_and_eval("(int) PyErr_Occurred()"):
+ gdb.parse_and_eval("PyErr_Print()")
+
+ pyerr_restore = ("PyErr_Restore("
+ "(PyObject *) *%d,"
+ "(PyObject *) *%d,"
+ "(PyObject *) *%d)")
+
+ try:
+ gdb.parse_and_eval(pyerr_restore % self.errstate)
+ finally:
+ self.free(self.pointer)
+
+
+class FixGdbCommand(gdb.Command):
+
+ def __init__(self, command, actual_command):
+ super(FixGdbCommand, self).__init__(command, gdb.COMMAND_DATA,
+ gdb.COMPLETE_NONE)
+ self.actual_command = actual_command
+
+ def fix_gdb(self):
+ """
+ So, you must be wondering what the story is this time! Yeeees, indeed,
+ I have quite the story for you! It seems that invoking either 'cy exec'
+ and 'py-exec' work perfectly fine, but after this gdb's python API is
+ entirely broken. Some unset exception value is still set?
+ sys.exc_clear() didn't help. A demonstration:
+
+ (gdb) cy exec 'hello'
+ 'hello'
+ (gdb) python gdb.execute('cont')
+ RuntimeError: Cannot convert value to int.
+ Error while executing Python code.
+ (gdb) python gdb.execute('cont')
+ [15148 refs]
+
+ Program exited normally.
+ """
+ warnings.filterwarnings('ignore', r'.*', RuntimeWarning, re.escape(__name__))
+ try:
+ long(gdb.parse_and_eval("(void *) 0")) == 0
+ except RuntimeError:
+ pass
+ # warnings.resetwarnings()
+
+ def invoke(self, args, from_tty):
+ self.fix_gdb()
+ gdb.execute('%s %s' % (self.actual_command, args))
+ self.fix_gdb()
+
+
+class PyExec(gdb.Command):
+
+ def invoke(self, expr, from_tty):
+ executor = PythonCodeExecutor()
+ global_dict = gdb.parse_and_eval('PyEval_GetGlobals()')
+ local_dict = gdb.parse_and_eval('PyEval_GetLocals()')
+
+ if pointervalue(global_dict) == 0 or pointervalue(local_dict) == 0:
+ raise gdb.GdbError("Unable to find the locals or globals of the "
+ "most recent Python function (relative to the "
+ "selected frame).")
+
+ executor.evalcode(expr, global_dict, local_dict)
+
+py_exec = FixGdbCommand('py-exec', '-py-exec')
+_py_exec = PyExec("-py-exec", gdb.COMMAND_DATA, gdb.COMPLETE_NONE)
\ No newline at end of file