[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


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.

    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
            # 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