You are on page 1of 13

BorDebug: Return Of The Giant

Making sense of TD32 debug information


by Hallvard Vassbotn

T he Delphi linker has always


had the option of including
so-called Turbo Debugger (TD32)
Turbo Debugger from the competi-
tion, for example. In any case, this
was a major obstacle for people
structure of the information. It
would still require hours and
hours of studying, trial and error
debug information (on the Linker who wanted to develop support for to turn the document into working
page of the Project Options Delphi modules in tools such code that could read the TD32
dialog). The internal IDE Debugger as debuggers, code analysers, debug information from a given
does not normally use this infor- profilers, memory checkers and so EXE file.
mation (Delphi 4 and 5 use it when on. In contrast, Microsoft have Then, in March 1999, Borland’s
debugging external DLLs and EXE openly documented their own Keimpe Bronkhorst contacted a
files), but instead relies on internal debugging format and even imple- number of interested people,
compiler structures built during an mented access routines in the including myself, asking us if we
interactive compile. IMAGEHLP.DLL (see the Delphi were willing to informally test this
External tools, such as Borland’s import unit in Source\Rtl\Win\ new DLL they were developing.
Turbo Debugger1, do rely on the ImageHlp.Pas). The DLL, called BorDebug.DLL,
TD32 information tacked on at the I know that a number of people would give relatively easy, albeit
end of the EXE or DLL file to enable have been forced to reverse engi- low-level, access to TD32 informa-
symbolic debugging. This informa- neer at least parts of the TD32 tion in any executable module.
tion is also used by a number of format to implement their tools. This was what we had been waiting
third party tools, such as This includes first-rate guys such for! A private mailing list was
Numega’s BoundsChecker2, Turbo- as Per Larsen of TurboPower established and we set out to test
3
Power’s suite of Sleuth QA tools, (Memory Sleuth, Sleuth QA), and start using this new DLL. Ini-
Atanas Stoyanov’s freeware Atanas Stoyanov of MemProof and tially, the only interface was
4
MemProof memory checking tool , QTime fame, and Stefan through a C header file, but I took
5
AutomatedQA’s QTime profiler Hoffmeister8, hacker extra- on the task of converting it to a
and Intel’s VTune sampling ordinaire and more recently a Delphi import unit. The result is
6
profiler . Borland employee. After a long the BorDebug.Pas unit presented
In this article we will see how we period of lobbying Borland, they in this article.
can utilise a relatively new DLL finally released a long and detailed In fact, I started writing this arti-
from Borland, BorDebug.DLL, to document to people that specifi- cle about a year ago, trying to get it
read and interpret the TD32 debug cally requested it in a file called published in the August 1999
information in our own applica- Giant.Txt. I got my copy from Char- ‘Gold’ issue of The Delphi Maga-
tions. This can be used for a lie Calvert in the middle of 1998. I zine. However, at that time,
number of purposes, although it later found that mostly the same Borland was not ready to publish
will be most useful for debuggers information had been released in the BorDebug.DLL, so the article
and other low-level tools. We will 1993 as part of a product called was put on hold, and I had to write
discuss the functionality provided Borland Open Architecture Hand- a new one9 in a hurry to fill the
by the BorDebug DLL, present an book for BC 4.0 (in a file named reserved pages! Then, in March
import unit that gives us access to BC32.TXT). 2000, Borland’s John Thomas
it from our Delphi applications, Internally, the TD32 format is released the BorDebug.DLL in a
look at a set of wrapper classes to known as the Giant format, pre- CodeCentral submission10. Now
simplify the usage and show some sumably because it can handle the DLL is freely available, I can
simple demonstration programs. larger amounts of information than finally finish off this article. Phew!
the older 16-bit format used in the
History old Turbo/Borland Pascal compil- Project settings
Traditionally, it has been very hard ers. Some might say it got its name There are a number of compiler
to get information on the internal because it makes your EXE files and linker options which can be
structure of the TD32 format. For giant-sized J. set in the IDE, the DCC32.CFG file,
many years, it was totally undocu- The release of this document or on the DCC32 command line,
mented, not even mentioned in was literally a giant step in the right that influence the level of detail of
places like Wotsit7. There might direction. However, the informa- the debug information included.
have been a number of reasons for tion in the document was very Bring up the Project Options
this: perhaps Borland wanted to terse, contained no example code dialog and select the Compiler
protect their own standalone and not even C structs defining the page, see Figure 1.

26 The Delphi Magazine Issue 63


➤ Above: Figure 1
Assertions ($C+) include the information and strip the debug
➤ Right: Figure 2 code for any Assert calls in the info out to a separate .TDS file. This
compiled code, this is a very useful way you don’t have to recompile
Although they are not strictly technique for catching problems the executable and most tools will
debug options, the code genera- early. Finally, the Use Debug DCUs still be able to load the debug infor-
tion option settings can greatly setting instructs the linker to use mation from the .TDS file.
influence how easy it is to debug the debug version of the RTL and The Linker page also contains
the resulting code. Turning off VCL pre-compiled DCUs (in your options to generate a text .MAP
Optimization ($O-) makes it easier Delphi\Lib\Debug directory). This file. The generated file contains
to follow the code (particularly in can make it easier to track prob- most of the debug information that
the CPU View) and to examine vari- lems that interact closely with the will be linked into the executable
able contents in after-the-fact situ- RTL and VCL. Note that this is when you select the Detailed
ations. Turning on Stack frames really a linker setting, although it option. However, it does not con-
($W+) can help the IDE and external appears in the Compiler page. tain information about parameters
tools to find correct return address Unless you plan to distribute your or local variables. Some tools
and parameter information from DCU files (for a shareware product, (such as HVEST9) that don’t know
the stack, helping you show how for instance), you probably want to how to deal with the native TD32
you ended up in the current keep both Debug information and format use this .MAP file instead.
routine. Local symbols checked at all times.
There are also a number of Next, select the Linker page of Files
debugging settings. The Debug the Project Options dialog. Included on the disk you will find
information setting ($D+) is a Normally all the DCUs of your pro- BorDebug.DLL and the original C
master setting, determining if the ject contain full debug information. header file, BorDebug.h. The
compiled DCU file contains any That debug information can be header file contains additional
debug information or not. Local included in the final executable file information on how to use each
symbols ($L+) turns on information by checking the Include TD32 debug API call. I decided not to duplicate
for local variables within routines info setting. The /V option of this documentation in the Delphi
and symbols only visible in the the command line compiler import file, BorDebug.Pas (it
implementation part of the unit. (DCC32.EXE) has the same effect. would be too much extra work to
Reference info ($Y+) and the The BorDebug.DLL, and thus all keep the two files in sync with each
related Definitions only ($YD) set- the example code presented in this other). So if you need documenta-
tings affect the level of information article, presume that the EXE files tion for a certain BorDebug API
available to the browser windows have been compiled with this set- call, search for the name in
in the IDE. These settings include ting. Note that this will typically BorDebug.h. I will include the most
extra information about where increase the size of your execut- relevant information and a higher-
symbols were declared and refer- able considerably. You probably level overview in this article.
enced. The BorDebug API has not don’t want to ship with debug
implemented access to this infor- information to your clients. Also, BorDebug Concepts
mation in the current version. To there is a tool shipped with To get a proper understanding of
11
get at it, you would have to glean C++Builder called TDStrp32.EXE the TD32 format and the BorDebug
the information yourself. that can take an EXE file with debug API, we must first establish the

November 2000 The Delphi Magazine 27


definition of some basic concepts orange and green DCU files both takes up some code and data
and relate those concepts to what have a grey box representing the space. The reason for this is the
we know as Delphi developers. debug information for each unit. automatically generated initializa-
They say an image is worth more The red System.DCU unit at the tion and finalization code and the
than a thousand words, so to keep top doesn’t contain any source- global counter variable this code
this article short (J), I’ve included level debug information, so we will uses (see an earlier article13 for
an overview image of how the not be able to single step into it. more details).
Pascal, DCU and EXE-files are Because the Delphi compiler and If the /V option is turned on
related with the different types of linker are so fast, and there is no (Include TD32 debug info), the
debug information in Figure 3. simple way to just compile the linker aggregates the debug infor-
This figure is most easily inter- units without linking the final mation it finds in the .DCU files and
preted from the right to the left. At executable, many Delphi develop- adds it to the end of the .EXE file
the bottom left, we have the source ers are unaware that there is a (represented by the grey box).
files for two units in the project, separate link step. The linker takes Because the System.DCU file does
Unit1 and Unit2. Unit1 is a plain, the generated .DCU files, does not have any debug information in
single file, unit, while Unit2 con- some magic, and then constructs a it, the linker only generates one
sists of an include file and an new .EXE file. The yellow and cyan Module section and one AlignSym
assembly file in addition to the areas in the figure represent section for it (the two red boxes
mandatory Pascal source file. A the code and data segments inside the grey debug info box).
standalone assembler (such as respectively. On the other hand, both the
TASM12) is used to turn the .ASM As you can see, in this example Unit1 and Unit2 units were
file into an .OBJ file. This OBJ file is all three units have both code and compiled with debug information,
sucked into the generated .DCU file data (shown as red, orange and and thus they both get SrcModule
by using the $L directive. From a green boxes inside the .EXE file). sections in addition to the manda-
debug info point of view, all we will There is also a segment for tory Module and AlignSym sections
see is that the unit has three source uninitialized global variables, but (the orange and green boxes).
files contributing to its contents: these don’t show up in the .EXE file Now we have a complete .EXE
Unit2.Pas, Unit2.Inc and Core.ASM. (other than the size field in the PE file with TD32 info attached. It will
We compile the source files and, header). It turns out that all Pascal run like a normal .EXE file, taking
provided we have used the right units always contribute to the code no extra memory at runtime,
settings ($D+,L+), the generated and uninitialized data segment, because the OS loader will simply
.DCU files have debug info tacked even though they don’t declare any ignore the debug information.
onto the end. You can see that the explicit code or variables. This However, when we load the .EXE
means that a unit containing only into a TD32-aware tool, the
constants or simple types still BorDebug.DLL (or corresponding
➤ Figure 3

EXE-file
Module Segment PE-Header
Linker
ModuleIndex LinkerSegment, System DCU
Code
Name Offset, Size
Segments Flags
Compiler
Initialized Data
Unit1.DCU
ScrModule ScrModRange
ModuleIndex Debug-info
Segment
Ranges Start EndOffset
SourceFiles Debug-info
Module Unit2.DCU
SourceFile Debug-info
SourceFileRange Module
Name
Segment Module
Ranges
Start-EndOffset
AlignSym {$D+,L+}
LineNumberOffsets
SrcModule
AlignSym
AlignSym SymbolInfo SrcModule
ModuleIndex Unit1.Pas
Name, Kind AlignSym
SymbolInfos Info, TypeInfo
(StartSymbols Names
NextSymbol) TypeInfo Types Unit2.Pas
Name Unit2.Inc
Browse-info Core.ASM
TypeKind, Info

28 The Delphi Magazine Issue 63


custom code) will be able to find source module section and symbol five: Modules, SrcModules, Symbols,
the debug information. section, that have the same Types and Names. The first three are
Finally, the white boxes at the module index number, belong all associated with a specific
left of the figure give more details together logically: they all derive module (ie a DCU unit). The last
about what kind of information the from the same unit. The Offset and two contain information on all
debug sections contain. It will be Size fields are low-level and tell us global types and all name strings.
useful to refer to these while I where in the .EXE file the debug Typically, we obtain indices
explain the structure of the TD32 information section can be found. obtained from the first three sec-
information. We use the BorDebugSubSection- tions (modules, source modules
Count routine to get the number of and symbols) and lookup the name
Gigantic Structure sections in the .EXE file and string or type information directly
As I have already discussed, the BorDebugSubSection to read the from the index.
TD32 debug information can be fields for each section.
tacked on to the end of the execut- Modules
able file, or it can be placed in a sep- Subsection Types In BorDebug-speak, a Module is an
arate .TDS file. To be able to read Each TD32 subsection contains a entity that contributes code
the debug information, we must of different type of information. The and/or data to the final executable.
course tell BorDebug where to find BorDebug defines eight different Examples of this are DCU and OBJ
it. We do this by calling the constants with a BORDEBUG_SST files. Even if you don’t have the
BorDebugRegisterFile function. If it prefix to designate the subsection source code for a DCU or OBJ file,
succeeds, this returns a handle types: see Table 1. the public information needed for
that we must use in all subsequent According to the documentation linking is added to a Module section
calls to the BorDebug routines. in BorDebug.h and Giant.Txt, the in the final debug information.
When we are done, we call the AlignSym section is reserved for Each module has a name index and
BorDebugUnregisterFile API to symbols defined in the implemen- we can use this to look up the name
close the file and release any asso- tation section or in a local scope of the DCU file. The module gets its
ciated resources. From the highest (ie, local variables). However, I module index from the subsection
level point of view, the TD32 infor- have found that this is not the case. header. It also has a segment
mation just consists of a number of In practice, AlignSym is the same as count, and we can retrieve infor-
subsections. GlobalSym and GlobalPub. In fact, mation about each module
during my testing I have only segment.
SubSections encountered AlignSym sections. A module can contribute code or
Subsections are a way to divide the You have to examine each symbol data to one or more segments.
debug information into logical inside the section to determine if it Typically, each module always
chunks. They don’t carry much has a global or local scope. We will states that it contributes to three
information in themselves, but act refer to all these three section segments: the code segment, the
as headers for the specific types as Symbol sections. initialized data segment (for typed
subsection types (see below). This simplifies things a little, as constants and initialized global
Each subsection has an associ- we are only left with six different variables) and the uninitialized
ated sequential module index, subsection types to worry about. data segment (for other global
starting at 1. A module section, Throw in the fact that the Browse variables). For some segments, the
section is not currently supported size can be 0 (if there are no initial-
by BorDebug and we are left with ized global variables in a unit, for
➤ Table 1
instance).
BorDebug_sstxxx Explanation When the subsection type is
BORDEBUG_SSTMODULE, you can read
MODULE Code and data from one OBJ or DCU file the module information with the
BorDebugModule function.
ALIGNSYM Symbols belonging to a local block
Segments
SRCMODULE Code from a single source file (.PAS unit) Each module typically contributes
to three segments. Each segment
GLOBALSYM Globally available symbols (variables, routines, etc) maintains information about the
corresponding logical segment
GLOBALPUB Same as GlobalSym index used by the linker (typically
0 for code, 1 for initialized data and
GLOBALTYPES Globally available type definitions 2 for uninitialized data), the offset
in the segment, the size and code
NAMES Mapping from name- and type-indices to name strings or data flags. We can use the
BorDebugModuleSegment API to read
BROWSE Browsing information (declaration and usage points) this information.

30 The Delphi Magazine Issue 63


Source Modules SysUtils.pas had two, all other index. This represents the type of
A source module corresponds to units had one range). the symbol (duh!). More interest-
the concept of a Pascal source unit ingly, we can use this type index to
with all its include files and any Line Numbers look up more detailed, logical
assembly files. The source module The mapping of addresses to unit information about the type. It is
inherits its module index from the line numbers and vice versa is a also possible to simply get a string
corresponding section header. very important part of the debug representation of the type.
This number can be used to associ- information. Each source file range All types have a fixed, common
ate the source module with the can have an array of line number header (sounds familiar?). This
DCU file it generated (ie, the Module and code-offset mappings. These header includes the type index, a
section) and the symbols it con- are read using the BorDebugSrc- type kind, an offset and a length (in
tains (in the Symbol section). Fur- ModuleLineNumbers routine. the debug info). Information spe-
ther, each source module contains cific to each type usually contains
a number of ranges and one or Symbols the name, but is otherwise very
more source files (more about this There are three subsection types diverse. For instance, an array
below). that logically map to the same type contains information about
When the subsection type is information: AlignSym, GlobalSym the type of the elements and index
BORDEBUG_SSTSRCMODULE, we can and GlobalPub. We just refer to all of (ie, a recursive type index), the
read the basic source module these as Symbol subsections. Just name of the type, the total size of
information using the BorDebug- as we have seen for Module and the array and the number of ele-
SrcModule function. Source Module sections, a Symbol ments in the array.
section inherits its module index With a type index in hand, we
Source Module Ranges from the corresponding section can get a string representation
A source module will contain one header. This allows us to easily from the BorDebugTypeIndexTo-
or more ranges. Each of these map the symbol information to the String procedure. If we are inter-
ranges is a combination of a seg- module (.DCU file) and source ested in the finer details, we first
ment index, a starting offset and an module (.Pas file) it belongs to. read the common type header
ending offset. This encodes infor- Because there are typically a using the BorDebugTypeFromIndex
mation about where the source large number of symbols associ- routine. Then we check the type
module contributes code and data. ated with a module, there is no kind field and call the correspond-
The source module ranges are read direct, index-based, method of ing BorDebugTypeXXX routine to get
in using the BorDebugSrcModule- reading them. Instead, we must the specific information. If this
Ranges routine. iterate over all of them, one by one. contains further type indices, we
Each symbol contains a fixed continue this process recursively.
Source Files common header. This includes a
Each source module will also con- symbol kind, offset and length (in Names
tain one or more source file entries the debug information). Then When we retrieve information
(one for the main .Pas file and one there is information specific to about an item that has a name (a
for each include or assembly file each kind of symbol. This often module, source file, symbol, type,
used). Each source file entry has a includes the name and a type index etc), we always get just a numeric
sequential index, a name and a (more on this below), what seg- name index. This name index isn’t
number of source file ranges asso- ment (code or data) it belongs to, very useful by itself, so we can use
ciated with it (more of below). The the offset where the information it to look up the actual name string
BorDebugSrcModuleSources function begins (inside the code or data seg- in the names section.
reads in the information for a ment), and so on. In the debug information, the
single source file entry. When the subsection type is names section is just a large block
BORDEBUG_SSTALIGNSYM, BORDEBUG_ of length byte prefixed and zero-
Source File Ranges SSTGLOBALSYM or BORDEBUG_SSTGLOB- terminated strings. There is no
As mentioned, each source file ALPUB, we can start to iterate over explicit name index encoded into
entry maintains information about the symbols using the BorDebug- the .EXE file, so this must be
the ranges where they contribute StartSymbols routine. Then we computed at runtime.
code or data (a segment index, plus read the common symbol header The BorDebugRegisterFile rou-
starting and ending offsets). These using the BorDebugNextSymbol func- tine can do this for you and it takes
are read using BorDebugSrcModule- tion. Finally, we check the symbol two Boolean parameters to decide
SourceRanges. Finally, each source kind field and call the correspond- how it should deal with this sub-
file range also contains mapping of ing BorDebugSymbolXXX routine to section. If the SkipNames parameter
line number and address informa- get the specific information. is True, BorDebug ignores the names
tion, see below. Normally, each section altogether and you cannot
source file has only a single range Types call any of the name-related APIs
defined for it (during my testing, As mentioned above, many sym- below. If SkipNames is False, the
System.Pas had three ranges and bols will have an associated type parameter CacheNames acts as a

November 2000 The Delphi Magazine 31


‘quick but memory consuming’ Browse information Win32 compiler and linker. Cur-
versus a ‘slow but memory lean’ The TD32 information can option- rently only the OS/2 version of
toggle. Normally you will set these ally contain information to help BC++ supports this browser infor-
parameters to False and True, browsing tools. The Browse section mation. Therefore it is not docu-
respectively. encodes information such as mented here.’
The following BorDebug routines where a symbol or type was
can be used to read the name defined and where it was used. The The Raw BorDebug API
section: BorDebugNamesTotalNames current version of the BorDebug API OK, with all that background infor-
returns the total number of name does not have any direct support mation under our belts, we are
strings. BorDebugNameIndexToName for reading this information. There ready to dive into the nitty-gritty
returns the string associated with is an undocumented routine called details of how to use BorDebug.
a specific name index. Alterna- BorDebugDumpBrowserInfo, but it The DLL exports a number of API
tively, we can call BorDebugName- seems like it is not implemented. If routines, listed out for you in List-
IndexToUnmangledName to get an you want to read and interpret this ing 1. Note that all the parameters
unmangled version of the name. information, you would have to and return types have been
Name mangling is the process of start with the offset and size infor- stripped from this listing to keep
encoding parameter and calling mation for the BORDEBUG_SSTBROWSE the size down: see the disk for the
convention information in the sub-section. After this you are complete version.
linker name of a routine. This pro- basically on your own. I will not discuss each and every
cess can make the information Information from the Giant.Txt BorDebug routine available with
nearly unreadable. Finally file (dated March 1999) even indi- all their parameters and their
BorDebugRegIndexToName converts a cates that browser information is meaning. If I did, the article would
register index into the string repre- not included in the TD32 info at all: probably fill this entire issue and I
sentation of the register (eg, EAX, ‘Note: There are several fields in would only be repeating much of
EBX etc). the symbol structures which refer the information found in the
to browser information. These BorDebug.h file. Instead, I will
fields are set to 0 by the current discuss the general structure of
➤ Listing 1

// General routines procedure BorDebugTypeARRAY;


function BorDebugRegisterFile; procedure BorDebugTypeCLASS;
procedure BorDebugUnregisterFile; procedure BorDebugTypeUNION;
function BorDebugSubSectionDirOffset; procedure BorDebugTypeENUM;
function BorDebugSubSectionCount; procedure BorDebugTypePROCEDURE;
procedure BorDebugSubSection; procedure BorDebugTypeMFUNCTION;
procedure BorDebugModule; function BorDebugTypeVTSHAPE;
procedure BorDebugModuleSegment; function BorDebugTypeLABEL;
procedure BorDebugStartSymbols; procedure BorDebugTypeSET;
procedure BorDebugNextSymbol; procedure BorDebugTypeSUBRANGE;
procedure BorDebugDumpBrowserInfo; procedure BorDebugTypePARRAY;
// SymbolXXXX routines: procedure BorDebugTypePSTRING;
procedure BorDebugSymbolCOMPILE; procedure BorDebugTypeCLOSURE;
procedure BorDebugSymbolREGISTER; procedure BorDebugTypePROPERTY;
procedure BorDebugSymbolCONST; function BorDebugTypeLSTRING;
procedure BorDebugSymbolUDT; function BorDebugTypeVARIANT;
procedure BorDebugSymbolSSEARCH; procedure BorDebugTypeCLASSREF;
procedure BorDebugSymbolOBJNAME; function BorDebugTypeWSTRING;
procedure BorDebugSymbolGPROCREF; function BorDebugTypeARGLIST;
procedure BorDebugSymbolGDATAREF; procedure BorDebugTypeStartFIELDLIST;
procedure BorDebugSymbolEDATA; procedure BorDebugTypeNextFIELDLIST;
procedure BorDebugSymbolEPROC; function BorDebugTypeDERIVED;
function BorDebugSymbolUSES; procedure BorDebugTypeBITFIELD;
function BorDebugSymbolNAMESPACE; function BorDebugTypeMETHODLIST;
function BorDebugSymbolUSING; procedure BorDebugTypeBCLASS;
function BorDebugSymbolPCONSTANT; procedure BorDebugTypeVBCLASS;
procedure BorDebugSymbolBPREL32; procedure BorDebugTypeIVBCLASS;
procedure BorDebugSymbolLDATA32; procedure BorDebugTypeENUMERATE;
procedure BorDebugSymbolGDATA32; procedure BorDebugTypeFRIENDFCN;
procedure BorDebugSymbolPUB32; function BorDebugTypeINDEX;
procedure BorDebugSymbolLPROC32; procedure BorDebugTypeMEMBER;
function BorDebugSymbolGPROC32; procedure BorDebugTypeSTMEMBER;
procedure BorDebugSymbolTHUNK32; procedure BorDebugTypeMETHOD;
procedure BorDebugSymbolBLOCK32; procedure BorDebugTypeNESTTYPE;
procedure BorDebugSymbolWITH32; procedure BorDebugTypeVFUNCTAB;
procedure BorDebugSymbolLABEL32; function BorDebugTypeFRIENDCLS;
procedure BorDebugSymbolENTRY32; function BorDebugTypeCHAR;
function BorDebugSymbolOPTVAR32; function BorDebugTypeSHORT;
procedure BorDebugSymbolPROCRET32; function BorDebugTypeUSHORT;
procedure BorDebugSymbolSAVREGS32; function BorDebugTypeLONG;
function BorDebugSymbolSLINK32; function BorDebugTypeULONG;
function BorDebugTypeREAL32;
// General routines 2: function BorDebugTypeREAL64;
procedure BorDebugSrcModule; function BorDebugTypeREAL80;
procedure BorDebugSrcModuleRanges; function BorDebugTypeQUADWORD;
procedure BorDebugSrcModuleSources; function BorDebugTypeUQUADWORD;
procedure BorDebugSrcModuleSourceRanges; function BorDebugTypeREAL48;
procedure BorDebugSrcModuleLineNumbers;
procedure BorDebugGlobalSym; // Name and type string routines
procedure BorDebugGlobalTypes; function BorDebugNamesTotalNames;
procedure BorDebugTypeFromIndex; procedure BorDebugNameIndexToUnmangledName;
procedure BorDebugTypeFromOffset; procedure BorDebugNameIndexToName;
procedure BorDebugRegIndexToName;
// TypeXXX routines: procedure BorDebugTypeIndexToString;
procedure BorDebugTypeMODIFIER; function BorDebugUnmangle;
procedure BorDebugTypePOINTER;

32 The Delphi Magazine Issue 63


the information, what it is, and how itself. Three CrackXXX routines help occurs. The BorDebug API isn’t
to typically use it. you interpret packed C bit fields. very forgiving if you give it invalid
The routines can be grouped There is also a conditional define handles or memory pointers.
broadly into four categories. There called DYNLINK_BORDEBUG. This is off Access violations can occur easily
are a number of general routines to by default, which means that if you are not careful.
open and close the debug informa- BorDebug.DLL will be implicitly We first call the BorDebug-
tion, iterate through the contents linked to in the usual manner. If the RegisterFile function to open the
and so on. Then there are the OS cannot find the DLL (in the file given on the command line. If it
BorDebugSymbolXXX routines that application directory or in the succeeds in locating the TD32 info,
decode each specific kind of path), the program will not load. By it returns a valid handle that we
symbol you might encounter. The enabling this define, the routines use in all subsequent BorDebug
BorDebugTypeXXX decodes the dif- are linked to dynamically at calls.
ferent kind of type information runtime (by calling LoadLibarary Then we get the total number of
available in the debug information. and GetProcAddress). This means names in the global symbol name
And finally you have a group of rou- that your application will still load section using the BorDebugNames-
tines used to iterate through and even if the DLL cannot be found on TotalNames routine. The indices
look up string representations of the system. This may be useful if it used by the name routines are
symbol and type indices. does not totally depend on reading 1-based, so we loop from 1 to
In addition to the constants and debug information (it could have NameCount, getting each name
routine imports translated from other functionality as well). The string using the BorDebugName-
the C header file, I’ve also added a magic that makes this possible is IndexToName routine, writing each
number of types and constants to defined in the HVDll unit, name to standard output. Finally,
make the interface more presented in Issue 4314. we close the BorDebug handle by
self-describing and to improve calling BorDebugUnregisterFile.
type safety. The BorDebug unit also Start Digging That wasn’t too hard, was it?
defines some extra helper routines Let’s get our hands dirty and write You can see parts of the (very
when BORDEB_EXTRAS is defined a little example program that uses long) output produced by this
(which it is by default). BorDebug- the BorDebug API. The small pro- program in Listing 3.
RegisterFileEx wraps the native gram in Listing 2 takes the filename After opening the debug info
BorDebugRegisterFile call and given on the command line and with BorDebugRegisterFile, you
raises an exception if an error dumps all the debug symbols to can get the number of available
occurs. It also optionally tries to standard output. sub-sections by calling BorDebug-
open a .TDS file if the debug info To keep the size of this sample SubSectionCount. Then you can
cannot be found in the executable down, it just aborts if an error iterate through these sub-
sections, calling BorDebugSub-
program BDDmpNam; Section for each index. The
{$APPTYPE CONSOLE}
uses retrieved subsections each have a
BorDebug; subsection type, offset and size.
var
Handle: TBorDebHandle; The subsection types will be one of
NameBuf: array[0..1024] of char;
NameCount : integer; the eight listed in Table 1.
i: integer;
BorDebError: TBorDebError;
We can enhance our simple con-
begin sole application to perform these
Handle := BorDebugRegisterFile(PChar(ParamStr(1)), false, true, BorDebError);
if BorDebError <> deOk then steps, writing information about
Halt;
NameCount := BorDebugNamesTotalNames(Handle); all the subsections available, and
writeln('Total names: ', NameCount); some extra information about the
for i := 1 to NameCount do begin // Note: Name index is 1-based!
BorDebugNameIndexToName(Handle, i, NameBuf, SizeOf(NameBuf)); modules and the source modules
Writeln(i, ' = ', NameBuf);
end; and counting the number of sym-
BorDebugUnregisterFile(Handle);
end.
bols in each symbol section, see
the BDSecInf program in Listing 4.
This is starting to get a little
➤ Above: Listing 2 ➤ Below: Listing 3
more involved. Let’s go through
Total names: 10820 the code.
1 = System A module subsection
2 = SysInit
3 = Windows (BORDEBUG_SSTMODULE) doesn’t have
4 = BorDebug
5 = BDDmpNam much extra information associ-
6 = @System@CloseHandle
7 = @System@CreateFileA ated with it, so we just retrieve its
... name index (with BorDebugModule)
10815 = @Bddmpnam@TCrackedClassMemberAttrib
10816 = ClassMemberProtection and look up the string for that
10817 = ClassMemberProperty index (with BorDebugNameIndexTo-
10818 = TClassMemberAttribs
10819 = TVtabOffset Name). We dump out this name and
10820 = TVtabOffsets
the segment count.

34 The Delphi Magazine Issue 63


program BDSecInf; [Name, SegmentCount]));
{$APPTYPE CONSOLE} end;
uses BORDEBUG_SSTSRCMODULE:
BorDebug, SysUtils, HVBorDebug; begin
var BorDebugSrcModule(Handle, Offset, RangeCount,
Handle : TBorDebHandle; SourceCount);
BorDebError : TBorDebError; // We'll ignore the ranges for now, lets get info
SubSectionCount: integer; // about the source files. Allocate enough memory
SubSectionIndex: integer; // for each source file array
SubsectionType : TSubsectionType; GetMem(SourceOffsets, SourceCount *
Module : TModuleIndex; SizeOf(SourceOffsets^[0]));
Offset : TFileOffset; GetMem(NameIndices , SourceCount *
Size : TByteCount; SizeOf(NameIndices ^[0]));
Overlay : TOverlayIndex; GetMem(RangeCounts , SourceCount *
LibIndex : TLibraryIndex; SizeOf(RangeCounts ^[0]));
Style : TDebuggingStyle; // Now get the source file segment offsets, name
NameIndex : TNameIndex; // indices and range counts
TimeStamp : TBDTimeStamp; BorDebugSrcModuleSources(Handle, Offset,
SegmentCount : TItemCount; SourceOffsets, NameIndices, RangeCounts);
Name : array[0..260] of char; // We don't care about the offsets and ranges
RangeCount : TItemCount; // right now, so just free them
SourceCount : TItemCount; FreeMem(SourceOffsets);
SourceOffsets : PFileOffsets; FreeMem(RangeCounts );
NameIndices : PNameIndices; // Write the names of the files that contribute to
RangeCounts : PItemCounts; // this unit
i : integer; for i := 0 to SourceCount-1 do begin
SymbolKind : TSymbolKind; BorDebugNameIndexToName(Handle, NameIndices^[i],
SymbolOffset : TFileOffset; @Name, SizeOf(Name));
SymbolLen : TByteCount; Write(' ', Name, ',');
SymbolCount : integer; end;
begin Writeln;
Handle := BorDebugRegisterFile(PChar(ParamStr(1)), FreeMem(NameIndices);
false, true, BorDebError); end;
if BorDebError <> deOk then BORDEBUG_SSTGLOBALSYM, BORDEBUG_SSTGLOBALPUB,
Halt; BORDEBUG_SSTALIGNSYM:
SubSectionCount := BorDebugSubSectionCount(Handle); begin
Writeln('Total subsections: ', SubSectionCount); SymbolCount := 0;
// Note: SubSection index is 0-based! BorDebugStartSymbols(Handle, SubSectionType,
for SubSectionIndex := 0 to SubSectionCount-1 do begin Offset, Size);
BorDebugSubSection(Handle, SubSectionIndex, while true do begin
SubsectionType, Module, Offset, Size); BorDebugNextSymbol(Handle, SymbolKind,
Writeln(Format('SUBSECTION #%d: %s, Module=%d, SymbolOffset, SymbolLen);
Offset=%.8x, Size=%d', [SubSectionIndex, if (SymbolKind = 0) and (SymbolOffset = 0)
SubsectionTypeToString(SubsectionType), Module, and (SymbolLen = 0) then
Offset, Size])); Break;
case SubsectionType of Inc(SymbolCount);
BORDEBUG_SSTMODULE: end;
begin Writeln(' ', SymbolCount, ' symbols');
BorDebugModule(Handle, Offset, Overlay, LibIndex, end;
Style, NameIndex, TimeStamp, SegmentCount); end;
BorDebugNameIndexToName(Handle, NameIndex, @Name, end;
SizeOf(Name)); BorDebugUnregisterFile(Handle);
Writeln(Format(' %s.DCU, SegmentCount=%d', end.

➤ Listing 4
A source module (BORDEBUG_ using the BorDebugStartSymbols
SSTSRCMODULE) has much more and BorDebugNextSymbol routines.
information available. For our pur- This gives us the kind of symbol, of what it produces when it’s run
poses, we are just interested in the and the offset and size of the on itself.
names of the source files that make symbol inside the debug informa- We can see that all the module
up the source module. We first get tion. Notice that the end of the sub-sections come first. Each sub-
the number of source files and symbol section is detected by section one has a sequential
ranges using the BorDebugSrcModule receiving the special value 0 for all module index, starting with 1. A
call. Ignoring the RangeCount for three parameters. To keep the size module index of 0 means that the
now, we then dynamically allocate of the output down, we don’t write sub-section is not associated with
three arrays to hold the offset, anything for each symbol, but just a specific module, this applies to
name index and range count for update a global counter and write the Global Types and the Names
each source file. Then we get the total count for each symbol sub-sections at the bottom of the
BorDebug to fill in information into section. listing.
our arrays by calling the BorDebug- Phew! As you can see we have to Furthermore, the offsets of each
SrcModuleSources API. At this point write a fair amount of code just to sub-section refer to the offset from
we don’t really care about the off- do some simple dumping of basic the beginning of the .EXE file,
sets and ranges, so we just free information. This API just screams where the debug information for
them again. Now that we have an to be wrapped in a set of classes. that sub-section can be found.
array of the name indices for the And we will do that. However, I Likewise, the sub-section size is
source files, we can just loop and think it is better to first use the raw the size of the debug information.
write each name out (again calling API in order to learn properly how Note that all Module subsections
BorDebugNameIndexToName to con- it works and how we can best have a segment count of 3. We’ll
vert each name index into a string). design our classes later. come back to this.
We must remember to free the Let’s see if we can gain some The program was compiled with
array of name indices. more understanding by studying the debug version of the RTL units.
Finally, for the symbol sections, the output from this program. So the first source module is the
we just iterate over all the symbols Listing 5 is an abbreviated version one for System.Pas. Note that the

36 The Delphi Magazine Issue 63


module index for this sub-section Class: Can I Have Your from having to call the BorDebug
is the same as the module index for Attention, Please? API routines directly.
the System.DCU module sub- By now you are probably already
section. This tells us that the dizzy with all the funny concepts Records Versus Classes
source files listed here produced and strange inter-dependencies: During the design of the TBorDebug
the System.DCU unit. We can also I know I was when I was writing this classes, several times I had to con-
see that the System unit actually article! Writing more involved sider when to use classes and
consists of one unit source file demo programs, or even full-scale when to use plain old records.
(system.pas), one include file applications, using the raw Classes are a higher-level con-
(getmem.inc) and five assembly BorDebug API could quickly become struct that allows you to add intel-
files (assign.asm, close.asm, very tedious indeed. Not to worry, I ligence and collaboration, the
opentext.asm, writestr.asm and have some Delphi classes which drawback is that you have to
_ll.asm). There is no mention of the will come to the rescue. Listing 6 explicitly create and free them.
intermediate .OBJ files produced shows the public sections of the Records can live in the stack and
by the assembly files: they are all classes in the HVBorDebug unit. need no lifetime management, but
sucked into the System.DCU file There are a number of classes in are dumber. Using old-style
and no trace of them can be seen in this unit, but normally you will objects might have been a good
the debug information. only create the top-level class: middle way, but they are not
Next we find an AlignSym sub- TBorDebug. Then you assign the officially supported, and I don’t
section. This also has a module Filename property to the file con- recommend using them.
index of 1, so it belongs to the taining the debug information and I ended up using classes for the
System.DCU module. The AlignSym set Active to True. Now you can higher-level concepts that own
sub-section contains all the sym- easily iterate or look up a name other items and thus simplify tasks
bols linked in from the System unit. index using the Names array prop- by making them somewhat intelli-
In this case we see that we have erty. This class also lets you easily gent. When the burden of continu-
1,268 symbols. This number will loop through all subsections and ally creating and destroying the
vary according to how much of conditionally create modules, items overweighed the potential
each unit we use. The smart linker source modules, symbol info and benefit of using classes, I stuck to
will remove symbols that are not type info classes. It will let you iter- plain records.
referenced and these will not show ate though the symbols within a You will also see in the
up in the debug information either. subsection. HVBorDebug unit on disk, that I have
We will look at the more detailed The other classes that support added a large number of record
information inside the symbol this main class are TBorDebug- types. These are used to hold the
sections a little later. Module, TBorDebugSrcModule, TMod- output parameters of the
The next source module is for uleSegment, TSourceFileEntry, BorDebugSymbolXXX and BorDebug-
the SysInit.DCU module (index 2) TLineNumberOffsets, TSymbolInfo TypeXXX routines. It is simpler to
and consists of one file, SysInit.pas. and TTypeInfo. These classes cor- have them as records, because I
Then follow the symbols for respond to the concepts we
SysInit. Then there is another discussed above and they shield us
➤ Listing 5
AlignSym sub-section, for module
index 3, which is the Windows.DCU Total subsections: 38
module. Notice the Windows unit SUBSECTION #0: MODULE, Module=1, Offset=00010828, Size=64
System.DCU, SegmentCount=3
doesn’t have any source module SUBSECTION #1: MODULE, Module=2, Offset=00010868, Size=64
SysInit.DCU, SegmentCount=3
defined for it, this is because SUBSECTION #2: MODULE, Module=3, Offset=000108A8, Size=64
Windows.DCU, SegmentCount=3
there is no debug version of (...more...)
Windows.DCU in the delphi5\lib\ SUBSECTION #11: MODULE, Module=12, Offset=00010AE8, Size=64
HVBorDebug.DCU, SegmentCount=3
debug directory. Only units com- SUBSECTION #12: MODULE, Module=13, Offset=00010B28, Size=64
BDSecInf.DCU, SegmentCount=3
piled with the debug information SUBSECTION #13: SRCMODULE, Module=1, Offset=00010B68, Size=23426
($D+) setting checked produce system.pas, GETMEM.INC, assign.asm, close.asm, opentext.asm,
writestr.asm, _ll.asm,
source module sub-sections in the SUBSECTION #14: ALIGNSYM, Module=1, Offset=000166EA, Size=28644
1268 symbols
EXE’s debug info. SUBSECTION #15: SRCMODULE, Module=2, Offset=0001D6CE, Size=388
SysInit.pas,
Also note that the browse sec- SUBSECTION #16: ALIGNSYM, Module=2, Offset=0001D852, Size=1038
tion type is absent, even if the units 44 symbols
SUBSECTION #17: ALIGNSYM, Module=3, Offset=0001DC60, Size=162350
were compiled with reference 7707 symbols
(...more...)
information included. The last SUBSECTION #32: SRCMODULE, Module=12, Offset=0005CAC6, Size=130
interesting bit of information HVBorDebug.pas,
SUBSECTION #33: ALIGNSYM, Module=12, Offset=0005CB48, Size=3888
gleaned from this output is that the 207 symbols
SUBSECTION #34: SRCMODULE, Module=13, Offset=0005DA78, Size=262
main project file is the only one D:\DelMag\BorDebug\Demos\BDSecInf.dpr,
SUBSECTION #35: ALIGNSYM, Module=13, Offset=0005DB7E, Size=966
that has a full path registered. Pre- 39 symbols
sumably, this can help a debugger SUBSECTION #36: GLOBALTYPES, Module=0, Offset=0005DF44, Size=327976
SUBSECTION #37: NAMES, Module=0, Offset=000AE06C, Size=373882
locate the project files.

November 2000 The Delphi Magazine 37


also use them in large variant create secondary classes and so scanning. The TCustomBorDebug-
records to have single TSymbolInfo on. To help with this, I wrote Scanner class doesn’t do anything
and TTypeInfo classes which can another class called TCustomBor- interesting by itself, but it defines a
store the representation of any DebugScanner. This class is defined number of virtual template meth-
symbol or type, respectively. in the BorDebugScanners unit, see ods in the protected section that
Listing 7. descendant classes are supposed
Simplified Scanning As you can see, this class has a to override.
Even with this level of support, I very simple public interface. You By inheriting from this class, it is
found that I still had to write a large firstly create it, giving it a reference fairly easy to write code to convert
amount of boiler-plate code to to the BorDebug instance that an address to the corresponding
iterate through the subsections, should be used when scanning. unit name and line number. The
calling the correct methods to Then you call the Scan method, TLineNumberScanner in the same
indicating what subsections and unit does this by overriding the
information you are interested in ScanLineNumberOffset method and
➤ Listing 6

unit HVBorDebug; property RangeCounts : PItemCounts ;


{ Simplified class interface for the BorDebug API property SourceFileList : TList ;
Written by Hallvard Vassbotn (hallvard.vassbotn@c2i.net) property SourceFiles[Index: integer]: TSourceFileEntry;
April 1999 - September 2000 } property SourceNames[Index: integer]: string;
interface end;
uses TModuleSegment = class(TObject)
Windows, Classes, TypInfo, BorDebug, SysUtils; public
type constructor Create(Module: TBorDebugModule;
// ... removed a lot of stuff -- see the code on disk SegmentIndex: TSegmentIndex);
TBorDebug = class(TObject) property LinkerSegment : TLinkerSegmentIndex;
public property Offset : TFileOffset ;
constructor Create(const aFilename: string = ''); property Size : TByteCount ;
destructor Destroy; override; property Flags : TSegmentFlags ;
procedure Open; end;
procedure Close; TSourceFileEntry = class(TObject)
property Handle: TBorDebHandle read GetHandle; public
property FileName: string; constructor Create(SrcModule: TBorDebugSrcModule;
property SkipNames: boolean; SourceFileIndex: TSourceFileIndex);
property CacheNames: boolean; destructor Destroy; override;
property Active: boolean; property BorDebug : TBorDebug ;
property NameCount: TItemCount; property Handle : TBorDebHandle ;
property Names[Index: TNameIndex]: string; property Offset : TFileOffset ;
property UnmangledNames[Index: TNameIndex]: string; property SrcModule : TBorDebugSrcModule;
property RegisterName[RegIndex: TRegNameIndex]: string; property Name : string ;
property SubSectionCount: TItemCount; property NameIndex : TNameIndex ;
property SubSections[Index: TSubSectionIndex]: property SourceFileIndex : TSourceFileIndex;
TBorDebugSubSection; property RangeSegments : PSegmentIndices ;
function CreateModule(const SubSection: property RangeSegmentStarts : PSegmentOffsets ;
TBorDebugSubSection): TBorDebugModule; property RangeSegmentEnds : PSegmentOffsets ;
function CreateSrcModule(const SubSection: property LineNumberCounts : PItemCounts ;
TBorDebugSubSection ): TBorDebugSrcModule; property LineNumerOffsetList: TList ;
procedure StartSymbols(const SubSection: property RangeCount : TItemCount ;
TBorDebugSubSection); property RangeLineNumbers[Index: integer]:
function GetNextSymbol(var Symbol: TBorDebugSymbol): TLineNumberOffsets;
boolean; end;
function CreateSymbolInfo(const Symbol: TLineNumberOffsets = class(TObject)
TBorDebugSymbol): TSymbolInfo; public
function CreateTypeInfo(const aType: TBorDebugType): constructor Create(SourceFile: TSourceFileEntry;
TTypeInfo; RangeIndex: TRangeIndex);
property TypeFromIndex[TypeIndex: TTypeIndex]: destructor Destroy; override;
TBorDebugType; property SourceFile : TSourceFileEntry;
property TypeFromOffset[Offset: TFileOffset]: property LineNumbers : PLineNumbers ;
TBorDebugType; property LineOffsets : PSegmentOffsets ;
property TypeName[TypeIndex: TTypeIndex]: string; property LineCount : TItemCount ;
property GlobalSymbols[const SubSection: property RangeIndex : TRangeIndex ;
TBorDebugSubSection]: TBorDebugGlobalSymbol; end;
property TypeCount: TItemCount; TSymbolInfo = class(TObject)
property TypesSignature: TSignature; public
property SubSectionDirectoryOffset: TFileOffset; constructor Create(BorDebug: TBorDebug; Symbol:
end; TBorDebugSymbol);
TBorDebugModule = class(TBorDebugObject) destructor Destroy; override;
public function GetTypeIndex(var TypeIndex: TTypeIndex):
constructor Create(BorDebug: TBorDebug; Offset: boolean;
TFileOffset); function GetNameIndex(var NameIndex: TNameIndex):
destructor Destroy; override; boolean;
property Overlay : TOverlayIndex ; property Symbol : TBorDebugSymbol;
property LibIndex : TLibraryIndex ; property SymbolOffset : TFileOffset ;
property Style : TDebuggingStyle; property Len : TByteCount ;
property TimeStamp : TBDTimeStamp ; property Kind : TSymbolKind ;
property SegmentCount : TItemCount ; property Info : TSymbolInfoRec ;
property NameIndex : TNameIndex ; property KindAsString : string ;
property Name : string ; end;
property ModuleSegmentList : TList ; TTypeInfo = class(TObject)
property Segments[Index: integer]: TModuleSegment; public
end; constructor Create(BorDebug: TBorDebug; aType:
TBorDebugSrcModule = class(TBorDebugObject) TBorDebugType);
public destructor Destroy; override;
constructor Create(BorDebug: TBorDebug; Offset: property BDType : TBorDebugType ;
TFileOffset); property TypeIndex : TTypeIndex ;
destructor Destroy; override; property TypeOffset : TFileOffset ;
property RangeCount : TItemCount ; property Length : TByteCount ;
property RangeSegments : PSegmentIndices; property TypeKind : TTypeKind ;
property RangeSegmentStarts : PSegmentOffsets; property Info : TTypeInfoRec ;
property RangeSegmentEnds : PSegmentOffsets; property NameIndex : TNameIndex ;
property SourceCount : TItemCount ; property KindAsString : string ;
property SourceOffsets : PFileOffsets ; end;
property NameIndices : PNameIndices ;

38 The Delphi Magazine Issue 63


unit BorDebugScanners; procedure ScanSrcModule(...); virtual;
interface procedure ScanSrcModuleRange(...); virtual;
uses procedure ScanSrcModuleSource(...); virtual;
BorDebug, HVBorDebug; procedure ScanSymbolInfo(...); virtual;
type procedure ScanSymbols(...); virtual;
TScanningOption = (soModule, soAlignSym, soSrcModule, procedure ScanModule(...); virtual;
soGlobalSym, soGlobalPub, soGlobalTypes, soNames, procedure ScanModuleSegment(...); virtual;
soBrowse, soSrcModuleRanges, soSrcModuleFiles); procedure ScanSubSection(...); virtual;
TScanningOptions = set of TScanningOption; procedure ScanSubsections; virtual;
TCustomBorDebugScanner = class(TObject) property CurrentSourceFileEntry: TSourceFileEntry;
protected property CurrentLineNumberOffsets: TLineNumberOffsets;
function WantSymbol(...); virtual; property CurrentSubSection: PBorDebugSubSection;
function WantType(...); virtual; property CurrentModule: TBorDebugModule;
function WantFieldList(...); virtual; property CurrentSrcModule: TBorDebugSrcModule;
function WantTypeInfoForSymbol(...); virtual; property ScanningOptions: TScanningOptions;
procedure StartFieldListScan(...); virtual; public
procedure EndFieldListScan(...); virtual; constructor Create(ABorDebug: TBorDebug);
procedure ScanLineNumberOffset(...); virtual; procedure Scan(ScanningOptions: TScanningOptions);
procedure ScanSrcModuleSourceRange(...); virtual; property BorDebug: TBorDebug;
procedure ScanSymbolTypeInfo(...); virtual; end;

➤ Listing 7
Also, all other sub-sections will be find a match, we keep the supplied
ignored, speeding up the scanning line number and unit name infor-
defining a new public method process. This could probably be mation (ie, the name of the current
called FindUnitnameLinenumber, see tuned further by ignoring source source file entry).
Listing 8. modules that have ranges which After the Scan call returns, we
The FindUnitnameLinenumber fall outside the address. If you need check if we found a match, and
function first saves the Address to look up a large number of return those values. On the disk is
parameter in a field so that the addresses quickly, you probably a small demonstration application,
ScanLineNumberOffset method can want to load the information into AddrLookup.dpr, which shows
see the value. Then it calls the Scan your own custom data structures. how to use this class: you can see it
method, indicating that it is inter- Inside the ScanLineNumberOffset running in Figure 4.
ested in the source modules and method we check if the current Note that the addresses used by
the files they contain. This ensures address is lower or equal to the this sample application are offsets
that the ScanLineNumberOffset will address we’re looking for. If we relative to the start of the code seg-
be called for all the source files don’t find a perfect match, we ment. Actual runtime addresses
found in the debug information. select the closest match. When we will be different, so you will have to

November 2000 The Delphi Magazine 39


TLineNumberScanner = class(TCustomBorDebugScanner) if (LineOffset <= FAddress)
private and (LineOffset > FBestMatch) then begin
FAddress: TSegmentOffset; FBestMatch := LineOffset;
FBestMatch: TSegmentOffset; FLinenumber := Linenumber;
FUnitname: string; FUnitName := CurrentSourceFileEntry.Name;
FLinenumber: TLinenumber; end;
protected end;
procedure ScanLineNumberOffset(LineNumber: TLineNumber; function TLineNumberScanner.FindUnitnameLinenumber(
LineOffset: TSegmentOffset); override; Address: TSegmentOffset; out Unitname: string;
public out Linenumber: TLinenumber): boolean;
function FindUnitnameLinenumber( begin
Address: TSegmentOffset; out Unitname: string; FAddress := Address;
out Linenumber: TLinenumber): boolean; FBestMatch := 0;
end; Scan([soSrcModule, soSrcModuleFiles]);
implementation Result := (FBestMatch > 0);
{ TLineNumberScanner } if Result then begin
procedure TLineNumberScanner.ScanLineNumberOffset( Unitname := FUnitname;
LineNumber: TLineNumber; LineOffset: TSegmentOffset); Linenumber := FLinenumber;
begin end;
end;

➤ Listing 8
convert those into relative offsets re-usable TDumpBorDebugScanner
by subtracting the base address of class. This inherits from TCustom-
the executable (typically $0040000 BorDebugScanner and overrides just I’ve created a little console pro-
for an .EXE file). In addition you about every virtual method to gram that uses this class to dump
normally have to subtract $1000 dump all the available debug infor- all the debug information of the file
(added by the linker). mation. The actual dumping is del- given on the command line to the
egated to a public OnDump event. standard output: see the code for
The Gigantic Dumper This way we can reuse all that BDDmpAll.Dpr in Listing 9.
No article about the TD32 debug boring dumping code (650 lines), Be warned that this program
format would be complete without and just plug in any media we like produces large outputs. When I ran
a generic dumper program. To (write it to standard output, dump it on itself, it produced a text file of
write this, I first developed a it to a TMemo control, or stream it). about 3.8Mb! No doubt much of
this information is redundant, but
it does give you a hint of just how
much information is in there. Just
for the heck of it, I also wrote a GUI
version: see Listing 10 and Figure
5.
Again, be aware that loading a
➤ Above: Figure 4 ➤ Below: Figure 5
file can take quite a few seconds.
Notice the special trick I use to
grow the Dump string. Without this,
the program eats memory like
crazy and takes forever to run. I’m
using a vanilla TMemo in this sample,
because it ran much slower with a
TRichEdit control.

Conclusion
I could continue this article almost
ad infinitum, but I have to put the

➤ Listing 9

program BDDmpAll; DumpToStdOut: TDumpToStdOut;


{$APPTYPE CONSOLE} begin
uses Debug := nil;
BorDebug, HVBorDebug, BorDebugScanners, DumpToStdOut := nil;
BorDebugDumpScanner; DumpScanner := nil;
try
type Debug := TBorDebug.Create(ParamStr(1));
TDumpToStdOut = class(TObject) DumpToStdOut := TDumpToStdOut.Create;
public DumpScanner := TDumpBorDebugScanner.Create(Debug);
procedure OnScannerDump( DumpScanner.OnDump := DumpToStdOut.OnScannerDump;
Sender: TObject; const Msg: string); DumpScanner.Scan([soModule, soAlignSym,
end; soSrcModule, soGlobalSym, soGlobalPub,
procedure TDumpToStdOut.OnScannerDump( soGlobalTypes, soNames, soBrowse,
Sender: TObject; const Msg: string); soSrcModuleRanges, soSrcModuleFiles]);
begin finally
Write(Msg); DumpScanner.Free;
end; DumpToStdOut.Free;
var Debug.Free;
Debug: TBorDebug; end;
DumpScanner: TDumpBorDebugScanner; end.

40 The Delphi Magazine Issue 63


procedure TScannerDemoForm.FormCreate(Sender: TObject); Move(Pointer(Msg)^, Pointer(@Dump[DumpPos+1])^,
begin Length(Msg));
Debug := TBorDebug.Create; // Update our position in the dump buffer
DumpScanner := TDumpBorDebugScanner.Create(Debug); Inc(DumpPos, Length(Msg));
DumpScanner.OnDump := OnScannerDump; end;
end; procedure TScannerDemoForm.ScanIt;
procedure TScannerDemoForm.FormDestroy(Sender: TObject); begin
begin SetLength(Dump, 4*1024*1024); // 4Meg!
DumpScanner.Free; DumpPos := 0;
Debug.Free; DumpScanner.Scan([soModule, soAlignSym,
end; soSrcModule, soGlobalSym, soGlobalPub,
procedure TScannerDemoForm.OnScannerDump(Sender: TObject; soGlobalTypes, soNames, soBrowse,
const Msg: string); soSrcModuleRanges, soSrcModuleFiles]);
var SetLength(Dump, DumpPos);
NewLength : integer; Memo.Lines.Text := Dump;
begin Dump := '';
// Grow the dump buffer if it is too small end;
if (Length(Dump) - DumpPos) < Length(Msg) then begin procedure TScannerDemoForm.OpenItemClick(Sender: TObject);
if Length(Msg) > Length(Dump) then begin
NewLength := Length(Dump) + Length(Msg) if OpenDialog.Execute then begin
else Debug.Filename := OpenDialog.Filename;
NewLength := Length(Dump) * 2; Debug.Open;
SetLength(Dump, NewLength); ScanIt;
end; Debug.Close;
// Just move the content quickly end;
// over to the dump buffer end;

➤ Listing 10
limit somewhere. Now I have given
you a Pascal wrapper around the
BorDebug.DLL, a set of wrapper References
classes, a simplified scanner tem-
plate class, a couple of 1. Borland Turbo Debugger, a free download:
www.borland.com/bcppbuilder/turbodebugger/
descendants that do some real
work, plus examples of how to use 2. Numega BoundsChecker for Delphi:
it all too. www.numega.com/products/aed/del.shtml
Possible usability extensions 3. Turbo Power Sleuth QA Suite:
would be to write a non-visual com- www.turbopower.com/products/sleuthqa/
ponent that has events for all the 4. Atanas Stoyanov’s MemProof home page:
virtual scanning methods. More www.totalqa.com/downloads/ memproof.asp
interesting would be to find appli-
cations for the debug information 5. AutomatedQA QTime:
www.totalqa.com/index.asp
itself.
One possible candidate is the 6. Intel VTune: http://developer.intel.com/vtune/
‘old’ Exceptional Stack Tracer 7. Wotsit’s Format – The programmer’s reference: www.wotsit.org/
code, presented back in Issue 50.
8. Stefan Hoffmeister: www.econos.de/index.html
Currently, Vitaly’s RTLI code uses
information from the .MAP file. It 9. Hallvard Vassbotn, TDM Issue 50, October 1999, Exceptional Stack Tracing
could be extended to take the infor- 10. John Thomas, Borland Debug Hook Library and Header File:
mation from the TD32 info, either http://ww6.borland.com/codecentral/ccweb.exe/listing?id=14513
at design-time to re-pack it, or 11. Unfortunately, TDStrp32.EXE is not included with Delphi, nor the free
directly at runtime. Another inter- BC++ compiler: www.borland.com/bcppbuilder/freecompiler/
esting project would be to write a
12. Borland’s Turbo Assembler 5.0:
custom Win32 symbolic debugger
http://shop.borland.com/Product/ 0,1057,3-15-CQ100146,00.html
or runtime inspector. Or maybe
take up the competition with 13. Hallvard Vassbotn, TDM Issue 38, November 1998, Slimming The Fat
QTime and Sleuth QA and write Off Your Apps
your own profiler? The world is 14. Hallvard Vassbotn, TDM Issue 43, March 1999, DelayLoading of DLLs
now very much your oyster!

Hallvard Vassbotn is a Senior


Systems Developer at Infront AS
(visit www.theonlinetrader.com),
where he develops systems for
distributing real-time financial
information over the internet.
You can reach him at
hallvard.vassbotn@c2i.net

November 2000 The Delphi Magazine 41

You might also like