As mentioned previously, debugging is an advanced topic in itself. However, there are several specific issues and cases that are beyond the basic debugging techniques presented in the first section of this chapter.
For any serious application development and debugging, I thoroughly recommend that you use the Windows NT (WinNT), Windows 2000 (Win2K, which is based on Windows NT), or Windows XP Professional operating systems, and not the Windows 95 or Windows 98 (Win9x) operating systems. The newer versions provide a much more stable environment, particularly with buggy applications.
WinNT-based operating systems handle application stopping and crashes much better than Win9x. On a Win9x system, it is much easier to crash C++Builder or even the whole system when debugging or stopping an application midstream. Use Run, Program Reset sparingly, and stop the application through normal user means if possible.
Note that on any system it might be possible for database applications to hang the BDE as a result of a Program Reset.
When your application performs an illegal operation or access violation while running within the C++Builder IDE, an error occurs, and you are presented with an error dialog box. On a Win9x system you should reset your application using Run, Program Reset before closing the dialog box. This usually recovers more reliably than when closing the dialog box first.
For really serious debugging, particularly of Windows system applications, you can obtain a debug version of the Windows operating system, called a debug binary for Win9x and checked/debug build for WinNT/2K/XP. The checked build provides error checking, argument verification, and system debugging code for the Windows operating system code and Win32 API functions, mostly in the form of assertions that are not present in the retail version. This checking imposes a performance penalty. These special builds of Windows operating systems are provided with some Microsoft Developer Network (MSDN) subscriptions (see http://msdn.microsoft.com for more information).
Sometimes it is useful to know if your application is running in the context of the debugger. The Win32 API function IsDebuggerPresent() returns true if it is. You can use this fact to alter the behavior of the application at runtime; for example, outputting additional debug information to make the application easier to debug.
Now let's look at several more advanced debugging tasks .
Earlier in this chapter we examined some basic techniques for locating bugs. Access violations (AVs) are sometimes more difficult to locate than general program bugs . Other application errors are similar to AVs, and the techniques described here apply to those also.
Access violations can be caused by access to memory that is not within the application's memory space. If at all possible, you should use CodeGuard to check your application at runtime. CodeGuard can detect many errors that would normally result in an AV and pinpoint the exact line of source code that produced the error. If you can't use CodeGuard for your application, or CodeGuard does not detect the error that caused the AV, there are other things you can do to track down the error.
When an AV occurs, a dialog box is presented with the message Access violation at address YYYYYYYY. Read of address ZZZZZZZZ . Application errors can present a different message, such as The instruction at 0xYYYYYYYY referenced memory at 0xZZZZZZZZ . In these cases, the YYYYYYYY address is the machine code that caused the error, and address ZZZZZZZZ is the invalid memory address that it attempted to access.
It is possible to locate where some access violations occurred by implementing a global exception handler. Alternatively, you can run your application within C++Builder and wait for the AV to occur.
If you can't reproduce the AV when running within C++Builder, simply pause your application using Run, Pause or by setting and hitting a breakpoint, open the CPU view and select Goto Address from the context menu. This is not foolproof, but it often works. Enter the code address given in the AV dialog box in hexadecimal as 0xYYYYYYYY . The code around this address might give you some clue as to where in your source code the AV occurred, particularly if the application was compiled with debug information.
When the memory address ZZZZZZZZ is close to zero, for instance 00000089 , the cause is often an uninitialized pointer that has been accessed. The following code would produce an AV with this memory address because the MyButton object was never created with new .
TButton *MyButton; MyButton->Height = 10;
What is actually happening is that when MyButton is declared it is initialized with a value of zero. The address 00000089 is actually the address of the Height property within the TButton object if it were located at address zero.
As a general rule, you should explicitly initialize pointers to some recognizable value before the memory or object is allocated, and back to that value once it has been freed. If you get an AV that lists this value, you know an uninitialized pointer caused it.
Sometimes an AV can occur in a multithreaded application in which concurrent access to objects and data is not controlled. These can be very difficult to find. Use data breakpoints and the outputting debug information techniques described earlier in this chapter if you suspect concurrency problems.
When a process is running outside the C++Builder IDE, you can still debug it using the integrated debugger by attaching to it while it is running. This feature can be handy during testing. When you detect the occurrence of a bug in the application, you can attach to the application process and track down the bug. The only drawback is that Windows does not provide a method for detaching from the process without terminating it.
To attach to a running process, select Run, Attach to Process. The Attach To Process window is displayed with a list of running processes on the local machine. Select the appropriate process from the list and click the Attach button. The C++Builder debugger will then attach to the process. The process will be paused , and the CPU view will be displayed at the current execution point. You can step through the code, set breakpoints, load the source code, if available, using View Source from the context menu, inspect values, and so on.
Attach To Process is even more useful for remote debugging. In the Attach To Process window, you can view and attach to processes on another machine that is running the remote debug server. This is covered in the "Using Remote Debugging" section, later in this chapter.
In the window you can also view system processes by checking Show System Processes.
You should be very careful about attaching to any old process; you can cause Windows to crash or hang by attaching to system processes. Stick to attaching to your own processes.
Just-in-time (JIT) debugging is a feature of the Windows NT and higher operating systems that enables you to debug a process at the time that it fails; for instance, when an access violation is caused. JIT debugging might not be available on Windows 9x machines.
If you've used Windows NT or Windows 2000 before, you've no doubt heard of Dr. Watson. This is a JIT debugging tool provided with Windows to help identify the cause of a program failure. The selected JIT debugging tool can be changed. The current JIT debugging tool is usually set via a Registry entry; however, the Borland debugger launcher, BORDBG*.EXE (the * refers to the current version number), can be called instead of Dr. Watson. Then, with each JIT debugging instance, you can select which debugger to use from the debugger launcher, such as the C++Builder debugger, Delphi debugger, Dr. Watson, or even the Borland Turbo Debugger.
Prior to C++Builder 5, the call to Dr. Watson could be replaced with a call directly to the C++Builder debugger; no debugger selection was available. If only one debugger is configured in the list, it is automatically launched. See "Just in time debuggers," in the C++Builder online help for instructions on how to configure the JIT debuggers to list in the debugger launcher.
After configured, JIT debugging is easy to use. When the application crashes, Windows will run the debugger launcher. Select the appropriate debugger from the list, BCB (C++Builder) in this case, and click OK. At this point, C++Builder will start if it is not already running, and the application will be paused as if it were attached while running. You can then use any of the techniques described earlier in this chapter to locate the source of the bug.
Remote debugging is the capability to debug an application running on another machine using the C++Builder interactive debugger running on your local machine. It is beneficial for applications running on remote machines that would be inconvenient to access physically, and it does not require C++Builder to be installed on the remote machine.
Remote debugging is very useful for debugging distributed applications, such as those that use DCOM or CORBA. Debugging should be performed locally whenever possible because of the reduced performance when debugging across a network.
Remote debugging is supported for executables, DLLs, and packages. The application must have been compiled with debugging information, and the debugging symbol's .tds file must be available with the application on the remote machine. The easiest way to achieve this is to load the application's project into C++Builder on the local machine. Then, specify the Final output path in the Directories/Conditionals tab of the project options to be the shared network folder on the remote machine where the application will run. And, finally, compile the application with debug information.
Remotely debugging an application is virtually seamless. After the remote debug session is connected, you work just as you would when debugging a local application.
Remote debugging works by running the Borland debug server on the remote machine. You might notice that the Borland debug server is the same program as the Borland debug launcher, described previously in the "Using Remote Debugging" section. It can perform either of these functions depending on the command-line options used to start it. The debug server requires additional DLLs to be installed. The local C++Builder debugger communicates with the debug server.
On remote Windows NT and higher machines, the debug server is usually installed as a service. It will show as Borland Remote Debugging Service in the Services applet of the Control Panel. The debug server service can be started or stopped from the applet and can be set to start automatically when the system boots. Use the -install and -remove command-line options to install and remove the service.
On remote Windows 9x machines, the debug server is a standalone process. This is also an option for WinNT and up machines. In any case, the remote debug server must be running before remote debugging can commence.
You can install the debug server with associated DLLs required from the C++Builder installation CD using the standard install dialog or by running SETUP.EXE in the RDEBUG folder of the CD. Remote debugging uses TCP/IP for communication between the local C++Builder IDE and the remote debug server. You must have TCP/IP networking configured correctly on both machines.
To start the debug server manually, run BORDBG*.EXE -listen . You will need administration or debugging rights to run the debug server.
When the debug server has been installed on the remote machine and it is already running, you can start debugging remotely. From the local C++Builder IDE, open the project for the remote application that you will be debugging. Select Run, Parameters, click the Remote tab, and set Remote Path to the remote application's full path and application filename as you would use locally on that machine, such as C:\Temp\MyProj.exe . If you are debugging a DLL on the remote machine, enter the path and name of the remote application that will host the DLL. Enter any command-line parameters for the application in the Parameters field. Set Remote Host to the hostname or IP address of the remote machine.
To start debugging immediately, or when you don't have the application project loaded in C++Builder, just click the Load button. If you have the application project loaded, you can check Debug Project On Remote Machine and click OK. When you perform any debug command on the application within C++Builder, the debugging connection to the remote application will be established. You can then debug the application just as if it were running on the local machine.
If you get the error Unable to connect to remote host , check that the debug server service or process is running, Remote Host is set correctly, and that you have connectivity to the remote host using ping.exe or another network tool. If you get the error Could not find program 'program' , check that Remote Path is correct and that the application is actually located there.
Another feature of remote debugging is an extension of Attach To Running Process. Select Run, Attach To Process, enter the name of the remote machine in the Remote Machine field, and press Enter. The processes on the remote machine are listed; select one and click Attach to debug it. To use remote process attachment, the remote machine must be running the debug server. Remember that when attaching to a running process, there is no way to detach without terminating it.
Debugging a DLL is very similar to debugging any normal executable application except that a host application is required to load it. You can create the host application that uses the DLL, but in most cases you will be using an existing host, such as an application written in another language that uses the DLL that you have developed.
Load the DLL project into C++Builder and set any breakpoints in the DLL source code as necessary. Specify the host application that will load the DLL by entering the full path and name of the host application in the Host Application field on the Local tab from the Run, Parameters dialog. Enter any command-line parameters for the application in the Parameters field if necessary.
When the host application is specified, either select Load to run the host application and begin debugging, or simply press OK and run the host application at a later time with Run, Run. You might do this after setting additional breakpoints or setting up watches , for example.
That's all there is to it. When the breakpoint in the DLL code is hit, you can step through the source code and use the Debug Inspector, watches, or any other technique during the debug process. You can use this technique for debugging COM objects and ActiveX components , but for separate processes you can do this only on Windows NT and Windows 2000 systems that allow cross-process debugging.
Top |