[Python-Dev] Set-next-statement in Python debuggers
Richie Hindle" <richie@entrian.com
Richie Hindle" <richie@entrian.com
Tue, 08 Oct 2002 16:19:11 +0100
Hi,
First a brief introduction, since this is my first post to python-dev: I'm a professional software engineer, mostly using C/C++ and Python. I use Python a lot for my own projects and some of you will know me from the spambayes project, as the author of pop3proxy.py.
I was interested to see Armin's patch [617312] to "allow the C profile and trace functions to alter some fields of the current PyFrameObject".
I'm writing a Python debugger, and as Armin says, his patch "could also be used by advanced debuggers to allow the execution point to be modified by the user." A "Set-next-statement" feature is on my list of nice-to-have-but-hard-to-do jobs, and this patch could make it easy. (I know the blockstack presents a problem, but if it's insurmountable I'm happy to ignore that and to only allow skipping to lines in the same block - 90% of my use of the equivalent feature in MSVC is to skip back one line or forward one line.)
There's one fly in the ointment - I'm trying to keep my debugger in pure Python, and Armin's patch only applies to C trace functions. If frame.f_lasti were writable by Python trace functions, pure Python debuggers (including pdb) could implement Set-Next-Statement.
Here's a small script that demonstrates what I mean, and the one-line patch to frameobject.c that enables it:
-----------------------------------------------------------------------
"""Demonstration of debugger 'Set-next-statement' feature. Requires
Python 2.2.2 with writeable frame.f_lasti. Prints "1, 2, 2, 3"."""
import sys, pprint
def example():
print 1,
print 2,
print 3 # Line 9; we skip back to 8 the first time we hit this.
class DebuggerHook:
def __init__(self):
self.done = False # Have we done the Set-next-statement yet?
self.lasti = 0 # The frame.f_lasti of the previous line.
sys.settrace(self.trace)
def trace(self, frame, action, arg):
if not self.done and frame.f_lineno == 9:
# This is the first time we've hit line 9; skip back to 8.
frame.f_lasti = self.lasti
self.done = True
else:
# Store the instruction offset of this line.
self.lasti = frame.f_lasti
return self.trace
debugger = DebuggerHook()
example() # Prints "1, 2, 2, 3"
-----------------------------------------------------------------------
*** frameobject.c Tue Oct 8 08:16:39 2002
--- frameobject-222b1.c Tue Oct 8 08:16:12 2002
***************
*** 16,20 ****
{"f_builtins", T_OBJECT, OFF(f_builtins),RO},
{"f_globals", T_OBJECT, OFF(f_globals), RO},
! {"f_lasti", T_INT, OFF(f_lasti)},
{"f_lineno", T_INT, OFF(f_lineno), RO},
{"f_restricted",T_INT, OFF(f_restricted),RO},
--- 16,20 ----
{"f_builtins", T_OBJECT, OFF(f_builtins),RO},
{"f_globals", T_OBJECT, OFF(f_globals), RO},
! {"f_lasti", T_INT, OFF(f_lasti), RO},
{"f_lineno", T_INT, OFF(f_lineno), RO},
{"f_restricted",T_INT, OFF(f_restricted),RO},
-----------------------------------------------------------------------
For completeness, f_lineno should probably be writable as well, but I'm keeping the changes minimal for the purposes of discussion.
Is this a reasonable suggestion for 2.3, or is it giving people too much rope? A nasty consequence is that you can write Python code that causes Python to seg-fault, but you have to be doing some fairly advanced stuff for that to happen. I would say that the power of a Set-next-statement feature in pdb and other debuggers is worth the price, but others may disagree...?
--
Richie Hindle
richie@entrian.com