Source-Level Debugger
v1.2

Purpose

    The MOO Source-Level Debugger is a server patch which makes available
somewhat "internal" information from the MOO virtual machine.  It
includes a Direct Stack Access-interace compliant set of functions as
well as several functions allowing step-by-step execution of verbs.
The system is designed to provide an interface through which a MOO
source-level debugger, containing many of the standard features of
source-level debuggers, without violating the MOOcode abstraction.
The access allowed by this interface does not provide any information
about the system underlying the virtual machine executing the MOOcode.


Overview

    The MOO Source-Level Debugger has two sub-parts.  The first is
interface-compatible with the Direct Stack Access extensions released
previously by the author.  The second is a set of verbs allowing
fairly sophisticated control over the execution of a task.

    The Direct Stack Access extension provided a function which
allowed a programmer to get a list of all variables in all frames of a
running task.  For those unfamiliar with the terminology, each time a
verb begins executing, a "frame" is created for it on a "stack".
Thus, if a command-line verb calls a second verb with calls a third
verb, there are three frames on the stack for that particular task.
All of the variables used by a verb are stored in its frame on the
stack.  Other functions allowed players to alter the values of
variables in running tasks or to undefine variables in running tasks.

    The novel verbs in the Source-Level Debugger allow one task to
resume a suspended task for one line of execution only.  Each time the
task is resumed in this way, it will execute one line of source code
and re-suspend indefinitely.  The task may also be resumed such that
it will stop execution before entering any verb called by the current
verb (a "step-in") or to complete the execution of the current verb
and suspend before executing any code from the calling verb (a
"step-out").  


Interface

   task_frames()
   task_frames(INT)
   
    Both formats return lists of stack frames for a given task; the first
format returns a stack frame list for the current task, while the
second returns the stack frame list for a the task whose ID is passed
in the first argument.  The returned value is a list of lists.  Each
element of the returned list represents one stack frame, with the
topmost frame of the stack being the first element of the list.  Each
stack frame is a list of three-member lists.  The first element of
each list is the name of the variable.  The second is the type of the
variable, with a 6 in this position representing the internal server
type TYPE_NONE given to undefined variables.  The third element is the
current value of the variable.  This third element is a random value
of type INT for currently undefined variables.
    
    The list returned by task_frames() is similar to the one below:
 
    ;task_frames() => { { {"NUM", 0, 0}, {"OBJ", 0, 1}, ...}, 
                        { {"NUM", 0, 0}, ...}, ...};
                    
task_frames() can also return E_INVARG if the specified task does not
exist or E_PERM if the specified task is owned by another player and
the player is not a wizard.
 
   alter_variable(INT, INT, INT, ANY)
   alter_variable(INT, INT, STR, ANY)
   
    The two formats of alter_variable() perform the same task.  They alter
the value of a variable in a given frame of a given stack.  The stack
is specified by the first argument, which is either a valid task ID or
0 to act on the current stack.  The second argument specifies which
frame of the stack to operate on.  The currently-executing frame is 1,
with verbs further down in the stack being represented by higher
numbers.  A given frame's level is its index in the list returned by
task_frames().  The third argument is either a number or a string.  If
it is a number, then the function affects the n-th variable in that
frame, counting in the order returned by task_frames().  If it is a
string, the variable with that name is altered.  The fourth argument
is the value to which the specified variable should be set.
    
In the above example, the following would all be valid, though perhaps
not a wonderful idea:
 
    ;alter_variable(0, 1, 1, "Test...")
    ;alter_variable(0, 1, "NUM", "Test...")
    ;alter_variable(0, 2, "NUM", "Test...")
 
    alter_variable() can return E_PERM if the caller is not a wizard,
E_VARNF if the specified variable index is out of range or the named
variable does not exist, E_RANGE if the specified stack frame does not
exist, or E_INVARG if the specified task does not exist.
    
   undef_variable(INT, INT, INT)
   undef_variable(INT, INT, STR)
   
    The two formats of undef_variable() undefine a given variable in a
given frame of a given stack.  The stack to be examined is specified
by the first argument, which is either a valid task ID, in which case
the given task's stack is affected, or 0, in which case the current
stack is the target.  The second argument is the index of the stack
frame to affect, with 1 being the top and higher numbers representing
lower levels of the stack.  The third argument is either the integer
index of the variable to be undefined, as returned in task_frames(),
or a string representing the name of that variable.
 
    In the above example, the following would all be valid (though they
probably ought to return E_BADIDEA...):
    
    ;undef_variable(0, 1, 2)
    ;undef_variable(0, 1, "OBJ")
    ;undef_variable(0, 2, "NUM")
    
    undef_variable() can return E_PERM if the caller is not a wizard,
E_VARNF if the specified variable index is out of range or the named
variable does not exist, E_RANGE if the specified stack frame does not
exist, or E_INVARG if the specified task does not exist.

    task_debug(INT, INT)

    task_debug() returns the debugging state of a given frame of a
given task.  That is, if the verb executing in the specified task was
+d when it was called, this returns true; otherwise it returns false.
The first argument specifies the task to be examined.  It must be
either a valid task ID or 0, with the latter indicating that the
current task is to be used.  The second argument is the index of the
stack frame to examine, with 1 being the top and higher numbers
representing lower levels of the stack.

   task_debug can raise E_INVARG if the task ID specified does not
exist, E_PERM if the caller does not own the task in question and is
not a wizard, or E_RANGE if the frame specified does not exist.

    set_task_debug(INT, INT, ANY)

    set_task_debug() sets the debugging state of a givenframe of a
given task.  A given verb in a given task may be set to be +d or -d
regardless of the debugging state of the verb being executed.  As this
could open security holes not immediately apparent to the author, it
is restricted to wizards.  The first argument specifies the task ID of
the task to be altered, or may be 0 to alter the current task.  The
second arguent is the index of the stack frame to change, specified in
the standard manner described above.

    set_task_debug can raise E_PERM is the caller is not a wizard,
E_INVARG if the specified task does not exist, or E_RANGE if the frame
specified does not exist.

    task_freeze(INT)

    task_freeze() alters a suspended task such that it will never
continue execution unless un-suspended with a resume(), or one of the
task_step() functions below.  The first argument is the task ID of the
suspended task.  

    task_freeze can return E_PERM if the player is not a wizard and
does not own the task in question.  It can also return E_INVARG if the
task ID specified does not exist.

    task_step(INT)

    task_step() causes a task to resume until it reaches the end of
the current line of code.  It will then suspend indefinitely.  If the
code was suspended with a suspend() built-in, the return value will be
zero.  The first argument specifies the task ID of the suspended task
to be stepped.

    task_step can return E_PERM if the programmer is not a wizard and
does not own the task in question.  It can also return E_INVARG if the
task ID specified does not represent a currently suspended task.

    task_step_in(INT)

    task_step_in() acts very much like task_step, except that it will
not execute any verbs called by the current verb.  Instead, it will
prepare itself for the execution of the verb being called by the
current verb and suspend before executing any code in the new verb.

    task_step_in can return E_PERM if the programmer is not a wizard and
does not own the task in question.  It can also return E_INVARG if the
task ID specified does not represent a currently suspended task.

    task_step_out(INT)

    task_step_out() causes a task to resume and complete the execution
of the current verb.  It will then suspend after destroying the frame
of the completed verb but before executing any code in the calling
verb.  If used on a command-line verb, it will cause the task to
complete.

    task_step_out can return E_PERM if the programmer is not a wizard and
does not own the task in question.  It can also return E_INVARG if the
task ID specified does not represent a currently suspended task.

    task_code(INT[, INT[, ANY[, ANY]]])

    task_code() operates much like verb_code().  Its first argument
specifies the task ID whose code is to be retreived.  This may be
zero, to specify the current task.  The second argument specifies the
frame of the task whose code is to be retreived.  If this is omitted,
the upper-most frame (the one currently executing) is selected.  The
third and fourth arguments control parentheses and indentation,
respectively, as they do in verb_code().  

   task_code can return E_PERM if the programmer is not a wizard and
does not own the task in question.  It can also return E_RANGE if the
specified frame does not exist.

   eval_suspended(STR)

   eval_suspended is in virtually every respect identical to the
eval() built-in function.  The only difference is that it causes the
task to be infinitely suspended precisely before the point at which it
would execute the code within the string to be evaluated.

   debugger_version()

   debugger_version returns a version string indicating the version of the
MOO SLD being used.  This string is identical to the version string
displayed in the server log on server startup.


Compatibility and Stability

This server patch has been tested under a wide variety of conditions
and on a wide variety of systems, including MkLinux DR2.1u4 (Linux
2.0.30) on a PowerPC, SusOS 5.5.1 on a Sparcstation 5, ULTRIX 4.1 on a
Decstation 3100, HP-UX 9.03 on an HP-730 (numerous compilation
warnings for the server itself, but it passed the tests--it may have
been local header files), and Irix 5.3 on an SGI Indy.  While it may
not be bug-free, it should be stable enough for use in a production
setting.

Due to changes in the data structures for suspended tasks, the format
of the database has been changed slightly.  The databases are
compatible under certain specific circumstances.  All standard 1.8.0p6
databases can be read by the 1.8.0p6+debugger server.  A
1.8.0p6+debugger database can be read by the 1.8.0p6 server provided
no suspended tasks in the database were most recently suspended by
task_step(), task_step_in(), or task_step_out().  The server normally
suspends tasks at certain, narrowly-defined locations.  The debugger
suspends tasks at other locations.  Tasks suspended at these other
locations will prevent the database from being loaded by a
non-debugging server.


Contact Information

The author, Nicholas Ingolia, may be reached by email at:
    ingolia at mit dot edu

Information about this code can be found at
    http://web.mit.edu/ingolia/www/SLD.html


Copyright

Copyright (c) 1997 Nicholas Ingolia.

This software is based on the LambdaMOO server.  The copyright
information for this server is included below:

/******************************************************************************
  Copyright (c) 1992, 1995, 1996 Xerox Corporation.  All rights reserved.
  Portions of this code were written by Stephen White, aka ghond.
  Use and copying of this software and preparation of derivative works based
  upon this software are permitted.  Any distribution of this software or
  derivative works must comply with all applicable United States export
  control laws.  This software is made available AS IS, and Xerox Corporation
  makes no warranty about the software, its performance or its conformity to
  any specification.  Any person obtaining a copy of this software is requested
  to send their name and post office or electronic mail address to:
    Pavel Curtis
    Xerox PARC
    3333 Coyote Hill Rd.
    Palo Alto, CA 94304
    Pavel@Xerox.Com
 *****************************************************************************/

The copyright information for this code:

This software may be freely distributed provided this information accompanies
it.  Derivative works based on this software may be freely created and
distributed provided this copyright information is included and the
derivitive work is clearly identified as such.

This software is made freely available and the author makes no
warranty, express or implied, about its performance, reliability,
compliance with any standard, or fitness for any purpose.

