Chapter 9: A Look at Debugging

Before we deal with the unfinished business from the last chapter and get on to the more recognisable parts of the Wimp, we’re going to take a small diversion to look at a tool to help us debug the application as it begins to grow.

Getting a report

Unlike other platforms, RISC OS is somewhat short of tools to help developers figure out why their software isn’t working as they expect. We’ve got the Desktop Debugging Tool (or DDT), which comes as part of the DDE, but it’s dated and somewhat clunky to use. There are some other alternatives – and one, which is surprisingly useful, is actually free.

Reporter was originally developed by Chris Morison of Organizer fame; it was later picked up by Martin Avison has been developed (and still is being developed) much further. A cursory read of Reporter’s manual might leave the impression that it’s a tool for Basic programmers, but that’s far from the truth: while there are a lot of built-in facilities for those using that language, it’s just as useful for those of us working in C.

The first thing to do is to download a copy of Reporter, and install it somewhere on your hard disc. You will also require a copy of Confix to configure Reporter, and StrongHelp (if you don’t already have it) to read its manual.

Reporter can be installed wherever you like, while according it its manual, Confix is best placed inside !Boot.Resources so that it’s seen when the machine starts up. Things should look something like Figure 9.1 afterwards.

Figure 9.1: Reporter and Confix should be installed on your machine

Once installed, Reporter can be started by double-clicking on it in the usual way, and it will open a small window in the bottom-left of the screen as shown in . By default, Reporter is set to log operating system commands which are going on in the background, and as a result it will very quickly start to fill the window with lines of black text on a pale blue background.

Figure 9.2: Reporter’s initial appearance on the desktop

Whilst such a level of detail can be useful when tracking down problems, it can also very easily swamp us with data in normal use – as soon as you start to interact with the desktop, you’ll quickly see more lines appear. For this reason, I would suggest opening Reporter’s Configuration dialogue (click Menu over its window and choose Config...) and adjusting its settings a bit. Go to the Options tab and make sure that Report OS Commands executed; is unticked as shown in Figure 9.3. You can then save the settings, and restart Reporter to make them take effect – you can do this automatically through the Restart tab before clicking on Save.

Note that Reporter uses Confix for its configuration, so you might need to have restarted the machine after installing the latter (or at least run Confix in its new home) before these changes can be made.

Figure 9.3: Configuring Reporter’s logging options in Confix

With this change made, Reporter should be a lot ‘quieter’ in normal use. Once you’re familiar with it you might wish to change the settings again – for now, however, we’re ready to start using it.

Writing messages

From the point of view of an application written in C, Reporter can be accessed via a number of SWI calls which largely replicate the *commands used in BASIC. Helpfully, OSLib gives us access to these through the oslib/report.h header file. For our purposes, the Report_Text0 SWI is probably the most useful: OSLib makes this available through the report_text0() function:

extern void report_text0(
        char const *text
);

We can make use of this immediately by adding #include "oslib/report.h" to the top of c.ibar and then amending ibar_mouse_click() as follows:

static void ibar_mouse_click(wimp_pointer *pointer)
{
        switch (pointer->buttons) {
        case wimp_CLICK_SELECT:
                repbox_message("Hello World!", "Howdy!,Go Away!");
                break;
        case wimp_CLICK_ADJUST:
                report_text0("This was an Adjust click - Goodbye!");
                main_quit_flag = TRUE;
                break;
        }
}

A full set of code can be found in Download 9.1. If this is compiled and run then when Adjust is clicked on the application’s iconbar icon, a message will be left in Reporter’s window before the application exits – as seen in Figure 9.4. This could be extremely useful, as we can now output messages from anywhere in our code and have them appear in another multitasking window on the desktop.

Figure 9.4: Logging Adjust clicks in Reporter

Download 9.1
The source code and files in this example are made available under Version 1.2 of the European Union Public Licence.

This isn’t quite the whole story, however, because there’s a potential problem. If we quit Reporter and try the same thing again, what we get is an error report (which, thanks to the last chapter, we can at least now identify as being a program report) telling us that “Example App may have gone wrong” and offering us the chance to Continue, Quit or Describe. The first two options will both cause the application to exit, while Describe takes us on to another report which contains the message “SWI &54C80 not known”.

The reason for this is simply that SWI &54C80 – more easily recognised as Report_Text0 – is provided by Reporter. If Reporter isn’t running, the SWI isn’t available. This means that once we start to include Reporter’s SWIs in our application, Reporter must always be running when the application is. This clearly isn’t ideal!

There’s also another problem. It would be useful to be able to include the values of variables in the reported text, in a similar manner to what we can do with printf(), but that isn’t possible unless we build the string up first using code similar to this:

char buffer[256];

snprintf(buffer, 256, "The value of buttons=%s", buttons);
buffer[255] = '\0';

report_text0(buffer);

Clearly it would be cumbersome to have to do this every time that we wished to output some information about the way that our application is working. For this reason, SFLib offers a debug library – which we can access by removing #include "oslib/report.h" and adding #include "sflib/debug.h" to the top of c.ibar. It provides one function:

#include "oslib/os.h"
#include "oslib/report.h"

#include <stdarg.h>
#include <stdio.h>


#define DEBUG_MAX_LINE_LENGTH 256

int debug_printf(char *cntrl_string, ...)
{
        char            s[DEBUG_MAX_LINE_LENGTH];
        int             ret;
        va_list         ap;

        if (xos_swi_number_from_string("Report_Text0", NULL) != NULL)
                return 0;

        va_start(ap, cntrl_string);
        ret = vsnprintf(s, DEBUG_MAX_LINE_LENGTH, cntrl_string, ap);
        s[DEBUG_MAX_LINE_LENGTH - 1] = '\0';
        report_text0(s);

        return ret;
}

The code makes use of the variable arguments provided by the standard library’s stdarg.h to implement something very similar to a conventional printf() call. The vsnprintf() function – which is in stdio.h – is equivalent to snprintf(), but allows us to pass the variable arguments identified by va_start() straight across. A call to OS_SWINumberFromString is used to see if Report_Text0 exists – and hence Reporter is loaded – before trying to use it.

That said, we’re drifting a little from the RISC OS Wimp into some of the murkier depths of C here, and we don’t really need to understand how debug_printf() works in order to be able to use it. With #include "sflib/debug.h" in place, we can change the call to report_text0() inside ibar_mouse_click() so that we instead use SFLib’s debug_printf().

static void ibar_mouse_click(wimp_pointer *pointer)
{
        switch (pointer->buttons) {
        case wimp_CLICK_SELECT:
                repbox_message("Hello World!", "Howdy!,Go Away!");
                break;
        case wimp_CLICK_ADJUST:
                debug_printf("This was an Adjust click - Goodbye!");
                main_quit_flag = TRUE;
                break;
        }
}

If this is compiled, it should become clear that the application will exit cleanly when Adjust is clicked, whether or not Reporter is currently loaded. As noted above, debug_printf() checks to see if Report_Text0 exists before trying to call it, so it’s even safe to load or quit Reporter while our application is running.

Getting more detail

The other advantage of debug_printf(), which should be apparent from its name, is that it provides the functionality of printf() while sending its output to Reporter via Report_Text0. We can now add another line to ibar_mouse_click() which reports the value of pointer->buttons:

static void ibar_mouse_click(wimp_pointer *pointer)
{
        debug_printf("The value of buttons=%d", pointer->buttons);

        switch (pointer->buttons) {
        case wimp_CLICK_SELECT:
                repbox_message("Hello World!", "Howdy!,Go Away!");
                break;
        case wimp_CLICK_ADJUST:
                debug_printf("This was an Adjust click - Goodbye!");
                main_quit_flag = TRUE;
                break;
        }
}

The result of this change can be seen in Figure 9.5. It’s a useful level of flexibility to have when developing and testing software, and one that we will be making use of as we progress.

Figure 9.5: Outputting a variable’s value with debug_printf

This screenshot also shows that Reporter picks up on report boxes being opened: their details are shown in its window, highlighted in red to make them stand out. Download 9.2 contains the full set of changes.

Download 9.2
The source code and files in this example are made available under Version 1.2 of the European Union Public Licence.

Crashes and postmortems

Before moving on, let’s return briefly to the problem that we had with Report_Text0 when Reporter wasn’t loaded. When the application crashed, we saw a different error report: shown in Figure 9.6. Some errors – which the Wimp identifies by checking the error number in the OS error block against a list that it holds – are considered to be ‘serious’ and given special treatment. The original error message is hidden from the user at first, and is replaced by the text shown.

Figure 9.6: Some error numbers are trapped by the Wimp and reported differently

The Continue and ‘Quit’ buttons correspond to Continue and Cancel in a ‘non-serious’ report – the only difference is that if the application didn’t request a Cancel button, then it will be forcibly terminated if Quit is chosen. Clicking on ‘Describe’ will take the user to a second report box, as seen in Figure 9.7, where the original error message is finally revealed.

Figure 9.7: Interested parties can still see the original error message if they wish

At this stage, it’s often possible to request a postmortem by clicking on ‘Postmortem’. This will be presented in a single-tasking text window on the desktop, ending with the familiar “Press SPACE or click mouse to continue” message. The postmortem should show us where in the program the error occurred, but – as shown in Figure 9.8 – all it tells us is “anonymous function”.

Figure 9.8: The postmortem doesn’t tell us very much

The reason for the unhelpful postmortem information is that as things stand, the Mk shared makefile defaults to passing the -ff parameter to the C Compiler. This instructs the compiler not to embed function names in the code that it generates: there’s around a 5% reduction in code size as a result, but the downside is that identifying the location of crashes can be a lot more difficult.

To overcome this we can instruct the makefile to remove this flag, and the easiest way to achieve this is to clear Make’s C_NO_FNAMES variable after the line to include CApp. As standard, C_NO_FNAMES is set to ‘-ff’ – this in turn sets the parameter in the call to the compiler. The modified makefile should look as shown in Listing 9.1.

# Makefile for Example App

COMPONENT = ExampleApp

OBJS = main ibar repbox

CINCLUDES = -IC:,OSLib:,SFLib:
LIBS = SFLib:o.SFLib OSLib:o.OSLib32

include CApp

C_NO_FNAMES =

# Dynamic dependencies:

Listing 9.1 (Makefile)

After running MkClean and then Mk, triggering the crash again should result in a slightly more useful postmortem screen as seen in Figure 9.9. The updated makefile can be found in Download 9.3.

Figure 9.9: With function names embedded, the postmortem is more useful

Download 9.3
The source code and files in this example are made available under Version 1.2 of the European Union Public Licence.