**** 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.21 NederWare Burgerstraat 17 5311 CW Gameren The Netherlands compuserve: 100120,3121 email: 100120.3121@compuserve.com fidonet: 2:281/527.23 1 C O N T E N T S ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Introduction Program with less pain 1 A more complex example ............ 27 Why this tool?........................ 1 Changes between Open Architecture Features ............................. 1 names and TDInfo names.............. 28 What's in this manual ................. 2 How to make use of TDInfo ............ 29 Typefaces used in this manual .......... 2 Initializing the TDInfo unit........... 29 How to contact NederWare............ 3 Getting information from a debug Acknowledgements................... 3 symbol file......................... 29 Getting line numbers.............. 29 Getting procedure names .......... 30 Part 1 User Manual Chapter 5 Assertions 31 Chapter 1 Working with log files 7 How to create or open a log file......... 7 How can you write information in the Part 2 Reference Manual log file.............................. 8 Chapter 6 Pascal Debug kit Controlling the maxium size of your Reference 35 logfiles .............................. 9 Sample procedure.................... 35 Chapter 2 Checking your memory 11 AddBackSlash function................ 35 What you must do before MemCheck Archive constant...................... 36 can work............................ 11 Assert procedure..................... 36 Updating the standard library ........ 11 Beep procedure....................... 36 Recompiling the system unit......... 12 BeepOff procedure.................... 36 What MemCheck does................ 13 BeepOn procedure .................... 37 How to enable memory checking....... 13 BrowserRecordSize variable............ 37 How MemCheck works............... 14 ClassRecordSize variable.............. 37 MemCheck and DPMI................. 14 CMPB function ....................... 37 Bugs in the Run-Time Library you CMPW function ...................... 38 encounter when using MemCheck ...... 15 ConvMemLimit variable............... 38 CorrelationRecordSize variable ......... 38 Chapter 3 Post Mortem Debugger 17 CPos function ........................ 38 What the Post Mortem debugger does ... 17 CR constant.......................... 38 Using the Post Mortem Debugger....... 18 CreateBAK procedure ................. 39 How much memory does PMD need .... 20 DateTime type....................... 39 The Post-Mortem-Debugger under DebugInfo variable................... 39 MS-Windows........................ 21 Delay variable........................ 39 Extra features under Windows....... 21 dfXXXX constants.................... 39 GPF's under Windows.............. 21 DirStr type........................... 40 Discard procedure .................... 40 Chapter 4 TDInfo 23 DisposeSLink procedure............... 40 How Borland's Debug information is DoneMemCheck procedure ............ 40 stored ............................... 23 DonePMD procedure.................. 41 How TDInfo models Borland's Debug DoneStrings procedure ................ 41 Information .......................... 25 DosCopy procedure................... 41 What is semantic data modeling? ..... 25 DosDel procedure.................... 42 A first example ..................... 27 i DosDelay procedure.................. 42 InitMemCheck procedure.............. 52 DosMove procedure.................. 42 InitObjMemory procedure ............. 53 DosTouch procedure .................. 43 InitPMD procedure................... 53 DosWipe procedure ................... 43 IsDirectory function................... 53 DStream variable ..................... 43 IsFileOpen function ................... 53 DumpStack variable.................. 44 IsValidPtr function.................... 54 DumpStackProcedureType type........ 44 LeadingZero function................. 54 Empty function....................... 44 LeftJustify function................... 54 ExtractFilePath function ............... 44 LF constant.......................... 54 ExtractStr function .................... 44 LineNumberRecordSize variable........ 55 ExtStr type........................... 45 LoadStrings procedure................ 55 FancyStr function..................... 45 LogError procedure ................... 55 FatalErrorText variable................ 45 LogFileOpened variable ............... 55 FCreate function...................... 45 LogFileSizeLimit variable.............. 56 FDefaultExtension function ............ 45 LowCase function.................... 56 ferr variable.......................... 46 LowStr function ...................... 56 FExpand function..................... 46 MaxConventionalMemoryBlock constant 56 FF constant.......................... 46 MaxMemPtrs constant................. 56 FForceDir function.................... 46 MaxSegments constant ................ 57 FForceExtension function .............. 46 MaxWord constant.................... 57 FileExist function ..................... 47 MemberRecordSize variable............ 57 FileRec type.......................... 47 MemCheckReport procedure........... 57 fmXXXX constants .................... 47 memfXXXX constants................. 57 FOpen function....................... 47 mfStandard constant .................. 58 FormatStr procedure .................. 48 Min function ......................... 58 FormFeed constant.................... 48 mm XXXX constants .................. 58 fsXXXX constants ..................... 48 ModuleClassRecordSize variable....... 59 FTCopy function..................... 48 ModuleRecordSize variable ............ 59 GetAddrStr function.................. 49 NameStr type........................ 59 GetDateStr function................... 49 NewSLink function................... 59 GetEnv function ...................... 49 NextChPos function................... 60 GetFileName function................. 49 OverloadRecordSize variable........... 60 GetLogicalAddr function.............. 49 ParentRecordSize variable............. 60 GetObjMemory function............... 49 PathStr type.......................... 60 GetPStr function...................... 50 PDebugInfo type..................... 60 GetStr function ....................... 50 PrintError variable.................... 60 GetTextFileName function ............. 50 PrintErrorType type................... 61 GetTickCount function................ 50 PSegmentCache type.................. 61 GetTimeStr function................... 51 PSLink type.......................... 61 GetUniqueFileName function.......... 51 PtrRec type.......................... 61 HandleRunTimeError variable......... 51 Registers type ........................ 61 HandleRunTimeErrorProcedureType type 51 RemoveBackSlash function............ 62 HexB function........................ 51 RepChar function..................... 62 HexStr function...................... 51 ReplaceStr procedure.................. 62 InitBBError function.................. 52 ReportFileName constant .............. 62 InitIntHandler procedure .............. 52 RightJustify function .................. 62 ii rsGet function........................ 63 TextRec type......................... 75 rsGet1 function....................... 63 tid voidXXXX constants ............... 75 rsGet2 function....................... 63 TLineNumber object.................. 76 ScanB function....................... 63 Fields............................. 76 ScanW function...................... 64 Methods........................... 76 ScopeClassRecordSize variable ......... 64 TMember object ...................... 77 ScopeRecordSize variable.............. 64 Fields............................. 77 scXXXX constants..................... 64 Methods........................... 77 SearchRec type ....................... 65 TModule object....................... 77 SegmentRecordSize variable........... 65 Fields............................. 77 SetHandleCount procedure............ 65 Methods........................... 78 sl XXXX constants.................... 65 TModuleClass type................... 79 SmallDebugHeaderSize constant....... 66 TObject object ........................ 79 SmallEndianI procedure ............... 66 Methods........................... 79 SmallEndianL procedure.............. 66 TObjMemory object ................... 79 SmallEndianW procedure.............. 66 Fields............................. 79 SourceFileRecordSize variable.......... 66 Methods........................... 80 Spc function......................... 67 TOverload type....................... 81 Spoiled function ...................... 67 TParent type ......................... 81 StrB function ......................... 67 TResourceCollection object............. 81 StrI function......................... 67 Methods........................... 81 Strings variable....................... 67 TResourceFile object.................. 82 StripSpc function..................... 67 Fields............................. 82 StrL function ......................... 68 Methods........................... 82 StrR function......................... 68 TResourceItem type................... 82 StrResBufSize variable................. 68 TrimRight function.................... 82 StrS function ......................... 68 TScope object........................ 83 StrW function ........................ 68 Fields............................. 83 stXXXX constants ..................... 68 Methods........................... 83 SymbolRecordSize variable............ 69 TScopeClass type ..................... 84 TBrowser object...................... 69 TSegment object ...................... 84 Fields............................. 69 Fields............................. 84 Methods........................... 70 Methods........................... 85 TClass object ......................... 70 TSLink type.......................... 85 Fields............................. 70 TSmartBufStream object ............... 86 Methods........................... 70 Fields............................. 86 TCorrelation object.................... 70 Methods........................... 86 Fields............................. 71 TSourceFile object.................... 86 Methods........................... 71 Fields............................. 86 TDebugHeader type.................. 71 Methods........................... 87 TDebugInfo object .................... 73 TSymbol object ....................... 87 Fields............................. 73 Fields............................. 87 Methods........................... 74 Methods........................... 87 TDInfoPresent function................ 74 TType object......................... 88 TDriveStr type....................... 74 Fields............................. 88 TextPrintError procedure.............. 75 Methods........................... 89 iii TypeRecordSize variable............... 90 UpStr function....................... 90 ValB function......................... 90 valcode variable ...................... 90 ValHex function ...................... 91 ValI function ......................... 91 ValL function......................... 91 ValR function........................ 91 ValW function........................ 92 Warning procedure................... 92 XChDir procedure .................... 92 XMkDir function..................... 92 ZeroRightJustify function .............. 92 200K constant....................... 93 Appendix A Errors and omissions in Open Architecture Handbook 95 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 þ Memory allocation and deallocation tracking, with warnings on errors like overwriting memory and deallocating not allocated memory. þ 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 Introduction 2 represent terms that you can use as they are, or that you can think 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 the Debugger/Tools library of the BPASCAL forum (now called Delphi forum). The library number is 15. Internet NederWare can be reached on the Internet through 100120.3121@compuserve.com. For the latest Debug Kit version, check-out ftp://garbo.uwasa.fi/pc/turbopa7. 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 Introduction 3 commercial software, read this book! It inspired me to put together all the pieces I had accumulated over the years in this package. 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. Introduction 4 P A R T ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 1 User Manual 5 6 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 three 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 writes information about which program was started up to the log file (i.e. ParamStr(0)). 3. 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. Note that the BBError unit should be the first unit in your program uses clauses (or at least it should be listed before any other unit which uses logging). 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. At the beginning of every line the current date and time is written. A second method is to call BBError's LogError procedure which essentially does the same. It accepts a string as parameter. Example: program WriteToLogError; uses BBError; begin InitBBError('TEST.LOG', TRUE); LogError('This goes to the log file.'); end. Chapter 1, Working with log files 8 Controlling the maxium size of your logfiles ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Log files grow infinitely if you don't control their growth. Luckily, the BBError unit does this too. Every time InitBBError is called (probably every time your program starts up), the size of the log file is checked. If this is larger than LogFileSizeLimit than the .log file is renamed to a file with extension .LO0 and a new .log file is created. If you want your logfiles to be larger or smaller, set LogFileSizeLimit before calling InitBBError. Chapter 1, Working with log files 9 Chapter 1, Working with log files 10 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 it is explained 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: standard library 1. First remove the `old' SYSTEM.TPU from TURBO.TPL (or TPP.TPL or TPW.TPL, please substitute as needed). 2. Next, add the `new' SYSTEM.TPU to TURBO.TPL (or TPP.TPL or TPW.TPL). Borland Pascal includes a utility to remove or add units from a Turbo Pascal Library (.TPL) file. It's called tpumover. For example you will type the following to replace the Dos real-mode SYSTEM.TPU: tpumover c:\bp\bin\turbo -system tpumover c:\bp\bin\turbo +c:\pmd\system.tpu Chapter 2, Checking your memory 11 Substitute the paths {- The Borland library path (c:/bp/bin) and the Post Mortem Kit path (c:/pmd) {- by the paths where you have put the Borland library and Post Mortem Kit. You will type the following to replace the Dos Protected Mode SYSTEM.TPP: tpumover c:\bp\bin\tpp -system tpumover c:\bp\bin\tpp +c:\pmd\system.tpp You will type the following to replace the Windows SYSTEM.TPW: tpumover c:\bp\bin\tpw -system tpumover c:\bp\bin\tpw +c:\pmd\system.tpw Note: don't forget tot type the correct extension after system, e.g. .tpu, .tpp or .tpw! If you don't want to overwrite Borland's library files you can copy them to another directory (for example c:/pmd), do the replace on the copied libraries and startup the Borland IDE with the -tmynewlibrarypath switch. If you have put the libraries in c:/pmd, startup your IDE as follows: bp -tc:/pmd You should call the command-line compiler bpc with: bpc -tc:/pmd ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Recompiling the Recompiling the system unit is more di icult. You need to have system unit the run-time library compiled before you can recompile the new SYSTEM.PAS unit. SHAREWARE users only: If you have the run-time library source, and if you want to 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. The registered users already have a complete SYSTEM.PAS file. Chapter 2, Checking your memory 12 If you have a complete SYSTEM.PAS file you can start to recompile the SYSTEM.PAS unit. Make sure the first entry in your unit and object path is set to the directory where the Post Mortem Debug Kit resides (c:\pmd for example). To compile SYSTEM.PAS with the command-line compiler you type: bpc -uc:\pmd;c:\bp\units -oc:\pmd;c:\bp\rtl\sys;c:\bp\rtl\lib system.pas 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). How to enable memory checking ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ You enable MemCheck by placing it in the USES clause of your main program and calling InitMemCheck with parameter Chapter 2, Checking your memory 13 mfStandard. Please bear in mind that using MemCheck will cost you an extra 24 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. If you need more pointers you can enable the BigCollection conditional directive in MEMCHECK.PAS. You need to have BIGCOL.ZIP for this. If possible, MemCheck allocates 4 or 8 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 4 or 8 bytes more. þ If it is possible to allocate 8 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 4 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 24 (or 20) 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. 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 Chapter 2, Checking your memory 14 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! Bugs in the Run-Time Library you encounter when using MemCheck ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Using MemCheck you occasionally will encounter bugs in the run-time library. Here's the list I know off: þ In the Memory unit (found in rtl/tv), a programmer used some trick to return aligned memory. When allocating too much memory, he gave a small part of it back...MemCheck will fail on this of course, because you need to give back the entire thing. And besides, this is an awful lousy way of programming. þ The ODialogs unit contains a bug in TEdit.Transfer. In certain cases this routine frees too less memory. Chapter 2, Checking your memory 15 Chapter 2, Checking your memory 16 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: 1996-05-03 21:54:02 ** M:\SRC\PMD\TEST\TESTPMDX.EXE started ** 1996-05-03 21:54:02 MemAvail on start: 15832904 1996-05-03 21:54:02 Post Mortem Debugger installed. 1996-05-03 21:54:02 MemAvail on exit: 15832788 1996-05-03 21:54:02 Program terminated with ExitCode 215. 1996-05-03 21:54:02 Error 215 at 0001:004A 1996-05-03 21:54:02 TESTPMD.PAS (38) procedure InSide((100,0,0)); 1996-05-03 21:54:02 w : Word = 0; 1996-05-03 21:54:02 d : Real = 3.1415000000E+00; 1996-05-03 21:54:02 *** Full stack dump *** 1996-05-03 21:54:02TESTPMD.PAS (43) procedure GenerateError((Input,'TESTPMD.PAS'),test2); 1996-05-03 21:54:02 s : string[255] = 'Hello world'; 1996-05-03 21:54:02 TESTPMD.PAS (77) 1996-05-03 21:54:02 *** End of stack dump *** Chapter 3, Post Mortem Debugger 17 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 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 Objects, {$IFDEF Windows} WinCrt, {$ENDIF} BBError, { implements the logging function } { should be listed before any unit } { which uses logging } Chapter 3, Post Mortem Debugger 18 ObjMemory, { Handles memory allocations larger than 64K } PMD; { this unit implements the Post Mortem dumps } type types = (test1, test2, test3); procedure GenerateError(var f : text; e: types); var s : string; procedure InSide(o : TObjMemory); var w : word; d : real; begin d := 3.1415; w := 0; w := w - 1; end; begin s := 'Hello world'; InSide(GetObjMemory(100, 0, memfAll)^); end; var f : text; begin { display information about this program } writeln('Post Mortem Debugger tester.'); writeln('If this program is compiled with debug info on,'); writeln('see TESTPMD.LOG after this program halts.'); { initialize the kit in the next three lines. You will always need these } { lines in your program too } { initialize log file unit } InitBBError('TESTPMD.LOG', TRUE); { initialize memory handling unit } InitObjMemory; { initialize PMD with symbolic stack dump and data Chapter 3, Post Mortem Debugger 19 segment dump } InitPMD(dfStandard+dfLocals); { you can also initialize PMD with symbolic stack dump and data segment dump } InitPMD(dfStandard+dfLocals+dfDataSeg); { open a text file, nice to show in symbolic stack dump and dseg dump } Assign(f, 'TESTPMD.PAS'); Reset(f); { generate the error } GenerateError(f, test2); { we don't need to clean up of course, because we will never reach this... } 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. How much memory does PMD need ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 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. Note that when running from the IDE, EMS or XMS is not available. You have to leav the IDE and run the program from the Chapter 3, Post Mortem Debugger 20 command-line. Or, if you have a multi-tasking OS, run it from another command-line box. 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 to 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. Chapter 3, Post Mortem Debugger 21 Chapter 3, Post Mortem Debugger 22 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 book contains quite a few errors, the reader is referred to appendix A where OA.TXT is lised. This file contains bugs and omissions to chapter 4 of Borland's ''Open Architecture Handbook for Pascal''. In this chapter, we first explain how Borland's Debug information is stored. Next we explain how we modelled Borland's Debug information so you can understand relationships between various components better and next we explain the use of TDInfo, the unit which can make use of Borland's Debug information. How Borland's Debug information is stored ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ . Borland's Debug information is just appended to an .EXE file. The basic layout of a .EXE file with debug information appended is: þ EXE header þ EXE image þ Debug header þ symbols þ modules þ source files þ line numbers þ scopes þ segments þ ... þ names Chapter 4, TDInfo 23 The debug header contains various information about the debug tables as which version it is or how many symbols or line numbers there are. A shortened version of the header (for the full header see the reference manual) follows: type TDebugHeader = record MagicNumber : word; { To be sure who we are ($52FB) } MinorVersion : byte; { in case we change things } MajorVersion : byte; NamesPoolSize : longint; { names pool size in bytes } NamesCount : word; { number of names in pool } TypesCount : word; { number f type entries } MembersCount : word; { structure members table } SymbolsCount : word; { number of symbols } GlobalsCount : word; { number of global symbols } ModulesCount : word; { number of modules (units) } LocalsCount : word; { optional; can be filler } ScopesCount : word; { number of scopes in table } LineNumbersCount : word; { number of line numbers } SourceFilesCount : word; { number of include files } SegmentsCount : word; { number of segment records } CorrelationsCount : word; { number of segment/file correlations } end; After the debug header just arrays of records follow. So after the header you find an array of Symbol records. How many Symbol records there are, is determined by the SymbolsCount field in the header. Let's take a look at the source files section. The source files section is again an array of SourceFile records. Definition: type SourceFilesArray = array[1..Header.SourceFilesCount] of SourceFileRecord; Chapter 4, TDInfo 24 A SourceFileRecord looks like: type SourceFileRecord = record SourceFileName : word; TimeStamp : longint; end; As you see, the name of a source file is not stored in the SourceFile record. SourceFileName is an index into the names section of the debug information. If SourceFileName = 10 it means that the name of the source file is stored as the 10th name in the names section. In the same manner is all other information stored. Line numbers are stored consecutively as an array[1..Header.LineNumbersCount] of LineNumberRecord, etc. The names section is the last section of the debug information and it consists if names seperated by zero's. There is no leading length byte as with Pascal type strings. A last remark: in cases when information is related, for example each Symbol has a Type, indexes are used. A symbol can be a procedure, a variable, a constant, etc. and its type tells if it is a procedure, a variable, a constant, etc. As you saw in the SourceFileRecord case, a name is not stored with the SourceFile but only an index to it. So doesn't a Symbol contains its Type, but only an index into the array of type records. 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. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ What is semantic data modeling? 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. Chapter 4, TDInfo 25 Browser Figure 4.1 Turbo Debug Semantic Model LineNumber Correlation Symbol oe  Type Scope  SourceFileSegment Module Name Chapter 4, TDInfo 26 *************************************** 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.