Debugging and Run-time Monitoring

The next set of EiffelStudio capabilities enables you to control and monitor the execution of your systems. The obvious immediate application is to debugging; but the more general goal is to let you follow the execution of your systems, explore the object structures, and gain a better understanding of the software.

A reminder about debugging in Eiffel

Before looking at debugging facilities don't forget that debugging in Eiffel is different. The presence of Design by Contract mechanisms gives the debugging process a clear sense of direction. The speed of the recompilation process makes it easy to recompile after a change; after getting rid of syntax and validity errors, you run the system again, and remaining errors are often caught as violations of contract clauses -- routine preconditions, routine postconditions, class invariants.

The facilities to be described now are also useful when you find such an error, as they will help you study its execution context. In fact, one of the characteristics of the debugging mechanism is that there is no "debugger" proper, no more than there is a "browser"; you have instead a set of facilities supporting controlled execution and debugging. This means for example that:

  • While debugging, you can access all the browsing capabilities to explore the features and classes surrounding the cause of an error.
  • While browsing, you can launch or resume execution, and follow its progress through the debugging facilities.
  • If execution stops on an exception -- for example, an assertion violation or arithmetic overflow -- you have all the environment's facilities at your disposal to understand what happened.

Setting breakpoints

To control the execution you will set breakpoints, indicating places where you want to interrupt the execution. You may set a breakpoint on an individual instruction of a routine, on the routine's precondition or postcondition, or on the routine as a whole, meaning its first operation (precondition or instruction).

A group of icons on the Project Toolbar help control breakpoints. They are known in EiffelStudio terminology as "buttonholes", meaning that they can serve both as buttons (you can click them to get some functions) and holes (you can pick-and-drop into them to get some other functions).

Run and debug buttons

The labels correspond to the icons' use as buttons: enable all set breakpoints (), disable them all (), remove them all (), and display information on current breakpoints using the Breakpoints tool (). The difference between "disabling" and "removing" is that disabling turns off breakpoints until further notice but remembers them, so that you can later re-enable them, whereas "removing" clears them for good.

Here you also see icons for controlling execution: run (), step-by-step (), step into routine (), step out of routine ().

Target a Development Window to the class TESTROOT and pick-and-drop the name of the procedure make (the first routine, after the declaration of the two attributes o1 and o2) to the Enable all icon, used here as a hole. This sets and enables a breakpoint on the routine. Click the button labeled Show information about breakpoints (). This will invoke the Breakpoints tool, as shown in the next figure.

The Breakpoints tool

This shows that so far you have enabled only one breakpoint. For a finer degree of control, let's look at the feature's flat form. Close the Breakpoints tool, then pick-and-drop make from the Editing Tool to the Feature tool (anywhere in the lower left pane should do; this sets the pane's context to the Feature Tool. Select the Flat view if that wasn't the last one used:

Breakpoint set in

The small circles on the left side of the Flat form indicate breakpoint positions. Empty ones are not set; enabled breakpoints are marked by a circle filled with red. At the moment only one is enabled, corresponding to the first instruction of the routine since, as noted, setting a breakpoint on a routine as a whole means setting it on its first operation.

By (left) clicking on a breakpoint mark, you toggle it between enabled and not set. You can also right-click on a mark to get a menu of possibilities:

Breakpoint context menu

Try enabling and unsetting a few of these marks; you might get something like this:

Multiple breakpoints: not set; enabled; set but disabled

The breakpoint mark for the routine's third instruction, create o2, is filled but not red (); this means it is set but not enabled. You can obtain this by right-clicking on the mark and choosing Disable breakpoint on the menu that comes up. Any potential breakpoint will be in one of three states: not set (); set and enabled (); set but not enabled ().

Remember, you can see the complete list of enabled and disabled breakpoints by making the Breakpoints tool visible ... which you do by clicking the Show information about breakpoints () button in the Project Toolbar.

For the remainder of this chapter it doesn't matter which exact breakpoints of make you've set, as long as the one on its first instruction is set and enabled (red-filled circle) as above. Please make sure this is the case before proceeding.

Executing with breakpoints

To execute, you will use the following Run buttons in the Project toolbar, or the corresponding entries in the Execution menu:

Run and debug buttons

Most of the buttons shown here are enabled, but at different times some of them will be disabled (grayed out.)

The Execution menu entries will also remind you of shortcuts: F10 for Step-by-step, F11 for Step into routine, Shift-F11 for Step out of routine, CTRL-F5 for Run ignoring breakpoints, F5 for Run with breakpoints, CTRL-Shift-F5 for Interrupt execution, Shift-F5 for Stop execution.

Execution menu

Start execution of the compiled system by clicking the Run button (). Run actually means "Run and stop at enabled breakpoints." EiffelStudio automatically switches to Execution mode to accommodate supplementary tools providing debugging information. Execution stops on the breakpoint that you have enabled on the first instruction of procedure make:

Stopped at first breakpoint in

By default, in Execution mode, EiffelStudio looks a little different. Specifically, the Feature tool now occupies the space that was previously held by the Editing tool. The Call Stack is in the tall pane on the right, and the Object tool and Watch tool are on the bottom, under the Feature tool.

The Call Stack indicates that execution has stopped in make. The Feature tool shows the flat form of that routine, with an icon (Stopped on enabled breakpoint) to indicate the stop point which execution has reached. The Object tool, which shows the content of current object and (later) related objects. At the moment you can see that:

  • The current object is an instance of class TESTROOT.
  • The class (as you could also see from its text in a Development Window) has two attributes o1 and o2, both declared as type PARENT, for which the corresponding fields in the current object are both void; this is as expected since you haven't yet executed the two creation instructions create {HEIR} o1 and create o2, as they come after the breakpoint.
  • Along with attributes, an Eiffel class may have once functions, executed at most once -- the first time they are called -- in a given session, and from then on always returning the same value. You can see the status of these by expanding the entry "Once routines" in the Object tool. Here the once function io has not yet been called, but after it has it will return an object of type STD_FILES.

The execution-time objects that you may display in the Object tool are our latest kind of EiffelStudio "development object", along with classes, features, explanations, clusters; notice the distinctive icon (), a rectangular mesh shape suggestive of an object's division into fields. It appears colored for actual objects and gray () for Void references such as operating_environment.

Monitoring progress

Click twice on Step-by-step (), or press the function key F10 twice. Monitor, in the flat form of make, the marker that shows execution progress; note that the marker always points to the next operation to be executed. After the two steps, the Feature and Object tools look like this:

The last instruction that you executed is create {HEIR} o1, meaning create an object and attach it to o1, but instead of using the declared type PARENT of o1 use its proper descendant HEIR. As a result, the entry for o1 in the Object tool no longer shows Void but an object of type HEIR. Note that all objects are identified by their addresses in hexadecimal; such an address is by itself meaningless, but enables you to see quickly whether two object references are attached to the same object. The addresses you see as you run the Guided Tour will -- except for some unlikely coincidence -- be different from the ones appearing here.

Note that since the garbage collector compacts memory and hence may move objects around, the address of a given object is not guaranteed to remain the same throughout a session.

To see the details of the object, expand its entry in the Object tool.

From the instance to the class

Now notice what happens if you pick the type name (HEIR) from the entry for object o1 in the Object tool, and then drop it in the Feature tool above. The context changes from the Feature tool to the Class tool and is retargeted on HEIR. The Class tool has switched to the default format for classes, Ancestors, and is showing the ancestors of HEIR. Click the Feature tab to get back to feature information for the continuation of our debugging session.

Stepping into and out of a routine

Click Step-by-step once more to advance just before the call o1.display.

Choosing Step-by-step again would execute the next step in the current routine, the call o1.display, treating the entire execution of display from class HEIR as a single operation. Assume instead that you want to go into that routine and follow the details of its execution. For one thing, you might not know that it's a routine of class HEIR, since o1 is declared of type PARENT and it's only through polymorphism, that is, o1 being dynamically of type HEIR at this point, and through dynamic binding, that the execution ends up calling a routine from HEIR. Of course here it's obvious because of the wording of the create a few lines up, but in many cases, especially all those for which polymorphism and dynamic binding are really interesting, the exact type won't be immediately clear from the neighboring software text.

Click the Step into routine button (or press F11). This brings execution to the beginning of the appropriate display routine in class HEIR.

While you're here notice the Call Stack tool. It shows that we are currently executing feature display of class HEIR, which, as can be seen on the second line of the stack, was called from feature make of class TESTROOT.

Now click Step out of routine (), or press Shift-F11 to finish the execution of display. This brings you back to the next instruction of the calling routine, make of TESTROOT.

Terminating

You may now click the Stop execution button (), or press Shift-F5, to end execution. The execution-specific tools go away and the display returns to what it was before execution.

Other debugging capabilities

In this little application nothing runs long enough to give you the time to interrupt it. In a longer-running application you may want to interrupt execution, without necessarily terminating it, while it's running (not stopped on a breakpoint). This is the purpose of the Interrupt execution button (), which can also be triggered by pressing CTRL-Shift-F5. It will interrupt execution at the closest potential breakpoint position, letting you -- as when execution stops because of an exception -- take advantage of all the debugging and browsing facilities to see what's going on inside your running system. You may then restart execution -- with or without breakpoints, single-stepping, out of the current routine, into the next routine -- by choosing the appropriate Run button

In debugging sessions for more advanced applications, you will also find self-explanatory mechanisms enabling you, in addition to what we have seen, to examine all the objects on the "call stack": arguments and local entities of the current routine, its caller, caller's caller and so on.

The combination of these facilities provides you with a level of dynamic information on the execution of your system that matches the static information that the browsing mechanisms studied in preceding sections provide about the system's structure.

cached: 10/06/2024 11:45:26.000 AM