This chapter describes how to examine and change data in your program while running it under dbx. Topics in this chapter include:
Many dbx commands accept one or more expressions as arguments. Expressions can consist of constants, dbx variables, program variables, and operators. This section discusses operators and constants. “Creating and Removing dbx Variables” in Chapter 4, describes dbx variables, and “Displaying and Changing Program Variables”, describes program variables.
In general, dbx recognizes most expression operators from C, Fortran 77, and Pascal. dbx also provides some of its own operators. Operators follow the C language precedence. You can also use parentheses to explicitly determine the order of evaluation.
The following list describes the operators provided by dbx.
not: unary operator returning false if the operand is true.
or: binary logical operator returning true if either operand is nonzero.
xor: binary operator returning the exclusive-OR of its operands.
/: binary division operator (// also works for division).
div: binary operator that coerces its operands to integers before dividing.
mod: binary operator returning op1 modulo op2. This is equivalent to the C % operator
#exp: unary operator returning the address of source line specified by exp.
file#exp: unary operator returning the address of source line specified by exp in the file specified by file.
proc #exp: unary operator returning the address of source line specified by exp in the file containing the procedure proc.
The # operator takes the line number specified by the expression that follows it and returns the address of that source line. If you precede the # operator with a filename enclosed in quotation marks, the # operator returns the address of the line number in the file you specify. If you precede the # operator with the name of a procedure, dbx identifies the source file that contains the procedure and returns the address of the line number in that file.
A pound sign (#) introduces a comment in a dbx script file. When dbx sees a pound sign in a script file, it interprets all characters between the pound sign and the end of the current line as a comment. See “Executing dbx Scripts” in Chapter 4, for more information on dbx script files. To include the # operator in a dbx script, use two pound signs (for example, ##27).
To print the address of line 27 in the current source file, enter:
(dbx) print #27 |
To print the address of line 27 in the source file foo.c (assuming that foo.c contains source that was used to compile the current object file), enter:
(dbx) print "foo.c" #27 |
To print the address of line 27 in the source file containing the procedure bar (assuming that bar is a function in the current object file), enter:
(dbx) print bar #27 |
The following list shows the C language operators recognized by dbx:
Unary: ! & + - * sizeof()
Binary: % << >> == <= >= != < > & && | || + - * / [ ] -> .
![]() | Note: C does not allow the use of the sizeof operator on bit fields. However, dbx allows you to enter expressions using the sizeof operator on bit fields; in these cases, dbx returns the number of bytes in the data type of bit fields (such as int or unsigned int). The C language exclusive-OR (^) operator is not supported. Use the dbx xor operator instead. |
The following list describes the Pascal operators recognized by dbx:
Unary: not ^ + -
Binary: mod = <= >= <> < > and or + - * / div [ ]
The following list describes the FORTRAN 77 and Fortran 90 language operators recognized by dbx. Note that dbx does not recognize Fortran logical operators (such as .or. and .TRUE.).
Unary: + -
Binary: + - * / %
Fortran array subscripts may be in either square brackets, [ ], or the standard parenthesis, ( ), and the Fortran 90 member selection operator (%) is allowed.
You can use both numeric and string constants under dbx. Expressions cannot contain constants defined by #define declarations to the C preprocessor.
Numeric constants: in numeric expressions, you can use any valid integer or floating point constants. By default, dbx assumes that numeric constants are in decimal. You can set the default input base to octal by setting the dbx $octin variable to a nonzero value. You can set the default input base to hexadecimal by setting $hexin to a nonzero value. If you set both $octin and $hexin to nonzero values, $hexin takes precedence.
You can override the default input type by prefixing 0x to indicate a hexadecimal constant, or 0t to indicate a decimal constant. For example, 0t23 is decimal 23 and 0x2A is hexadecimal 2A.
By default, dbx prints the value of numeric expressions in decimal. You can set the default output base to octal by setting the $octints variable to a nonzero value. You can set the default output base to hexadecimal by setting the dbx $hexints variable to a nonzero value. If you set both $octints and $hexints to nonzero values, $hexints takes precedence.
String constants: most dbx expressions cannot include string constants. The print and printf commands are two of the dbx commands that accept string constants as arguments. You can also use the set command to assign a string value to a dbx variable.
Otherwise, string constants are useful only as arguments to functions that you call interactively. See “Using Interactive Function Calls”, for information on interactive function calls.
You can use either the double-quotation mark (“) or the single-forward quotation mark (') to quote strings in dbx. In general, dbx recognizes the following escape sequences in quoted strings (following the standard C language usage):
\\ \n \r \f \b \t \' \" \a |
Enclosing a character string in back quotation marks ( `) indicates that the whole string is the name of a program element, not a character-string constant. This is useful, for example, when referring to C++ templates, which include in their names the greater-than (>) and less-than (<) characters. Without back quotation marks, dbx would attempt to interpret the characters as operators. For further discussion, see “Qualifying Names of Program Elements”, and “Referring to C++ Functions” in Chapter 6.
dbx provides the following commands for printing values of expressions:
print [exp1,[exp2, ...]]: prints the value(s) of the specified expression(s).
printd [exp1 ,[exp2], ...]: prints the value(s) of the specified expression(s) in decimal. pd is an alias for printd. See “Creating and Removing dbx Variables” in Chapter 4, for more information about dbx aliases.
printo [exp1 ,[exp2], ... ]: prints the value(s) of the specified expression(s) in octal. po is an alias for printo.
printx [exp1 ,[exp2], ...]: prints the value(s) of the specified expression(s) in hexadecimal. px is an alias for printx.
For displaying information about variables, the duel command is a flexible alternative to the print command; see “Using the High-Level Debugging Language duel”.
The variable types are as follows:
Type | Variable Name | Value |
---|---|---|
Signed char | sc | 0xff |
Unsigned char | usc | 0xff |
Signed short | ssh | 0xffff |
Unsigned short | ush | 0xffff |
Example 5-2. Printing expressions
Examples include:
(dbx) pd sc -1 (dbx) pd ssh -1 (dbx) px sc 0xff (dbx) px ssh 0xffff (dbx) pd usc 255 (dbx) pd ush 65535 |
dbx always prints the bits in the appropriate type. pd is an exception; it expands signed types with sign extension so the decimal value looks correct.
Another example:
(dbx) print sc, usc '\377' '\377' |
If the dbx $hexchars variable is set, this command displays 0xff 0xff. (This is a change from releases previous to IRIX 5.2. Previously, the px, po cases on signed char expanded to 32 bits, so px sc printed 0xffffffff.)
If the printed data type is pointer, dbx uses the format specified by the $addrfmt or $addrfmt64 predefined dbx variable ($addrfmt64 is used on only 64-bit processes).
The following is the syntax of the printf command:
printf string ,[exp1 ,[exp2] ...] |
This command prints the value(s) of the specified expression(s) in the format specified by the string, string. The printf command supports all formats of the IRIX printf command except %s. For a list of formats, see the printf(3S) man page.
Values printed by the print command as well as values returned by the ccall command can be saved so they can be displayed later or used in other expressions.
Use the following command to enable this feature:
% set $printhistory=0 |
The value variables are created with names starting with $, followed by a number and displayed after each print and ccall command. These values can be later referred to by using the generated name. The last value can also be referred to simply as $.
(dbx) set $valuehistory=1 (dbx) print foof() $1 = 9.9899997711181641 (dbx) print $1/4567.98987 $2 = 0.0021869575142289366 (dbx) print $ $3 = 0.0021869575142289366 |
These values are kept until a givenfile command is used. They are then discarded.
The set command can be used to print the complete list of value history, in addition to the dbx variables.
You can use data types for type conversion (also known as casting) by including the name of the data type in parentheses before the expression you want to cast. For example, to convert a character into an integer, use (int) to cast the value as shown in the following example:
(dbx) print (int) 'b' 98 |
To convert an integer into a character, use (char) to cast the value as shown in the next example:
(dbx) print (char) 67 'C' |
This is standard C language type casting.
You can use the same name for different program elements, such as variables, functions, statement labels, several times in a program. This is convenient and, during program execution, the potential ambiguity presents no problem. For example, you can use a temporary counter named i in many different functions. The scope of each variable is local; space is allocated for it when the function is called and freed when the function returns. However, in dbx you sometimes need to distinguish occurrences of identical names.
dbx allows unambiguous reference to all program elements by using source file and routine names as qualifying information that makes otherwise indistinguishable names unique. To find the fully qualified name of the active version of a name, use the which command. To find the fully qualified names of all versions of a name, use the whereis command. Note that if a variable, such as i, is used many times, whereis can generate many lines of output.
The fully qualified name of a program element allows you not only to refer within a procedure to variables of the same name with different scopes, but to refer unambiguously to program elements outside your current frame or activation stack.
dbx qualifies names with the file (also called module), the procedure, a block, or a structure. You can manually specify the full scope of a variable by separating scopes with periods. For example:
mrx.main.i |
In this expression, i is the variable name, main is a procedure in which it appears, and mrx is the source file (omitting the file extension) in which the procedure is defined.
For languages without modules, such as C, C++, and Fortran, the base name of the source file, that is the file name up to the first dot in the name, is taken as a module name. For example, if b is a Fortran subroutine in t.f, then t.b names the routine.
To illustrate how names are qualified, consider a C program called test that contains a function compare. In this example, the variable i is declared in both the main procedure and the compare function:
int compare ( int ); main( argc, argv ) int argc; char **argv; { int i; ... } int compare ( arg1, arg2 ) { int i; ... } |
To trace the value of the i variable that appears in the function compare, enter:
(dbx) trace test.compare.i |
To print the value of the i that appears in the procedure main, enter:
(dbx) print test.main.i |
It is possible to have variable scopes in C and C++ that are in unnamed program blocks. dbx provides names for these scopes, starting with __$$blk1 and followed by __$$blk2, __$$blk3, etc., which it places in the fully qualified name of the variable as it would an explicit scope name. The whereis and which commands show the names assigned. dbx provides a special name __aout for your base executable. So for example, you can use __aout.main to refer to a global C function main in your executable. You can, of course, also refer to the function using the name of your executable; if your executable is named myaout, myaout.main also refers to the global C function main.
If you wish to refer to a variable that occurs in a DSO, dbx uses a naming convention similar to that for variables in your executable. If, for example, strcpy is a function from the file stuff.c in the library libc.so.1, then both libc.stuff.strcpy and libc.strcpy refer to the function strcpy.
In C, struct, union, and enum tags can conflict with other names. From the context, dbx usually interprets correctly a reference to one of these tags. However, if dbx does not, prefix the tag with the marker __$T_ to prevent confusion with variables or functions. For example; use __$T_foo if you wish to refer to:
struct foo { /* ... */ } |
In ANSI C, statement label names also can conflict with other names. The ambiguity is removed by applying a prefix of __$L_ to the label name. Thus, for example:
int myfoo { int x; x: goto x; ++x} |
If you enter the following, the output is the address of the variable x:
(dbx) print &x |
If you enter the following, the output is the address of label x. The -32 compiler provides no debugging information on C labels.:
(dbx) print &__$L_x |
To refer to Fortran statement labels you must either use the __$L_ prefix or use back quotation marks to force dbx to recognize a numerical label as a name. For example, if you have the following:
do 10 i = 1,10 10 continue |
Both of the following commands lists the address of the label:
(dbx) print &`10` (dbx) print &__$L_10 |
You may have multiple source files with the same name, for example myfile.c, that are in different directories. The module name myfile may refer to either source file. dbx resolves this ambiguity by prefixing the fully qualified file names with a unique, numeric label. The which and whereis commands show the label used. For example, suppose the main executable has two myfile.c source files, then __aout.myfile refers to either source file, __aout._$1_myfile refers to one of them, and __aout._$2_myfile refers to the other.
A leading dot (a period at the beginning of the identifier) tells dbx that the first qualifier is not a module (file).
The leading dot is useful when a file and a procedure have the same name. For instance, suppose mrx.c contains a function called mrx. Further, suppose that mrx.c contains a global variable called mi and a local variable, also called mi. To refer to the global variable, use the qualified form .mrx.mi, and to refer to the local variable, use the qualified form mrx.mrx.mi.
You can use the value of program variables in dbx expressions. You can also change the value of program variables while running your program under dbx control.
You can access the value of a variable only while it is in scope. The variable is in scope only if the block or procedure with which it is associated is active.
After you start your program, whenever your program executes a block or procedure that contains variables, your program allocates space for those variables and they “come into scope.” You may access the values of those variables as long as the block or procedure is active. Once the block or procedure ends, the space for those variables is deallocated and you may no longer access their values.
You can display the value of a program variable by using the printd, printf, printo, and printx commands and the pd, po, and px aliases described in “Printing Expressions”.
Example 5-5. Displaying Variable Values
To print the value of the program variable total, enter the following:
(dbx) print total 235 |
The print command also displays arrays, structures, and other complex data structures. For example, if message is a character array (a string), dbx prints the string:
(dbx) print message "Press <Return> to continue." |
As a more complex example, consider a simple linked list stored as an array of elements, each element consisting of a pointer to the next element and an integer value. If the array is named list, print the entire array by entering:
(dbx) print array |
dbx prints the value of each element in the array:
{ [0] struct list { next = (nil) value = 1034 } [1] struct list { next = 0x10012258 value = 1031 } [2] struct list { next = 0x10012270 value = 1028 } [3] struct list { next = 0x10012288 value = 1025 } [4] struct list { next = 0x100122a0 value = 1022 } [5] struct list { next = 0x100122b8 value = 1019 } ... } |
To print an individual element, enter a command such as:
(dbx) print array[5] struct list { next = 0x100122b8 value = 1019 } |
If your array consists of simple elements such as integers or floating point values, you can directly examine the contents of memory using the / (examine forward) command described in “Examining Memory and Disassembling Code” in Chapter 7.
Suppose a single-precision floating point array is named float_vals. To see the six consecutive elements beginning with the fifth element, enter:
(dbx) &float_vals[4] / 6f 10012018: 0.25000000000000000 0.20000000298023224 0.16666699945926666 0.14280000329017639 10012028: 0.12500000000000000 0.11111100018024445 |
You can also print parts of arrays and complex structures with duel, a high-level debugging language. For more information, see “Using the High-Level Debugging Language duel”.
The assign command changes the value of existing program variables. You can also use the assign command to change the value of machine registers, as described in “Changing Register Values” in Chapter 7.
The following is the syntax of the assign command:
assign variable=expression |
This command assigns the value of expression to the program variable.
Example 5-7. Using casts to change variable values
If you receive an incompatible types error when you try to assign a value to a pointer, use casts to make the assignment work. For example, if next is a pointer to a structure of type element, you can assign next a null pointer by entering:
(dbx) assign *(int *) (&next) = 0 0 (dbx) assign next = 0 (nil) (dbx) assign next = (struct list*) 0; (nil) |
In this example, nil denotes that the value of the pointer is 0; nil is similar to NULL in the C language.
When naming variables in your program, avoid using any dbx keywords. If you have a variable with the same name as a dbx keyword and you attempt to use that variable in a dbx command, dbx reports a syntax error.
If you do have a program variable with the same name as a dbx command, you can force dbx to treat it as a variable by enclosing the variable in parentheses.
dbx keywords include:
all not and or at pgrp div pid if sizeof in to mod xor |
Example 5-8. Variable name and keyword conflicts
For example, if you try to print the value of a variable named in by entering the following command, dbx displays an error.
(dbx) print in print in ^ syntax error Suggestion: in is a dbx keyword; a revised command is in history. Type !16 or !! to execute: print (in) |
The correct way to display the value of in is to enter the following command:
(dbx) print (in) 34 |
Whether dbx is case sensitive when it evaluates program variable names depends on the value of the dbx $casesense variable.
If $casesense is 2 (the default), then the language in which the variable was defined is taken into account (for example, C and C++ are case sensitive while Pascal and Fortran are not). If $casesense is 1, case is always checked. If $casesense is 0, case is always ignored. Note that file (module) names are always case sensitive since they represent UNIX file names.
You can control the values of environment variables used by a program without affecting the shell. The dbx commands printenv, setenv, and unsetenv control the debugging environment much like their csh counterparts control the shell environment. However, they only affect your program while it is being debugged. dbx passes the values shown by the printenv command to the shell as the environment to use while your program is run by the run or rerun commands.
The following commands control your program's environment variables:
printenv: prints the list of environment variables affecting the program being debugged.
setenv: same as printenv.
setenv var: sets the environment variable var to an empty value.
setenv var value: sets the environment variable var to value, where value is not a dbx variable.
setenv var $var: sets the environment variable var to $var, where $var is a dbx variable.
setenv var “charstring”: sets the environment variable var to "charstring".
unsetenv var: removes the specified environment variable.
If you attempt to change the PAGER or EDITOR environment variables, the effect on dbx is undefined; the new values may, or may not, affect how dbx runs.
The duel language is a high-level debugging language that you can invoke from dbx. duel is an acronym for Debugging U (might) Even Like.
The duel language does not control processes; it provides the following commands for printing data such as parts of arrays and complex structures. The following is the syntax of this command:
duel [alias] [clear] |
The duel command invokes the debugging language. alias shows all current duel aliases. clear deletes all duel aliases.
To invoke duel from within dbx, type:
(dbx) duel |
To print the array elements x[1] to x[10] that are greater than 5, enter:
(dbx) duel x[1..10] >? 5 x[3] = 14 x[8] = 6 |
The output includes the values 14 and 6, as well as their symbolic representation x[3] and x[8].
The duel language is implemented by adding the duel command to dbx. All dbx commands work as before. The duel command, however, is interpreted by duel, and its concepts are not understood by other dbx commands.
![]() | Note: This version of duel does not allow interactive function calls. |
duel is based on expressions that return multiple values. The x..y operator returns the integers from x to y; the x,y operator returns x and then y.
Example 5-10. duel and multiple values
(dbx) duel (1,9,12..15,22) |
This command prints 1, 9, 12, 13, 14, 15, and 22.
You can use such expressions wherever a single value is used. For example:
(dbx) duel x[1,9,12..15,22] |
This command prints elements 1, 9, 12, 13, 14, 15, and 22 of the array x. duel incorporates C operators, and casts C statements as expressions.
The semicolon (;) prevents duel output. duel aliases are defined with x:=y and provide an alternative to variable declaration. You can also return x[i] instead of using printf:
(dbx) duel if(x[i:=0..99]<0) x[i] x[i] = -4 |
Example 5-11. duel and symbolic output
The symbolic output x[i] can be fixed by surrounding i with {}. For example:
(dbx) duel if(x[i:=0..99]<0) x[{i}] x[7] = -4 |
The braces ({}) are like parentheses (), but force the symbolic evaluation to use i's value, instead of i. You can usually avoid this altogether with direct duel operators:
(dbx) duel x[..100] <? 0 x[7] = -4 |
The ..n operator is a shorthand for 0..n-1. For example, ..100 is the same as 0..99. The operators x<?y, x==?y, x>=?y compare their left side operand to their right side operand as in C, but return the left side value if the comparison result is true. Otherwise, they look for the next values to compare, without returning anything.
duel's x.y and x->y allow an expression y, evaluated under x's scope:
(dbx) duel emp[..100].(if(code>400) (code,name)) emp[46].code = 682 emp[46].name = “Ela” |
The if() expression is evaluated under the scope of each element of emp[], an array of structures. In C language terms, we have to write:
for(i = 0; i < 100; i++ ) { if(emp[1].code > 400) { printf(“%d %s\n”,emp[i].cond,emp[i].name); |
Example 5-12. duel and loop alternatives
A useful alternative to loops is the x=>y operator. It returns y for each value of x, setting the underbar (_) to reference x's value. For example:
(dbx) ..100 => if(emp[_].code>400) emp[_].code,emp[_].name |
Using _ instead of i also avoids the need for {i}. Finally, the x-->y operator expands lists and other data structures. If head points to a linked list threaded through the next field, then:
(dbx) duel head-->next->data head->data = 12 head->next->data = 14 head-->next[[2]]->data = 20 head-->next[[3]]->data = 26 |
This produces the data field for each node in the list. x-->y returns x, x->y, x->y->y, x->y->y->y, ... until a NULL is found. The symbolic output x-->y[[n]] indicates that ->y was applied n times. x[[y]] is also the selection operator:
(dbx) duel head-->next[[50..60]]->data |
This example returns the 50th through the 60th elements in the list. The #/x operator counts the number of values. For example:
(dbx) duel #/( head-->next->data >? 50 ) |
This example counts the number of data elements over 50 on the list. Several other operators, including x@y, x#y, and active call stack access are described in the “duel Operators”.
Most duel operators have the same precedence as their C counterparts. Table 5-2, lists duel operators in decreasing precedence.
Table 5-2. duel Operator Summary
Associativity | Operators | Details |
---|---|---|
left | {} () [] -> . f() --> | x-->y expands x->y x->y->y ... |
| x[[y]] x#y x@y | Generate x; select, index, or stop at y |
right | #/ - * & ! ~ ++ -- (cast) | #/x number of x values |
| frame(n) sizeof(x) | Reference to call stack activation level n |
| = | Simple assignment |
left | x/y x*y x%y | Multiply, divide, and reminder |
left | x-y x+y | Add and subtract |
left | x<<y x>>y | Shift left and shift right |
none | x..y ..y x.. | ..y 0..y-1. x..y Return x, x+1...y |
left | < > <= >= <? >? <=? >=? | x>?y Return x if x>y |
left | == != ==? !=? | x==?y Return x if x=y |
left | x&y | Bit-and |
left | x^y | Bit-xor |
left | x|y | Bit-or |
left | x&&y &&/x | &&/x Are all x values nonzero? |
left | x||y ||/x | ||/x Is any x value nonzero? |
right | x? y:z | For each x, if(x) y else z |
right | x:=y | x:=y set x as a duel alias to y |
left | x,y | Return x, then y |
right | x=>y | For each x, evaluate y with x value `_' |
right | if() else while() for() | C statements cast as operators |
left | x;y | Evaluate and ignore x, return y |
right | ,, | Fortran multidimensional array separator: x[7,,5]. Use square brackets; x(7,,5) will not work in duel. |
Table 5-3, lists and briefly explains duel examples.
Example | Explanation |
---|---|
duel (0xff-0x12)*3 | Compute simple expression |
duel (1..10)*(1..10) | Display multiplication table |
duel x[10..20,22,24,40..60] | Display x[i] for the selected indexes |
duel x[9..0] | Display x[i] backwards |
duel x[..100] >? 5 | Display x[i] that are greater than 5 |
duel x[..100] >? 5 <? 10 | Display x[i] if 5<x[i]<10 |
duel x[..100] ==? (6..9) | Same as above |
duel x[0..99]=>if(_>5 && _<10) _ | Same as above |
duel y[x[..100] !=? 0] | Dsplay y[x[i]] for each nonzero x[i] |
duel emp[..50].code | Display emp[i].code for i=0 to 49 |
duel emp[..50].(code,name) | Display emp[i].code & emp[i].name |
duel val[..50].(is_dbl? x:y) | Display val[i].x or val[i].y depending on val[i].is_dbl. |
duel val[..50].if(is_dbl) x else y | Same as above |
duel (hash[..1024]!=?0)->scope | hash[i].scope for non-null hash[i] |
duel x[i:=..100] >? x[i+1] | Check if x[i] is not sorted |
duel x[i:=..100] ==? x[j:=..100]=> if(i<j) x[{i,j}] | Check if x has nonunique elements |
duel if(x[i:=..99] == x[j:=i+1..99]) x[{i,j}] | Same as above |
duel (x[..100] >? 0)[[0]] | Return the first (0th element) positive x[i] |
duel (x[..100] >? 0)[[2]] | Return the third positive x[i] |
duel (x[..100] >? 0)[[..5]] | Return the first five positive x[i] |
duel (x[0..] >? 6)[[0]] | Return the first x[i]>6, no limit on i |
duel argv[0..]@0 | argv[0] argv[1].. until first null |
duel x[0..]@20 >? 9 | x[0..n]>9 where n is first x[n]==20 |
duel emp[0..]@(code==0) | emp[0]..emp[n-1] where emp[n].code==0 |
duel head-->next->val | Return val of each element in a linked list |
duel head-->next[[20]] | Return the twenty-first element of a linked list |
duel *head-->next[[20]] | Display above as a struct |
duel #/head-->next | Count elements on a linked list |
duel x-->y[[#/x-->y - 1]] | Reutrn last element of a linked list |
duel x-->y[[#/x-->y - 10..1]] | Return last ten elements of a linked list |
duel head-->next-> if(next) val >? next->val | Check if the list is sorted by val |
duel head-->(next!=?head) | Expand cyclic linked list (tail->head) |
duel head-->(next!=?_) | Handle termination with p->next==p |
duel root-->(left,right)->key | Expand binary tree, and show keys |
duel root-->(left,right)->( (left!=?0)->key>=?key, (right !=?0 )->key<=?key) | Check bin tree as sorted by key |
duel (T mytype) x | Convert x to user defined type mytype |
duel (struct s*) x | Convert x to struct s pointer |
duel if(x) y; else z *ERR* | `;' must be followed by an expression |
duel {x} y *ERR* | `}' requires `;' if followed by exp |
fortarray[2..5,, 6,7] | Print two-dimensional Fortran array elements |
The duel semantics are modeled after the Icon programming language. The input consists of expressions that return sequences of values. C statements are cast as expressions, too. Expressions are parsed into abstract syntax trees, which are traversed during evaluation. The evaluation of most nodes (operators) recursively evaluates the next value for each operand, and then applies the operator to produce the next result. Only one value is produced each time, and duel's eval function keeps a state record for each node (backtracking, co-routines, consumer-producer or threads are good metaphors for the evaluation mechanism).
For example, in (5,3)+6..8, the evaluation of + first retrieves the operands 5 and 6, to compute and return 5+6. Then 7, the next right operand is retrieved and 5+7 is returned, followed by 5+8. Since no other right operand value exists, the next left operand, 3 is fetched. The right operand's computation is restarted returning 6, and 3+6 is returned. The final return values are 3+7 and 3+8.
The computation for operators like x>?y is similar, but when x<=y, the next values are fetched instead of returning a value, forming the basis for an implicit search. Operators like .. return a sequence of values for each pair of operands.
The duel values follow the C semantics. A value is either an lvalue (can be used as the left-hand side of assignment), or an rvalue. Therefor, objects like arrays can not be directly manipulated. However, operators like x..y can accomplish such tasks.
The duel types also follow the C semantics, with some important differences. C types are checked statically; duel types are checked when operators are applied. For example, (1,1.0)/2 returns 0 (int) and 0.5 (double); (x,y).z returns x.z and y.z even if x and y are of different types, as long as they both have a field z.
Values and types of symbols are looked up at run-time (using the dbx lookup rules).
To avoid this ambiguity, the keyword T must precede a user-defined type. For example, if value is a typedef, C's (value (*)()) x is written in duel as (T value (*)()) x. Types that begin with a reserved keyword don't need T. For example, (struct value*) x and (long *[5]) y are accepted. As special cases, (type)x and (type*)x are accepted but discouraged (it causes (printf)(“hi”), which is valid in C, to fail). A side effect is that sizeof x must be written as sizeof(x).
The duel operators are described in the following list:
x=y x+y x-y x*y x/y x%y x^y x|y x&y x<<y x>>y
The following list describes the differences between duel and the C, and Fortran languages.
Differences from C: both {} and ; are operators, not statements or expression separators. For example, if(x) y; else {z;} u is illegal; use if(x) y else {z} ; u. Ambiguities require preceding user-defined types (typedef) with the keyword T.
For example, if value is a user type, C's sizeof(value*) is written sizeof(T value*), except for the casts (t)x and (t*)x; sizeof(x) requires parenthesis for variable x.
Differences from Fortran: because the comma (,) is used to separate a sequence of values, the usual dbx syntax for multi-dimensional array references of myarr[3,4] does not mean the same thing to duel as it does to dbx.
In duel, refer to the dimensions of a multi-dimensional Fortran array using ,, as the dimension separator. In other words, if myarr is a two-dimensional array, myarr[3,,4] refers to the Fortran array element myarr(3,4).
The base dbx syntax for this element remains unchanged. For example, to show that element of myarr, use one of the following:
(dbx) print myarr[3,4] (dbx) duel myarr[3,,4] |
The which command allows you to determine the scope of a variable. This command is useful for programs that have multiple variables with the same name occurring in different scopes.
The which command prints the fully qualified name of the active version of a specified variable. For example, to determine the scope of the variable i, enter:
(dbx) which i .foo.foo2.i |
In this example, the variable i that is currently active is local to the procedure foo2 that appears in the module foo (corresponding to the file foo.c in a C language program).
The which command also determines the fully qualified name of other program elements, such as procedures or type descriptors, that are submitted as arguments for the command. The whereis command prints the fully qualified names of all versions of the name of any program element. dbx searches (a possibly limited part of) your program for all occurrences of the name and returns the fully qualified names. The range of the search is determined by the dbx $whereisdsolimit variable. By default, $whereisdsolimit is 1 and only the main executable is checked by whereis. To search all objects, set $whereisdsolimit to 0. To check just the first n objects, set $whereisdsolimit to n.
The whatis command displays the type declaration for a specified variable or procedure in your program.
To display the type declaration for the variable i, enter:
(dbx) whatis i int i; |
The following example illustrates the output of whatis for an array of structures:
(dbx) whatis array struct list { struct list* next; int value; } array[12]; |
When you provide a procedure name to whatis, dbx reports the type of the value returned by the procedure and the types of all arguments to the procedure:
(dbx) whatis foo int foo(i) int i; (dbx) whatis main int main(argc, argv) int argc; char** argv; |
Each time your program executes a procedure, the information about where in the program the call was made from is saved on a stack. The stack also contains arguments to the procedure and all of the procedure's local variables. Each procedure on the stack defines an frame. Activation levels can also consist of blocks that define local variables within procedures.
The most recently called procedure or block is numbered 0. The next active procedure (the one that called the current procedure) is numbered 1. The last activation level is always the main program block.
The stack determines the scope of many dbx commands and expressions. For example, unless you qualify a variable, as described in “Qualifying Names of Program Elements”, dbx assumes that variables you reference are local to the current activation level. If a variable does not appear in the current activation level, dbx successively examines previous activation levels in the stack until it finds the referenced variable. The maximum number of activation levels examined is determined by the dbx $stacktracelimit variable, which has a default value of 100.
The where command prints stack traces. Stack traces show the current activation levels (procedures) of a program.
Consider the following stack trace for a program called test:
(dbx) where > 0 foo2(i = 5) [“/usr/var/tmp/dbx_examples/test.c”:44, 0x1000109c] 1 foo(i = 4) [“/usr/var/tmp/dbx_examples/test.c”:38, 0x1000105c] 2 main(argc = 1, argv = 0xffffffad78) [“/usr/var/tmp/dbx_examples/test.c”:55, 0x10001104] 3 __start() [“/shamu/lib/libc/libc_64/crt1text.s”:137, 0x10000ee4] |
This program has four activation levels. The most recent, a call of the procedure foo2, is numbered 0. The currently selected activation level is 0, indicated by the > character.
The stack trace also reports that foo2 was passed one argument: the value 5 was assigned to the local variable i. The trace indicates that the program was stopped at line 44 of the file test.c, which translates to machine address 0x1000109c.
The stack trace reports similar information for the next two activation levels in this example. You can see that the function foo called foo2 from line 38 in test.c. In turn, foo was called by main at line 55 of the file test.c. Finally, the run-time start-up level was called at line 137 from the file ctrltext.s.
If a program is highly recursive, stack traces can get quite long. The dbx $stacktracelimit variable controls the maximum number of activation levels that appear in a stack trace. In the example above, setting $stacktracelimit = 2 before issuing the where command reduces the set of reported frames to just levels 0 and 1.
Example 5-16. Stack trace and -g compiler option
If you compile with -g0 or with no -g option, limited symbols are reported. In cases such as this, where detailed symbolic information is not available, the four hexadecimal values returned represent dbx's guess that the function has four integer arguments.
The following example illustrates such a case:
(dbx) where > 0 fooexample(0x300000000, 0x4000000ff, 0x5000000ff, 0x0) [“/usr/var/tmp/dbx_examples/test3.c”:10, 0x10000cf8] 1 main(0x3, 0x4, 0x5, 0x0) [“/usr/var/tmp/dbx_examples/test3.c”:5, 0x10000cbc] 2 __start() [“/shamu/lib/libc/libc_64/csu/crt1text.s”:137, 0x10000c64] (dbx) quit Process 22582 terminated int fooexample(int,int,int); int main() { fooexample(3,4,5); return 0; } int fooexample(int i, int j, int k) { int x = i + j + 3*k; return x; } |
The following examples show register values from code compiled without a -g option. MIPS1 or MIPS2 code using the 32-bit ABI (for example, on an Indy workstation):
(dbx) where > 0 subr1(0x3, 0x7fffaf14, 0x7fffaf1c, 0x0) [“t.c”:3, 0x4009ec] 1 test(0x3, 0x7fffaf14, 0x7fffaf1c, 0x0) [“t.c”:8, 0x400a10] 2 main(0x1, 0x7fffaf14, 0x7fffaf1c, 0x0) [“t.c”:13, 0x400a48] 3 __start() [“crt1text.s”:133, 0x40099c] |
There are four hexadecimal values displayed in most lines of the code above since the 32-bit MIPS ABI has four integer argument passing registers. No user-useful registers are passed to __start().
MIPS3 or MIPS4 code using the 64-bit ABI (for example, on a Power Challenge system):
(dbx) where > 0 subr1(0x3, 0xffffffaed8, 0xffffffaee8, 0x0, 0x2f, 0x10, 0x0, 0xfbd82a0) [“/usr/people/doc/debug/t.c”:3, 0x10000c9c] 1 test(0x3, 0xffffffaed8, 0xffffffaee8, 0x0, 0x2f, 0x10, 0x0, 0xfbd82a0) [“/usr/people/doc/debug/t.c”:9, 0x10000ce8] 2 main(0x1000000ff, 0xffffffaed8, 0xffffffaee8, 0x0, 0x2f, 0x10, 0x0, 0xfbd82a0) [“/usr/people/doc/debug/t.c”:14, 0x10000d2c] 3 __start() [“/shamu/redwood2/work/irix/lib/libc/libc_64/csu/crt1text.s”:137, 0x10000c70] |
There are eight hexadecimal values displayed in most lines of the code above since the 64-bit MIPS ABI has eight integer argument passing registers. No user-useful registers are passed to __start().
The values listed as arguments are the integer argument-passing register values. Typically, only the 0 entry of the stack has those argument values correct. Correctness is not guaranteed because the code generator can overwrite the values, using the registers as temporary variables.
The debugger reports the integer argument-passing registers because this information may be of some value.
For example, for the code samples above, the following code calls subr1():
int test(void) { subr1(3); } |
This code displays 0x3 as the argument register value. The other registers listed for subr1 contain arbitrary data.
The up and down commands move up and down the activation levels in the stack. These commands are useful when examining a call from one level to another. You can also move up and down the activation stack with the func command described in “Moving to a Specified Procedure”.
The up and down commands each take num as an argument. up [num] moves up the specified number of activation levels in the stack. The default is one level. down [num] moves down the specified number of activation levels in the stack. The default is one level.
When you change activation levels, your scope changes. For example, unless you qualify a variable, as described in “Qualifying Names of Program Elements”, dbx assumes that variables you reference are local to the current activation level. Also, dbx changes the current source file to the file containing the procedure's source.
Consider examining the stack trace for a program called test4 and moving up in the activation stack:
(dbx) where > 0 foo2(i = 5) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 1 foo(i = 4) [“/usr/var/tmp/dbx_examples/foo.c”:40, 0x100011d4] 2 main(argc = 1, argv = 0xffffffad78) [“/usr/var/tmp/dbx_examples/test4.c”:25, 0x10000fa0] 3 __start() [“/shamu/lib/libc/libc_64/csu/crt1text.s”:137, 0x10000f34] (dbx) print i 5 (dbx) up foo: 40 r = foo2(i+1); |
The current activation level is now the procedure foo. As indicated in the output, the variable i receives the argument passed to foo and is therefore local to foo. The variable i at this activation level is different from the variable i in the foo2 activation level. You can reference the currently active i as i; whereas you must qualify the reference to the i in foo2:
(dbx) print i 4 (dbx) print foo2.i <symbol not found> |
Moving up one more activation level brings you to the main procedure:
(dbx) up main: 25 j = foo(j); (dbx) file /usr/var/tmp/dbx_examples/test4.c |
In this example, the source for main is in test4.c, whereas the source for foo and foo2 is in foo.c; therefore, dbx changes the current source file when you move up to the main activation level.
dbx resets the source file when you return to the foo2 activation level:
(dbx) down 2 foo2: 46 printf(“foo2 arg is %d\n”,i); (dbx) file /usr/var/tmp/dbx_examples/foo.c |
The func command moves you up or down the activation stack. You can specify the new activation level by providing either a procedure name or an activation level number.
The syntax for the func command is:
func [activation_level][procedure] |
The following arguments are available:
func (with no arguments): displays the name of the procedure corresponding to the current activation level.
activation_level|procedure: changes the current activation level. If you specify an activation_level by number, dbx changes to that activation level. If you specify a procedure, dbx changes to the activation level of that procedure. If you specify a procedure name and that procedure has called itself recursively, dbx changes to the most recently called instance of that procedure.
When you change your activation level, your scope changes. For example, unless you qualify a variable as described in “Qualifying Names of Program Elements”, dbx assumes that variables you reference are local to the current activation level. Also, dbx changes the current source file to the one containing the procedure's source and the current line to the first line of the procedure.
You can also give the func command the name of a procedure that is not on the activation stack, even when your program is not executing. In this case, dbx has no corresponding activation level to make current. However, dbx still changes the current source file to the one containing the procedure's source and the current line to the first line of the procedure.
For example, consider the following activation stack:
(dbx) where > 0 foo2(i = 5) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 1 foo(i = 4) [“/usr/var/tmp/dbx_examples/foo.c”:40, 0x100011d4] 2 main(argc = 1, argv = 0xffffffad78) [“/usr/var/tmp/dbx_examples/test4.c”:25, 0x10000fa0] 3 __start() [“/shamu/lib/libc/libc_64/csu/crt1text.s”:137, 0x10000f34] |
In this case, you can go to the main activation stack by entering:
(dbx) func main main: 25 j = foo(j); |
This command changes the current activation level to 2 and changes the current source file to test4.c.
If you use the func command to go to a function that is not on the activation stack, dbx changes only the current source file to the one containing the procedure's source and the current line to the first line of the procedure:
(dbx) func bar 3 { (dbx) file /usr/var/tmp/dbx_examples/bar.c |
The dump command prints information about the variables in an activation level. The following is the syntax for this command:
dump [procedure] [.] |
The following arguments are available:
dump (with no arguments): prints information about the variables in the current procedure.
procedure: prints information about the variables in the specified procedure. The procedure must be active. Starts searching for procedure at the current activation level as set by the up or down command. (See “Moving within the Stack”, for more information about the up and down commands.)
. : prints information about the variables in all procedures in all activation levels.
Executing dump while in a function called foo2 appears as:
(dbx) dump foo2(i = 5) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] |
To examine the information for the procedure main, enter:
(dbx) dump main main(argc = 1, argv = 0xffffffad78) [“/usr/var/tmp/dbx_examples/test4.c”:25, 0x10000fa0] j = 4 i = 12 r = <expression or syntax error> a = 0 total = 0 |
To perform a complete dump of the program's active variables, enter:
(dbx) dump . > 0 foo2(i = 5) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 1 foo(i = 4) [“/usr/var/tmp/dbx_examples/foo.c”:40, 0x100011d4] r = 0 2 main(argc = 1, argv = 0xffffffad78) [“/usr/var/tmp/dbx_examples/test4.c”:25, 0x10000fa0] j = 4 i = 12 r = <bad operand> a = 0 total = 0 |
You can interactively call a function in your program from dbx.
If the function returns a value, you can use that function in a normal dbx expression. For example, consider a function prime defined in your program that accepts an integer value as an argument, and returns 1 if the value is prime and 0 if it is not. You can call this function interactively and print the results by entering a command such as:
(dbx) print prime(7) 1 |
If your function does not return a value, or if you want to execute a function primarily for its side effects, you can execute the function interactively with the dbx command ccall. The following is the syntax for this command:
ccall func [arg1, arg2,... ,argn] |
This command calls a function with the given arguments. Regardless of the language the function was written in, the call is interpreted as if it were written in C, and normal C calling conventions are used.
![]() | Note: Structure and union arguments to a function, and structure and union returns from a function, are not supported. |
Functions called interactively honor breakpoints. Thus you can debug a function by setting breakpoints and then calling it interactively.
Example 5-19. Activation levels and stack trace
If you perform a stack trace using the where command while stopped in a routine executed interactively, dbx displays only those activation levels created by your interactive function call. The activation levels for your active program are effectively invisible.
For example, a stack trace looks like this during an interactive function call:
(dbx) where > 0 foo2(i = 9) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 1 foo(i = 8) [“/usr/var/tmp/dbx_examples/foo.c”:40, 0x100011d4] ===== interactive function call ===== 2 foo2(i = 5) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 3 foo(i = 4) [“/usr/var/tmp/dbx_examples/foo.c”:40, 0x100011d4] 4 main(argc = 1, argv = 0xffffffad78) [“/usr/var/tmp/dbx_examples/test4.c”:25, 0x10000fa0] 5 __start() [“/shamu/lib/libc/libc_64/csu/crt1text.s”:137, 0x10000f34] |
If you stop execution of an interactively called function, you are responsible for eventually unstacking the call and returning from the function call. To unstack a call, you can complete the call using dbx commands such as cont, resume, next, or step as many times as necessary. If you run or rerun your program, dbx automatically unstacks all interactive function calls.
Another way to unstack an interactive function call is to execute the clearcalls command, which clears all stopped interactive calls.
(dbx) clearcalls |
When stopped or faulted within one or more nested interactive calls, the clearcalls command removes these calls from the stack and returns the program to its regular callstack. This command is useful when a segmentation fault, infinite loop, or other fatal error is encountered within the interactive call.
When stopped in an interactive call, the call stack displayed by where shows the following line at the end of each stack of interactive call instantiation.
==== interactive function call ==== |
Example 5-20. Use of clearcalls
If the procedure foo() is interactively called from main(), you see the following stack:
> 0 foo2(i = 9) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 1 foo(i = 8) [“/usr/var/tmp/dbx_examples/foo.c”:40, 0x100011d4] ===== interactive function call ===== 2 foo2(i = 5) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 3 foo(i = 4) [“/usr/var/tmp/dbx_examples/foo.c”:40, 0x100011d4] 4 main(argc = 1, argv = 0xffffffad78) [“/usr/var/tmp/dbx_examples/test4.c”:25, 0x10000fa0] 5 __start() [“/shamu/lib/libc/libc_64/csu/crt1text.s”:137, 0x10000f34] |
You can also nest interactive function calls. In other words, if you have one or more breakpoints in a function, and you call that function repeatedly, each interactive call is stacked on top of the previous call. Breakpoints in a function affect all nesting levels, so you cannot have different breakpoints at different nesting levels.
The where command shows the entire stack trace from which you can determine the nesting depth. The following example has two nesting levels.
(dbx) where > 0 foo2(i = 17) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 1 foo(i = 16) [“/usr/var/tmp/src/dbx_examples/foo.c”:40, 0x100011d4] ===== interactive function call ===== 2 foo2(i = 9) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 3 foo(i = 8) [“/usr/var/tmp/dbx_examples/foo.c”:40, 0x100011d4] ===== interactive function call ===== 4 foo2(i = 5) [“/usr/var/tmp/dbx_examples/foo.c”:46, 0x10001214] 5 foo(i = 4) [“/usr/var/tmp/dbx_examples/foo.c”:40, 0x100011d4] 6 main(argc = 1, argv = 0xffffffad78) [“/usr/var/tmp/src/dbx_examples/test4.c”:25, 0x10000fa0] 7 __start() [“/shamu/lib/libc/libc_64/csu/crt1text.s”:137, 0x10000f34] |
To set a conditional breakpoint, for example, type:
(dbx) stop in foo if j == 7 Process 0: [3] stop in foo if j==7 |
If j is not within the scope of foo, then you will receive an error message if you attempt to call foo interactively. To prevent this, disable or delete any such breakpoints, conditional commands, or traces before executing the interactive function call.
dbx permits interactive control of a pixie-instrumented binary.
pixie clear clears the basic block counts for the current execution. pixie write writes out the counts file with the current basic block counts. The counts reflect the execution of the program since the run command or since the last pixie clear command, whichever was more recent.
When you debug a program that has been instrumented by pixie, it is often desirable to perform experiments over different code paths and do comparisons of the results. You can do this by capturing the pixie basic block counts at any point in the program's execution.
Example 5-22. Basic block counts
Suppose you want to determine the basic block counts for the section of code between lines 10 and 15 of a given file. Just set breakpoints at the two lines of interest, zero the counts when the first breakpoint is encountered, and then write out the counts file when the second breakpoint is encountered.
(dbx) stop at “pix.c”:15 Process 0: [3] stop at “pix.c”:15 (dbx) stop at “pix.c”:20 Process 0: [4] stop at “pix.c”:20 (dbx) run Process 997 (pix.pixie) started [3] Process 997 (pix.pixie) stopped at [main:15 ,0x400a48 (pixie 0x404570)] 15 first = 12; (dbx) pixie clear (dbx)cont [4] Process 997 (pix.pixie) stopped at [main:20 ,0x400aa8 (pixie 0x404684)] 20 total = multiply(total, 2); (dbx) pixie write (dbx) sh prof -pixie prog -------------------------------------------------------------------------- Profile listing generated Tue Feb 14 11:08:46 1995 with: prof -pixie prog -------------------------------------------------------------------------- Total cycles Total Time Instructions Cycles/inst Clock Target 53 5.3e-07s 27 1.963 100.0MHz R4000 10: Total number of Load Instructions executed. 40: Total number of bytes loaded by the program. 3: Total number of Store Instructions executed. 12: Total number of bytes stored by the program. 2: Total number nops executed in branch delay slot. 0: Total number conditional branches executed. 0: Total number conditional branches actually taken. 0: Total number conditional branch likely executed. 0: Total number conditional branch likely actually taken. 18: Total cycles waiting for current instr to finish. 26: Total cycles lost to satisfy scheduling constraints. 5: Total cycles lost waiting for operands be available. *---------------------------------------------------------------------* -p[rocedures] using basic-block counts. * Sorted in descending order by the number of cycles executed in each * procedure. Unexecuted procedures are not listed. * *----------------------------------------------------------------------* cycles(%) cum % secs instrns calls procedure(file) 27(50.94) 50.94 0.00 19 1 main(prog:prog.c) 18(33.96) 84.91 0.00 4 1 multiply(prog:prog.c) 8(15.09) 100.00 0.00 4 2 add(prog:prog.c) |
The above example uses the sh command to invoke prof directly from dbx.
For more information about the prof and pixie commands, refer to the prof(1) and pixie(1) man pages.
Debugging a program written in C++ is somewhat different from debugging programs written in other languages. This section describes features that affect how you access variables. See also “Referring to C++ Functions” in Chapter 6.
Typically you use standard C++ syntax to access member variables of objects. For example, if the string _name is a member variable of the object myWindow, you can print its value by entering:
(dbx) print myWindow._name 0x1001dc1c = “MenuWindow” |
To display a static member variable for a C++ class, you must specify the variable with the class qualifier. For example, to print the value of the static member variable costPerShare of the class CoOp, enter:
(dbx) print CoOp::costPerShare 25.0 |