From 4e60355065105482a300dbccc57ec95ef8ee8056 Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Fri, 25 Mar 2011 22:18:48 +0100 Subject: [PATCH] Debugger: Added $cy_eval() GDB function --- .../Debugger/Tests/test_libcython_in_gdb.py | 16 ++++ Cython/Debugger/libcython.py | 85 +++++++++++++++---- Cython/Debugger/libpython.py | 23 ++--- 3 files changed, 92 insertions(+), 32 deletions(-) diff --git a/Cython/Debugger/Tests/test_libcython_in_gdb.py b/Cython/Debugger/Tests/test_libcython_in_gdb.py index 13e22623..46dba686 100644 --- a/Cython/Debugger/Tests/test_libcython_in_gdb.py +++ b/Cython/Debugger/Tests/test_libcython_in_gdb.py @@ -378,6 +378,22 @@ class TestExec(DebugTestCase): gdb.execute('cy exec some_random_var = 14') 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): def break_and_run_func(self, funcname): diff --git a/Cython/Debugger/libcython.py b/Cython/Debugger/libcython.py index 8d7771b5..e242bada 100644 --- a/Cython/Debugger/libcython.py +++ b/Cython/Debugger/libcython.py @@ -607,6 +607,7 @@ class CyCy(CythonCommand): completer_class, prefix=True) commands = dict( + # GDB commands import_ = CyImport.register(), break_ = CyBreak.register(), step = CyStep.register(), @@ -624,9 +625,12 @@ class CyCy(CythonCommand): globals = CyGlobals.register(), exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'), _exec = CyExec.register(), + + # GDB functions cy_cname = CyCName('cy_cname'), cy_cvalue = CyCValue('cy_cvalue'), cy_lineno = CyLine('cy_lineno'), + cy_eval = CyEval('cy_eval'), ) for command_name, command in commands.iteritems(): @@ -1169,15 +1173,14 @@ class CyGlobals(CyLocals): 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): "Fill a remotely allocated dict with values from the Cython C stack" cython_func = self.get_cython_function() @@ -1208,28 +1211,22 @@ class CyExec(CythonCommand, libpython.PyExec): raise gdb.GdbError("Unable to execute Python code.") finally: # PyDict_SetItem doesn't steal our reference - executor.decref(pystringp) + executor.xdecref(pystringp) 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)): + frame.select() 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 - - expr, input_type = self.readcode(expr) - executor = libpython.PythonCodeExecutor() + def _evalcode_cython(self, executor, code, input_type): with libpython.FetchAndRestoreError(): # get the dict of Cython globals and construct a dict in the # inferior with Cython locals @@ -1240,9 +1237,51 @@ class CyExec(CythonCommand, libpython.PyExec): try: self._fill_locals_dict(executor, libpython.pointervalue(local_dict)) - executor.evalcode(expr, input_type, global_dict, local_dict) + result = executor.evalcode(code, input_type, global_dict, + local_dict) 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 @@ -1312,6 +1351,18 @@ class CyLine(gdb.Function, CythonBase): def invoke(self): 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() cy = CyCy.register() cython_info.cy = cy diff --git a/Cython/Debugger/libpython.py b/Cython/Debugger/libpython.py index 4729412c..44d9f33d 100644 --- a/Cython/Debugger/libpython.py +++ b/Cython/Debugger/libpython.py @@ -2190,12 +2190,12 @@ class PythonInfo(LanguageInfo): try: tstate = frame.read_var('tstate').dereference() 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_value = tstate['curexc_value'] + if inf_type: - return 'An exception was raised: %s(%s)' % (inf_type, - inf_value) + return 'An exception was raised: %s(%s)' % (inf_value,) except (ValueError, RuntimeError), e: # Could not read the variable tstate or it's memory, it's ok pass @@ -2342,7 +2342,7 @@ class PythonCodeExecutor(object): "Increment the reference count of a Python object in the inferior." 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." # 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 @@ -2382,10 +2382,11 @@ class PythonCodeExecutor(object): with FetchAndRestoreError(): try: - self.decref(gdb.parse_and_eval(code)) + pyobject_return_value = gdb.parse_and_eval(code) finally: self.free(pointer) + return pyobject_return_value class FetchAndRestoreError(PythonCodeExecutor): """ @@ -2484,17 +2485,9 @@ class PyExec(gdb.Command): def invoke(self, expr, from_tty): expr, input_type = self.readcode(expr) - 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, input_type, global_dict, local_dict) + result = executor.evalcode(expr, input_type, global_dict, local_dict) + executor.xdecref(result) gdb.execute('set breakpoint pending on') -- 2.26.2