**** W A R N I N G **** The documentation for this program is typesetted exactly like the Borland manuals. When this documentation is converted to plain ascii it doesn't look very pretty. Especially graphics are distorted, don't see chapter 4. This manual is primarily intended to give you an idea of its contents. Registered users receive a printed and bound manual. Besides they receive the manual in postscript format on disk. **** **** Borland Pascal Debug Kit ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Programming Guide Version 1.10 NederWare Burgerstraat 22 5311 CX Gameren The Netherlands compuserve: 100120,3121 email: berend@beard.nest.nl fidonet: 2:281/527.23 C O N T E N T S ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Introduction Program with less pain 1 Chapter 5 Assertions 23 Why this tool?........................ 1 Features ............................. 1 Part 2 Reference Manual What's in this manual ................. 2 Typefaces used in this manual .......... 2 Chapter 6 Pascal Debug kit How to contact NederWare ............ 3 Reference 27 Acknowledgements ................... 3 Sample procedure .................... 27 Archive constant...................... 27 Assert procedure ..................... 28 Part 1 User Manual Beep procedure....................... 28 Chapter 1 Working with log files 7 BeepOff procedure.................... 28 How to create or open a log file ......... 7 BeepOn procedure .................... 28 How can you write information in the BrowserRecordSize variable............ 29 log file .............................. 8 BrowsersOffset variable ...............29 ClassesOffset variable .................29 Chapter 2 Checking your memory 9 ClassRecordSize variable ..............29 What you must do before MemCheck CMPB function ....................... 29 can work ............................ 9 CMPW function ...................... 30 Updating the standard library ........ 9 ConvMemLimit variable............... 30 Recompiling the system unit ......... 10 CorrelationRecordSize variable .........30 What MemCheck does ................ 10 CorrelationsOffset variable............. 30 How to enable memory checking ....... 11 CPos function ........................ 30 How MemCheck works ............... 11 CR constant .......................... 30 MemCheck and DPMI................. 12 CreateBAK procedure ................. 31 DataOffset variable ...................31 Chapter 3 Post Mortem Debugger 13 DateTime type ....................... 31 What the Post Mortem debugger does ... 13 DebugHeader variable ................ 31 Using the Post Mortem Debugger....... 14 DebugInfoStart variable ............... 31 The Post-Mortem-Debugger under Delay variable........................ 32 MS-Windows ........................ 16 dfXXXX constants .................... 32 Extra features under Windows ....... 16 DirStr type........................... 32 GPF's under Windows .............. 16 Discard procedure .................... 32 Chapter 4 TDInfo 17 DisposeSLink procedure............... 33 How TDInfo models Borland's Debug DonePMD procedure.................. 33 Information .......................... 17 DoneStrings procedure ................ 33 Changes between Open Architecture DosCopy procedure................... 33 names and TDInfo names.............. 19 DosDel procedure .................... 34 How to make use of TDInfo ............ 20 DosDelay procedure .................. 34 Initializing the TDInfo unit...........20 DosMove procedure .................. 34 Getting information from a debug DosTouch procedure .................. 35 symbol file......................... 20 DosWipe procedure ................... 35 Getting line numbers.............. 21 DStream variable ..................... 36 Getting procedures ............... 21 DumpStack variable .................. 36 DumpStackProcedureType type ........ 36 i Empty function....................... 36 LoadStrings procedure ................ 46 ExtractStr function .................... 36 LogError procedure ................... 46 ExtStr type........................... 36 LowCase function .................... 46 FancyStr function ..................... 37 LowStr function ...................... 46 FatalErrorText variable ................ 37 MaxConventionalMemoryBlock constant 46 FCreate function...................... 37 MaxMemPtrs constant................. 46 FDefaultExtension function ............ 37 MaxWord constant.................... 47 ferr variable.......................... 37 MemberRecordSize variable............ 47 FExpand function..................... 38 MembersOffset variable ............... 47 FF constant .......................... 38 MemCheckReport procedure ........... 47 FForceDir function.................... 38 memfXXXX constants ................. 47 FForceExtension function .............. 38 Min function ......................... 48 FileExist function ..................... 38 ModuleClassesOffset variable .......... 48 FileRec type.......................... 38 ModuleClassRecordSize variable ....... 48 fmXXXX constants .................... 39 ModuleRecordSize variable ............ 48 FOpen function....................... 39 ModulesOffset variable................ 48 FormatStr procedure .................. 39 NamesOffset variable ................. 49 FormFeed constant.................... 39 NameStr type ........................ 49 FTCopy function ..................... 40 NewSLink function ................... 49 GetAddrStr function .................. 40 OverloadRecordSize variable........... 49 GetDateStr function ................... 40 ParentRecordSize variable ............. 49 GetEnv function ...................... 40 ParentsOffset variable .................49 GetFileName function ................. 40 PathStr type.......................... 50 GetLogicalAddr function .............. 40 PrintError variable ....................50 GetObjMemory function............... 41 PrintErrorType type................... 50 GetStr function ....................... 41 prnXXXX constants ................... 50 GetTextFileName function ............. 41 PSLink type.......................... 51 GetTickCount function ................ 41 Registers type ........................ 51 GetTimeStr function................... 42 RepChar function..................... 51 GetUniqueFileName function .......... 42 ReplaceStr procedure.................. 51 HandleRunTimeError variable ......... 42 ReportFileName constant .............. 51 HandleRunTimeErrorProcedureType type 42 RightJustify function ..................52 HexB function........................ 42 rsGet function ........................ 52 HexStr function ...................... 42 rsGet1 function ....................... 52 InitBBError function .................. 43 rsGet2 function ....................... 52 InitIntHandler procedure .............. 43 ScanB function ....................... 53 InitObjMemory procedure ............. 43 ScanW function ...................... 53 InitPMD procedure ................... 43 ScopeClassesOffset variable ............53 IsDirectory function................... 44 ScopeClassRecordSize variable ......... 53 IsFileOpen function ................... 44 ScopeRecordSize variable .............. 53 IsValidPtr function.................... 44 ScopesOffset variable ................. 53 LeadingZero function ................. 44 scXXXX constants..................... 53 LeftJustify function ................... 45 SearchRec type ....................... 54 LF constant .......................... 45 SegmentRecordSize variable ........... 54 LineNumberRecordSize variable........ 45 SegmentsOffset variable ............... 54 LineNumbersOffset variable ........... 45 SetHandleCount procedure ............ 54 ii SmallDebugHeaderSize constant ....... 55 Methods........................... 67 SmallEndianI procedure ............... 55 TObjMemory object ................... 67 SmallEndianL procedure .............. 55 Fields .............................67 SmallEndianW procedure.............. 55 Methods........................... 67 SourceFileRecordSize variable .......... 55 TOverload type....................... 68 SourceFilesOffset variable ............. 55 TParent type ......................... 68 Spc function ......................... 56 TResourceCollection object............. 69 Spoiled function ...................... 56 Methods........................... 69 StrB function ......................... 56 TResourceFile object ..................69 StrI function ......................... 56 Fields .............................69 Strings variable....................... 56 Methods........................... 69 StripSpc function ..................... 56 TResourceItem type................... 70 StrL function ......................... 57 TrimRight function.................... 70 StrR function......................... 57 TScope object ........................ 70 StrResBufSize variable................. 57 Fields .............................70 StrS function ......................... 57 Methods........................... 71 StrW function ........................ 57 TScopeClass type ..................... 71 stXXXX constants ..................... 57 TSegment object ...................... 71 SymbolRecordSize variable ............ 58 Fields .............................71 SymbolsOffset variable ................ 58 Methods........................... 72 TBrowser object ...................... 58 TSLink type.......................... 73 Fields ............................. 58 TSmartBufStream object ............... 73 Methods........................... 59 Fields .............................73 TClass object ......................... 59 Methods........................... 73 Fields ............................. 59 TSourceFile object .................... 73 Methods........................... 59 Fields .............................74 TCorrelation object.................... 59 Methods........................... 74 Fields ............................. 60 TSymbol object ....................... 74 Methods........................... 60 Fields .............................74 TDebugHeader type .................. 60 Methods........................... 74 TDInfoPresent function................ 62 TType object ......................... 75 TDriveStr type ....................... 62 Fields .............................76 TextPrintError procedure .............. 62 Methods........................... 76 TextRec type ......................... 63 TypeRecordSize variable............... 77 tid voidXXXX constants ............... 63 TypesOffset variable ..................77 TLineNumber object .................. 63 UpStr function ....................... 77 Fields ............................. 63 ValB function......................... 77 Methods........................... 63 valcode variable ...................... 77 TMember object ...................... 63 ValHex function ...................... 77 Fields ............................. 65 ValI function ......................... 78 Methods........................... 65 ValL function......................... 78 TModule object....................... 65 ValR function ........................ 78 Fields ............................. 65 ValW function........................ 78 Methods........................... 66 Warning procedure ................... 78 TModuleClass type ................... 66 ZeroRightJustify function ..............79 TObject object ........................ 67 iii Appendix A Errors and omissions in Open Architecture Handbook 81 iv I N T R O D U C T I O N ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Program with less pain Why this tool? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Once, I tracked down a bug that caused some strange and unpredictable behaviour. After finally having located the problem, it appeared that I had released a bigger block of memory than what I had previously allocated. Borland Pascal, naturally, didn't like this. Well, neither did I since, all in all, this bug took me 10 hours to find and fix. Some time later, I encountered another bug that caused a behaviour which I remembered but all too well. At that stage I thought: let's put these 10 hours to some better use. That thought was the start of MemCheck, a utility that tracks memory allocations and deallocations. If you release too much memory or too little, MemCheck aborts your application, writing the address of the erroneous code to a log file. MemCheck has since been enhanced to write the name of the file and the line of code using TDInfo, a utility that accesses the Debug information, if present, in an executable file. The above mentioned tools, MemCheck, TDInfo and PMD, together with Assertions, a unit which provides you with C like assertions, form the Borland Pascal Debug Kit. It's my sincere hope that these tools will prove useful to Pascal programmers all around the world, helping them to develop bug-free programs easier and faster. Features ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The Borland Pascal Debug Kit provides you with many things that make debugging easier. If you use these tools properly, you can even be sure that some bugs will not occur in your programs! For example, releasing more or less memory than previously allocated immediately halts your program and the offending line is written to a log file. In summary, the Borland Pascal Debug Kit gives you: Introduction 1 þ Allocation and deallocation tracking þ A report of not deallocated memory after your program termination þ A full stack dump (procedure names and parameters) if a run-time error occurs þ C-like assertions to `fortify your subsystems' What's in this manual ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This manual explains how to use the Borland Pascal Debug Kit. It doesn't tell you how to write Borland Pascal programs. The first part of this manual contains the following chapters: þ Chapter 1, ``Using log files'', describes how to use log files to make debugging easier, especially when your program is running at a remote site. þ Chapter 2, ``Memory Checker'', introduces the memory allocation and deallocation tracker and explains how to use it. þ Chapter 3, ``Post Mortem Debugger'', describes how to get annotated stack dumps. þ Chapter 4, ``TDInfo'', explains a unit which gives access to Borland's debug information. þ Chapter 5, ``Assertions'', introduces you to assertions and how to use them in your programs. The second part of this manual is contains reference material for the source code supplied with the Borland Pascal Debug Kit. Typefaces used in this manual ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This manual was typeset using LATE X2î, converted to postscript using dvips, and printed on a HP Laserjet IV. All typefaces used in this manual are standard Postscript fonts. Their uses are as follows: The monospace typeface represents text as it appears on-screen or in a program. It is also used for anything you must type (e.g. BP to start up the Borland Pascal IDE). The boldface typeface is used in text for command line options. Italics is used to indicate identifiers that appear in text. They can represent terms that you can use as they are, or that you can think 2 Borland Pascal Debug Kit Manual up new names for (your choice, usually). They are also used to emphasize certain words, such as new terms. This typeface indicates a key on your keyboard. For example, ``Press Esc to exit this menu.'' Key combinations produced by holding down one or more keys simultaneously are represented as Key1+Key2. How to contact NederWare ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ NederWare offers a variety of services to answer your questions about the Borland Pascal Debug Kit. Of course, this is only true for registered users. If you haven't registered yet, support shareware (and me and my family) by registering this Debug Kit today. We've made it very easy for you. You can register by CompuServe, fax, email or telephone. Please consult REGISTER.FRM for more details. CompuServe Subscribers to CompuServe can reach NederWare at 100120,3121. The latest version of the Borland Pascal Debug Kit is available in library 3 of the BPASCAL forum. Internet NederWare can be reached on the Internet through NederWare@beard.nest.nl or berend@beard.nest.nl. For the latest Debug Kit version, check-out garbo.uwasa.fi:/pc/turbopas. Fidonet NederWare can be reached on Fidonet at 2:281/527.23. The latest version of the Borland Pascal Debug Kit is also available at 2:281/527, Contrast BBS, The Netherlands. Its full international telephone number is +31 70-3234903. From Holland dial 070-3234903. Acknowledgements ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ First and foremost, many thanks to Stephen Maguire, who in his excellent book [Maguire93] showed us all that writing bug free (well, almost) code is possible. If you are serious about writing commercial software, read this book! It inspired me to put together all the pieces I had accumulated over the years in this package. Introduction 3 Many thanks also to Andy McFarland, author of TDI, a utility that displays the contents of the debug information the the BP compiler appends to an executable. Andy generously provided me with the source of his utility, which gave me a jump start in developing my TDInfo unit. Andy can be reached at CompuServe as 71055,2743 or by email as amcfarl@ndlc.occ.uky.edu. Last but not least thanks to Dag Hovden (dhovden@runner.knoware.nl) for cleaning up this manual and improving its english. All remaining faults are mine. 4 Borland Pascal Debug Kit Manual P A R T ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 1 User Manual 5 6 Borland Pascal Debug Kit Manual C H A P T E R ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 1 Working with log files Log files form the basis of Post Mortem Debugging. Using log files means no more scribbling down error codes and addresses on a piece of paper when your program crashes because it's already all there, in the log file. This feature becomes especially useful when you use it in tandem with PMD, the Post Mortem Debugger, because PMD gives you a full symbolic stack dump. Try to write that down as it scrolls past you on the screen! Another benefit of using log files is that you know exactly what your program was up to when it crashed running at a customer site. No more long telephone conversations, simply ask the customer to modem (or mail) you the log file. How to create or open a log file ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The unit BBError makes using log files easy. All you have to is to call InitBBError with the name of the logfile and a Boolean parameter saying to create or to open the log file if it already exists. The following program demonstrates this: program Test; uses BBError; begin writeln('Installing log file writing'); InitBBError('TEST.LOG', TRUE); writeln('Done!'); end. A call to the procedure InitBBError does two things: 1. It creates the log file if it does not exists or if the second parameter is FALSE. The file is opened in append mode if it exists and the second parameter is TRUE. Chapter 1, Working with log files 7 2. It installs an exit procedure which writes the error address and exit code to the log file if an error occurs. This exit procedure also dumps a stack trace to the log file with the addresses of all the callers that lead up to the code that caused the error. How can you write information in the log file ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ You can write your own data to the log file using the ferr text file variable by adding BBError to the USES statement of any unit that wants to write to the log file. Example: program WriteToferr; uses BBError; begin InitBBError('TEST.LOG', TRUE); writeln(ferr, 'This goes to the log file.'); end. A second method is to call BBError's LogError procedure which first writes the current date and time to the log file and next any text you passed to it. Calling LogError is the preferred method. Example: program WriteToLogError; uses BBError; begin InitBBError('TEST.LOG', TRUE); LogError('This goes to the log file.'); end. 8 Borland Pascal Debug Kit Manual C H A P T E R ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 2 Checking your memory Use the MemCheck unit, if you want to check if your allocations match your deallocations or if you want to check if you didn't overwrite memory before or after an allocated memory block. This chapter tells you how to use MemCheck and what MemCheck does. What you must do before MemCheck can work ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Before you can use MemCheck you need to replace the standard SYSTEM unit by the SYSTEM unit supplied with the Borland Pascal Debug Kit. There are two cases: þ If you always work with the Borland Pascal library TURBO.TPL, TPP.TPL or TPW.TPL, you need to replace the SYSTEM unit contained in the library by the supplied one. Below we will explain how to do this. þ If you don't load the library at startup but instead link in the units as separate files, simply copy the supplied SYSTEM.TPx to the directory where you keep these units. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Updating the Replacing SYSTEM.TPx is quite easy. First remove the `old' standard library SYSTEM.TPU from TURBO.TPL (or TPP.TPL or TPW.TPL, please substitute as needed). You can do this with the following command: tpumover turbo -system Next install the new SYSTEM.TPU by: tpumover turbo +system.tpu To install the new SYSTEM.TPW in the windows library type: tpumover tpw +system.tpw Note: don't forget tot type the correct extension after sytem, e.g. .tpu, .tpp or .tpw! Chapter 2, Checking your memory 9 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Recompiling the If you have the run-time library source, and if you want to system unit recompile the SYSTEM unit with our changes incorporated, you need to patch the original SYSTEM.PAS, HEAP.ASM and WMEM.ASM with the diff (i.e. difference) files SYSTEM.DIF, HEAP.DIF and WMEM.DIF. Please note: these files are new structure diff files, created with the GNU diff utility. You can use the GNU patch utility (we use the one coming with the DJGPP GNU C compiler port) to patch your run-time library source. For example, execute patch system.dif c:/bp/rtl/sys/system.pas to update the SYSTEM.PAS file in the C:/BP/RTL/SYS directory. What MemCheck does ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The MemCheck unit checks for the following three bugs: þ Attempts to dispose a pointer, specifying a size other than the value used when creating the pointer. This causes a program halt. þ Allocating memory and never deallocating it (i.e. memory leakage). By calling MemCheckReport, you obtain a list of pointers still allocated together with the addresses in your program where these pointers were created. þ Writing to memory that lies outside an allocated block of memory, i.e. before or after the block. This check is equivalent to the Range check ({$R+}) option for normal arrays. This bug also causes a program halt. The demo program TestMem demonstrates these three bugs. Execute TestMem and select one of the three options presented to you. Afterwards, see TESTMEM.LOG for choice 1 and 2, and MEMCHECK.RPT for choice 3. If you have appended debug information to your executable and if you have initialized the post mortem debugger, MemCheck will also print the source file, line number and procedure name where the error occured. This is also true for MEMCHECK.RPT. When debug informaton is appended, you will find in MEMCHECK.RPT the source file and line number file where the allocation occured and on the next indented line the source file and line number of the caller of that routine (if any). 10 Borland Pascal Debug Kit Manual How to enable memory checking ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ You enable MemCheck by placing it in the USES clause of your main program. No additional calls are needed. Please bear in mind that using MemCheck will cost you an extra 32 bytes per allocated memory block! Errors detected by MemCheck are normally printed to the screen. A much better alternative is to use MemCheck together with BBError, the log file unit. When you use BBError the errors will be written to a log file instead. How MemCheck works ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ MemCheck keeps track of every allocated pointer and the size associated with it. For each allocated pointer, 16 bytes are needed to store this information. The information itself is stored in a collection which can hold a maximum of 16384 pointers. It is easy to enlarge this but as yet, there has been no need for that. If you should need more, please let me know and I'll enhance MemCheck. If possible, MemCheck allocates 8 or 16 bytes more for every memory block than what your program asked for. Because the maximum memory block that can be allocated is 65536-8 bytes, MemCheck always checks if it is possible to allocate 8 or 16 bytes more. þ If it is possible to allocate 16 bytes more, MemCheck is able to check for memory overwrites before and after your allocated memory block. MemCheck does this by filling the first and last 8 bytes with the value 0CCh. When you dispose this memory block, MemCheck checks if these values are still there. If not, memory has been overwritten and MemCheck halts your program. þ If it is possible to allocate only 8 bytes more, MemCheck only checks for overwrites at the end of your memory block. To recap: every pointer needs 16 bytes to store information about it plus an extra 16 (or 8) bytes to check for 'out of range' errors. Together, this accounts for the 32 (or 24) bytes extra per allocated block. Under protected mode or in Windows, this is not a serious problem. Under real mode, however, this could well be a serious obstacle in using MemCheck, especially if your application uses many, small memory blocks. Chapter 2, Checking your memory 11 MemCheck and DPMI ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ When running under protected mode, as some of you may know, you can check for writing outside an allocated memory block by setting HeapLimit to zero. This will enable the processor's built-in segment checking. The problem with this approach is that you can easily run out of selectors, especially if you use MemCheck at the same time. As we've seen above, MemCheck allocates a 16 byte memory block from the heap for every pointer (and uses that block to store information about the pointer). With HeapLimit set to zero, every allocation actually costs you two selectors! 12 Borland Pascal Debug Kit Manual C H A P T E R ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 3 Post Mortem Debugger The Post Mortem Debugger gives you the ability to print full symbolic stack dumps when an error condition occurs in your program. This is a feature you will not want to miss as soon as you have used it to develop programs. What the Post Mortem debugger does ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Currently, the name debugger is a bit of a misnomer. The Post Mortem Debugger does nothing more than hooking into the exit procedure chain and dumping the stack, with all procedure names and parameter values, to the log file if an error occurs. If you have enabled the Post Mortem Debugger, MemCheck writes actual source file names and line numbers instead of hexadecimal addresses when it creates its report of not disposed memory. An example of the log entries created when a program exits with a fatal error is: ** Program started on 1994-02-26 at 19:07:38 ** Post Mortem Debugger (PMD) installed. 1994-02-26 19:07:38 Error 215 at 0001:0031 1994-02-26 19:07:38 TESTPMD.PAS (26) procedure InSide(100,0,0); 1994-02-26 19:07:38 *** Full stack dump *** TESTPMD.PAS (31) procedure TestError((Open,'TESTPMD.BAK'),test2); TESTPMD.PAS (44) This is the log file created if you run the TestPMD program. The advantage of a symbolic stack dump is great, perhaps far greater than you would expect. You find errors much faster due to the full stack trace created. Previously, you could only get such an overview when running inside the debugger. For a frequently Chapter 3, Post Mortem Debugger 13 called procedure, failing perhaps only every 100th time, it is not really easy (or much fun) to use breakpoints to wait for the correct moment to halt and view the stack. The Post-Mortem debugger has one flaw which shows up sometimes. If the stack trace consists of near calls, PMD may not always be able to find the address. As soon as a far call occurs in the stack frame, the stack dump is correct from that point on. Currently we do not know how to fix this. Luckily this case occurs seldom. Using the Post Mortem Debugger ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ To use the Post Mortem Debugger, you need to include three units in your USES clause. And you need three calls to initialize these three units. The order in which you call these routines is important. The order in which they appear in the USES clause is not important. The three units are BBError, ObjMemory and PMD. The initialization routines in their correct oder are InitBBError, InitObjMemory and InitPMD. The program listed below, also available as TESTPMD.PAS on your distribution disk, demonstrates the Post Mortem Debugger. {$Q+} program TestPMD; uses BBError, BBUtil, Objects, ObjMemory, {$IFDEF Windows} WinCrt, {$ENDIF} PMD; type types = (test1, test2, test3); procedure TestError(var f : text; e : types); procedure InSide(o : TObjMemory); var w : word; d : word; begin w := 0; 14 Borland Pascal Debug Kit Manual w := w - 1; end; begin InSide(GetObjMemory(100, 0, memfAll)^); end; var f : text; begin writeln(prnCR, 'Post Mortem Debugger tester.'); (* initialize *) InitBBError('TESTPMD.LOG', TRUE); InitObjMemory; InitPMD(dfStandard); (* and show the error *) Assign(f, 'TESTPMD.PAS'); Reset(f); TestError(f, test2); end. As you can see, there are three initialization routines that must be called before PMD can be used: InitBBError to open or create a log file, InitObjMemory to initialize the memory management routines that PMD uses, and (finally) InitPMD to initialize the Post Mortem Debugger itself. The InitPMD procedure accepts certain flags on which the Post Mortem Debugger bases its behaviour. The default is dfStandard, you will get only a symbolic stack dump when an error occurs. If you specify the dfDataSeg flag as well (InitPMD(dfStandard + dfDataSeg) ) the datasegment is also dumped to the log file. Compile the example program with full debug information enabled by typing tpc -m -l -v testpmd at the Dos command line prompt. The program contains a delibarate error, namely an overflow bug (therefore it has to be compiled with {$Q+}).. Run it, then look at the created log file TESTPMD.LOG. To use PMD you need quite some free memory. For large programs this can be as much as 100K or even more. Under real mode, the Post Mortem Debugger uses EMS or XMS memory if that's available. Chapter 3, Post Mortem Debugger 15 The Post-Mortem-Debugger under MS-Windows ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Under MS-Windows the Post-Mortem debugger has some extra capabilities including the ability to give a stack dump when a gpf occurs. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Extra features When the PMD unit is included in a windows program, you get under Windows the following extra features: MS-Windows: þ Any text send to OutputDebugString is written to the log file also. þ Any error Windows displays, is written to the log file also. þ If you make a parameter error when calling a Windows function, this is logged. Almost no need for WinScope or BoundsChecker anymore! ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ GPF's under GPF's are not caught under MS-Windows unlike under DPMI. If Windows your program crashes, Windows will still display the UAE dialog box. If you want a symbolic stack dump in this case, you need to call InstallIntHandler in the PMD unit. InstallIntHandler is not called by default, because I couldn't get it work right when using the debugger. Without the debugger, everything runs fine. When I use the debugger to examine a program, leave the debugger to go back to the IDE, edit something and recompile, I'm bombed back to Dos, or Windows just hangs. I would be indebted, if anyone has a fix for this. 16 Borland Pascal Debug Kit Manual C H A P T E R ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 4 TDInfo The TDInfo unit is used by MemCheck and PMD to provide access to the debug info stored in your executable. Information about this debug info can be found in [Borland92]. You probably need this book if you want to use TDInfo yourself. Because this books contains quite a few errors, I refer the reader to appendix A where I've listed OA.TXT, a file containing bugs and omissions to chapter 4 of Borland's "Open Architecture Handbook for Pascal". How TDInfo models Borland's Debug Information ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Since the presented information in [Borland92] is less than clear, I've used semantic principles (see [Bekke92]) to make the information contained in the debug symbol tables and their mutual relationships clearer. I'll explain the semantic approach using the Turbo Debug Semantic Model presented in figure 4.1. Semantic data modeling has two notions: types and relationships. Types are basic data elements. Types are not stand-alone, but are related to each other. As can be seen in figure 4.1, types are drawn as rectangles. Relationships between types are drawn as lines. A type can be related to another type in two ways: þ When a type consists of other types we talk about an aggregated type. þ When a type is a special kind of another type, we talk about a specialized type. Aggregation can be seen in the Browser type, the uppermost rectangle. Somewhat simplified, aggregation can be said to be equal to the Pascal record type. A record type has a name and consists of various fields. So does the Browser. Each Browser 1 to a SourceFile record in the symbol table consists of a pointer 1The Symbol table actually does not store pointers, but record numbers Chapter 4, TDInfo 17 Figure 4.1 Turbo Debug Semantic Browser Model LineNumber Correlation Symbol oeÄÄÄÄÄ  Type Scope ÄÄÄÄÄÄÄ  SourceFile Segment Module Name 18 Borland Pascal Debug Kit Manual record, a LineNumber record and a Symbol record. The latter record is also an aggregated type. It consists of a pointer to a Module record, a Name record, a Scope record and a Type record. There is no example of specialization in this figure, but specialization can be compared to inheritance in Turbo Pascal. The way the types (records) are drawn is also important. Read from top to bottom, we always find fixed properties, i.e. a Browser always contains a SourceFile. Read from bottom to top, we have optional properties. Take for example the type Module. A Module can have zero or more Segments. In the same way, a Scope can have zero or more Symbols. The relationships expressed in figure 4.1 show the ways you can access information. If you have a certain line number, you first have to search the correct LineNumber record. If you now want to know in which source file this line appears, then you must first go to the LineNumber's Correlation record and from there to the SourceFile. If you want to know the name of the SourceFile you've found, you can go directly from the SourceFile record to the Name record. In the semantic query language Xplain, we would express this as: get LineNumber its Correlation its SourceFile its Name. Note that you can also get the name of the source file in which the line number appears in by going through Correlation its Segment its Module and, finally, its Name. Changes between Open Architecture names and TD- Info names ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The TDInfo unit tries to model the debug symbol information as semanticly correct as possible. Each type in TDInfo is prefixed with a `T'. Note that I have changed some names so that they are not consistent with [Borland92] (which is fairly liberal with naming conventions to say the least). A list of Open Architecture names and TDInfo names is presented below. The TDInfo names refer to objects. The names in the Open Architecture book refer to records. The types are listed in the order they appear in the Open Architecture handbook. Note that almost all fields in TDInfo's object are different from their corresponding names in the Open Architecture Handbook too. Chapter 4, TDInfo 19 Table 4.1 TDInfo name OA name List of corresponding TDInfo names and OA names TSymbol TSymbolRecord TModule TModuleHeader TSourceFile SourceFile TLineNumber Line number TScope Scope TSegment Segment info TCorrelation Typedef TType Type Rec TMember Struct offset rec TClass TParent Table (?) TParent TParent Table TOverload Overload TScopeClass TClassTable TModuleClass LocalClass TBrowser TDefinitionRecord How to make use of TDInfo ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ In this section some information is given how you can make use of the TDInfo unit yourself. The information is this section is not yet complete. You should really take a look at the sources of the PMD unit and the AnnotateLog program (see ANNLOG.PAS, only in the registered version of this program). ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Initializing the Before you can make use of TDInfo, you need to initialize it by TDInfo unit calling the function TDInfoPresent. Pass TDInfoPresent a filename which contains debug information. This file may be a stand-alone symbol file created with TDStrip or an executable with debug information appended to it. TDInfoPresent returns TRUE when it has found debug information in the file. Only when it returns TRUE is it save to use the other objects of the TDInfo unit. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Getting information from a debug symbol You can almost always directly express a `semantic query' using TDInfo. To get the name of a module, you use TModule.ItsName. To get the scope of a symbol you say TSymbol.ItsScope. These ItsXXXX methods always return a pointer to the object that file corresponds with the name of the method. When you call an ItsXXXX method for the first time, a new object of that type is created. When you call the ItsXXXX method for the second time, this object is returned again, so an object is not created twice. You don't have to dispose these pointers, they are disposed by the provider when you dispose the provider, i.e. if 20 Borland Pascal Debug Kit Manual you have called TSymbol.ItsScope, TSymbol.Done disposes the allocated TScope object. Let's take a look at a basic example. Given an physical address, Getting line how can we display its the source file and line number? The first numbers thing we need to do is to turn physical addresses into logical addresses. This is done by calling GetLogicalAddr. A physical address goes in, a logical address comes out. Using this logical address we can now proceed to find the source file and line number. One approach would be to look the logical address up in a .MAP file. Much easier is it to use the debug information appended to an executable. We get the LineNumber by calling the constructor TLineNumber.AtAddr. For example: LineNumber := New(PLineNumber, AtAddr(Addr)); LineNumber will be nil if no line number could be found. Else it will contain a pointer to a TLineNumber object. To display the line number we can write LineNumber^.Value. To get the name of the source file, see also figure 4.1, we say LineNumber^.ItsCorrelation^.ItsSourceFile^.ItsName. Getting procedures To get the procedure to which this line number belongs, we have to know that a procedure is a symbol. To find a symbol at a given address, we can use TSymbol.AtSegment. We need to pass it a PSegment and an address. Because we already have a line number (see the previous section), the segment is LineNumber^.ItsCorrelation^.ItsSegment. The address is simply the logical address. The AtSegment constructor does quite an amount of work. It searches all scope records belonging to a certain segment to find a scope which covers the given address. It does this by calling TSegment.FirstScopeThat. This repeatedly calls the passed function until this function returns true. If this function returns TRUE, TSegment.FirstScopeThat returns the scope record for which the function returned TRUE. Although this is not displayed in figure 4.1, there exists a certain specialization of Scope, which contains a pointer to a Symbol record. TSymbol.AtSegment checks if TSegment.FirstScopeThat has returned such a scope. If that's the case, TSymbol.AtSegment loads the correct symbol record from the debug symbol table and returns. With the Symbol we now have, we have the procedure or function to which this line number belongs. The name of the procedure or Chapter 4, TDInfo 21 function is Symbol^.ItsName. To determine of we have a procedure or a function we look at the type of the symbol. If Symbol^.ItsType^.ReturnType = 1 we have a procedure , else we have a function. There is a lot more to say about TDInfo, but this should get you started. In the PMD.PAS, ANNLOG.PAS and MEMCHECK.PAS you find some other or more elaborated uses of TDInfo. 22 Borland Pascal Debug Kit Manual C H A P T E R ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 5 Assertions Once you have acquainted yourself with Assertions, you will not want to be without them! The Assertions unit provided here, makes it easy for Borland Pascal programmers to use them. Add Assertions to the USES clause of any unit or program which wants to use assertions. In your code, simply call the procedure Assert with a Boolean value and a string containing the message to be printed if the Boolean value evaluates to FALSE. After the message has been printed (which means that the assertion failed), your program halts. Example program: program Example uses BBError, Assertions; begin InitBBError('TEST.LOG', TRUE); Assert(TRUE, 'This assertion passes.'); Assert(FALSE, 'This assertion fails.'); end. If you call InitBBError , the message is written to the log file as well. If you use the PMD unit (properly initialized!), Assert also prints a symbolic stack trace. You will probably want to use Assertions only in your test code, not in your production code. All Assertions take time and space, and some Assertions take quite a lot of time. That's no problem, or less of a problem, as long as you're working with test code, but your production code should be as fast as possible. Also, you shouldn't need assertions there. To conditionally include Assertions in your program, embrace calls to Assert within a pair of conditional defines. The example program then looks like this: Chapter 5, Assertions 23 {$DEFINE Debug} program Example uses BBError {$IFDEF Debug} , Assertions {$ENDIF} ; begin InitBBError('TEST.LOG', TRUE); {$IFDEF Debug} Assert(TRUE, 'This assertion passes.'); Assert(FALSE, 'This assertion fails.'); {$ENDIF} end. If you enable the Debug directive, the Assertions are included. If you disable the Debug directive, they are not included. 24 Borland Pascal Debug Kit Manual P A R T ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 2 Reference Manual 25 *************************************** E N D O F T H I S M A N U A L *************************************** The complete reference manual is distributed with the registered version only. Again, the reference manual looks exactly like to Borland reference manuals. The registered version manual also contains a list of bugs in the Borland Pascal Open Architecture Book, compiled by Andy McFarland.