10.5. Debugging a program

The use of an interpreter engine enables some unique approaches to the process of debugging and testing software. This section describes some of the tools and techniques for debugging an application.

10.5.1. Interacting with an Active Program

Gamma can provide an active view-port into the running application. Another task (or shell) can, at any time, interact with a running Gamma program, without halting it or otherwise disturbing its real-time response. This provides an approach to debugging that is much more powerful than adding debug print statements.

For example, suppose that we started a process with the name "my_task" interactively:

Gamma> init_ipc ("my_task");
t
		  

The gsend (for Gamma) or lsend (for Lisp) utility is used to interact with a running application from a shell:

[sh]$ gsend my_task
my_task>
		

The lsend utility accepts Lisp input as the default and gsend accepts Gamma input as its default.

Once connected to a running Gamma program using gsend/lsend, the developer can:

    query/set variables/objects/instance_vars in the global scope

    call functions/methods

    re-define function definitions

    run any Gamma command interactively

The syntax for starting the gsend utility is as follows:

gsend [-l] [-g] [program] [pid]

-l

Accept Lisp input from the keyboard.

-g

Accept Gamma input from the keyboard.

program

a Gamma program name, attached by name_attach, init_ipc, or qnx_name_attach

pid

(QNX 4 only) a task ID

gsend and lsend attach to a running Gamma program and allow the user to send commands without exiting the event loop of the attached process. Any statement may be issued, including changing the definitions of existing functions. In our simple example we can call the princ function for my_task to execute:

[sh]$ gsend my_task
my_task> princ ("Hello!\n");
t
		  

Notice that event processing stops for the duration of the command. Now let's look at my_task. In order to respond to requests from gsend/lsend, my_task must be executing an event loop. We can start one using the next_event function in a while statement:

Gamma> init_ipc ("my_task");
t
Gamma> while(t) next_event();
Hello!
		

The my_task program continues to run as normal during this operation.

This presents an excellent opportunity for rapid development by programmers. Typically developers are used to the [quot ] code, compile, link, run, debug, code...[quot ] iterative approach to programming. Once a Gamma developer makes a program with a well written event loop, such as the one shown in the section below on trapping errors, programming and testing can become operations that happen in parallel.

Programmers will find that after a piece of code has been written, it can be uploaded to an already running Gamma process with gsend/lsend by using a simple [quot ]cut and paste[quot ]. The code is automatically assimilated into Gamma and ready to run. Better yet, if there is a problem with the code, the programmer receives immediate feedback and can track the problem down through an interactive debugging prompt that can be built right into the event-loop.

10.5.2. Trapping and Reporting Errors

Gamma provides a pair of functions referred to as the try/catch mechanism, that is very important for debugging. Consider the following simple event loop:

while (t)
{
    next_event();
}
	

This will run until the program exits or an event triggers some code that produces an error condition. There is no protection against errors. Now consider the following setup:

while (t)
{
    try
    {
        next_event();
    }
    catch
    {
        princ("error occurred\n");
    }
}
	  

This setup of try/catch will try to evaluate the block of code contained in the [quot ]try[quot ] portion and jumps to the [quot ]catch[quot ] portion when an error occurs. A more effective example of the catch code block is:

while (t)
{
    try
    {
        next_event();
    }
    catch
    {
        princ("internal error: ", _last_error_,
            " calling stack is: ",stack(),"\n");
    }
}
	

This setup provides the developer with information about the last error and the calling stack which led to the last error. Tutorial II provides an example which illustrates the try/catch and protect/unwind mechanisms to get reports on an error.

Another setup that the developer may find useful is to automatically start an interactive session in the case of an error. The example of such a setting can be found in Tutorial II.

10.5.3.  Determining Error Location

The stack function will show the current function calling stack, expressed as a list of functions that the interpreter is currently evaluating. To trace the execution path of parts of a program it is useful to print out the code as it is evaluated. The trace and notrace functions act as delimiters to areas when tracing should occur. The tracing information is delivered to standard output.

The following table of predefined global variables provides additional information useful for debugging:

Table 10.1. Global Variables in Gamma

Global VariableDescription
_error_stack_The stack at the time the last error occured.
_unwind_stack_The stack at the time that an error was discovered.
_last_error_A string containing the last error.

10.5.4. Filtering Object Query Output

Gamma permits the user to control the level of detail reported, and the format used, when an object is queried. This is done by defining a function named _ivar_filter with two arguments. For example, each class instance has a number of instance variables that are reported during interactive mode in the format:

Gamma> stats = qnx_osinfo(0);
{Osinfo (bootsrc . 72) (cpu . 586) (cpu_speed . 18883) (fpu . 587) 
      (freememk . 16328) (machine . "PCI") (max_nodes . 7) (nodename . 2)
      (num_handlers . 64) (num_names . 100) (num_procs . 500) 
      (num_sessions . 64) (num_timers . 125) (pidmask . 511) (release . 71)
      (reserve64k . 0) (sflags . 28675) (tick_size . 9999) (timesel . 177)
      (totmemk . 32384) (version . 423)}
	  

The following example provides a function named _ivar_filter that controls the output format. Note that each instance variable consists of a name and a value. If we define the following:

function _ivar_filter (!instance,!value)
{
    princ(format("\n%-20s %-20s", string(car(value)), string(cdr(value))));
    nil;
}
	  

then the output for Gamma in interactive mode now looks like:

Gamma> stats;
{Osinfo 
      bootsrc         72				  
      cpu             586				 
      cpu_speed       18883			   
      fpu             587				 
      freememk        15544			   
      machine         PCI				 
      max_nodes       7				   
      nodename        2				   
      num_handlers    64				  
      num_names       100		         
      num_procs       500                 
      num_sessions    64                  
      num_timers      125                 
      pidmask         511                 
      release         71                  
      reserve64k      0                   
      sflags          28675               
      tick_size       9999                
      timesel         177                 
      totmemk         32384               
      version         423
      }