| Rajinder Yadav - C++ Windows Development Tools & Resources :Design, Code, Test, Debug |
#include <ryDiagnostics.h>
Introduction
In order to help the C and C++ developer with
debugging and diagnostic issues, I have created a set of useful macros. I will
shortly make the source code available for download so you can begin using it
within you projects.
This project file will grow over time to include more helpful debugging macros. I've made the conscious decision to develop the code with the following things in mind:
NOTE: At the moment I haven't had the chance to compile and test under UNIX and LINUX.
The following services are provided both in debug and release builds under Windows and UNIX.Win32
extended debugging and diagnostic features (Win95, Win98, Win2000, WinNT)
NOTE: The following diagnostic monitored messages are logged
automatically, and only available in a debug build.
* heap access
monitoring {malloc, new, realloc, free, delete, _expand}
* memory leak
detection
* breaking on a heap allocation (Tracking memory leaks)
* heap
pre/post comparison (statistical heap dump)
* crash detection (Win32 SEH)
* logging unhandled C++ exceptions
NOTE: The one benefit you will notice right away is the ability to link a message with it's filename and line number. The message string is displayed first followed by the filename and line number on the next line.
The output has been beautified, the fields are tab delimited so the log files can be easily improted into Excel.
----------------------------------------
Log created on: Fri Sep 21 04:03:42 2001
----------------------------------------
(date) (time) (tid) (message) (source file and line number)
2001/9/21 04:03:42:125 0x50C Allocating Memory {42}, Block Size 10 bytes D:\dev\common\ryDiagnostics\test\test.cpp(16)
2001/9/21 04:03:42:125 0x50C Allocating Memory {43}, Block Size 99 bytes D:\dev\common\ryDiagnostics\test\test.cpp(17)
2001/9/21 04:03:42:125 0x50C Freeing Memory
2001/9/21 04:03:42:125 0x50C Test log message D:\dev\common\ryDiagnostics\test\test.cpp(25)
2001/9/21 04:03:42:125 0x50C Hello World! D:\dev\common\ryDiagnostics\test\test.cpp(26)
2001/9/21 04:03:42:125 0x50C Assertion (n > 0) failed D:\dev\common\ryDiagnostics\test\test.cpp(31)
2001/9/21 04:03:42:125 0x50C Memory Dump > szTestData[i]=0x42DC80 (51 bytes) D:\dev\common\ryDiagnostics\test\test.cpp(36)
0x42DC80 61 62 63 64 65 66 67 68 69 6A abcdefghij
0x42DC8A 6B 6C 6D 6E 6F 70 71 72 73 74 klmnopqrst
0x42DC94 75 76 77 78 79 7A 20 30 31 32 uvwxyz 012
0x42DC9E 33 34 35 36 37 38 39 30 2B 2D 34567890+-
0x42DCA8 2E 21 40 23 24 25 5E 26 2A 28 .!@#$%^&*(
0x42DCB2 29 xx xx xx xx xx xx xx xx xx )
2001/9/21 04:03:42:125 0x50C Memory Dump > szTestData[i]=0x42DCB4 (51 bytes) D:\dev\common\ryDiagnostics\test\test.cpp(36)
0x42DCB4 41 42 43 44 45 46 47 48 49 4A ABCDEFGHIJ
0x42DCBE 4B 4C 4D 4E 4F 50 51 52 53 54 KLMNOPQRST
0x42DCC8 55 56 57 58 59 5A 20 30 31 32 UVWXYZ 012
0x42DCD2 33 34 35 36 37 38 39 30 2B 2D 34567890+-
0x42DCDC 2E 21 40 23 24 25 5E 26 2A 28 .!@#$%^&*(
0x42DCE6 29 xx xx xx xx xx xx xx xx xx )
Detected memory leaks!
Dumping objects ->
D:\dev\common\ryDiagnostics\test\test.cpp(17) :
{43} client block at 0x005407E0, subtype 0, 99 bytes long.
Data: 54 68 69 73 20 73 74 72 69 6E 67 20 77 69 6C 6C
Object dump complete.
Usage Log File
Here are the default settings for the log file:
The default logging settings can be changed through the initialization macros.
NOTE: C++ Developers don't need to can ryTermLog
C Syntax: ryInitLog(filename, file-attributes);
C Syntax:
ryTermLog( );
C++ Syntax: ryInit(filename, file-attributes);
Here are some examples: (C++ Developer replace ryInitLog with ryInit)
Specifying the log filename:
ryInitLog("my_log.txt", 0);
Specifying the log filename and location:
ryInitLog("c:\temp\my_log.txt", 0);
Allowing the log file to grow:
ryInitLog(0, "a+");
NOTE: Passing in a value of zero or NULL to the macro will select it's defaults.
Compile time message notification
Most developers are all
too familiar with commenting their code with TO DO!
The problem with this is, it's too easy for a comment to get buried away and forgotten. Well with compile time messaging, you can tag all your files with any comment you like. The idea is that they get shown during each compile! Plus you are told in which file and what line you made the (reminder) comment.
To see compile time message, just place the following tag anywhere in your file:
Syntax: #pragma BuildMsg(message string)
NOTE: The message string does not need to be enclosed in quotes, unless it contains characters other than letters, numbers and white spaces.
Here is a sample output of the build messages you get when you compile with <ryDiagnostics.h>
Output
e:\rydiagnostics\rydebug.h(58):## Build Message > This is a Win32 debug build e:\rydiagnostics\rydebug.h(77):## Build Message > ryDebug macros are enabled e:\rydiagnostics\rydiagnostics.h(80):## Build Message > Structured Exception logging enabled e:\rydiagnostics\rydiagnostics.h(83):## Build Message > CRT Memory tracking enabled e:\rydiagnostics\rydiagnostics.h(84):## Build Message > C++ Unhandled exception logging enabled Linking... ryDebug.exe - 0 error(s), 0 warning(s)
NOTE: These messages help you to determine what had been enabled. The type of build (release or debug) and the platform (Win32 and non-Win32). This can be helpful with multiple OS supported code.
NOTE: For developers working with DevStudio, if you double click on the build comment, the file will be opened for you and you will be taken to that line! This is great for those TO DO reminders.
Log Header
A log entry will start with a header showing the
date and time of a diagnostic session.
---------------------------------------- Log created on: Thu Sep 21 19:16:55 2000 ----------------------------------------
Filename and line number association with messages
Below we
see an example of a logged message. From the output we can see that this message
was generated on line 181 in the file, D:\dev\src\test\server.cpp
Error starting the server. D:\dev\src\test\server.cpp(181)
Assert validation
You should get into the good habit of
validating parameters and state variables for pre and post conditions.
The good thing about using this assert macro is that it works in silent mode. No message box pops up and halts the program. Later, you can refer to the log file and see which assert failed and where it failed.
Syntax: ryAssert(condition).
This macro will cause a log entry to be made if the assertion fails. Also it will show you what assertion failed.
For example
Output Assertion (n > m) failed c:\temp\test.cpp 11
Memory dump
This technique is very useful for tracking any
type of data corruptions. To dump a segment of memory you need to specify the
starting address and the number of bytes to display. The macro that does this
is:
Syntax: ryMemDump(char* p, int nSize);
Output Memory Dump [szData=0x42524C: 52 bytes] E:\RajinderY\Research\ryDebug\ryDebug.cpp 18 41 42 43 44 45 46 47 48 49 4A ABCDEFGHIJ 4B 4C 4D 4E 4F 50 51 52 53 54 KLMNOPQRST 55 56 57 58 59 5A 20 30 31 32 UVWXYZ 012 33 34 35 36 37 38 39 30 2B 2D 34567890+- 2E 21 40 23 24 25 5E 26 2A 28 .!@#$%^&*( 29 00 xx xx xx xx xx xx xx xx )
NOTE: The memory dump message states the variable, it's address and the number of bytes displayed
File dump
A binary file dump macro works just like the
memory dump macro.
Syntax: ryFileDump(filename);
If the file does not exist, the following message will be logged:
file dump: File <filename> could not be opened
If a read error is encountered, the following message is logged:
file dump: Error reading from file <filename>
NOTE: The file dump macro opens the file in binary mode. What this means is that translations involving carriage-return and linefeed characters are suppressed.
Heap access monitoring {malloc, new, realloc, free, delete}
This service is automatically provided for you on the Win32 platform.
A typical message will state the point that memory was allocated and when it is released.
Memory leak detection
This service is automatically
provided for you on the Win32 platform
Memory leak notification will occur when the process terminates. From the log you can see the reference block number and the line where memory was allocated but was not released.
For example, from the sample log message above, we know:
Output
Allocating Memory {34}, Block Size 99 bytes
E:\RajinderY\Research\ryDiagnostics\test\test.cpp, line 17
:
:
Detected memory leaks!
Dumping objects ->
E:\RajinderY\Research\ryDiagnostics\test\test.cpp(17) :
{34} client block at 0x00300220, subtype 0, 99 bytes long.
Data: <This string will> 54 68 69 73 20 73 74 72 69 6E 67 20 77 69 6C 6C
Object dump complete.
Breaking on a heap allocation (Tracking memory leaks)
Finding and tracking down memory leaks just got a lot easier!
When a memory leak occurs, the memory dump will display a reference block number for each block that was not released. If you are working with DevStudio you can break the execution flow at the point a memory block is allocated. This will allow you to track who is making the call to allocate the memory and at the same time what is failing to be released.
To halt the process when this happens, run your app from DevStudio by hitting the [F10] function key. This will cause the process to break as soon as it's started.
At this point go to the "Watch" window, if it's not visible hit [ALT + 3].
In the window type "ryAllocBreak" and hit enter. A value of -1 should be displayed, this indicats that the process will not halt on any memory allocation. Now change the value of ryAllocBreak to equal the reference block number and hit [F5] to run the process.
When this memory allocation occurs you will get a message box stating, "User breakpoint call from code at..."
When you see this, hit "OK" and then refer to the call stack to see who's making the call to allocate the memory. At this point it should become evident what needs to be released!
From our sample code earlier we can see that we are failing to free pointer p2!
Call Stack shows where the allocation occured
Source File shows what is not being released
NOTE: If the app was run outside of DevStudio you will not get the same reference number! Each run will produce the same reference number, but the numbers shown from a debug session in DevStudio and a standalone execution will differ. The best thing to do is run the process from DevStudio once, get the memory leak reference number, then run it again to set ryAllocBreak with this value.
Crash detection (Win32 SEH)
A list of the Win32 structured
exceptions that are logged for you automatically are shown below.
| Error | Meaning |
|---|---|
| EXCEPTION_ACCESS_VIOLATION | The thread tried to read from or write to a virtual address for which it does not have the appropriate access. |
| EXCEPTION_ARRAY_BOUNDS_EXCEEDED | The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking. |
| EXCEPTION_BREAKPOINT | A breakpoint was encountered. |
| EXCEPTION_DATATYPE_MISALIGNMENT | The thread tried to read or write data that is misaligned on hardware that does not provide alignment. For example, 16-bit values must be aligned on 2-byte boundaries; 32-bit values on 4-byte boundaries, and so on. |
| EXCEPTION_FLT_DENORMAL_OPERAND | One of the operands in a floating-point operation is denormal. A denormal value is one that is too small to represent as a standard floating-point value. |
| EXCEPTION_FLT_DIVIDE_BY_ZERO | The thread tried to divide a floating-point value by a floating-point divisor of zero. |
| EXCEPTION_FLT_INEXACT_RESULT | The result of a floating-point operation cannot be represented exactly as a decimal fraction. |
| EXCEPTION_FLT_INVALID_OPERATION | This exception represents any floating-point exception not included in this list. |
| EXCEPTION_FLT_OVERFLOW | The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type. |
| EXCEPTION_FLT_STACK_CHECK | The stack overflowed or underflowed as the result of a floating-point operation. |
| EXCEPTION_FLT_UNDERFLOW | The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type. |
| EXCEPTION_ILLEGAL_INSTRUCTION | The thread tried to execute an invalid instruction. |
| EXCEPTION_IN_PAGE_ERROR | The thread tried to access a page that was not present, and the system was unable to load the page. For example, this exception might occur if a network connection is lost while running a program over the network. |
| EXCEPTION_INT_DIVIDE_BY_ZERO | The thread tried to divide an integer value by an integer divisor of zero. |
| EXCEPTION_INT_OVERFLOW | The result of an integer operation caused a carry out of the most significant bit of the result. |
| EXCEPTION_INVALID_DISPOSITION | An exception handler returned an invalid disposition to the exception dispatcher. Programmers using a high-level language such as C should never encounter this exception. |
| EXCEPTION_NONCONTINUABLE_EXCEPTION | The thread tried to continue execution after a noncontinuable exception occurred. |
| EXCEPTION_PRIV_INSTRUCTION | The thread tried to execute an instruction whose operation is not allowed in the current machine mode. |
| EXCEPTION_SINGLE_STEP | A trace trap or other single-instruction mechanism signaled that one instruction has been executed. |
| EXCEPTION_STACK_OVERFLOW | The thread used up its stack. |
Copyright © 2000 Rajinder Yadav, All rights reserved