523 lines
19 KiB
Plaintext
523 lines
19 KiB
Plaintext
|
|
|
|
============================================================
|
|
|
|
About MzTake
|
|
|
|
_MzTake_ is a _scripted debugger_ for PLT Scheme. It helps
|
|
programmers monitor the execution of a target program as it
|
|
unfolds (and optionally pause or resume its execution). MzTake
|
|
gives you the power to easily write real programs that debug real
|
|
programs. You are no longer limited to a tool chest of buttons
|
|
like "add breakpoint", "step-next", "step-into", and "step-over".
|
|
|
|
MzTake scripts are written in the FrTime programming
|
|
language, which is bundled with DrScheme. FrTime supports the
|
|
implementation of reactive systems in a functional style.
|
|
The key abstraction it adds is a type of value called a 'signal',
|
|
which can change over time. FrTime infers dataflow dependencies
|
|
between signals and automatically recomputes them when necessary. In
|
|
order to use MzTake, you will need to familiarize yourself with the
|
|
FrTime language by reading its own documentation.
|
|
|
|
With signals it is possible to respond to outside events concisely,
|
|
without using callbacks. Consider a MzTake script to monitor the
|
|
behavior of the program "highway.ss", in the demos directory of the
|
|
MzTake collection:
|
|
|
|
(require (lib "mztake.ss" "mztake"))
|
|
(define/bind (loc "highway.ss" 3) speed)
|
|
(printf-b "current speed: ~a" speed)
|
|
(set-running! true)
|
|
|
|
This code executes a target module in the file "highway.ss"
|
|
after installing a _trace point_ (also known as a _watch
|
|
point_) just before the Scheme expression on the third line
|
|
of "highway.ss". SPEED is a FrTime behavior that always
|
|
contains the *current* value of the variable named SPEED in
|
|
the target program.
|
|
|
|
PRINTF-B works like Scheme's PRINTF function, consuming a
|
|
format-string and fill-values, printing the result in
|
|
DrScheme's interaction pane. Whereas PRINTF accumulates
|
|
outdated text on the screen, PRINTF-B will replace old text
|
|
with updated text if any of the fill-values change. In this
|
|
invocation, it prints the current speed to screen, throughout
|
|
the execution of "highway.ss". The last line invokes SET-RUNNING!,
|
|
which lunches the execution of highway.ss
|
|
|
|
MzTake scripts are also powerful tools for building external
|
|
test suites. Whereas typical test cases may only assert that
|
|
the result of a computation is correct, MzTake scripts
|
|
can dynamically break open an execution, record inner state,
|
|
and *compute* with it. This allows you to confirm that the
|
|
intermediate steps which lead to a correct answer were
|
|
also correct. In the highway example, perhaps knowing the last ten speeds that
|
|
would prove useful. You could PRINTF the value
|
|
onto a new line each time, but after ten updates the screen
|
|
is already starting to fill up with information -- we are
|
|
only interested in the last ten speeds, after all.
|
|
One possible solution:
|
|
|
|
(printf-b "last ten speeds: ~a" (history-b (changes speed) 10))
|
|
|
|
HISTORY-B consumes an event stream (CHANGES SPEED) and an
|
|
optional number n, returning a FrTime behavior containing a
|
|
FIFO ordered list of the n values emitted on that event
|
|
stream. In this case, HISTORY-B maintains a list of the ten
|
|
most recent SPEEDS seen on SPEED.
|
|
|
|
We might want to pause the program when something goes awry. We do
|
|
this by exploiting the fact that SET-RUNNING! function consumes a
|
|
FrTime behavior. The value of a behavior can change over time, and
|
|
SET-RUNNING! monitor these changes. Whenever the behavior is true, the
|
|
target program runs, whenever it is false, the target program
|
|
pauses. We can indicate to MzTake to pause when the speed exceeds 55
|
|
as follow:
|
|
|
|
(printf-b "last ten speeds: ~a" (history-b (changes speed) 10))
|
|
(set-running! (< speed 55))
|
|
|
|
Once paused, it becomes possible to interactively explore the state of
|
|
the paused process. You can use the BIND function to reach into the
|
|
scope of the target process and read the value of the variable:
|
|
|
|
(bind (speed) (printf "the speed is ~a~n" speed))
|
|
|
|
This lines finds the variable named "speed" in the scope at the point
|
|
where the execution is paused, then binds its values to a variable
|
|
named "speed" in the MzTake script, then executes its body. In this
|
|
case, it print the value with PRINTF.
|
|
|
|
Since MzTake defines a #%top syntax, you can also directly type the
|
|
name of a variable. MzTake will first look in the scope in the MzTake
|
|
script. If the variable is not found, it will then in the target
|
|
process. So, the example above can be written as:
|
|
|
|
(printf "the speed is ~a~n" speed)
|
|
|
|
so long as the script itself does not declare a SPEED variable.
|
|
|
|
You can resume execution with "(set-running #t)", or
|
|
some other behavior, or end the execution altogether with "(kill)".
|
|
|
|
Finally, FrTime provides a rich animation library. Combined
|
|
with the MzTake debugger, it takes only a few lines to animate
|
|
your algorithms and see them in action, easily letting you
|
|
confirm (or refute!) that they are working correctly.
|
|
|
|
(require (lib "animation.ss" "frtime"))
|
|
(display-shapes (make-speed-gauge (hold speed)))
|
|
|
|
|
|
============================================================
|
|
|
|
Demos
|
|
|
|
The demos directory contains a sub-directory for each of the demos.
|
|
For instance, the highway directory contains "highway.ss" and
|
|
"highway-mztake.ss". To run this demo, switch to the "FrTime" language
|
|
level from the "Experimental Languages" section of DrScheme's language
|
|
dialog, load "highway-mztake.ss", and click "Run". What you see is
|
|
generated by the debugging script. Each demo directory contains
|
|
the following two files: one is the program being debugged
|
|
(named after the directory), and the other is a file ending
|
|
in "...-mztake.ss" (the MzTake script).
|
|
|
|
The demos are (starting with the simplest one):
|
|
|
|
./highway/highway-mztake.ss - The program simulates a very simple
|
|
speedometer, and the MzTake script
|
|
monitors it.
|
|
|
|
./sine/sine-mztake.ss - Plots values extracted from a program
|
|
which generates coordinates for a
|
|
single sine wave.
|
|
|
|
./random/random-mztake.ss - Tests the quality of Scheme's random
|
|
number generator with a histogram.
|
|
|
|
./exception/exception-mztake.ss - Demonstrates how MzTake catches exceptions.
|
|
|
|
./djikstra/dijkstra-mztake.ss - Debugs a buggy implementation of
|
|
Dijkstra's algorithm
|
|
|
|
|
|
The demos demonstrate many ways to debug with MzTake using
|
|
FrTime, even if you are not very familiar with the language.
|
|
That said, in order to become more proficient in using MzTake,
|
|
you will want to learn more about the FrTime language.
|
|
|
|
You can refer to FrTime's own documentation by searching for
|
|
"FrTime" in DrScheme's Help window. It explains how to use
|
|
time-varying behaviors and event streams in greater depth, and
|
|
also describes the many useful functions FrTime provides to work
|
|
with them.
|
|
|
|
|
|
|
|
============================================================
|
|
|
|
_Debugging with MzTake_
|
|
|
|
MzTake is a library for the FrTime languages which provides functions
|
|
that execute a target program (or many), and "connect" to points in
|
|
its code. MzTake then provides the running FrTime script with
|
|
interesting information (such as a variable's current value) which it
|
|
derives from these "connections". FrTime then handles the rest.
|
|
|
|
MzTake defines the following functions and macros:
|
|
|
|
_Installing Trace Points_
|
|
|
|
> (loc require-spec line-number)
|
|
> (loc require-spec line-number column-number)
|
|
|
|
Creates a LOC structure containing the target file, the target line
|
|
number, and (optionally) the target column number. LOC structures
|
|
are consumed by TRACE and by DEFINE/BIND. The first argument to LOC
|
|
is a file specification suitable for require, provided as a
|
|
datum. For instance, to install a trace point on the tenth line of
|
|
the MzLib's list library, use:
|
|
|
|
(trace (loc '(lib "list.ss") 10) ...)
|
|
|
|
> (trace loc body ...)
|
|
|
|
Install a trace point at the location indicated by the LOC
|
|
value. The result is a FrTime event stream containing one value
|
|
each time the target reaches the location specified. To get the
|
|
value event, TRACE evaluates its body (once per event. During the
|
|
evaluation of the body, the target process is paused, and BIND is
|
|
available to inspect the state of the paused program.
|
|
|
|
The body is optional. If no body is provided, the value #t is used
|
|
by default.
|
|
|
|
Unless SET-MAIN! is used, the first call to trace sets the file
|
|
name that will be run when SET-RUNNING! is invoked the first time.
|
|
|
|
> (trace* process loc thunk)
|
|
|
|
Like TRACE, but takes an explicit process argument, and a thunk
|
|
rather than a body.
|
|
|
|
> (bind (name ...) body ...)
|
|
|
|
When the target process is paused (or during the execution of a
|
|
trace body), BIND reaches in the lexical context at the point of
|
|
the pause (or of the trace point) and find the values for the
|
|
variables whose names are given. These values are then bound in the
|
|
body (in the MzTake script) to variable of the same name.
|
|
|
|
It is an error to call BIND while the target process is running.
|
|
|
|
|
|
> (bind* process symbol)
|
|
|
|
In the given process, find the variable whose name has the given
|
|
symbol, and returns its value.
|
|
|
|
|
|
> (define/bind loc name ...)
|
|
|
|
Define the NAMEs the to behaviors reflecting the values of the
|
|
giving name in the target program, at the given
|
|
location. DEFINE/BIND is short for:
|
|
|
|
(define name (hold (trace loc (bind (name) name))))
|
|
|
|
|
|
> (define/bind-e loc name ...)
|
|
|
|
Same as DEFINE/BIND, but returns an event stream instead of a behavior.
|
|
|
|
|
|
> (exceptions)
|
|
> (exceptions process)
|
|
|
|
Returns an event stream containing one EXN structure for each
|
|
exception raised in the target process and not caught.
|
|
|
|
|
|
> (exited?)
|
|
> (exited? process)
|
|
|
|
Returns a behavior which starts as #f and take on the value #t when
|
|
the target process exits.
|
|
|
|
|
|
> (set-running! val)
|
|
> (set-running! val process)
|
|
> (set-running! event)
|
|
> (set-running! event process)
|
|
|
|
Lunches the execution of the target process. Execution continues as
|
|
long as the given behavior is true (aka, any value beside #f), or
|
|
until an event comes on the given event stream with the value
|
|
#f. When execution pauses, the target remains on the line where the
|
|
paused occured. You can then inspect the state of the program with
|
|
BIND, or resume execution with another call to SET-RUNNING!.
|
|
|
|
|
|
> (set-main! require-spec)
|
|
> (set-main! require-spec process)
|
|
|
|
Sets the file where execution begins when SET-RUNNING! is called
|
|
for the first time. When SET-MAIN! is not used explicitly,
|
|
execution begins with the file specified in the first call to
|
|
trace. It is an error to call SET-RUNNING! without first calling
|
|
either TRACE or SET-MAIN!.
|
|
|
|
|
|
> (where)
|
|
> (where process)
|
|
|
|
Returns an event stream that contains one event for each expression
|
|
evaluated in the target process. Combined with HISTORY-B, this let
|
|
you record entire execution traces for the target program.
|
|
|
|
|
|
> (kill)
|
|
|
|
Kills the target process and releases all resources
|
|
it used -- you cannot resume after a KILL.
|
|
|
|
This will not stop of evaluation of the MzTake script, however. In
|
|
particular, if the script depends on input the varies independently
|
|
of the target process, FrTime will continue to update them. You can
|
|
use "Kill" command from DrScheme's "Scheme" menu to stop both the
|
|
MzTake script and its target process at once.
|
|
|
|
Also, note that closing a FrTime animation/graphics window does *not*
|
|
kill a running MzTake process.
|
|
|
|
> (kill-all)
|
|
|
|
When using more than one target process at a time, KILL-ALL invokes
|
|
KILL on all of them at once.
|
|
|
|
|
|
> (current-process)
|
|
> (current-process process)
|
|
|
|
The CURRENT-PROCESS parameter gets or sets the process manipulated
|
|
by the MzTake function when they are not provided with a process
|
|
argument. The CURRENT-PROCESS parameter is initialized with a blank
|
|
process, and you can create additional processes using the
|
|
CREATE-DEBUG-PROCESS function. Using more than one process at a
|
|
time lets your MzTake run multiple programs different at once and
|
|
compare their output using a single script.
|
|
|
|
|
|
> (create-debug-process)
|
|
|
|
Creates a fresh blank debug process. Each debug process has its own
|
|
set of trace points, its own run trigger (set via SET-RUNNING!),
|
|
its own exceptions stream, etc. Each debug process run
|
|
independently from the others, and they can be paused and killed
|
|
individually. All debug processes in a single MzTake script share
|
|
the same FrTime event space, and so it is possible to compare
|
|
output and traces between each of them.
|
|
|
|
> (current-policy)
|
|
> (current-policy policy)
|
|
|
|
Every file executed under MzTake can run either in fast mode or in
|
|
debuggable mode. The CURRENT-POLICY decides which.
|
|
|
|
- debuggable mode: the file is instrumented with MzTake debugging
|
|
information. It can be the target of trace point and it generate
|
|
events on the WHERE stream. Execution can also be paused in the middle
|
|
of code running in debuggable mode. The instrumentation overhead
|
|
is considerable, however, of the order of 10x-20x slowdown.
|
|
|
|
- fast mode: the file is not instrumented, and runs at its normal
|
|
speed, but cannot be debugged. Inserting trace points into fast
|
|
mode files after the beginning of the execution has no
|
|
effect. Also, pausing while executing a fast mode file will be
|
|
delayed until execution reaches a debuggable mode file.
|
|
|
|
MzTake uses the following rules, in order, to decide between fast
|
|
mode and debuggable mode:
|
|
|
|
1) Files that are compiled to .zo run in fast mode.
|
|
|
|
2) Files that are the target of a trace point when first lunching
|
|
the process run in debuggable mode, so is the main file set by SET-MAIN!
|
|
|
|
3) If none of the two previous rules apply, the current policy is
|
|
consulted to decide between fast and debuggable mode.
|
|
|
|
If the check reaches the third step and the policy does not decide,
|
|
MzTake raises an error.
|
|
|
|
Policies have the following contract:
|
|
|
|
(listof (list/c (symbols 'fast 'debuggable)
|
|
(union (symbols 'everything-else)
|
|
path?
|
|
string?
|
|
(listof (union path? string?)))))
|
|
|
|
A policy consist of a list of entries. Each entry is a pair
|
|
specifying either fast mode or debuggable mode, then a directory,
|
|
or a list of directories. Files in these directories, or their
|
|
subdirectories will run under the given mode. The special symbol
|
|
'everything-else can be used instead of a directory, and this will
|
|
match any file. The policy is checked in order, and the first entry
|
|
that applies to the given filename assign a mode the file.
|
|
|
|
The default policy run files of the directories specified by
|
|
CURRENT-LIBRARY-COLLECTIONS-PATHS in fast mode, and runs everything
|
|
else in debuggable mode. This poloicy is set as follow:
|
|
|
|
(current-policy `((fast ,(current-library-collection-paths))
|
|
(debuggable everything-else)))
|
|
|
|
You can change this policy by calling the
|
|
CURRENT-POLICY function with a new policy as an argument. The
|
|
policy is assigned to a process when the process lunches.
|
|
|
|
|
|
_Useful Functions for Time-Varying Values_
|
|
|
|
Note: FrTime uses a naming convention where functions which
|
|
return behaviors have names that end in "-b", and
|
|
functions that return event streams end in "-e".
|
|
|
|
Tips: When you have a behavior that you want to turn into
|
|
an event, use "(changes behavior)".
|
|
|
|
When you have an event that you want to be a
|
|
behavior, use "(hold event)"
|
|
|
|
|
|
MzTake defines a few functions on time-varying values
|
|
that are particularly useful when debugging. You can require these
|
|
functions with (require (lib "useful-code.ss" "mztake"))
|
|
|
|
> (history-e stream)
|
|
> (history-b stream)
|
|
|
|
Keeps a complete history of all the values seen
|
|
on an event stream as a list, oldest events last.
|
|
|
|
Use with BINDs: (history-b x-trace)
|
|
|
|
> (history-e stream n)
|
|
> (history-b stream n)
|
|
|
|
Keeps a list of the last n values of a behavior
|
|
Returns a list of at most n elements, where the
|
|
elements are the n last values seem on the stream,
|
|
in order, oldest first.
|
|
|
|
> (count-b stream)
|
|
|
|
Counts number of events seen on an eventstream.
|
|
|
|
Often used directly on ENTRY traces, counting how many
|
|
times ENTRY occured: (count-b entry-trace)
|
|
|
|
Also useful to count how many times a BIND changed
|
|
by calling: (count-b (changes bind-trace))
|
|
|
|
> (largest-val-b stream)
|
|
> (smallest-val-b stream)
|
|
|
|
Keeps track of the largest/smallest values seen on a stream.
|
|
Use with BINDs: (largest-val-b (changes bind-trace)).
|
|
|
|
> (sequence-match? seq stream)
|
|
|
|
Matches a sequence of items in a list to the history
|
|
of event pings, on the event stream evs. Returns #t
|
|
when it matches, and #f otherwise. Use when you expect
|
|
a certain pattern to show up, and want to know when:
|
|
(sequence-match? '(0 1 2 1 0) (changes bind-trace))
|
|
|
|
> (printf-b format-string arg ...)
|
|
|
|
Displays the value of the behaviors with the given format,
|
|
using "~a" just like in Scheme's FORMAT function.
|
|
|
|
|
|
============================================================
|
|
|
|
Known Problems
|
|
|
|
* In general, you should not REQUIRE in your MzTake script any
|
|
functions that act on the structures of your target program.
|
|
|
|
ORIGINAL FILE:
|
|
(define-struct foo (a b))
|
|
(let ([x (make-foo 1 2)])
|
|
x)
|
|
|
|
MZTAKE SCRIPT:
|
|
(require "original-file.ss")
|
|
(define/bind (loc "original-file.ss" 3) x)
|
|
(foo-a x) ;; this will fail!
|
|
|
|
The target program and the MzTake will have different
|
|
instances of the struct, and the call to FOO-A will fail.
|
|
Instead, use BIND to bring the function from the target program to
|
|
the script:
|
|
|
|
(define/bind (loc "original-file.ss" 3) x foo-a)
|
|
(foo-a x) ;; this succeeds
|
|
|
|
* The break button will *not* kill runaway client processes.
|
|
You must type (kill) or (kill-all).
|
|
|
|
* Some legal syntax locations (used in setting trace points)
|
|
are unreachable during program execution (they do not get
|
|
triggered and produce empty eventstreams). For instance,
|
|
the name clause of a LET is never the current point of execution:
|
|
|
|
(define x 12)
|
|
(let ([x (add1 x)]) x)
|
|
^
|
|
Recommended syntax locations to use for trace points:
|
|
|
|
(define x 12)
|
|
(let ([x (add1 x)]) x)
|
|
^ ^^ ^ ^
|
|
|
|
* Watch out: when you change target code, your line/col locations in
|
|
the script will drift out of align.
|
|
|
|
* Error handling is not perfect -- e.g., the little "bug"
|
|
buttons on syntax errors don't reference the correct code.
|
|
However, the messages that are printed are as accurate as
|
|
possible.
|
|
|
|
* You can add trace points to the body first-class functions, and they
|
|
will send trace update from anywhere they are passed to and invoked.
|
|
|
|
============================================================
|
|
|
|
Authors and Thanks
|
|
|
|
MzTake is an experimental debugger. It should enable new
|
|
debugging approaches that were not possible (easily) before.
|
|
Please send feedback to the PLT-Scheme mailing list:
|
|
http://www.plt-scheme.org/maillist/
|
|
|
|
We are eager to hear about how you are using MzTake!
|
|
|
|
Jonathan Spiro
|
|
Guillaume Marceau
|
|
Gregory Cooper
|
|
John Clements
|
|
Shriram Krishnamurthi
|
|
|
|
|
|
Please send bug reports to: jspiro@cs.brown.edu
|
|
|
|
---
|
|
Icons for MzTake come from the Gnome Project: Nautilus Emblems.
|
|
These are provided under the GPL license.
|
|
http://jimmac.musichall.cz/ikony.php3
|