DJMDebug – Delphi debugging aid
Copyright © 1997 D.J.Murdoch
Version 0.5
DJMDebug uses TD32 debug information in the currently running program to translate addresses into meaningful identifiers.
Note: This version is for debugging use only. DO NOT DISTRIBUTE DJMDebug.DLL with your programs!
If you have questions about DJMDebug, send email to djmdebug@murdoch-sutherland.com. You may also want to visit my web page, http://murdoch-sutherland.com/programs/, for possibly newer versions of DJMDebug, and other programs.
Usage:
There are three ways to use DJMDebug: using the form (simplest), or using the component (best but needs installation), or calling the low level routines directly (most flexible). In all cases, make sure that "Include TD32 debug info" is selected in the Project|Options|Linker dialog, and if necessary rebuild your project. You also need to make sure that the DJMDEBUG.DLL file is in the directory of your project, or somewhere on the search path for DLLs.
Using the form interface
This is the easiest way to try out DJMDebug.PAS; it's used by the PROJECT2.DPR/UNIT2.PAS sample. Simply use the "Add file to project" button to add the DJMDebug.PAS unit and its associated form DebugBox to your project. Then add "DJMDebug" to the Uses clause of any unit where you need to use it, and when you want to see the debug form, make a call like
DebugBox.Snapshot('Some message');
The debug form will pop up, giving information about where it was called from and information from the stack at the time of the call. It will look something like this:
Exename: C:\STUFF\PROJECT2.EXE
Message: Some message
Address: 004319D0
Src line: Unit2.pas#50
Routine: Unit2.TForm2.Button1Click.Nested.Nested2
Stack view: Trace
007AF8B0: 004319D0 RETURN Unit2.TForm2.Button1Click.Nested.Nested2
[Unit2.pas#50]
007AF8BC: 00431A01 RETURN Unit2.TForm2.Button1Click [Unit2.pas#56]
007AF934: 0041CBAB RETURN Controls.[between TWinControl.DefaultHandler
and TWinControl.PaintHandler]
007AF9D4: 004232C5 RETURN Forms.[between Subclass3DWnd and
MakeObjectInstance]
007AF9EC: BFF7365E RETURN ?????.?????
007AFA0C: BFF9288F RETURN ?????.?????
007AFA20: 00008A55 RETURN ?????.?????
The first column of the stack trace gives the stack address, the second gives the value there, the third lists the type of thing, and the fourth tries to identify it. You'll probably see a somewhat different dump even in the demo project; it depends on the options you used during the compile and the exact version of Delphi that you've got. (This one is from Delphi 3.01; earlier versions didn't produce correct debug information for nested functions, so you might see a few extra question marks if you're using one of those.)
If you are running in the IDE, you can jump to the address listed by double clicking on an address value. This will raise an exception at that address and cause the IDE to jump there. (You need to have "Break on exception" selected in Tools|Environment options.)
If you don't have source for the line you click on, the IDE will pop open the CPU Window at the appropriate spot, provided you have it enabled. To enable it in Delphi 3, use the Registry Editor to set the string value "EnableCPU" to "1" in the HKEY_CURRENT_USER\Software\Borland\Delphi\3.0\Debugging registry key.
You can also make notes in the stack dump window and cut and paste from it; it's just a standard TMemo control. If you want to create an exception at some special address, just type the address (in hex) into the stack dump window, and double click on it.
From an exception handler, you should call
DebugBox.ShowException;
to get the debug form to report on the location of the exception rather than the location where it was called.
Using the component interface
The best way to use DJMDebug is to install it as a component file. This is used by the PROJECT1.DPR/UNIT1.PAS demo. It will add a component called "DebugDLG" to a "DJM" page of your component palette. Drop one of these on any form in a project, and then activate it in just the same way as above, replacing DebugBox with DebugDLG1 (or whatever you decide to name the component).
You can have as many of these components as you like; each will pop up a different dialog-like window.
The properties of the DebugDLG component are:
Caption:string;
{ The title of the debug window }
Exceptions:boolean;
{ Whether to respond to unhandled exceptions automatically. Only set this
to true for one DebugDLG component. Defaults to false. }
Modal:boolean;
{ Whether to respond pop up as a modal dialog. If it's modal, execution in
the thread won't continue until you close the debug window. Defaults to
false. }
ShowHint:boolean;
{ Whether to have popup hints describing the various parts of window. A
little useful at first, but they get irritating. }
Stackview: TStackView; { None, Trace, Dump or FullDump }
{ Which sort of stack display to give. See the description below. }
Name : string; and Tag:integer;
{ Standard TComponent properties }
Note: If you set Exceptions = true, then you get much better information by also using Stackview = Dump. The reason for this is that by the time the display occurs, the RTL has set the stack frame back to the point at which execution will resume. However, all the information about what called what is still there between the stack frame and the top of stack, and the Dump option can show it to you.
Using the low level interface
Add "DebugLow" to the Uses clause of any unit that needs it. Add a call to DebugLow.Initialize(exename), giving the name of the executable you want to look at (normally application.exename, or paramstr[0]).
Functions and Procedures in DebugLow:
function __file__:shortstring;
{ Gives the filename of the source code line where the
call occurs. }
function __line__:word;
{ Gives the line number of the source code line where the
call occurs }
function SrcLine(addr:pointer):string;
{ Gives the source file and line number of addr, or
blank if not found }
function Procname(addr:pointer):string;
{ Gives the unit and procedure name of containing addr, or
an approximation to them }
function Dataname(addr:pointer):string;
{ Gives the unit and name of the data object at the given address }
function StackTrace(Skip:integer):TStringlist;
{ Walks up the stack frame records, reporting on the
procedure and source line as it goes. Skips the given number
of records before starting. }
function StackDump(Skip,maxlines:integer;
full:boolean):TStringlist;
{ Goes all the way up the stack, reporting on anything that
looks like a return address or a pointer to static data.
May make false reports, but should include return addresses
to functions/procedures even if they don't set up a stack
frame. Skips the given number of stack frame records before
starting, then reports on everything it sees in the stack,
to a maximum of maxlines reports.
If full is true, then all values on the stack are dumped;
those that are unrecognized as addresses are shown as values
and as ascii equivalents. }
procedure Initialize(filename:string);
{ Set the EXE file to a new file. Called automatically
in the initialization section }
function StackLimit:integer;
{ The address just above the upper end (logical bottom) of the
stack. All addresses on the stack should be between ESP and
StackLimit. }
function CallerAddr:pointer;
{ The address of the caller of the current function. Requires
stack frames }
function CurrentIP:pointer;
{ The current address }
Reporting on run-time errors
When an error occurs during a running Delphi program, it's usually caught by the exception handler. However, certain errors get past it and you're left with a rather inscrutable error message. The DJMDebug unit installs an exit procedure that should give a more informative display in a windowed program; the DebugLow unit won't install it by default, but if you define "HandleRunErrors", it will do so.
Package Contents:
README.1ST This file
DJMDEBUG.PAS The unit for the form interface
DJMDEBUG.DFM The form file
DEBUGLOW.PAS The unit for the lower level unit
DJMDEBUG.DLL The support DLL which knows how to read TD32 information
PROJECT1.DPR A sample project, which should compile in D2 or D3; uses the DebugDLG component method, so you need to install DJMDEBUG.PAS to your palette first
UNIT1.PAS A unit of the sample project
UNIT1.DFM The form for the sample project. Click on the buttons to see debugging info.
PROJECT2.DPR A similar sample project, which uses the form method: it will work without installing the component.
UNIT2.PAS A unit of the 2nd sample project
UNIT2.DFM The form for the 2nd sample project.
License
This package is copyrighted freeware. You may use it for debugging purposes, but may not incorporate the code into projects that you distribute, nor redistribute it in any way. Do not give it to someone else, but feel free to give out the URL http://murdoch-sutherland.com/programs where you can pick up a new copy.
History
0.0 - Limited test release
0.1 - First release, distribution missing a file
0.2 - First complete release, changed hard links to DJMDEBUG.DLL to
soft links and improved error messages.
0.3, 0.4 - Minor bug fixes and enhancements, including doubleclick
jump to address, full dump, added runerror reporting
0.5 - Freeware release
Duncan Murdoch