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.
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.
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:.
You can use the break command to establish method breakpoints within your GemStone Smalltalk code:
break aClassName >> aSelector [@ stepNumber]
break aClassName class >> aSelector [@ stepNumber]
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
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:
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
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.
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
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.
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.
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.
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
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.
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]
In addition to examining stack, you can:
When you have finished debugging, use the topaz commands in the debugging session:
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.
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.
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]
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
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.
<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.