2. Debugging Your GemStone Smalltalk Code

Previous chapter

Next chapter

Topaz can maintain up to eight simultaneous GemStone Smalltalk call stacks that provide information about the GemStone state of execution. Each call stack consists of a linked list of method or block contexts. Topaz provides debugging commands that enable you to:

This chapter introduces you to the Topaz debugging commands and provides some examples. For a detailed description of each of these commands, see Chapter 3.

2.1  Step Points and Breakpoints

For the purpose of determining exactly where a step will go during debugging, a GemStone Smalltalk method can be decomposed into step points. The locations of step points also determine where breakpoints can be set, although not all step points are legal for breakpoints.

Generally, step points correspond to the message selector and, within the method, message-sends, assignments, and returns of nonatomic objects. Compiler optimizations, however, may occasionally result in a different, nonintuitive step point, particularly in a loop.

The Topaz list steps method: command lists the source code of a given instance method and displays all step points (allowable breakpoints) in that source code.

For example:

Example 2.1 listing step points in a method

topaz 1> set class Dictionary
topaz 1> list steps method: removeKey:ifAbsent:
   removeKey: aKey ifAbsent: aBlock
 * ^^                               1,2                               *******
   "Removes the Association with key equal to aKey from the receiver and returns
   the value of that Association.  If no Association is present with key
   equal to aKey, evaluates the zero-argument block aBlock and returns the
   result of that evaluation."
   
   | anAssoc |
   anAssoc:= self removeKey: aKey otherwise: nil .
 *        ^4      ^3                                                  *******
   anAssoc == nil ifTrue:[ 
 *         ^5                                                         *******
     aBlock == nil ifTrue:[^ nil ].
 *          ^6             ^7                                         *******
     ^aBlock value
 *   ^9      ^8                                                       *******
     ].
   ^ anAssoc value
 * ^11       ^10                                                      *******
 

As shown here, the position of each method step point is marked with a caret (^) and a number.

If you use the Topaz step command to step through this method, the first step stops execution at the beginning of the method. The next step takes you to the point where removeKey:otherwise: is about to be sent to self. Stepping again would execute that message-send and halt execution at the point where anAssoc is about to be assigned. Another step would cause that assignment to be happen, and then halt execution just before the message == is sent to anAssoc.

The call stack becomes active, and the debugging commands become accessible, when you execute GemStone Smalltalk code containing a breakpoint (as well as when you encounter an error).

You can use the break command to set a method breakpoint at a particular step point within a method.

While you can set a breakpoint on any method, methods with optimized selectors, such as Boolean>>ifTrue: never hit the break points unless you invoke them with perform: or one of the GciPerform... functions, because sends of special selectors are optimized by the compiler. Note that in the step points listed above for Dictionary >> removeKey:ifAbsent:, above, there are no step points on ifTrue:.

Breakpoints

You can use the break command to establish method breakpoints within your GemStone Smalltalk code:

break aClassName >> aSelector [@ stepNumber]
break aClassName class >> aSelector [@ stepNumber]

For example:

topaz 1> break GsFile class >> openRead: @ 2

Establishes a breakpoint at step point 2 of the class method openRead: for GsFile. There are a number of ways to specify the specific class and method: see BREAK for details.

The break list command allows you to see all breakpoints set

topaz 1> break list
1: GsFile >> nextLine @ 1
2: GsFile class >> openRead: @ 2
3: String >> < @ 2

In the break list result, each breakpoint is identified by a break index. To disable a breakpoint, supply that break index as the single argument to the break disable command:

topaz 1> break disable 2
topaz 1> break list
1: GsFile >> nextLine @ 1
2: GsFile class >> openRead: @ -2 (disabled)
3: String >> < @ 2

A similar command line reenables the break point:

topaz 1> break enable 2

To delete a single breakpoint, supply that break index as the argument to the break delete command:

topaz 1> break delete 2

To delete all currently set breakpoints, type the following command:

topaz 1> break delete all

2.2  Examining the GemStone Smalltalk Call Stack

When you execute the code on which you have enabled a breakpoint, execution pauses. For example, if we put a breakpoint on the setter method for Animal’s instance variable #name:

topaz 1 > break Animal >> name: 

Then run this code:

topaz 1 > run
Animal new name: 'Dog'
%
a Breakpoint occurred (error 6005), Method breakpoint encountered.
1 1 Animal >> name:                               @1 line 1

You can display all of the contexts in the active call stack by issuing the where, stk or stack commands with no arguments. The where and stk command display a summary call stack, with one line for each context. Use the stack command to display method arguments and temporaries. When using the stack command, the volume of output displayed is controlled by the current level setting.

This is an example of the where summary:

topaz 1> where
==> 1 Animal >> name:                               @1 line 1
2 Executed Code                                 @3 line 1
3 UndefinedObject (GsNMethod class) >> _gsReturnToC @1 line 1
 

With display oops active, the where command provides more detail for each frame:

topaz 1> display oops
topaz 1> where
==> 1 Animal >> name:                               @1 line 1 [methId 25534209]
2 Executed Code                                 @3 line 1   [methId 25504513]
3 UndefinedObject (GsNMethod class) >> _gsReturnToC @1 line 1 [methId 4912641]
  [GsProcess 27551489]
 

Using the stack command provides additional information about the instance and temporary variable names and values for each context. With level 0 (the default), only the variable values themselves are displayed. This example is with display oops.

topaz 1> stack
==> 1 Animal >> name:                               @1 line 1 [methId 25534209]
    receiver [25517313 sz:3 cls: 27556097 Animal] a Animal
      name                [20 sz:0 cls: 76289 UndefinedObject] nil
      favoriteFood        [20 sz:0 cls: 76289 UndefinedObject] nil
      habitat             [20 sz:0 cls: 76289 UndefinedObject] nil
    newValue [25481729 sz:3 cls: 74753 String] Dog
2 Executed Code                                 @3 line 1   [methId 25504513]
    receiver [20 sz:0 cls: 76289 UndefinedObject] nil
3 UndefinedObject (GsNMethod class) >> _gsReturnToC @1 line 1 [methId 4912641]
    receiver [20 sz:0 cls: 76289 UndefinedObject] nil
 

With 1, or higher s, the variables for each instance variable is included in the display for stack. For example, with omit oops:

topaz 1> omit oops
topaz 1> level 1
topaz 1> stack
==> 1 Animal >> name:                               @1 line 1
    receiver a Animal
      name                nil
      favoriteFood        nil
      habitat             nil
    newValue Dog
2 Executed Code                                 @3 line 1
    receiver nil
3 UndefinedObject (GsNMethod class) >> _gsReturnToC @1 line 1
    receiver nil

The display of each context includes:

The display is governed by the setting of other Topaz commands such as limit, level, and display or omit.

Proceeding After a Breakpoint

When GemStone Smalltalk encounters a breakpoint during normal execution, Topaz halts and waits for your reply. Topaz provides commands for continuing execution, and for stepping into and over message-sends.

continue
Tells GemStone Smalltalk to continue execution from the context at the top of the stack, if possible. If execution halts because of an authorization error, for example, then the virtual machine can’t continue. As an option, the continue command can replace the value on the top of the stack with another object before it attempts to continue execution.

c
Same as continue.

step over
Tells GemStone Smalltalk to advance execution to the next step point (message-send, assignment, etc.) in the active context or its caller, and halt. The active context is indicated by the ==> in the stack; it is the context specified by the last frame, up, down or another command. Initially it is the top of the stack (the first context in the list).

step into
Tells GemStone Smalltalk to advance execution to the next step point (message-send, assignment, etc.) and halt. If the current step point is a message-send, then execution will halt at the first step point within the method invoked by that message-send.

Notice how this differs from step over; if the next message in the context contains step points itself, execution halts at the first of those step points. That is, the virtual machine “steps into” the new method instead of silently executing that method’s instructions and halting after the method has completed. The next step over command will then take place within the context of the new method.

Examining and Modifying Temporaries and Arguments

The Topaz temporary command lets you examine or modify the values of temporaries in the active context. If, for example, the method under inspection had a temporary variable named count, that currently had a value of 5, you could obtain its value by typing temporary and the variable name:

topaz 1> temporary count5

Similarly, you can use the temporary command to assign a new value to a temporary variable:

topaz 1> temporary count 8

For example, the following code sets a breakpoint, executes code, views and updates the value of a temporary variable, then continues execution to return the results of the code; which has been changed during debugging.

topaz 1> break classmethod String withAll:
topaz 1> run
String withAll: 'abc'
%
a Breakpoint occurred (error 6005), Method breakpoint encountered.
1 String class >> withAll:        								@1 line 1
topaz 1> stack
==> 1 String class >> withAll:                      @1 line 1
    receiver String
    aString abc
2 Executed Code                                 @2 line 1
    receiver nil
3 UndefinedObject (GsNMethod class) >> _gsReturnToC @1 line 1
    receiver nil
topaz 1> temporary
    aString abc
topaz 1> temporary aString 'xyz'
topaz 1> stack
==> 1 String class >> withAll:                      @1 line 1
    receiver String
    aString xyz
2 Executed Code                                 @2 line 1
    receiver nil
3 UndefinedObject (GsNMethod class) >> _gsReturnToC @1 line 1
    receiver nil
topaz 1> continue
xyz

Select a Context for Examination and Debugging

The Topaz commands frame, up, and down, as well as stack up, stack down, and stack scope, let you redefine the active context (used by the temporary, stack, and list commands) within the current call stack. Consider the call stack we examined earlier, with level 0 and omit oops:

topaz 1> stack
==> 1 Animal >> name:                               @1 line 1
    receiver anAnimal
    newValue Dog
2 Executed Code                                 @3 line 1
    receiver nil
3 UndefinedObject (GsNMethod class) >> _gsReturnToC @1 line 1
    receiver nil

The active context is indicated by ==>. You can also show the active context by using the frame command with no arguments:

topaz 1> frame1 Animal >> name:                               @1 line 1
    receiver anAnimal
    newValue Dog

The following command selects the caller of this context as the new active context:

topaz 1> frame 22 Executed Code                                 @3 line 1
    receiver nil

Now confirm that Topaz redefined the active context:

topaz 1> where
1 Animal >> name:                               @1 line 1
==> 2 Executed Code                                 @3 line 1
3 UndefinedObject (GsNMethod class) >> _gsReturnToC @1 line 1

You can also use up and down commands to make a different frame the active context.

Multiple Call Stacks

By default, when you continue executing code and encounter another breakpoint, the original call stack is lost.

The Topaz command stack save lets you retain the previous stack. This needs to be invoked for each stack you want to save.

The Topaz command stack all lets you display your list of saved call stacks. This display includes the top context of every call stack:

topaz 1> stack all 0:  1 Animal >> habitat                   @1 line 1
 1:  1 AbstractException >> _signalWith:   @6 line 25
*2:  1 Executed Code                       @3 line 1

The asterisk (*) indicates the active call stack, if one exists. If there are no saved stacks, a message to that effect is displayed.

When you type the stack change command, Topaz sets the active call stack to the call stack indicated by the integer in the stack all command output, and displays the newly selected call stack:

topaz 1> stack change 1
Stack 1 , GsProcess 27447553
1 AbstractException >> _signalWith:        @6 line 25

2.3  Debugging in a different Gem

Normally, you will debug problems that you encounter within your running topaz session. However, you can also connect to another Gem (the executing session) from another topaz session (the debugging session), to debug a problem in the executing session. This is particulalry useful when the executing session is executing via a script.

To do this, the executing Gem (the Gem with the problem that you wish to debug) must be configured to start a separate thread in the VM that listens for the command to connect. This can be done in two ways.

Listening for debug: DEBUGGEM pid random32BitDebugToken

In order to debug, you must also have the OOP of the instance of GsProcess that is executing and encounteres an error or breakpoint in the executing session. This is printed when you have display oops set, and enter a where or stk command. When setting up the executing session, include these commands:

display oops
iferr 1 where

Then, in the debugging session, make the calls to connect, specifying both the PID of the executing Gem session, and the token.

topaz> DEBUGGEM pid random32BitDebugToken
successful attach
topaz 1> 

If the connection is successful, the debugging session will have debugging control over the executing session.

When you are done debugging, use the LOGOUT command (or CONTINUE) in the debugging session will close that session and allow the executing session to continue.

For example, say you have a shell script with the following contents:

topaz -il -C 'GEM_LISTEN_FOR_DEBUG=TRUE' << EOF
set user DataCurator password swordfish gemstone gs64stone
login
display oops
iferr 1 where
iferr 2 topazwaitfordebug
exec { 'foo' } at: 3  %
logout
exit
EOF

When this shell script is executed, the executing topaz session will encounter the index-out-of-bounds error, print the error information, and wait for the debugging session to attach.

In the executing session:

<login details>
Listening for debug: DEBUGGEM 21213 10219491105650816486 
successful login
topaz 1> display oops
topaz 1> iferr 1 where
topaz 1> iferr 2 topazwaitfordebug
topaz 1> exec { 'foo' } at: 3  %                                
ERROR 2003 , a OffsetError occurred (error 2003), reason:objErrBadOffsetIncomplete, max:1 actual:3
topaz > exec iferr 1 : where 
==> 1 OffsetError (AbstractException) >> _signalWith: @5 line 25
2 OffsetError (AbstractException) >> signal     @2 line 47
3 Array (Object) >> _error:args:                @15 line 11
4 Array (Object) >> _errorIndexOutOfRange:      @2 line 6
5 Array >> at:                                  @4 line 12
6 Executed Code                                 @2 line 1
7 GsNMethod class >> _gsReturnToC               @1 line 11
  [GsProcess 42086145]
topaz > exec iferr 2 : topazwaitfordebug 
11/05/2020 16:45:37.423 PST
 Waiting for debugger to attach, topaz process 21213 gem process 21213

To attach to this, you will need to start another topaz, execute the debuggem command, followed by stack set. The information needed for these commands is in bold in the above output from the executing session.

Startup topaz and attach to the executing session:

topaz> debuggem 21213 10219491105650816486
successful attach
topaz 1> stack set @42086145

At this point you can get the stack trace and examine variables.

topaz 1> where
==> 1 OffsetError (AbstractException) >> _signalWith: @5 line 25
2 OffsetError (AbstractException) >> signal     @2 line 47
3 Array (Object) >> _error:args:                @15 line 11
4 Array (Object) >> _errorIndexOutOfRange:      @2 line 6
5 Array >> at:                                  @4 line 12
6 Executed Code                                 @2 line 1
7 GsNMethod class >> _gsReturnToC               @1 line 11
topaz 1> frame 5
5 Array >> at:                                  @4 line 12
    receiver a Array
      #1 foo
    anIndex 3
(skipped 2 evaluationTemps)

When you are done debugging in the debugging session, execute the logout command. This detaches the debugger and allows the executing session to proceed.

 

Previous chapter

Next chapter