============================================================ 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-mztake.ss", in the demos directory of the MzTake collection: (require (lib "mztake.ss" "mztake")) (define/bind (loc "highway.ss" 4) speed) (printf-b "current speed: ~a" speed) ;; more code (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 fourth 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 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 the SET-RUNNING! function consumes a FrTime behavior. The value of a behavior can change over time, and SET-RUNNING! monitors these changes. Whenever the behavior is true, the target program runs, and 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 is possible to interactively explore the state of the paused process. You may enter names of variables bound in the target program, and MzTake will look up and return their values. For example, typing 'nap-time' in the REPL while the program is paused yields the value 0.8. 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) ) > (loc require-spec pattern) The LOC function can also accept a pattern. MzTake will scan the text of the target source file and install a breakpoint at every location where the text matches the pattern. The pattern language is very simple. Every symbol in the pattern is taken literally, with the exception of the underscore. In a pattern, and underscore stand for zero, one, or many items. Unlike the patterns used by match.ss, MzTake does not make use of an ellipsis to indicate repetition. The repetition is always implicit. The following expression inserts a tracepoint on every lambda expression that has exactly one argument, called "l" : (trace (loc '(lib "list.ss") '(lambda (l) _)) ) > (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 the body can 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 finds the values for the variables whose names are given. These values are then bound in the body (in the MzTake script) to the variables of the same name. It is an error to call BIND while the target process is running. You can use BIND to look up values of identifiers in the target program that are shadowed by identifiers in the script. > (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 to behaviors reflecting the values of the given names 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 binds event streams to the names instead of behaviors. > (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) Launches 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 pause occured. You can then inspect the state of the program, 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 makes it possible to 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 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 tracepoint 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. 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!. Otherwise, the current policy is consulted to decide between fast and debuggable mode. In this case, if 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