[Python-Dev] Set-next-statement in Python debuggers

Richie Hindle" <richie@entrian.com Richie Hindle" <richie@entrian.com
Tue, 08 Oct 2002 16:31:49 +0100


Hi,

[Hell's bells, sorry, here's this again with the line-breaks fixed!
How embarrassing...]

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