8. Debugging and the Inspector

Amulet contains many features to aide in debugging programs. Many of these are available interactively through the ``Inspector''. Other features are available programmatically. This chapter provides a reference manual for the Inspector and the other debugging facilities, and concludes with a set of recommendations about how to debug various situations that we have seen frequently.

8.1 Introduction

We want to make Amulet programs very easy to develop and debug. Therefore, we have added extensive error checking to Amulet, as well as a number of interactive and tracing tools. These are designed to work with your regular C++ debugging tools like breakpoints. For example, the Inspector will allow you to break into the debugger when a slot is set, but then you need to use your regular C++ debugger to figure out why the slot was set. The debugging features are all implemented using machine-independent code, except for a single routine that breaks into the debugger. Therefore, we assume you can do stack traces and look at variables in your debugger, rather than needing to do this in our tools.

We would very much like to enhance the debugging capabilities of Amulet. If you think of a facility that would be useful, please let us know. An article about the debugging facilities in Amulet is available from the Amulet web site.

8.2 Include Files

The interactive Inspector is included by default in the Amulet library when you build the debugging option (see Section 8.4 of the Overview document). To use the Inspector, if you have the debugging library, you do not need to do anything special in your application. However, if you want to use any of the debugging features procedurally, you must include the debugger.h file, because it is not included by default when you include amulet.h. The most portable way to include the debugger header file is:

#include <am_inc.h>
#include DEBUGGER__H

8.3 Inspector

The Inspector is an interactive program that provides access to a large number of debugging features. At its most basic, it displays the slots of an object in a window. The values of most types of slots can be edited. The properties of the slots and the object can be inspected, as well as dependencies of any formulas in the slots. Traces and breakpoints can be set when slots are accessed or set. The Interactor's tracing mechanisms are also available from the Inspector.

8.3.1 Invoking the Inspector

To pop up the Inspector on an object, you can put the cursor over the object, and hit the F1 key. The inspector will first print out the window and location in the window of the cursor to the transcript (console) window, and then a new top-level window will appear displaying all the slots of the object and their current values. If no object is under the cursor, then the window itself will be inspected. The Inspector first tries to find a primitve (leaf) object under the cursor, but if that fails, then it presents the front-most group object. If the correct object does not appear, it is usually best to get to the object by going up and down the owner/part tree, as will be explained below.

If you press the F2 key over an object, the Inspector will do the same search for an object, but will only print out the position in the window and the object at that position. This is very useful for finding out the coordinates of a point in the window.

The F3 key will ask in the console window for the name of an object to inspect. The name typed must be the exact name of the object, which is sometimes an easy-to-remember constant name (like Amulet's or the user's prototypes) or often cut and pasted from the output of one of the tracing functions.

If you want to use F1, F2, and F3 for your own functions, you can always eliminate all of debugging facilities, including the inspector, but building a non-debug version of the library. A much less drastic option is to set the following global variables (exported by debugger.h) with an Am_Input_Char that you prefer to use for debugging. Assigning any of these with the null character, using Am_Input_Char(), will mean that that function is not available.

Am_Input_Char Am_Show_Inspector_Char; // default = F1
Am_Input_Char Am_Show_Position_Char;  // default = F2
Am_Input_Char Am_Ask_Inspect_Char;    // default = F3
The inspector can also be invoked procedurally using either of the functions:

// inspect the specific object
void Am_Inspect(Am_Object object);

// The next one takes the name of the object.  This is useful from an interpreter.
void Am_Inspect(const char * name);

8.3.2 Overview of Inspector User Interface and Menus

The inspector window is shown above. All of the slots of the objects are shown along with their current values. Inherited slots are shown in blue, and local slots are shown in black. You can click on a slot value to show a cursor, and then the value can be edited.

You can double-click on a slot name, an object name, or a constraint name to select it, and then perform other operations on that slot, object or constraint (such as viewing its properties). For the commands which pop-up windows, you can select names in those windows as well by double-clicking on them. The name which is selected by double-clicking is also copied into the cut buffer (clipboard) so it can be pasted into this or other applications.

Also shown in the Inspector window, below the list of slots (in this case, you would need to scroll down using the scroll bar on the left) is (optionally) a list of the parts of the object, and the instances of the object.

Commands available from the menus are (many of these are described in detail below):

8.3.3 Viewing and Editing Slot Values

When you inspect an object, the values of the slots are displayed. You can control which slots are viewed, by hiding or showing the inherited slots and/or the internal slots. You can also control the sorting of the slots (either alphabetical or in the order they appear in the object).

If you single click with the left mouse button over a slot value, a cursor will appear and you can edit the slot's value. The editing keys are the same as for all other text interactors (Section 5.3.5.5.1 of the Interactors chapter). Currently, the inspector will not let you change the type of the value in the slot. Therefore, it uses the current type of the value to decide how to parse the input value. You can edit primitive values, like integers, floats and strings. Depending on whether your compiler supports bools as a primitive type, they will either print out as true and false or 1 and 0. Floating point values print out just like integers if there is no fraction part. You can use the slot properties pop-up window to find out the exact slot type.

For slots which contain named values, like styles (Am_Red, Am_Line_8), objects (Am_Rectangle_123), constraint names (windows_is_color) and method names (rectangle_draw), you can type in a new name. Amulet remembers the names of all built-in or user-defined objects, methods and formulas. If you create your own styles or wrappers, you can arrange for them to have names registered in the database using the ``registry'' mechanism defined in registry.h. For any wrapper object, you can register its name using the Am_Register_Name procedure, such as:

  Am_Register_Name (my_color_object, "my_color");
Then, the user will be able to type in my_color as the value of a slot.

Unfortunately, you cannot yet set the value of slots that are Am_Value_Lists, and you cannot set the items of an Am_Value_List.

8.4 Accessing Debugging Functions Procedurally

Sometimes it might be useful to access the debugging functions from a program, instead of interactively from the Inspector. For example, you program might be crashing even before it fully starts up, so you cannot access the inspector. If you program gets past the Am_Initialize(), then you can still trace slot setting and print the values of the slots of objects. Also, some of these procedures might be executed from a debugger such as gdb that supports calling functions.

The functions for invoking the Inspector procedurally have already been listed:

// inspect the specific object
void Am_Inspect(Am_Object object);
// The next one takes the name of the object.  This is useful from an interpreter.
void Am_Inspect(const char * name);
You can cause an object to be ``flashed'' so you can see where it is on the screen. If it is not visible, then this functions writes the reason to the specified stream:

void Am_Flash (Am_Object o, ostream &flashout = cout);
The tracing functions provide significantly more features than are available interactively from the inspector. The tracing and breaking function takes an optional object, an optional slot, and an optional value. Whatever ones of these are supplied will control whether to trace or break. Thus, if only the object is supplied, then the trace or break will happen whenever any of the slots of that object are set. If only a value is supplied, then a trace or break will happen whenever any slot of any object is set to that value. If all three parameters are supplied, then a trace or break will happen only when that slot of that object is set to that value.

void Am_Notify_On_Slot_Set (Am_Object object = Am_No_Object,
				Am_Slot_Key key = 0,
				Am_Value value = Am_No_Value);
void Am_Break_On_Slot_Set (Am_Object object = Am_No_Object,
				Am_Slot_Key key = 0,
Am_Value value = Am_No_Value);

The next procedure clears a slot notify or break set with the above procedures:

void Am_Clear_Slot_Notify (Am_Object object = Am_No_Object,
				Am_Slot_Key key = 0,
				Am_Value value = Am_No_Value);

8.5 Hints on Debugging

This section lists some hints of procedures we have found useful for debugging certain situations that we have found occur more than once. If you know anything that should be added to this list, please let us know!


Last Modified: 12:42pm EDT, April 12, 1996