7.6   Tcl: Debugger Automation

CrossWind exploits Tcl at two levels: like other Tornado tools, it uses Tcl to build the graphical interface, but it also includes a Tcl interpreter at the GDB command level. This section discusses using the Tcl interpreter inside the CrossWind enhanced GDB, at the command level.


*

NOTE: For information about using Tcl to customize the CrossWind GUI, see 7.7 Tcl: CrossWind Customization. The discussion in this section is mainly of interest when you need complex debugger macros; you might want to skip this section on first reading.

Tcl has two major advantages over the other GDB macro facility (the define command). First, Tcl provides control and looping (such as for, foreach, while, and case). Second, Tcl procedures can take parameters. Tcl, building on the command interface, extends the scripting facility of GDB to allow you to create new commands.

7.6.1   Tcl: A Simple Debugger Example

To submit commands to the Tcl interpreter within GDB from the command panel, use the tcl command. For example:

(gdb) tcl info tclversion

This command reports which version of Tcl is integrated with GDB. All the text passed as arguments to the tcl command (in this example, info tclversion) is provided to the Tcl interpreter exactly as typed. Convenience variables (described in Debugging with GDB: Convenience Variables) are not expanded by GDB. However, Tcl scripts can force GDB to evaluate their arguments; see 7.6.3 Tcl: Invoking GDB Facilities.

You can also define Tcl procedures from the GDB command line. The following example procedure, mld, calls the load command for each file in a list:

(gdb) tcl proc mload args {foreach obj $args {gdb load $obj}}

You can run the new procedure from the GDB command line; for example:

(gdb) tcl mload vxColor.o priTst.o

To avoid typing tcl every time, use the tclproc command to assign a new GDB command name to the Tcl procedure. For example:

(gdb) tclproc mld mload

This command creates a new GDB command, mld. Now, instead of typing tcl mload, you can run mld as follows:

(gdb) mld vxColor.o priTst.o

You can collect Tcl procedures in a file, and load them into the GDB Tcl interpreter with this command:

(gdb) tcl source tclFile

If you develop a collection of Tcl procedures that you want to make available automatically in all your debugging sessions, write them in the file .wind/gdb.tcl under your home directory. The GDB Tcl interpreter reads this file when it begins executing. (See 7.7.1 Tcl: Debugger Initialization Files for a discussion of how all the CrossWind and GDB initialization files interact.)

7.6.2   Tcl: Specialized GDB Commands

The CrossWind version of GDB includes four commands to help you use Tcl. The first two were discussed in the previous section. The commands are:

tcl command
Passes the remainder of the command line to the Tcl interpreter, without attempting to evaluate any of the text as a GDB command.

tclproc gdbName TclName
Creates a GDB command gdbName that corresponds to a Tcl procedure name TclName. GDB does not evaluate the arguments when gdbName is invoked; it passes them to the named Tcl procedure just as they were entered.


*

NOTE: To execute tclproc commands automatically when GDB begins executing, you can place them in .gdbinit directly (see GDB Initialization Files), because tclproc is a GDB command rather than a Tcl command. However, if you want to keep the tclproc definition together with supporting Tcl code, you can exploit the gdb Tcl extension described in 7.6.3 Tcl: Invoking GDB Facilities to call gdb tclproc in ~/.wind/gdb.tcl.

tcldebug
Toggles Tcl debugging mode. Helps debug Tcl scripts that use GDB facilities. When Tcl debugging is ON, all GDB commands or other GDB queries made by the Tcl interpreter are printed.

tclerror
Toggles Tcl verbose error printing, to help debug Tcl scripts. When verbose error mode is ON, the entire stack of error information maintained by the Tcl interpreter appears when a Tcl error occurs that is not caught. Otherwise, when verbose error mode is OFF, only the innermost error message is printed. For example:

(gdb) tcl puts stdout [expr $x+2] 
can't read "x": no such variable
(gdb) tclerror TCL verbose error reporting is ON.
(gdb) tcl puts stdout [expr $x+2] can't read "x": no such variable     while executing "expr $x..."     invoked from within "puts stdout [expr $x..."

Tcl also stores the error stack in a global variable, errorInfo. To see the error stack when Tcl verbose error mode is OFF, examine this variable as follows:

(gdb) tcl $errorInfo

For more information about error handling in Tcl, see B.2.9 Tcl Error Handling.

7.6.3   Tcl: Invoking GDB Facilities

You can access GDB facilities from Tcl scripts with the following Tcl extensions:

gdb arguments
Executes a GDB command (the converse of the GDB tcl command). Tcl evaluates the arguments, performing all applicable substitutions, then combines them (separated by spaces) into one string, which is passed to GDB's internal command interpreter for execution.

If the GDB command produces output, it is shown in the command panel.

If Tcl debugging is enabled (with tcldebug), the following message is printed:

execute: command 
If the GDB command causes an error, the Tcl procedure gdb signals a Tcl error, which causes unwinding if not caught (for information about unwinding, see B.2.9 Tcl Error Handling).

gdbEvalScalar exprlist
Evaluates a list of expressions exprlist and returns a list of single integer values (in hexadecimal), one for each element of exprlist.1 If an expression represents a scalar value (such as int, long, or char), that value is returned. If an expression represents a float or double, the fractional part is truncated. If an expression represents an aggregate type, such as a structure or array, the address of the indicated object is returned. Standard rules for Tcl argument evaluation apply.

If Tcl debugging is enabled, the following message is printed for each expression:

evaluate: expression 
If an expression does not evaluate to an object that can be cast to pointer type, an error message is printed, and gdbEvalScalar signals a Tcl error, which unwinds the Tcl stack if not caught (see B.2.9 Tcl Error Handling for information about unwinding).

gdbFileAddrInfo fileName
Returns a Tcl list with four elements: the first source line number of fileName that corresponds to generated object code, the last such line number, the lowest object-code address from fileName in the target, and the highest object-code address from fileName in the target. The argument fileName must be the source file (.c, not .o) corresponding to code loaded in the target and in the debugger.

For example:

(gdb) tcl gdbFileAddrInfo vxColor.c 
{239 1058 0x39e2d0 0x39fbfc}
gdbFileLineInfo fileName
Returns a Tcl list with as many elements as there are source lines of fileName that correspond to generated object code. Each element of the list is itself a list with three elements: the source-file line number, the beginning address of object code for that line, and the ending address of object code for that line. The argument fileName must be the source file (.c, not .o) of a file corresponding to code loaded in the target and in the debugger.

For example:

(gdb) tcl gdbFileLineInfo vxColor.c  
{239 0x39e2d0 0x39e2d4} {244 0x39e2d4 0x39e2ec} ...
gdbIORedirect inFile outFile [ taskId ]
Redirect target input to the file or device inFile, and target output and error streams to the file or device outFile. If taskId is specified, redirect input and output only for that task; otherwise, redirect global input and output. To leave either input or output unchanged, specify the corresponding argument as a dash (-). Ordinary pathnames indicate host files or devices; arguments with an @ prefix indicate target files or devices. For target files, you may specify either a path name or a numeric file descriptor.

For example, the following command redirects all target output (including stderr) to host device /dev/ttyp2:

(gdb) tcl gdbIORedirect - /dev/ttyp2
The following command redirects input from task 0x3b7c7c to host device /dev/ttyp2, and output from the same task to target file descriptor 13:

(gdb) tcl gdbIORedirect /dev/ttyp2 @13 0x3b7c7c
gdbIOClose
Close all file descriptors opened on the host by the most recent gdbIoRedirect call.

gdbLocalsTags
Returns a list of names of local symbols for the current stack frame.

gdbStackFrameTags
Returns a list of names of the routines currently on the stack.

gdbSymbol integer
Translates integer, interpreted as a target address, into an offset from the nearest target symbol. The display has the following format:

symbolName [ + Offset ]
Offset is a decimal integer. If Offset is zero, it is not printed. For example:

(gdb) tcl puts stdout [gdbSymbol 0x20000] 
floatInit+2276
If Tcl debugging is on, gdbSymbol prints the following message:

symbol: value 
gdbSymbolExists symbolName
Returns 1 if the specified symbol exists in any loaded symbol table, or 0 if not. You can use this command to test for the presence of a symbol without generating error messages from GDB if the symbol does not exist. This procedure cannot signal a Tcl error.

When Tcl debugging is on, gdbSymbolExists prints a message like the following:

symbol exists: symbolName 

7.6.4   Tcl: A Linked-List Traversal Macro

This section shows a Tcl procedure to traverse a linked list, printing information about each node.2 The example is tailored to a list where each node has the following structure:

struct node 
    { 
    int data; 
    struct node *next; 
    }

A common method of list traversal in C is a for loop like the following:

for (pNode = pHead; pNode; pNode = pNode->next) 
...

We imitate this code in Tcl, with the important difference that all Tcl data is in strings, not pointers.

The argument to the Tcl procedure will be an expression (called head in our procedure) representing the first node of the list.

Use gdbEvalScalar to convert the GDB expression for a pointer into a Tcl string:

set pNode [gdbEvalScalar "$head"]

To get the pointer to the next element in the list:

set pNode [gdbEvalScalar "( (struct node *) $pNode)->next"]

Putting these lines together into a Tcl for loop, the procedure (in a form suitable for a Tcl script file) looks like the following:

proc traverse head {  
    for {set pNode [gdbEvalScalar "$head"] }    \ 
        {$pNode}   \ 
        {set pNode [gdbEvalScalar "( (struct node *)$pNode)->next"]} \ 
            {puts stdout $pNode}  
}

In the body of the loop, the Tcl command puts prints the address of the node.

To type the procedure directly into the command panel would require prefacing the text above with the tcl command, and would require additional backslashes (one at the end of every line).

If pList is a variable of type (struct *node), you can execute:

(gdb) tcl traverse pList

The procedure displays the address of each node in the list. For a list with two elements, the output would look something like the following:

0xffeb00 
0xffea2c

It might be more useful to redefine the procedure body to print out the integer member data, instead. For example, replace the last line with the following:

{puts stdout [format "data = %d" \ 
    [gdbEvalScalar "((struct node *) $pNode)->data"]]}

You can bind a new GDB command to this Tcl procedure by using tclproc (typically, in the same Tcl script file as the procedure definition):

tclproc traverse traverse

The traverse command can be abbreviated, like any GDB command. With these definitions, you can type the following command:

(gdb) trav pList

The output now exhibits the contents of each node in the list:

data = 1 
data = 2

1:  A more restricted form of this command, called gdbEvalAddress, can only evaluate a single expression (constructed by concatenating all its arguments). gdbEvalAddress is only supported to provide compatibility with Tcl debugger extensions written for an older debugger, VxGDB. Use the more general gdbEvalScalar in new Tcl extensions.

2:  Remember, though, that for interactive exploration of a list the structure browser (Figure 7-12) described in CrossWind Buttons is probably more convenient.