The QM interactive debugger enables the developer to step through an application program in a convenient manner, stopping at desired points and examining data items.
Programs to be debugged must be compiled with the DEBUGGING option to the BASIC command or by including the $DEBUG compiler directive in the program source. At run time, the debugger will stop at selected places in the execution of these programs but will run normally through programs not compiled in this mode. Catalogued programs, subroutines and class modules may be debugged in exactly the same way as other programs.
The debugger is activated either by use of the DEBUG command in place of RUN or by a DEBUG statement encountered during execution of a program. The latter method enables debug mode to be entered part way through execution of the program or on meeting some specific condition. The debugger can also be entered from the break key action prompt if any program currently being executed has been compiled in debug mode.
During application development it is often worth compiling the entire application in debug mode. Execution of the program with the RUN command will not invoke the debugger unless a DEBUG statement is encountered. There is a small performance impact of running a debug mode program in this way but it is usually not significant.
Phantom processes and those acting as the server side of a QMClient connection can be debugged using the PDEBUG command.
The debugger will identify the program from which it was entered and locate the source program record. If this is not available, a warning is displayed and execution of the program continues in non-debug mode though other programs and subroutines called by it will still be subject to debugging if their source records are available.
When used with QMConsole on a Windows system, via the QMTerm terminal emulator or AccuTerm using a terminal type with the -at suffix, the debugger operates in full screen mode. When used on other terminals, the debugger output is mixed with the application output.
The display is divided into two areas. The upper portion of the screen shows the source program with the line about to be executed highlighted. The lower portion of the screen is used to echo commands and to display their responses. The top line of the screen displays the program name and current line and element number. The display may be toggled between the debugger and the application by use of the F4 key. Full screen mode also supports a command stack similar to that found at the command prompt.
Where the terminfo definition for the terminal device in use includes mouse support, the full screen debugger additionally displays a menu bar at the top of the screen and a pop-up menu when the mouse is clicked on a source line. All mouse actions in the debugger use the left mouse button.
The top menu is divided into three sub-menus; Run, Debug and View. The Run menu allows the user to step through a program in various ways or to terminate debugging. The Debug menu allows a breakpoint to be set or cleared on the current line. The View menu has options to view the application screen, to show all variables, and to show the call stack.
Clicking on the line number shown to the left of a source line toggles a breakpoint on that line.
Clicking on the source text shows a context sensitive pop-up menu that allows the user to set or clear a breakpoint, show the content of the variable named at the mouse click position or set watch mode on a variable. Debugger actions related to variables in the pop-up menu cannot be used for private variables in local subroutines or functions and cannot include qualifiers for matrix indices or dynamic array references. These advanced options must be accessed via the debugger command prompt.
On first entry to the debugger in a QM session, it checks for the presence of an X-type VOC record named $DEBUG.OPTIONS. If this is present, fields 2 onwards may contain configuration data for the debugger. The parameters are case insensitive and are as shown below.
Unrecognised parameters are ignored.
Using the Debugger
The current position in a program is referenced by a line number and an element number. Most QMBasic source lines hold only a single element (element 0) but lines with multiple statements separated by semicolons or clauses of IF/THEN/ELSE constructs, etc, are considered to represent separate execution elements. The debugger can step line by line or element by element through a program.
The debugger cannot step through statements inserted into a program from an include record. In such cases, it will step over the included statements as though they were part of the immediately preceding statement.
Debugger commands fall into two groups; function keys and word based commands. In many cases both forms are available. Not all terminals support function keys.
Entering a blank line as a debugger command repeats the last command.
Function Key Commands
(Some function keys may not be available on all terminal emulations)
If an application dynamically rebinds the codes sent by keys used by the debugger, setting the DEBUG.REBIND.KEYS mode of the OPTION command will cause the debugger to reset these to the bindings specified in the terminfo entry for the current terminal type on each entry to the debug screen. Note that the debugger cannot revert to the user bindings on exit as it has no way to determine what these were. This feature is available only with AccuTerm.
Word Based Commands
Where a short form is available, this is the upper case portion of the command as shown. Commands may be entered in any mix of upper and lower case.
The following commands apply only to full screen mode debugging:
The following commands apply only to non-full screen mode debugging:
Displaying Program Variables
Entering a variable name preceded or followed by a slash (/) or a question mark (?) displays the type and content of the given variable (var/, /var, var?, ?var). This name may be local variable or a variable in a common block defined in the current program. If the common block has not been linked at the time the command is entered, the variable will appear as unassigned. For programs compiled with case insensitive names, the debugger is also case insensitive.
Private local variables in a subroutine declared using the LOCAL statement can be referenced using a name formed by concatenating the subroutine name and variable name with a colon between them. If a subroutine is executed recursively, it is only possible to view the current instance of the variables. To simplify display of another variable from the same local subroutine, a name of the form :var assumes the same local subroutine name as the previous local variable reference.
The debugger recognises variable names STATUS(), INMAT(), COL1(), COL2() and OS.ERROR() to display the corresponding system variable. All @-variables may also be displayed except for @VOC (which is a file variable) and those representing constants such as @FM and @TRUE.
When debugging an object instance, the names of arguments to a public subroutine or function are not known to the debugger. Instead, a special positional reference of the form "*Arg1" is needed when displaying argument variables. The *Arg prefix is case insensitive and the argument number may include leading zeroes. For example, display of the second argument could use a debugger command such as
All of the qualifiers to variable references such as array indices and dynamic array element positions may be used.
Display of long strings is broken into short sections to fit the available display space. Entering Q at the continuation prompt will terminate display.
When displaying strings with an active remove pointer, the position of this pointer is also shown.
If the variable is a matrix, the name may be followed by the index value(s) for the element to be displayed. Entry of the name without an index will display the dimensions of the matrix. Subsequent presses of the return key display successive elements of the matrix until either all elements have been displayed or another command is entered.
Array: Dim (20)
CLI.REC(0) = Unassigned
CLI.REC(1) = String (8 bytes): "J Watson"
CLI.REC(2) = 13756
The variable name may be followed by a field, value or subvalue reference which will be used to restrict the display if the data is a string. Note that this qualifier has no effect on other data types.
String (11 bytes,R=4): "487FM912VM338"
String (3 bytes): "487"
String (3 bytes): "912"
Entering a slash alone will repeat the most recent display command.
Analysis of very large character strings is sometime easier from outside the debugger. The DUMP command can be used to dump the contents of a variable to an operating system level file that can then be processed with other tools.
The /* command, available in full screen mode only, displays all variables in the program, one per line. The page up, page down, cursor up and cursor down keys can be used to move through the data. When the current line marker in the leftmost column is positioned on an array or a data collection, pressing the return key shows the elements of the item. When positioned on a character string, pressing the return key shows the string data in hexadecimal and character form. In all cases, the Q key returns to the previous screen.
Changing Program Variables
The SET command can be used to alter the value of a variable.
The WATCH command causes the debugger to monitor the named variable.
Whenever a value is assigned to this variable (even if the value is the same as currently stored), the debugger will stop program execution and display the new value. Only one variable can be watched at a time.
There is an extended form of WATCH that will only report the new value and stop if the value meets a specific condition. This form of WATCH is
WATCH var op value
The UNWATCH command cancels the watch action. The watch action is automatically cancelled when the watched variable ceases to exist. This might be return from the program in which the variable exists, re-dimensioning a common block, etc.
Referencing Equate Tokens
If a program is compiled in debug mode and with the $MODE DEBUG.EQUATES compiler mode enabled, equate tokens from EQUATE or $DEFINE used in the program source code can also be used in the debugger for displaying, changing and watching variables.