up next

Introduction

Whenever an unhandled error or exception occurs in Smalltalk/X, the system enters a debugger (1).

It is also possible, to enter the debugger by pressing the interrupt key "Pause" or "Interrupt" in any Smalltalk window. In some older Unix configurations, use: "Ctrl-c". Also, for compatibility with other Smalltalk dialects, such as Squeak or ObjectWorks, the "Ctrl-." and "Alt-." keys are also treated as interrupt keys. This stops the corresponding process and opens a debugger on it. If you started ST/X with a controlling terminal window (console), pressing "Ctrl-c" there also interrupts the currently active process, but may lead you directly into a low-level MiniDebugger, which is described below.

You can also place a method-entry breakpoint on any method via the browser's breakpoint menu (the red traffic light button) or a line breakpoint in a code editor's line number area.

Finally, a programmed stop is added by placing a "halt", "breakPoint" or "assert"-message into the code. These messages are implemented in the Object class and therefore understood by every object.

Try It!

Try evaluating:
    self halt
in a Workspace.
To open a workspace for that, press:

Modes

The debugger may run in one of three modes:

Usage

The debugger consists of 4 major subviews plus some action buttons. Newer versions of the debugger also show a menu panel and might have the buttons split into two separate panels. The picture below is slightly outdated, but the principles are still the same.


The actual appearance of the debugger depends on the viewStyle setting; the picture above was taken with the "Silicon Graphics iris-style" in effect, which is also quite old.

The subviews are:

  1. Context walkback list (sender stack frame chain)
  2. Method source view (code of selection)
  3. Receiver inspector (receiver of selection)
  4. Context inspector (locals of selection)
The context walkback list shows the context chain which lead to the exception (i.e. starting at the current context on top, the list of senders is displayed). It should be read top to bottom as "was called by...". The context of the method (or block) which was responsible for the debugger's invocation is shown at or near the top (2).

Selecting a context in this list will show the corresponding method's source in the method source view, and updates the receiver- and context inspectors.

The recevier inspector (left) shows the receiver of the selected message; the context inspector (right) provides information about the arguments and local variables of this context.

The method source view highlights the current position of execution. This view is a codeView - which means that you can change the code and "accept" to change the method (i.e. fix errors and compile). However, once accepted, the source code of the method is no longer in sync with the state of the process (that one is still suspended in the previous, old code). If the process is resumed, it will continue executing code from the original method, until the same message is sent again. Then, any new accepted code will be executed. Therefore, after any change, it is common to not proceed, but instead resend the current message (for which a separate button exists in the action panel).
To avoid confusion, the debugger shows an indicator and toggle to switch between the original and the accepted code.

You can also evaluate expressions (like in a workspace) in any of the views. Variable references are resolved as usual (i.e. you can refer to local variables, class variables, instance variables etc. as if the code was written in a normal method) (3).

Multiple Threads and the State of a Debugged Thread

Be aware that the debugger shows the state of a single thread. In contrast to other debuggers, the rest of the system is usually not affected while a thread is being debugged.

This also implies, that it is possible to have multiple debuggers open simultaneously, which makes sense, to single step through mutually interacting threads (eg. when they send messages to each other).

Be also aware, that the debugger shows the thread in a suspended but otherwise alive state. When the debugger presents an exceptional situation, the responsible thread is only suspended; the call frame hierarchy as such is still alive, and it is possibly to resume, restart or otherwise manipulate the control flow of the debugged thread.

Footnotes

(1) this is a bit oversimplified. Actually, all errors raise an exception, which (if not handled) raises another exception (the unhandled exception exception), which has a default handler. This default handler actually opens the debugger. Both the original exception and the unhandled exception can be cought by an exception handler. Also, the default handlers can be configured to perform arbitrary other actions (for example: to save a walkback to a file, show a warnbox, and abort the ongoing operation, or exit the program completely). Thus the system's reaction on those errors is actually under the programmer's control.

(2) Actually, there are additional contexts which are hidden from this list by default. For example, if a "division by zero" error happens, the division operation method raises a ZeroDivide exception, which looks for a handler and finally raises an UnhandledException-exception. This finally enters the debugger. All those exception handling helpers are not shown by default, and the debugger presents the place of the divide as the top most context. Unless you are debugging the exception handling mechanism itself, this is usually the preferred presentation, as you as a developer are interested in the division error, not its handling. You can change this behavior (and make all contexts visible) via the debugger's view menu entry "Show Support Code".

In addition, contexts for some support code, such as block-values in enumerations and other helpers are hidden by default. To show them, turn off the "Show Dense Walkback" toggle in the same menu.

(3) Ocasionally, the debugger has trouble with this, as the compiler(s) move local variables of inlined blocks into their surrounding context (i.e. a local variable within an ifTrue:[]-block is typically moved to the method's stack frame). Also, some inner blocks are actually compiled either inline or otherwise compiled more efficient (lambda lifted) and the debugger has not been implemented clever enough to figure out all such situations. Although trying hard, the debugger sometimes gets a wrong idea of where the variable is actually located, as it may not know about all optimizations done by the compilers, especially with "stc" compiled code. This is a known weakness (call it "a little bug"), with which we have to live (for now). In practice, such situations do occur, but are usually not limiting the programmer's productivity.

Continue in "Button Functions"...


[stx logo] Copyright © 1995 Claus Gittinger Development & Consulting, all rights reserved

<cg at exept.de>

Doc $Revision: 1.30 $ $Date: 2021/03/13 18:24:53 $