Rajinder Yadav - C++ Windows Development Tools & Resources :Design, Code, Test, Debug

C\C++ Runtime Debugging and Diagnostic Macros

Author: Rajinder Yadav
Date: Sept 18, 2000
Version: Beta (Research & Development)

#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

Here is a sample log file!
Click here to see the source code that generated this log file

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
To begin working with the diagnostic macros, you need to:
  1. #include <ryDiagnostics.h>
  2. link with ryDiagnostic.lib

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.

A brief description of the log file

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

01  #define ryLOG2CONSOLE
02  #include "ryDebug.h"
03
04  
int main() 
05  {
06     int 1;
07     int 2;
08
09     
ryInitLog(00);
10     ryAssert(n !m);
11     ryAssert(n >m);
12     return 0;
13   }
Colorized by: CarlosAg.CodeColorizer
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);

int main()
{
   
char szData[] "ABCDEFGHIJKLMNOPQRSTUVWXYZ 01234567890+-.!@#$%^&*()";

   
ryInitLog(00);
   
ryMemDump(szData, sizeof(szData));
   return 
0;
}
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:

  1. The reference block number is 34
  2. Memory was allocated in test.cpp on line 17
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.

Win32 Structured Exceptions
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.

Home

Copyright © 2000 Rajinder Yadav, All rights reserved