Debugging a program

Gamma: Version 4.0
Prev | Chapter 10. Interactive Development and Debugging | Next

# 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 " code, compile, link, run, debug, code..." 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 "cut and paste". 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 "try" portion and jumps to the "catch" 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 Variable | Description
--- | ---
`_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
          }

Prev | Home | Next
Using the Debug Prompt | Up | Advanced Types and Mechanisms

Copyright 1995-2002 by Cogent Real-Time Systems, Inc.