3. 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 4.

3.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 3.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

3.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 [methId 25534209]
2 Executed Code                           @3 line 1   [methId 25504513]
3 GsNMethod class >> _gsReturnToC              @1 line 11 [methId 4912641]
  [GsProcess 27551489]
 

The frame command allows you to see more information about the instance and temporary variable names for a particular stack frame. With level 0 (the default), only the variable values themselves are displayed; increasing the level displays durther details.

For example, at the defaul level 0:

 
topaz 1> frame 1
1 Animal >> name:                               @1 line 1
    receiver [45404929  Animal]       anAnimal
    newValue [45404161 size:3  String] Dog

and with level 1:

topaz 1> level 1
topaz 1> frame 1
1 Animal >> name:                               @1 line 1
    receiver [45404929  Animal]       a Animal
  name                nil
  favoriteFood        nil
  habitat             nil
    newValue [45404161 size:3  String] Dog
 

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.

To see source code around the selected frame, use the listw command. This requires setting the listwindow size, which defines the number of lines of source code to display.

For example:

topaz 1> set listwindow 3
topaz 1> listw
   name: newValue
 * ^1 					*******
           name := newValue

The stack command provides additional information about the instance and temporary variable names and values for each context.

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 GsNMethod class >> _gsReturnToC 								@1 line 11 [methId 4912641]
    receiver [20 sz:0 cls: 76289 UndefinedObject] nil
 

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.

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 GsNMethod class >> _gsReturnToC              @1 line 11
    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 GsNMethod class >> _gsReturnToC              @1 line 11

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

3.3  Debugging from a different session

Normally, you will debug problems that you encounter within your running topaz session. However, you can also connect to the Gem that has the problem (the executing session) from another topaz session (the debugging session), so you can debug a problem in a session that does not have a console, such as code executing in a script.

To do this, the executing session, an RPC or linked session started from topaz or from another environment, starts a separate thread in the VM that listens for a debug connection, and provides the debug token, random32BitDebugToken, which is a random 32-bit integer. In a separate topaz environment, use the debuggem command with the PID of the executing Gem session and the debug token, to attach to the executing Gem process.

There are several ways to handle this in the executing session; waitForDebug encapsulates multiple functions in the executing session, but these operations can also be done separately.

Note that the waitForDebug handling is designed to operate where normal debugging is not possible. If you try out waitForDebug in interactive linked topaz, it is handled as a normal exception, which is much easier to debug, rather than enabling debuggem handling.

waitForDebug

The method System >> waitForDebug can be invoked in the Smalltalk code that will be executed; this handles the various steps required to setup for another session to attach. This can be included anywhere in your code, such within a exception handler.

For example, the executing session could run the following code:

[ 3 / 0]
on:Error 
do: [:ex | System waitForDebug] 

The waitForDebug method enables the remote debugging thread, and then waits for a debugging session to attach. When the error occurs, the session’s PID and the random32BitDebugToken token are printed to the gem log (for an RPC login) or to stdout of a non-interactive linked topaz. The line will look similar to:

Listening for debug: DEBUGGEM 2323342 17959702887437659363 

This DEBUGGEM expression includes all the required arguments, and is executed in the environmen tin which you will debug, such as a separate topaz environment. The debuggem command does the login in, You do not need to login explicitly.

topaz> DEBUGGEM 2323342 17959702887437659363
successful attach
ERROR 2706 , a Break occurred (error 2706)
topaz 2>

If the attach is successful, a SoftBreak or HardBreak is signaled to the executing session, which interrupts the wait loop in System class >> waitForDebug. This break is delivered to the debugging session, which assumes control over the executing session.

After the attach, the executing session prints a message to stdout:

Yielding control to Debugger Thread

The debugging Gem can start debugging; for example, examine the stack using where:

topaz> DEBUGGEM 2323342 17959702887437659363
successful attach
ERROR 2706 , a Break occurred (error 2706)
topaz 2> where
==> 1 Break (AbstractException) >> _defaultAction   @6 line 6
2 Break (AbstractException) >> _signal          @2 line 20
3 Break (AbstractException) >> signalToGci      @3 line 7
4 Break class (AbstractException class) >> signalToGci @3 line 7
5 System class >> waitForDebug                  @5 line 29
6 [] in Executed Code                           @8 line 3
7 ZeroDivide (AbstractException) >> _executeHandler: @7 line 11
8 ZeroDivide (AbstractException) >> _signal     @1 line 2
9 ZeroDivide (AbstractException) >> signal      @2 line 47
10 SmallInteger (Number) >> _errorDivideByZero   @6 line 7
11 SmallInteger >> /                             @6 line 7
12 [] in Executed Code                           @5 line 1
13 ExecBlock0 (ExecBlock) >> on:do:              @3 line 44
14 Executed Code                                 @2 line 2
15 GsNMethod class >> _gsReturnToC               @1 line 11
  [GsProcess 4363521]

Debugging

In addition to examining stack, you can:

  • Use the step command to step through code. The waitForDebug in the executing session is cancelled.
  • The continue command cancels the waitForDebug in the executing session, so it can continue running, without detaching the debugging session.

When you have finished debugging, use the topaz commands in the debugging session:

  • the kill command will terminate the executing session; this also logs out the debugging session.
  • the detach and logout commands detach and log out the debugging session, but leave the executing gem in waitForDebug. debuggem can be executed a second time to reconnect.
  • the resume command detaches and logs out the debugging session, and cancels the waitForDebug.

Manual setup of remote debugging

There are other configuration parameters, commands and methods that allow you to setup remote debugging. The debugging thread must be enabled for the executing session, and for debugging, the executing session should be in the code you wish to debug. If you are not using waitForDebug, you will also need to explicitly set the process stack in the debugging session.

Enabling the debugging thread

The Gem configuration parameter GEM_LISTEN_FOR_DEBUG can be set to true for a specific Gem or Gems, either in arguments or in a configuration file. This enables the debugging thread for the Gem on login, and prints the DEBUGGEM parameters. For example, using

topaz -l -C 'GEM_LISTEN_FOR_DEBUG=TRUE'

You may also invoking System class>>listenForDebugConnection in the executing session, which will enable the debugging thread. This also prints the prints the DEBUGGEM parameters.

Determining the OOP of executing session’s process

The debugging session must explicitly load the stack object from the executing session (the GsProcess associated with the error condition to be debugged).

Provided that display oops is set in topaz, the GsProcess is printed at the end of a stack displayed using (for example) stk or where. Look for a line such as:

[GsProcess 42086145]

Executing session waiting for debug

For debugging a particular issues, usually you will want the executing session to be in a known place, and wait there for the debugging session to attach.

The command topazwaitfordebug can be used, for example:

topaz> iferror topazwaitfordebug

Example

The following example shell script include the setup required for the executing session; debugging is enabled, and on an error the stack is printed and then the session will pause and wait for the remote debugger to attach.

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 linked 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:

<startup 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) >> _signalToDebugger @10 line 8
2 OffsetError (AbstractException) >> defaultAction @2 line 18
3 OffsetError (AbstractException) >> _defaultAction @4 line 4
4 OffsetError (AbstractException) >> _signal    @2 line 20
5 OffsetError (AbstractException) >> signal     @2 line 47
6 Array (Object) >> _error:args:                @15 line 11
7 Array (Object) >> _errorIndexOutOfRange:      @2 line 6
8 Array >> at:                                  @4 line 12
9 Executed Code                                 @2 line 1
10 GsNMethod class >> _gsReturnToC               @1 line 11
  [GsProcess 42086145]
topaz > exec iferr 2 : topazwaitfordebug 
08/05/2023 16:45:37.423 PST
 Waiting for debugger to attach, topaz process 21213 gem process 21213

Now you can start the separate debugging topaz.

In the debugging session, 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) >> _signalToDebugger @10 line 8
2 OffsetError (AbstractException) >> defaultAction @2 line 18
3 OffsetError (AbstractException) >> _defaultAction @4 line 4
4 OffsetError (AbstractException) >> _signal    @2 line 20
5 OffsetError (AbstractException) >> signal     @2 line 47
6 Array (Object) >> _error:args:                @15 line 11
7 Array (Object) >> _errorIndexOutOfRange:      @2 line 6
8 Array >> at:                                  @4 line 12
9 Executed Code                                 @2 line 1
10 GsNMethod class >> _gsReturnToC               @1 line 11

If the connection is successful, the debugging session will have debugging control over the executing session, and debugging can be done as described here.

 

Previous chapter

Next chapter