Commit 4e603550 authored by Mark Florisson's avatar Mark Florisson

Debugger: Added $cy_eval() GDB function

parent 63bcd561
...@@ -378,6 +378,22 @@ class TestExec(DebugTestCase): ...@@ -378,6 +378,22 @@ class TestExec(DebugTestCase):
gdb.execute('cy exec some_random_var = 14') gdb.execute('cy exec some_random_var = 14')
self.assertEqual('14', self.eval_command('some_random_var')) self.assertEqual('14', self.eval_command('some_random_var'))
class TestCyEval(DebugTestCase):
"Test the $cy_eval() gdb function."
def test_cy_eval(self):
# This function leaks a few objects in the GDB python process. This
# is no biggie
self.break_and_run('os.path.join("foo", "bar")')
result = gdb.execute('print $cy_eval("None")', to_string=True)
assert re.match(r'\$\d+ = None\n', result), result
result = gdb.execute('print $cy_eval("[a]")', to_string=True)
assert re.match(r'\$\d+ = \[0\]', result), result
class TestClosure(DebugTestCase): class TestClosure(DebugTestCase):
def break_and_run_func(self, funcname): def break_and_run_func(self, funcname):
......
...@@ -607,6 +607,7 @@ class CyCy(CythonCommand): ...@@ -607,6 +607,7 @@ class CyCy(CythonCommand):
completer_class, prefix=True) completer_class, prefix=True)
commands = dict( commands = dict(
# GDB commands
import_ = CyImport.register(), import_ = CyImport.register(),
break_ = CyBreak.register(), break_ = CyBreak.register(),
step = CyStep.register(), step = CyStep.register(),
...@@ -624,9 +625,12 @@ class CyCy(CythonCommand): ...@@ -624,9 +625,12 @@ class CyCy(CythonCommand):
globals = CyGlobals.register(), globals = CyGlobals.register(),
exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'), exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'),
_exec = CyExec.register(), _exec = CyExec.register(),
# GDB functions
cy_cname = CyCName('cy_cname'), cy_cname = CyCName('cy_cname'),
cy_cvalue = CyCValue('cy_cvalue'), cy_cvalue = CyCValue('cy_cvalue'),
cy_lineno = CyLine('cy_lineno'), cy_lineno = CyLine('cy_lineno'),
cy_eval = CyEval('cy_eval'),
) )
for command_name, command in commands.iteritems(): for command_name, command in commands.iteritems():
...@@ -1169,15 +1173,14 @@ class CyGlobals(CyLocals): ...@@ -1169,15 +1173,14 @@ class CyGlobals(CyLocals):
max_name_length, ' ') max_name_length, ' ')
class CyExec(CythonCommand, libpython.PyExec):
class EvaluateOrExecuteCodeMixin(object):
""" """
Execute Python code in the nearest Python or Cython frame. Evaluate or execute Python code in a Cython or Python frame. The 'evalcode'
method evaluations Python code, prints a traceback if an exception went
uncaught, and returns any return value as a gdb.Value (NULL on exception).
""" """
name = '-cy-exec'
command_class = gdb.COMMAND_STACK
completer_class = gdb.COMPLETE_NONE
def _fill_locals_dict(self, executor, local_dict_pointer): def _fill_locals_dict(self, executor, local_dict_pointer):
"Fill a remotely allocated dict with values from the Cython C stack" "Fill a remotely allocated dict with values from the Cython C stack"
cython_func = self.get_cython_function() cython_func = self.get_cython_function()
...@@ -1208,28 +1211,22 @@ class CyExec(CythonCommand, libpython.PyExec): ...@@ -1208,28 +1211,22 @@ class CyExec(CythonCommand, libpython.PyExec):
raise gdb.GdbError("Unable to execute Python code.") raise gdb.GdbError("Unable to execute Python code.")
finally: finally:
# PyDict_SetItem doesn't steal our reference # PyDict_SetItem doesn't steal our reference
executor.decref(pystringp) executor.xdecref(pystringp)
def _find_first_cython_or_python_frame(self): def _find_first_cython_or_python_frame(self):
frame = gdb.selected_frame() frame = gdb.selected_frame()
while frame: while frame:
if (self.is_cython_function(frame) or if (self.is_cython_function(frame) or
self.is_python_function(frame)): self.is_python_function(frame)):
frame.select()
return frame return frame
frame = frame.older() frame = frame.older()
raise gdb.GdbError("There is no Cython or Python frame on the stack.") 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
expr, input_type = self.readcode(expr)
executor = libpython.PythonCodeExecutor()
def _evalcode_cython(self, executor, code, input_type):
with libpython.FetchAndRestoreError(): with libpython.FetchAndRestoreError():
# get the dict of Cython globals and construct a dict in the # get the dict of Cython globals and construct a dict in the
# inferior with Cython locals # inferior with Cython locals
...@@ -1240,9 +1237,51 @@ class CyExec(CythonCommand, libpython.PyExec): ...@@ -1240,9 +1237,51 @@ class CyExec(CythonCommand, libpython.PyExec):
try: try:
self._fill_locals_dict(executor, self._fill_locals_dict(executor,
libpython.pointervalue(local_dict)) libpython.pointervalue(local_dict))
executor.evalcode(expr, input_type, global_dict, local_dict) result = executor.evalcode(code, input_type, global_dict,
local_dict)
finally: finally:
executor.decref(libpython.pointervalue(local_dict)) executor.xdecref(libpython.pointervalue(local_dict))
return result
def _evalcode_python(self, executor, code, input_type):
global_dict = gdb.parse_and_eval('PyEval_GetGlobals()')
local_dict = gdb.parse_and_eval('PyEval_GetLocals()')
if (libpython.pointervalue(global_dict) == 0 or
libpython.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).")
return executor.evalcode(code, input_type, global_dict, local_dict)
def evalcode(self, code, input_type):
"""
Evaluate `code` in a Python or Cython stack frame using the given
`input_type`.
"""
frame = self._find_first_cython_or_python_frame()
executor = libpython.PythonCodeExecutor()
if self.is_python_function(frame):
return self._evalcode_python(executor, code, input_type)
return self._evalcode_cython(executor, code, input_type)
class CyExec(CythonCommand, libpython.PyExec, EvaluateOrExecuteCodeMixin):
"""
Execute Python code in the nearest Python or Cython frame.
"""
name = '-cy-exec'
command_class = gdb.COMMAND_STACK
completer_class = gdb.COMPLETE_NONE
def invoke(self, expr, from_tty):
expr, input_type = self.readcode(expr)
executor = libpython.PythonCodeExecutor()
executor.xdecref(self.evalcode(expr, executor.Py_single_input))
# Functions # Functions
...@@ -1312,6 +1351,18 @@ class CyLine(gdb.Function, CythonBase): ...@@ -1312,6 +1351,18 @@ class CyLine(gdb.Function, CythonBase):
def invoke(self): def invoke(self):
return self.get_cython_lineno() return self.get_cython_lineno()
class CyEval(gdb.Function, CythonBase, EvaluateOrExecuteCodeMixin):
"""
Evaluate Python code in the nearest Python or Cython frame and return
"""
@gdb_function_value_to_unicode
def invoke(self, python_expression):
input_type = libpython.PythonCodeExecutor.Py_eval_input
return self.evalcode(python_expression, input_type)
cython_info = CythonInfo() cython_info = CythonInfo()
cy = CyCy.register() cy = CyCy.register()
cython_info.cy = cy cython_info.cy = cy
......
...@@ -2190,12 +2190,12 @@ class PythonInfo(LanguageInfo): ...@@ -2190,12 +2190,12 @@ class PythonInfo(LanguageInfo):
try: try:
tstate = frame.read_var('tstate').dereference() tstate = frame.read_var('tstate').dereference()
if gdb.parse_and_eval('tstate->frame == f'): if gdb.parse_and_eval('tstate->frame == f'):
# tstate local variable initialized # tstate local variable initialized, check for an exception
inf_type = tstate['curexc_type'] inf_type = tstate['curexc_type']
inf_value = tstate['curexc_value'] inf_value = tstate['curexc_value']
if inf_type: if inf_type:
return 'An exception was raised: %s(%s)' % (inf_type, return 'An exception was raised: %s(%s)' % (inf_value,)
inf_value)
except (ValueError, RuntimeError), e: except (ValueError, RuntimeError), e:
# Could not read the variable tstate or it's memory, it's ok # Could not read the variable tstate or it's memory, it's ok
pass pass
...@@ -2342,7 +2342,7 @@ class PythonCodeExecutor(object): ...@@ -2342,7 +2342,7 @@ class PythonCodeExecutor(object):
"Increment the reference count of a Python object in the inferior." "Increment the reference count of a Python object in the inferior."
gdb.parse_and_eval('Py_IncRef((PyObject *) %d)' % pointer) gdb.parse_and_eval('Py_IncRef((PyObject *) %d)' % pointer)
def decref(self, pointer): def xdecref(self, pointer):
"Decrement the reference count of a Python object in the inferior." "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 # 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 # to check for NULL. This should also decref all our allocated
...@@ -2382,10 +2382,11 @@ class PythonCodeExecutor(object): ...@@ -2382,10 +2382,11 @@ class PythonCodeExecutor(object):
with FetchAndRestoreError(): with FetchAndRestoreError():
try: try:
self.decref(gdb.parse_and_eval(code)) pyobject_return_value = gdb.parse_and_eval(code)
finally: finally:
self.free(pointer) self.free(pointer)
return pyobject_return_value
class FetchAndRestoreError(PythonCodeExecutor): class FetchAndRestoreError(PythonCodeExecutor):
""" """
...@@ -2484,17 +2485,9 @@ class PyExec(gdb.Command): ...@@ -2484,17 +2485,9 @@ class PyExec(gdb.Command):
def invoke(self, expr, from_tty): def invoke(self, expr, from_tty):
expr, input_type = self.readcode(expr) expr, input_type = self.readcode(expr)
executor = PythonCodeExecutor() executor = PythonCodeExecutor()
global_dict = gdb.parse_and_eval('PyEval_GetGlobals()') result = executor.evalcode(expr, input_type, global_dict, local_dict)
local_dict = gdb.parse_and_eval('PyEval_GetLocals()') executor.xdecref(result)
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, input_type, global_dict, local_dict)
gdb.execute('set breakpoint pending on') gdb.execute('set breakpoint pending on')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment