This document describes the macro debugger in PerfectScript Suite 8 Enterprise Edition.
The macro debugger has been changed for the Enterprise version of PerfectScript 8. It now provides some significant improvements over the macro debugger in version 8, 7 and 6. The debugging of macros is now done with a full source listing available.
Normally the source lines for the macro statements are not available to the debugger. In order to make the source lines available, the macro must be compiled for debug. This can only be done directly from the PerfectScript File/Debug/Compile menu item. Compiling for debug does 2 things. First, the macro source statements are embedded into the object code of the macro. Having the macro source embedded into the object code allows the source line to be displayed when a macro ends with an error. This will cause the compiled macro to occupy more disk space than when this setting is not used. Second, a macro compiler listing file is generated. This listing file is used by the debugger to display the source of the macro.
Once a macro is compiled for debug, then a macro is debugged by playing for debug from the PerfectScript File/Debug/Play menu item. As with debug compiling, direct debug playing is only available from PerfectScript.
Though direct debug compiling and playing is only available from PerfectScript, indirect access to debugging is available from the applications. This is done by enabling the permanent debugging options from the PerfectScript Edit/Settings... menu item, on the Compile and Debug property pages. On these settings pages, enable the Compile/"Include debug information", the Compile/"Generate listing file" and Debug/"Invoke debugger on startup" items. From now on, all macros compiled will be compiled for debug, and every macro played will start the macro debugger, whether compiled/played from PerfectScript or from the applications.
The Debug page of the settings dialog also contains several other settings that control the behavior of the debugger. The debugger may be brought up automatically whenever a macro terminates due to an error, though no macro source code will be displayed unless the macro was compiled for debug before it was played. Even if this option is not turned on, whenever a macro terminates due to an error, the debugger may also be activated by pressing the Debug button on the error message box.
Interface
Once the debugger is invoked, it brings up its dialog interface. The debugger dialog consists of 4 main areas. From top to bottom, these are: the debugger state message line, the macro source list, the call history list, and the macro variable list.
The debugger state message line indicates whether the debugger (or the macro) is active, and the reason the debugger is active (such as a breakpoint on a statement start, error condition, etc). While the debugger interface is active, execution of the macro is suspended, and attempts to interact with prompts, message boxes or dialogs that are being displayed by the macro cause a message box to be displayed, indicating that macro execution is suspended while the debugger is active, and interaction with the macro is not possible. Once the debugger has displayed its dialog, but while the macro is executing statements (i.e. the debugger is between breakpoints, etc), the debugger is considered to be inactive, and the debugger state message line displays the text "Macro is running...". However, the debugger is not disabled, and some commands are still available (e.g. breakpoints may be set). All commands that would access information about the macro state, however, are disabled while the macro is running and the debugger is inactive.
The next 3 areas are lists. Between each of these lists is a split bar that is used to adjust the size of the individual lists in relation to its neighbor. Each of the 3 lists support a context sensitive (right mouse) menu containing items relevant to that list. The items available on these menus are also available from the toolbar, and the main menu bar of the debugger dialog.
Toolbar
The display of the toolbar in the macro debugger can be turned off if the toolbar is not needed or desired. The toolbar contains a large set of buttons. The buttons that are displayed, and the ordering of the buttons can be customized by double clicking on a blank space in the toolbar. Doing this brings up the toolbar edit dialog. From this dialog, the buttons for the debugger commands can be removed, added, or reordered. Though the configuration of the toolbar is saved, it is not associated with the specific macro file that is being debugged. The same configuration is used when debugging all macros.
Macro Source List
The macro source listing displays the source of the macro being debugged (taken from the compiler listing file). In the macro source, the next macro statement to be executed is indicated by a red arrow in the left margin. The left margin also contains an indicator showing which statements have breakpoints, and whether those breakpoints are enabled or disabled.
If the mouse pointer is positioned over a variable, label, token or command in the source list, then after a short delay, a tooltip is displayed showing details about the item being pointed at. If the item is a variable that has not yet been defined, or a label defined in a "use" file that has not yet been loaded, then the debugger may be unable to identify it and will display "??".
A breakpoint may be placed on any macro source line containing macro statements by double clicking the source line. Once defined, a breakpoint may be enabled or disabled from the source listing as well.
To examine and set breakpoints in another macro file (such as a use file), any other macro may be displayed in the macro source area. This is done by using the File/Open Macro menu item. The last 9 accessed macro files are listed in an MRU file list on the File menu.
Call History List
The call history list area lists in reverse order, the user-defined functions/procedures and labels that have been called. The current location is listed at the top. The name of the function/procedure or label is shown, along with the line number where execution within that function/procedure or label was interrupted, and the file that the function/procedure or label is contained in. By selecting the various entries in this list, the macro source for that macro is displayed in the source listing area, and the associated line is highlighted and indicated by a green triangle in the left margin (unless the top entry is selected, in which case the red arrow is displayed). The variables accessible to the macro at that point are then listed in the variables area below.
Variables List
The variables area displays the list of variables accessible to the macro at the indicated location (shown in the Call History List). Also displayed are the variable's pool type (Local, Global or Persistent), the type of value the variable contains and the current value of the variable. The list of variables may be restricted to any combination of the Local, Global or Persistent pool variable types. Even though multiple variables with the same name in different pools may appear in the list, only the most locally scoped variable with that name is accessible to the macro as it executes (e.g. If there is both a Local and a Global variable named B, only the Local B may be accessed by the macro. See variable scoping rules in the macro manual). Once a variable has been declared with the DECLARE, LOCAL, GLOBAL or PERSIST statements in a macro, the variable will show up in this list even though its contents may be undefined.
Array variables are displayed in this list with their declared dimensions and a contents type of Array. Array variables may be expanded to display the individual array elements in the list, or collapsed to hide the individual elements. The individual elements of the array may then be examined as normal variables. If a variable is an address (alias) parameter to a user- defined function/procedure, then its contents type is displayed as Alias, and it may be expanded and contracted like an array to show the actual variable that it is mapped to. If an alias variable is mapped to a Global or Persistent variable, then the variable pool type is displayed appropriately. If it is a Local variable, then the pool type is displayed as "Local to Caller", to distinguish it from a variable that is local to the current function/procedure.
The list of variables may be sorted by any of the columns by clicking on the column heading. The sort order may be toggled from ascending to descending by clicking on the same heading a second time. If the variable list is sorted by variable name or by pool, then expanded array elements are kept with their corresponding array. Sorting by the other columns may cause elements of an array to become separated from each other, depending on the contents of the array element. The current sort column and sort order are indicated by a ">" or "<" symbol before the column heading name.
New variables may be created in any variable pool, by selecting the Variable/New menu item. An array variable may be created by specifying the dimensions of the array after its name. Variables are created with undefined contents, that may then be changed. A variable may also be reset or discarded by pressing the delete key. You should consider carefully before discarding a variable, since the macro may rely on the variable being defined. Discarding an array variable (not an array element), will first reset the contents of all of its array elements.
The contents of a variable (or array element) may be changed by either double clicking on the variable, or clicking in the contents column for that variable. An edit box is displayed with the current variable's contents, which may then be changed. The contents of the variable are updated by clicking outside the edit field, or pressing the return key. The changes are canceled by pressing the escape key. The contents of an alias variable cannot be changed, but the variable mapped to the alias can be changed. If the contents of an array is changed, then the new value is placed into all of its array elements. Changing the contents of an array element changes that element only, and does not affect the contents of any other array elements.
If only certain macro variables are important, these variables may be added to the variable watch list, and then the variable watch list can be displayed, which lists only the watch variables. Only entire arrays or non array variables may be watched. Individual array elements cannot be watched separate from their corresponding parent array. A variable displayed in the macro source may be placed on the watch list using the context menu in the source list. When the variable watch list is displayed, it replaces the normal variables list.
Once a variable is added to the variable watch list, then that variable name is displayed on the variable watch list until removed from the list, even if the actual variable ceases to exist (such as being discarded in the macro, or when returning from a procedure/function, which discards all its local variables). If the actual variable ceases to exist, then the pool type of the watch variable will display "out of scope".
Search/Find
To help locate parts of the macro or items in the macro source that are of interest, the macro debugger has several ways of "finding" or searching. Text may be searched for (as well as the next or previous occurrence of the same text), a specific line number may be located, or the next or previous breakpoint may be located. Using the context menu in the macro source list, the definition of a procedure/function or label can also be located.
Breakpoints
Breakpoints are used to indicate locations in the macro file where execution of the macro should be interrupted and suspend, and where the debugger should become active to allow the examination of the state of the macro.
The most common type of breakpoint, is the line number breakpoint. This causes macro execution to be suspended and brings up the macro debugger when execution of the macro reaches a specified line number. This type of breakpoint is easily added by double clicking on a source line in the macro source list window. This is the only type of breakpoint that can be added from the macro source list window.
A number of other options and types of breakpoints, however, are available from the Breakpoints Edit dialog. From this dialog, other types of breakpoints may be defined, as well as changing options for existing breakpoints. Each different type of breakpoint has a number of common options, as well as options that are specific to each specific breakpoint type.
Most (but not all) breakpoints allow the macro file to be specified. If the macro file is omitted, then this breakpoint applies to all macro files. Most breakpoints also allow a passcount to be specified. A passcount is the number of times that the breakpoint conditions can occur before the breakpoint actually takes place. Each time the conditions occur, the passcount is decremented, and when the count reaches zero, then the breakpoint triggers and causes a break into the debugger.
All breakpoints allow a message to be logged to the debugger event log. When the breakpoint triggers, then this message is written to the debugger event log (if the event log is enabled). A breakpoint may be set up to only log a message, to only cause a break into the debugger, or both. In the left margin of the breakpoint list, a hand symbol is displayed. A yellow hand indicates a breakpoint that will cause a break into the debugger (and may also log an event message), while a blue hand indicates a breakpoint that will only log an event message and not cause a break into the debugger. The debugger event log is enabled from the debug settings page of the PerfectScript settings dialog.
Each individual breakpoint may be disabled. When disabled, a breakpoint is ignored even if its conditions occur. Additionally, all breakpoints may be temporarily disabled. This causes no breakpoints to be recognized. This is temporary, and all breakpoints revert to their previous state when the temporary disable is ended, or when the macro ends and the debugger terminates.
The types of breakpoints that may be added are: Line number; Error, Label/routine call, Label/routine return, Product token call, DLLcall, and Variable access and Variable assign. Each breakpoint type can help in determining the cause of different types of problems.
A line number breakpoint occurs when the macro reaches a specified line number. An error breakpoint occurs when the macro terminates due to an error condition. A label call breakpoint occurs when a specified label or procedure/function is called. A label return breakpoint occurs when returning from a specified label or procedure/function. A product token call breakpoint occurs when a specified product token is called. A DLLcall breakpoint occurs when a specified DLL routine is called. The variable breakpoints occur when the contents of a variable are either accessed (read) or assigned (written) to.
Stop/Continue/Step/Animate
When the macro debugger stops on a macro statement, it stops before the indicated statement has been executed. To continue execution of the macro, a number of different options are available. Macro execution is continued to the next breakpoint by choosing Continue. Execution of a single statement is done by choosing Step Into. If the next statement is a label or routine call, then execution will step into the specified label or routine, even if that label or routine is in another macro file (such as a use file). To execute the call of the label or routine without stopping until it has completed, then Step Over is used. This will stop the macro at the next statement in the current label or routine. If you have entered a label or routine, then Step Out will execute until the next return is encountered.
To continue execution down to the line under the mouse cursor in the macro source list, then Run to Cursor is used. If a series of statements need to be skipped over without executing, or if some statements need to be repeated, then Skip to Cursor is used. This sets the next statement that will be executed by the debugger, without executing any statements between the current point and the new line. This option should be used with extreme caution, since skipping to a line that is not within the same label or routine could cause the internal macro execution state to become invalid, which will almost certainly result in execution failure.
While the macro is executing statements, the debugger is considered to be inactive, and the debugger state message line displays the text "Macro is running...". However, the debugger is not completely disabled. The macro can be interrupted by choosing Break. This will cause the debugger to become active, as if there were a breakpoint at the point where the macro is current executing. The breakpoint commands are also available while the macro is running, so that breakpoints can be added or removed. Most other commands of the debugger are disabled while the macro is running.
Execution of the macro may be animated by using the animate mode. When this mode is turned on, then the next Continue or Step command is repeated until stopped, or the macro ends. Between each command, the debugger is displayed for a brief period of time (which may be specified in the debug page of the setting dialog).
The macro is stopped, and debugging is terminated by choosing Stop. When the macro is stopped, and the debugger ends, all defined breakpoints, watchlist variables and opened macro files are stored in a debugger configuration file specific to the macro being debugged, and are saved in a file with a ".dbg" file name extension in the same directory as the macro. This configuration file is reloaded the next time that macro is debugged.
Informational Windows
A number of separate informational windows may be opened to display various types of information about the current state of the macro. These windows are modeless dialogs and are refreshed whenever the debugger becomes active. Most of the windows display information that is specific to the current execution point in the macro. By selecting a different entry in the Call History list of the debugger, the information specific to that selected entry will be displayed. In any of these windows where labels, line numbers or filenames are displayed, double clicking on that item will locate that position in the macro and display it in the macro source list. These items are indicated by a grey arrow in the left margin.
The informational windows that are available, are: The label table, the Use File table, the Product table, the Dialog list, the Condition Handlers list, the Macro info list, the Callback Queue, and the Macro Header.
The Label table is a list of all the labels defined at the execution point selected in the Call History list. For each label, the label name, type, line number and file name are displayed. The label type is either local or global. Local labels are defined by the Label statement in a macro, and are only visible within the procedure/function where they are defined. Global labels are user defined procedures and functions, and are visible anywhere in a macro file, as well as in other macro files that have a Use statement of the file containing the procedure/function. The name of the file where the label is defined is displayed, as well as the line number of the source line where the label or procedure/function is defined. By double clicking on any of these labels, the macro file is displayed in the macro source list window, and the source line containing the label definition is highlighted.
The Use File table is a list of all Use files referenced in Use statements by the macro file selected in the Call History list. If the labels for that Use file have been loaded (this happens the first time a procedure/function is called from the Use file), then the Loaded column shows True. Double clicking on any Use file in this list, causes that file to be loaded into the macro source list window.
The Product table is a list of all the applications/products that have commands in the macro file selected in the Call History list. Even if an Application statement for an application is in a macro, if the macro does not actually contain any commands for that application, then that application will not be displayed in this list. For each application/product listed, the version number of the PID file (product interface description file) that was used when this macro was compiled is displayed. This version number is used to determine if a compiled macro has become out of date when a new version of an application is installed, and the macro is played.
The Dialog list is a list of all user created macro dialogs that are currently defined or that exist in the prefix packet are of the macro file selected in the Call History list. For each dialog, the name, state, type, callback label, position/size and styles are displayed. There are 2 types of dialogs: Text dialogs, which are defined using DialogDefine, and DialogAdd... statements in a macro; and Binary dialogs, which are created by the Macro Dialog Editor, and are stored in the prefix packet area of a macro file. The states of a dialog are Defined (Text dialogs only - the dialog has been defined by a DialogDefine statement, but the dialog has not been loaded or shown yet), In Prefix (Binary dialogs only - the dialog was found in the prefix packet of the current macro file, but hasn't been loaded or shown yet), Loaded (Text and Binary dialogs - the dialog has been loaded by a DialogLoad statement, or by a Region... command), Showing (Text and Binary dialogs - the dialog is currently showing by a DialogShow statement). If the dialog is currently showing, and a callback label was specified, then the Callback label is displayed. Double clicking on this dialog will cause the macro file where the callback label is defined to be displayed in the macro source list window, and the source line containing the label definition will be highlighted. The position and size of the dialog when it was defined is show. This does not display the current position or size of the dialog. The styles associated with this dialog in the DialogDefine statement or in the Macro Dialog Editor are also shown. In the lower half of this window, the list of controls defined for the dialog selected in the upper half are displayed. For each control, its order, name, type, position/size, associated variable, styles and data is shown. As with the dialog position and size, the position and size of the control that is displayed is the position and size when the control was defined, not its current position and size.
The Condition Handlers list is a list of all condition handlers defined for the execution point selected in the Call History list. For each condition handler, an action and data is displayed. The action column displays whether the condition will cause the macro to abort or quit, or whether the condition will cause a label to be called to jumped to, and the appropriate label. If the handler has been disabled, then the action will say Ignore, indicating that the abort, call or jump will be ignored. If there is a label associated with a condition handler, then double clicking on the item will cause the macro file where the label is defined to be displayed in the macro source list window, and the source line containing the label definition will be highlighted. The standard condition handlers, such as Error, Cancel and NotFound are displayed in this list, as well as handlers for callbacks, such as OnDDEAdvise and callback dialogs.
The Macro info list is a list of all the data that can be obtained from the MacroInfo command in a macro for the execution point selected in the Call History list. Some of these items can have labels, line numbers or filenames associated with them, and if so, double clicking on these items will cause the macro file to be displayed in the macro source list window, and the source line containing the label definition or line number will be highlighted.
The Callback Queue list is a list of all pending items in the callback queue. The callback queue contains entries for callback dialogs, and also for OnDDEAdvise notifications. The label that will be called by this callback is specified. Double clicking on this line will cause the macro file to be displayed in the macro source list window, and the source line containing the label definition will be highlighted. The status column indicates whether this callback is for notification only, or whether the callback can affect the action performed by the macro system when the callback is complete. Dialog callbacks are always for notification only. The contents of the callback data array is also displayed, and an interpretation of the specific array elements is also displayed where possible.
The last window is the Macro Header window. This window displays the macro object header information for this macro file, including the version number of the macro system that this macro file was compiled by.
Execute Token
When the macro debugger is active (the macro has stopped at a breakpoint) and the macro is suspended, the debugger also allows any PerfectScript command (token) to be executed in a very localized temporary environment. This is done by picking the View/Execute Token menu item. This brings up a dialog similar to the macro command browser. This dialog allows any PerfectScript command to be selected, and then displays all parameters and their types, and allows a value to be specified for each parameter. Then by pressing the Execute button, that PerfectScript command is performed, and any return value is displayed. If a variable name was specified for the return value, then the return value will also be assigned to that variable. Some PerfectScript commands may cause the internal state of the running macro to be changed, and may cause errors to occur later in the macro execution, so caution should be taken with some commands. Most of these commands have been eliminated from the command list, and cannot be selected.
Debugging Techniques
Debugging usually requires a great amount of patience, but can sometimes reveal problems very quickly.
The main benefit of the macro debugger in assisting in the determination of problems in macros, is by providing access to information as a macro executes. The debugger provides information on the contents of variables, and on the flow of execution in a macro.
By setting the appropriate types of breakpoints in the appropriate places, and by examination of the information provided by the debugger, it is usually possible to narrow down, and determine the cause of macro problems.
By allowing the normal flow of execution to be modified when needed, the debugger can also allow some temporary corrections to be made to the current state of a macro to bring the actual state back in alignment with the expected state.
The actual techniques used to debug a macro largely depends on the type of failure that is occurring. To begin debugging a macro, first compile the macro, and any other macro file that it Uses or Runs or Chains to, for debug to include debugging information, and to generate a compiler listing file that the debugger can use.
One of the easiest problems to start with is when a macro terminates due to an error. To begin to determine the cause of this problem, play the macro as usual, and when it aborts, then press the Debug button on the error message box. This will bring up the macro debugger, and it will load the macro and the compiler listing file. The debugger will display in the macro source list the line of the macro file with the problem. It will display in the call history list the reverse ordered list of what labels and procedures/functions were called in which order to get to that point. The contents of all the macro variables will also be displayed.
At this point, examine the source line to determine what was being done. Examine the contents of the variables that are being used at the current line, to see if they contain the proper values. If some of the variables have contents that don't appear to be correct, then this could be the cause. If every thing appears to be proper, then select a previous line in the call history list, and examine the source line and the contents of the variables at that point. If an improper variable value has been located, then examine the source code that leads up to the problem area, and locate a spot where the variable could have been changed, and set a line number break point at that spot.
Now stop the debugger. The macro execute cannot be continued, since an error has occurred and the macro state can't continue. Restart the macro by selecting the file/debug/play menu item in PerfectScript (or set the Debug/"Invoke debugger on startup" item). This will cause the macro debugger to come up just as the macro is about to start. Since a line number breakpoint has been set, just select the Continue option to cause the macro to run until it encounters the line number containing the breakpoint. When the macro reaches the line with the breakpoint, the debugger will come up. Examine the variable contents to verify that all appears to be as expected. If not, then a breakpoint may have to specified at an earlier location, and the macro may have to be restarted, or execution can be skipped to the earlier location (depending on how much earlier it needs to be). If everything appears to be ok, then single step (execute 1 statement at a time), and examine the variables between each statement until something happens that was not expected. Perhaps the contents of a variable has changed unexpectedly, or a label was called that shouldn't have been.
If it appears that the problem is caused because a variable has the wrong contents, then it might be easier to set a "variable assign" breakpoint on that variable. When ever the contents of the variable are changed, then a breakpoint occurs and the debugger comes up. If the variable is changed too often, and too many breakpoints are occurring on the variable, then disable the variable assign breakpoint, and set a line number breakpoint at a point after most of the variable assignments are done, but before the wrong contents are suspected to be assigned.
If the problem in the macro appears to associated with calling a certain product token, then a product token call breakpoint could be set up for that product token, and/or for a related product token that may not be setting the application in the proper state.
Perhaps the problem is that a certain macro label or procedure/function is being called too often. Then set a label call breakpoint for that label to see when and where it is being called from.
Debugging Callbacks
Debugging problems with dialog callbacks is one of the most challenging types of debugging. Many problems with dialog callbacks occur because the callback doesn't look at the callback data close enough to determine if the exact conditions have occurred before performing some action, and so the action is performed at the wrong time, or too many times. A dialog callback label is called quite often, and most of the callbacks are usually not associated with the event of interest. As soon as a callback dialog is shown, at least 2 or 3 callback events occur. These events are associated with the dialog being created, the controls being initialized, and with the dialog receiving the initial input focus.
One of the difficulties associated with debugging callbacks is due to the fact that when the debugger becomes active at a breakpoint, it gains focus, and this means that whatever other window had focus before, just lost focus, including the callback dialog in the macro. When a callback dialog loses or gains focus, a callback event is generated. If a breakpoint is set in a dialog callback label, then when the debugger comes up, a lose focus callback event could occur on the callback dialog, and when execution in the debugger is continued, then a gain focus callback event could occur, which could cause the breakpoint in the debugger to occur (because the callback label is called for the lose/gain focus of the dialog), which would cause more lose/gain callback events, which could cause another breakpoint, which could cause more lose/gain focus callback events, etc. The macro interpreter and debugger attempt to minimize this cyclic occurrence, by determining if the dialog is losing or gaining focus because of the macro debugger, and if so, the focus callback events are not generated, but this cannot not always be reliably determined.
The best way to prevent this, is to write the dialog callback label so that it carefully examines the callback data array to determine the type of the callback event (examine element [5] of the callback array), and have it respond to only the callback event types it is interested in (usually the callback wants to respond to events associated with manipulating a control, which are WM_COMMAND events with element [5] = 273). Then set a breakpoint only on a line of code in this case, but not on one that responds to losing or gaining focus. Then no matter how often the dialog loses or gains focus, the breakpoint doesn't occur, and the debugger doesn't come up, which causes more focus lose/gain, etc.