Advanced stepping and custom debugging actions in the new Pharo debugger

In this article, we describe the new advanced stepping menu available in the debugger. These advanced steps provide convenient and automated debugging actions to help you debug your programs. We will see how to build and add new advanced steps, and how to extend the debugger toolbar with your own customized debugging menus.

Advanced steps

Have you noticed the bomb in the debugger toolbar? These are the advanced steps! These steps provide you with usefull and convenient debugging actions. Basically, they automatically step the execution until a given condition is satisfied. When it is the case, the execution is interrupted and its current state is shown in the debugger. In the following, we will describe these advanced steps and implement and integrate a new advanced step that skips the current expression.

Advanced steps menu in the debugger tool bar

What do these advanced steps do?

These advanced steps are a bit experimental (notice the bomb!). This means they can sometimes be a bit buggy, and in that case you should open an issue to report the bug. However most of the time, they do the job. Some of them have a failsafe that stops to give feedback to developers, in order to avoid an infinite stepping due to the impossibility to meet the expected conditions. For now, that failsafe limits the automatic stepping to 1000 steps before notifying the developer and asking if she wants to continue. The current advanced steps and what they do are describe below.

Steps until any instance of the same class of the current receiver receives a message. For instance, the receiver is a point executing the extent message. This command will step until another point receives a message.

Steps until the current receiver receives a message. For example, the next time a visitor is called back from a visited object.

Steps until the execution enters a new method. Stops just after entering that new method.

Steps the execution until the next object creation, that is, the next class instantiation.

Steps the execution until the current method is about to return. Stops just before returning.

Building a new advanced step: skipping expressions

In the following, we build a new advanced step to demonstrate how you can easily add new debugging commands to the advanced step menu.

Building the command class

First, we must create your class as a subclass of SindarinCommand. The SindarinCommand class provides small facilities to build debugger commands, such as accessors to the debugger API and to the debugger UI.

SindarinCommand subclass: #SindarinSkipCommand
    instanceVariableNames: ''
    classVariableNames: ''
    package: 'NewTools-Sindarin-Commands'

Second, we must write three class methods to configure the command: you have to provide an icon name, a description and a name. The defaultName method also contains the pragma <codeExtensionDebugCommand: 50>: this pragma is how the debugger automatically finds the command to display it in the advanced steps menu. The parameter of the pragma is the order of appearance of the menu action (we will not bother with it in this tutorial).

SindarinSkipCommand class>>defaultIconName
SindarinSkipCommand class>>defaultDescription
	^ 'Skips the current expression'
SindarinSkipCommand class>>defaultName
	<codeExtensionDebugCommand: 50>
	^ 'Skip'

If we open a new debugger, we see now that a new advanced step is available: the skip debugging action.

The new “Skip” advanced step automatically appeared in the menu.

Building the skip action

Now that we have our menu button, we need to write what it does! We must write the execute method in the SindarinSkipCommand. This method is the one called every time you click on an advanced step button.

Ideally, commands should not contains the logic of the debugging code because it requires to access and modify elements from the debugger UI (or debugger presenter) and to access and control the debugging model. This is not always possible (everything is not accessible from outside the debugger) and this also leads to complex and hard to test code in those execute methods.

That is why we provide an access to the debugger UI through the debuggerPresenter accessor, and that we only call its API in those execute commands. In our implementation below, we call the skipCurrentExpression API that implements the skipping behavior. We do not show this implementation here as our focus is the adding of new advanced steps. In addition, we prefer to create the skipCurrentExpression API as an extension method of the debugger presenter and located in the same package as our skip command class.

	self debuggerPresenter skipCurrentExpression

Experimenting our new skip action

We see a demonstration of this new advanced step in the video below. Notice that everything is not possible: at the end, the debugger refuses to skip the return of the method.

Additionally, skipping code is a sensible operation. It can lead to an inconsistent program state, and you must use it with caution. Remember: there is a bomb in the menu 🙂

How to build your own debugger action by extending the debugger action bar

Extending the toolbar of the debugger with your own menu and commands is fairly easy. You can do it in a few steps, that we describe below.

First, we need to create an extension method of the debugger that will be automatically called by the Spec command building mechanics. This methods takes two parameters: stDebuggerInstance as the debugger instance requesting to build commands, and rootCommandGroup, the default command tree built by that debugger instance. The first instruction of this extension method is the <extensionCommands> pragma. Spec uses this pragma to find all methods extending the command tree of a given presenter (here the debugger) to automatically build extensions.

This method starts like this:
StDebugger>>buildMyExtentionMenuWith: stDebuggerInstance forRoot: rootCommandGroup

Now, let us assume that you built a set of commands, that we refer to as yourCommandClasses in the following. We instantiate all your commands and store them into a commands temporary variable. Each time, we pass the debugger instance to the instantiated command. All these commands can then obtain a reference to the debugger by executing self context, which returns the debugger, and use its API.

commands := yourCommandClasses collect: [:class | class forSpecContext: stDebuggerInstance ].

The next step is to obtain the toolbar command tree from the debugger command tree. This tree contains all the default commands of the debugger, that we want to extend:
toolbarGroup := rootCommandGroup / StDebuggerToolbarCommandTreeBuilder groupName.

Then, we build our own command group and we add this group to the toolbar. The following code configures that new group as a menu button that opens with a popover (as for advanced steps described above):
yourToolbarGroup := CmCommandGroup forSpec
name: 'Advanced Step';
icon: (stDebuggerInstance application iconNamed: #smallExpert);

toolbarGroup register: yourToolbarGroup.

Finally, we register our commands to our new command group, which will make then available in the debugger toolbar:
commands do: [ :c | yourToolbarGroup register: c ].

The full method looks like this:

StDebugger>>buildMyExtentionMenuWith: stDebuggerInstance forRoot: rootCommandGroup 
commands := yourCommandClasses collect: [:class | class forSpecContext: stDebuggerInstance].
yourToolbarGroup := CmCommandGroup forSpec
    name: 'Advanced Step';
    icon: (stDebuggerInstance application iconNamed:     #smallExpert);
toolbarGroup register: yourToolbarGroup.
commands do: [ :c | toolbarSindarinGroup register: c ].


We have seen the advanced steps, what they do, and how we can build and add new advance steps. We have then see how to extend the debugger toolbar with our own customized debugger actions.

Now, you have more power over your debugger, and you can use it to build awesome debugging tools suited to your own problems!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: