Gauche icon indicating copy to clipboard operation
Gauche copied to clipboard

Debug REPL

Open pclouds opened this issue 4 years ago • 4 comments

Reference section 3.4 Debugging

Gauche doesn’t have much support for debugging yet. The idea of good debugging interfaces are welcome.

and since Gauche is approaching 1.0 (and I happen to just have played around with more schemes). Perhaps we can enter a "debug repl" when an exception (or some other errors) occurs.

Larceny (and I think another scheme) has this, to quote their repl

Error: unhandled condition:
Compound condition has these components: 
#<record &assertion>
#<record &who>
    who : lookup-library
#<record &message>
    message : "library not loaded"
#<record &irritants>
    irritants : ((chibi test))

Entering debugger; type "?" for help.
debug> ?

  a           Abort (exit from Larceny).
  b           Print backtrace of continuation.
  c           Print source code (if available).
  d           Down to previous activation record.
  e n expr    Expr is evaluated in the current interaction environment 
              and must evaluate to a procedure.   It is passed the contents
              of slot n from the current activation record, and the result, 
              if not unspecified, is printed.
  e (n1 ... nk) expr
              Expr is evaluated in the current interaction environment and
              must evaluate to a procedure.   It is passed the contents of
              slots n1 through nk from the current activation record, and 
              the result, if not unspecified, is printed.
  i n         Inspect the procedure in slot n of the current activation record.
  i @         Inspect the active procedure.
  n           Enter a nested REPL.
  q           Quit the debugger and abort the computation.
  r           Return from the debugger and continue the computation.
  s           Summarize the contents of the current activation record.
  u           Up to the next activation record.
  x           Examine the contents of the current activation record.

The b, d, and u commands can be prefixed with a count, for example, 
`5 u' moves up five activation records, and `10 b' displays the next 
10 activation records.  The default for b is to display all the 
activations; the default count for d and u is 1.

I think Gauche VM can support most of this (but then I don't know exception handling very well).

This could be an optional feature, enabled with -f. Multithread is a whole other can of worms (stopping one thread or stopping all threads, gdb supports both) but I think we can work on that later.

Hmm?

pclouds avatar Feb 15 '20 01:02 pclouds

We once had an interactive stepping debugger long time ago. Then I rewrote VM completely and the code became obsoleted, and I didn't bother reimplementing it, for I didn't find the gdb-style stepping debugger isn't very much useful compared to the work of maintaining it. But a simple inspector-type interface where you can peek around the environment and continuation frames may be an option.

One thing I don't like about existing Lisp-family debugger is when an error automatically triggers the debugger. 99 out of 100 cases I don't need to inspect the error frame, nor the debugger gives too little information, so I wish it just fail and save me extra keystrokes of "exit the debugger". So, if Gauche ever has a debugger, it must work like this:

  • An error or a break point saves its continuation someplace globally, and exit to the REPL
  • If a user wish to inspect, he can pass that continuation to the debugger (inspector) then he can re-enter the frame when an error/break occurred.

To realize this, we want a lightweight continuation capture. Currently Gauche needs to copy VM stack at the continuation capture time. Lightweight continuation capture is useful for a few other purposes as well, so that's the task I put in my todo list before tackling the debugger.

shirok avatar Feb 15 '20 16:02 shirok

when an error automatically triggers the debugger. 99 out of 100 cases I don't need to inspect the error frame...

I'd say 8 or 9 out of 10 times. When the problem is in unfamiliar code base, the number of times to add #?= then rerun can increase very quickly because I don't even know where to put #?=. There are also cases where error replication takes more time (e.g. setup connection, send and receive stuff to trigger it). An interactive explorer would help in these cases.

But yeah I did find it annoying to keep pressing some key to get out of the debugger in the short time I used Larceny.

... saves its continuation

Hah! My mind still operates in no-continuation mode so I kept thinking about interrupting a program.

and exit to the REPL

Just to be sure, does this mean we have to start from a REPL first (e.g. execute a function from REPL prompt)? Or will the REPL still pop up when I run gosh some-script.scm?

Another aspect of debugging (perhaps I'm going to far) is some equivalent of linux core dump. For server programs running in background, there will be no I/O for REPL interaction. Maybe save the continuation on disk in order to load it later in a new REPL (new gosh process)...

pclouds avatar Feb 16 '20 01:02 pclouds

Just to be sure, does this mean we have to start from a REPL first (e.g. execute a function from REPL prompt)? Or will the REPL still pop up when I run gosh some-script.scm?

So far I'm only thinking when you're running on REPL. Currently when you get an error you have *e being bound to the exception. How about the exception holds a continuation, and (debug) reenters into that frame.

But actually, debugger doesn't need to be associated with error; we can enter debugger from a breakpoint, or one thread can stop another thread (there's already such a feature) then examine the stopped thread. That might be useful.

Another aspect of debugging (perhaps I'm going to far) is some equivalent of linux core dump. For server programs running in background, there will be no I/O for REPL interaction. Maybe save the continuation on disk in order to load it later in a new REPL (new gosh process)...

Some Scheme have serializable continuation (I think SISC did), but it has its own trade-off. Debugging the server is an interesting topic, though. I used to have REPL on the socket in the server process (gauche.listener comes handy), but what you want to do is to break while it is handling a request. Breakpoint-and-attach-debugger may be a nice tool to have.

shirok avatar Feb 16 '20 08:02 shirok

But actually, debugger doesn't need to be associated with error; we can enter debugger from a breakpoint, or one thread can stop another thread (there's already such a feature) then examine the stopped thread.

Stopping all threads (except the repl one) is also sometimes useful if you want to examine global variables.

Some Scheme have serializable continuation (I think SISC did), but it has its own trade-off.

I forgot one thing when I wrote saving the continuation. That's not enough because it does not capture other states (mostly variables I guess). It seems easiest to just force a real core dump, then provide some gdb helper functions to explore the Scheme world. Something like C++ STL pretty printers but probably more complex.

Yeah the repl-over-socket is fun (a bit racy unless one goes full functional) but still useful to peek into a running server.

pclouds avatar Feb 16 '20 10:02 pclouds