Debugging is the process of locating, isolating and fixing coding errors. An error may be syntactical, logical or simply an unwanted side effect of the code, so debugging can be an art form as much as an applied science.
As with testing, it is easiest to break the debugging process down into the smallest constituent parts and address each error as it appears. By definition, testing identifies only that there is an error ”but the distinction between testing and debugging can be blurred. Hence, some of the techniques already covered in this chapter could be equally identified as debugging.
While debugging techniques can be used to fix the problems highlighted by formal testing, they will also prove useful during the normal development process. As you build and run your code on a day-to-day basis, you will doubtless encounter errors, and the techniques you learn in this section will help you to locate and fix them.
The main tool you will use for debugging is likely to be the debugger integrated with your IDE. Different IDEs (and sometimes different editions of the same IDE) provide different debugging features, so it is worth researching both current and forecasted support before choosing one that is right for you.
Traditionally, most Symbian OS development involved using Microsoft Visual C++. Now, for Series 60, the development options have expanded to include Metrowerks CodeWarrior and Borland C++ Builder ”these tools provide native support for Series 60, and new features are being added all the time.
Due to the differences in IDEs, only general debugging tips will be given in this section. For more specific details you should refer to your IDE help facilities or consult the documents available on Forum Nokia ”see http://forum.nokia.com.
The debugging facilities of each available IDE vary, but the windows and dialogs available are generally very similar (albeit sometimes with different names ). For example:
Call stack ” This is a list of the functions that have been called, and it can usually be accessed only when program execution is halted ”for example, a breakpoint has been reached. The most recently called function is at the top, and you can trace where each function was called from.
Memory window ” This shows the contents of memory and can be used to look at the contents of data outside of the current function and also to examine when memory changes.
Watch window ” This will show the values of user -selected variables or expressions. Sometimes it is possible to change the values and see how the code reacts. Some windows will show all variables with relevant local scope.
Breakpoints, threads and modules ” These will show lists of the currently set breakpoints, currently running threads, and currently attached libraries. Breakpoints sometimes allow you to stop the execution at a point in the program based on a set of criteria.
You should familiarize yourself with these terms, as they will be referred to throughout the rest of this section.
The following section deals with some common debugging issues and provides some helpful hints to aid you in debugging your Series 60 application.
Not all IDEs support viewing the contents of descriptors. However, with the simple method described here, the contents of any descriptor can be easily viewed :
Make sure that any IDE options to show Unicode strings are selected.
Stop the debugger at the required line by using a breakpoint, or by stepping through the code.
Enter the name of the descriptor into the watch window and expand it to establish the value of its iType attribute.
Apply one of the casts shown in Table 13-6 to the required descriptor in the watch window, depending on the value of iType and whether it is a Unicode or narrow (8-bit) descriptor. Note that in the table the name of the descriptor is assumed to be aDes .
Value of iType
(TText16*)(&aDes) + 2
(TText16*)(&aDes) + 4
(TText16*)(*((int*)&aDes + 2)) + 2
(char*)(&aDes) + 4
(TText8*)(&aDes) + 8
(TText8*)(*((int*)&aDes + 2)) + 4
Note that in Series 60, all descriptors are Unicode unless their class name ends in an "8".
For example, Figure 13-2 shows a tdesC being decoded to view its contents in Visual C++.
When using a class such as CEditableText , any descriptors returned from its member functions may contain, along with readable text, some unrecognized characters . These are typically text-formatting characters such as EParagraphDelimiter .
There are a few macros which are useful to know when it comes to Series 60 debugging. Some of these are well known (such as _DEBUG , which is defined only for debug builds and can be used for conditional compilation ”this is used with the TRACE macro defined earlier in "Serial Output"), and some have been covered earlier in this chapter (such as assertions, invariants and heap checking macros).
However, there is one important macro not yet covered: __DEBUGGER() , defined in e32def.h . This macro can be used to programmatically stop execution before a panic (since, after a panic occurs, all contextual debugging information is lost), and as such is a very powerful tool for quickly tracking down programming errors.
This macro applies only to debug emulator builds, and it will cause the emulator to stop the debugger at the current line if "just-in-time" ( JIT ) debugging is enabled ”this means that if the debugging emulator is running outside of the debugger, then it will attempt to open the currently defined JIT debugger and attach it to the running emulator process.
Just-in-time debugging is enabled by default, but it can be turned off for the emulator in the epoc.ini file (see the SDK documentation for details). It must also be enabled in the IDE.
There is no real way to debug resource files ”they define code resources that do not actually get executed. However, problems in resource files will produce one of two immediately visible symptoms:
The resource file will not build.
The resource file complies, but does not do what is intended.
The first issue is sometimes the more difficult one to solve. If the compiler does not correctly identify syntax errors within the file, then the solution is generally to simplify the resource file, and gradually build it back up until you can identify the problem.
If the resource file compiles successfully but causes problems at runtime, then the file should be checked for logical errors. The SDK documentation and the information in Chapters 5 through 8 detail many of the resource structures used.
Incorrect display of user-visible text may occur because the text defined in resource is overwritten in the code, or because the required localization files do not exist (in which case the name of the resource will be displayed instead). Check that the correct localized file is included and that the correct language is specified. Also check that the correct resource name is used in both the resource file and the localization file.
Note also that antinesting macros are not used in resource files, so check that files are not included multiple times.
Screen flicker is generally caused by CCoeControl -derived classes clearing their rectangle before drawing to it. Also, excessive use of DrawNow() , rather than the buffered DrawDeferred() , can make matters worse (this is covered in more detail in Chapter 11).
In order to find the source of the problem, try using the debug emulator and arrange your screen so that you can see both the emulator and your IDE. Run the application causing the flicker, and use the keyboard shortcut Ctrl+Alt+Shift+F to enable automatic Window Server flushing ”this will let you see the effect of each draw command, as drawing will no longer be buffered. (This will also slow down the application and make flicker from other sources more pronounced!)
Place a breakpoint in the suspect Draw() method(s) and use the keyboard shortcut Ctrl+Alt+Shift+R to cause a full-screen redraw ”the application will stop at your breakpoint. By stepping through the code, you will be able to observe the effect of each draw command on the emulator. Details of all the available keyboard shortcuts are available in Appendix A.
Sometimes the emulator will freeze after a given time ”particularly if the debugger has been stopped for a long time at a breakpoint. A simple solution is to press F11 twice. This generates a case open/close event, which wakes up the emulator. It should be noted that this keyboard shortcut is part of Symbian OS and has been inherited from earlier Psion machines that made use of it. This event is not used within Series 60, so it should cause no unwanted side effects.
Memory leaks occur when allocated memory is orphaned. There are two basic types of memory leak: Static leaks are repeatable under the same conditions each time. They are caused by mismatched allocation and deallocation, and so always occur in the same place. Dynamic leaks, however, are nonrepeatable ”typically being caused by an error or race condition. These are trickier to find, as they will not occur on every run, or will appear to occur in different places.
Either type of leak will cause an error on the debug emulator when the application closes , as the heap checking macros that automatically surround the framework of every Series 60 UI application will panic. Make sure that the line JustInTime 0 is not present in the file \Epoc32\Data\Epoc.ini in the root directory of your SDK, or is changed to read JustInTime 1 . This ensures that the debugger will be halted if your application panics.
Some versions of the SDK display only a "Program closed" error message if an application panics. Creating an empty file called ErrRd in \Epoc32\Wins\C\System\Bootdata will ensure that the traditional panic code dialog is displayed.
The following description is valid for locating memory leaks in Microsoft Visual C++. Other IDEs may require the use of other methods or may provide further tools for tracking memory leaks ”see the documentation of your chosen IDE for further help.
Note that debug-build emulator code that may be available with other Symbian OS platforms is not necessarily provided by Series 60, so methods documented elsewhere may not work with a Series 60 SDK.
This method is useful mainly for locating static memory leaks:
First run the application that contains your memory leak (by pressing F5 ) ”it will halt the debugger when the panic occurs, but as the necessary source and debug-build object code is not supplied with the SDK, no useful information can be gleaned here.
Continue the debugger (press F5 ) until the panic dialog appears ”the address of the heap cell that leaked will be printed in the debugging output window (and also in the panic dialog if the ErrRd tip above is followed). For example, the text might read Panic ALLOC : 10052684 . Make a note of this value.
At this point it is important to stop debugging and restart the emulator. Every time the application needs to be run, the emulator must be restarted, or the memory leak will occur at a different memory address.
Set a breakpoint (press F9 ) in the Application class's AppDllUid() method. This allows you to stop the application before it allocates any memory.
Run the application again, and when it stops at the breakpoint, open the breakpoints dialog ( Ctrl+B ) and temporarily disable this breakpoint by deselecting its tick box. Then, using the Data tab of the dialog, set a new breakpoint at the leaking address by typing 0x , followed by the address previously noted, into the expression textbox. Using the previous example, this would be: 0x10052684 . Also set the number of elements to watch to 4 (the size of a pointer in bytes).
Continue execution (press F5 ) ”you should get a dialog containing text such as Break when '0x10052684' (length:4) changes each time the memory at the watch point changes, and the debugger will stop. The call stack window can be used to trace the line of user code that caused the allocation (where applicable ). Note that this memory location may be allocated and deallocated several times. It is the last allocation that causes the leak. Also, note that the memory may be changed outside an allocation ”an existing object may simply be updated. However, the memory leak will generally link to a call to NewL() , NewLC() , new (ELeave) or similar in your code.
Once the erroneous line is located, the source code must be examined to determine the cause of the error ”typically you will not have deleted some allocated memory.
Further contextual information may possibly be gleaned from a memory address by casting it to a CBase* in a watch window (in case it is a CBase -derived object), or casting it according to the rules for type Unicode or narrow descriptors in Table 13-6 ( HBufC* and HBufC8* ). Also, examining the contents of that address in a memory window may provide further clues.
As can be seen, this is not a trivial exercise, so it is important to follow coding guidelines and test regularly (small changes narrow the field of possibilities)! All memory allocated must be deleted once, and once only, and any objects deleted outside of a destructor should immediately have their pointers set to NULL , to avoid dangling pointers.
The subsection Differences of Testing on Target vs. Emulator earlier in this chapter explained that the behavior of an application when run on the emulator may differ from its behavior when run on target hardware. If you encounter errors that occur only when your application is run on the device, then debugging on target may be necessary.
The GNU debugger ( gdb ) can be used for on-target debugging of Series 60 devices, and in this subsection we will show you how to set up and run gdb from a Windows command prompt.
The Borland C++ Builder IDE also provides support for on-target debugging using gdb , and the setup for this is the same as described here. For further information on using on-target debugging from within Borland C++ Builder you should consult its documentation.
Metrowerks CodeWarrior also supports on-target debugging in its Professional and OEM editions, but this does not use gdb . See its IDE documentation for further details on any support provided.
To be able to carry out on-device debugging you need a copy of the GNU debugger, gdb , on your PC, and a copy of the GNU debugger stub, gdbstub , on your device. Required versions are provided with the Series 60 SDK. You also require a serial link for communication between PC and device.
Obviously it is important that the serial link between the PC and the Series 60 device is configured correctly. In this discussion, an infrared connection is used ”it is also possible to configure a connection over Bluetooth or (where available) a serial cable.
To ensure that communication between gdb and gdbstub is possible, you need to make certain that no other application is using the required COM port on your PC ”make sure that you close any connectivity applications or port monitors . Note also that, due to some limitations in gdb , it is necessary that the COM port used is configured to COM1 , COM2 , COM3 or COM4 .
As most infrared devices generally add their own communication stack on top of the COM stack, you might need to install a virtual COM driver to correct this. One suitable application is IrCOMM2k , which can be found at http://www.ircomm2k.de/ or http:// sourceforge .net/projects/ircomm2k.
Some Series 60 devices may have gdbstub preinstalled . If not, then you need to install the file \epoc32\release\armi\urel\gdbstub.sis from the root of your SDK to your Series 60 device. This will create the executable gdbstub.exe in the \System\Programs directory of your device, and the libraries gdbseng.dll and gdbseal.dll in the \System\Libs directory.
Depending on the version of gdbstub.sis used, a configuration file ( gdbstub.ini ) may also be created in the directory c:\gdbstub on your device. If not, then you will have to create this file yourself. The configuration file settings for infrared communication are shown below. If you wish to use Bluetooth, or some other serial link, then you will need to amend the settings as shown in the GDB stub configuration file syntax section of the SDK documentation.
[COMMSERV] PDD=EUART%d LDD=ECOMM CSY=IRCOMM PORT=0 RATE=115200
Once the necessary configuration file has been created, gdbstub is ready to be executed. Note that you will need a file manager application (such as FExplorer from http://www.gosymbian.com) installed on the device to enable you to locate and execute gdbstub .
To initialize the gdb environment on your PC, you should create an initialization file, gdb.ini , in your application's group directory. An example of the format of this file is shown below along with an explanation of the purpose of each line:
symbol-file //c/symbian/6.1/series60/epoc32/release/armi/udeb/sample.sym epoc-exec-file c:\system\apps\sample\sample.app target epoc com1 break NewApplication source //c/symbian/6.1/series60/epoc32/gcc/share/epoc-des.ini
symbol-file ” specifies the symbol file used to provide the debugging information for your example. This will be in the \epoc32\release\armi\udeb\ directory relative to the root of your SDK, so the path defined here will depend on where your SDK is installed. Note also that the file path is specified using forward slashes (" / "), not backslashes (" \ "). In this case the example application is called sample.app , so the symbol file is correspondingly called sample.sym .
epoc-exec-file ” specifies where the actual application will be located on the device. Note that this example determines that the application is installed to the device's c: drive.
target ” specifies which COM port on your PC to use (in this case, which port the IR transmitter is attached to).
break ” sets a breakpoint on the function NewApplication() .
source ” loads the gdb script to support Unicode descriptor debugging. Again, the actual value of this depends on where your SDK is installed, and it may actually be in a separate Shared directory outside of the root of your SDK. As before, the path is specified using forward slashes.
Note that you need to build a debug target ( armi udeb ) version of your application, rather than the usual armi urel release build. Most Series 60 SDKs do not actually come with debug libraries, so it is possible that the build process will fail due to linker errors! If this is the case, you should copy everything from your SDK's target release directory ( \epoc32\release\armi\urel ) to your SDK's target debug directory ( \epoc32\release\armi\udeb ) and then rebuild.
Once your application has built successfully, you will need to create a new debugging .sis file. Do not forget to specify the udeb directories in your .pkg file, so as to package the correct (debugging) version of the binaries. This .sis file should be installed onto your Series 60 device in the usual way ”further details can be found in Chapter 2.
The first step is to start up gdbstub on the Series 60 device. Remember that it is located at \system\programs\gdbstub.exe ”you will need a file manager application to navigate to it and execute it, as previously noted. When it is running, you should see the infrared icon flashing ”this means that the gdbstub has been started.
To make the connection, you then need to start gdb on your PC, making sure that the Series 60 device and the PC have a line-of-sight connection. Open up a Windows command prompt and navigate to your application's group directory (where you placed the gdb.ini file) and type:
Gdb should start up and display some information, including the breakpoints that have been set:
GNU gdb 4.17-psion-98r2 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This version of GDB has been modified by Symbian Ltd. to add EPOC support. Type "show epoc-version" to see the EPOC-specific version number. This GDB was configured as "host=i686-pc-cygwin32 target=arm-epoc-pe". Breakpoint 1 at 0x10001012: file ..\..\..\..\..\..\SYMBIAN\6.1\SERIES60_ 1.2\SERIES60EX\HELLOWORLD\SRC\Helloworld.cpp, line 15. (gdb)
If you do not see the Symbian modification notice, then you may be using the wrong version of gdb ”check that your PC PATH environment is set up to correctly find the version of gdb in your Series 60 SDK.
If all is well, then the infrared connection will have been established and the infrared icon on the phone will be permanently on. To start debugging you should type run at the command prompt. Further information about the available debugging commands can be found under How to use GDB in the SDK documentation, and also at http://www.refcards.com/about/gdb.html.
When you have finished debugging, you need to close the session. Type q or quit at the Windows command prompt to stop the debugging session and exit gdb . Note, however, that it is not possible to bring gdbstub to the foreground on a device, as it is an .exe , so to close the session on the device, you will need to manually reset the phone (switch it off and back on again).