__MODULE_DEBUG_TOOLS__

Yesterday, at least at the time I am writing this entry, I spoke about the Zune and its time bug that caused so many of the devices to just freeze on boot. It its clear that this bug is the result of poor testing (in addition to a poor implementation of a conversion using loops rather than explicit divisions and moduli) and this week, I’d like to tell you about one of my tricks to debug code.

In addition to incremental development, frequent and complete unit testing using over-night regression testing (if necessary), I very rarely rely on the macro assert. I do not like assert because it is a bad substitute for design by contract, and because it just crashes your program, which is not always what you want. Granted, there are many types of asserts, and you can implement your own variant if you want to. In fact, the macros I want to tell you about this week may help you to create more robust debug-time code with no penalty on release code (debug code is usually not optimized, and contains debugging symbols, while release code have been thoroughly optimized by the compiler and contain no extra code).

Consider the following header file:

#ifndef __MODULE_DEBUG_TOOLS__
#define __MODULE_DEBUG_TOOLS__

/* Basic conditional compilation utilities
   (c) 2008 Steven Pigeon
*/

/* test to determine what mode
   we're in.

   We need to define or detect
   __DEBUG_MODE__    defined if _DEBUG is defined (under MSVC)
   __RELEASE_MODE__  defined if _DEBUG and __DEBUG_MODE__ are undefined
   __TEST_MODE__

   (symbols that do not conflict
   with the standard defines.)
*/

/* Detect Microsoft Visual C/C++ */
#if defined(_MSC_VER) && \
    !(defined(__DEBUG_MODE__) || defined(__RELEASE_MODE__))
  /* this is most probably Visual C/C++ */
  #if defined(_DEBUG)
    #define __DEBUG_MODE__
  #else
    #define __RELEASE_MODE__
  #endif
#else
  /* if not Microsoft Visual C/C++,
     let us assume that it is gcc or g++,
     and let's suppose the symbols are
     defined externally */
#endif

/* let us deal with mutual exclusions. */
#if defined(__DEBUG_MODE__) && defined(__RELEASE_MODE__)
  #error Define only one of __DEBUG_MODE__ or __RELEASE_MODE__ at the same time
#endif

/* macros for debug mode */
#if defined __DEBUG_MODE__
  #define DEBUG_ONLY(x) x
  #define BEGIN_DEBUG_ONLY
  #define END_DEBUG_ONLY
#else
  #define DEBUG_ONLY(x)
  #define BEGIN_DEBUG_ONLY if (0) {
  #define END_DEBUG_ONLY }
#endif

/* macros for release mode */
#if defined __RELEASE_MODE__
  #define RELEASE_ONLY(x) x
  #define BEGIN_RELEASE_ONLY
  #define END_RELEASE_ONLY
#else
  #define RELEASE_ONLY(x)
  #define BEGIN_RELEASE_ONLY if (0) {
  #define END_RELEASE_ONLY }
#endif

/* macros for (unit) testing */
#ifdef __TEST_MODE__
  #define TEST_ONLY(x) x
  #define BEGIN_TEST_ONLY
  #define END_TEST_ONLY
#else
  #define TEST_ONLY(x)
  #define BEGIN_TEST_ONLY if (0) {
  #define END_TEST_ONLY }
#endif

/* more of similar #defines would go
   here */

#endif
 /* __MODULE_DEBUG_TOOLS__ */

This header file defines three sets of macros, X_ONLY, BEGIN_X_ONLY and END_X_ONLY, where X can be one of DEBUG,RELEASE, or TEST that you can use to conditionally enable code depending on which type of compilation you’re doing. If you are running under Visual Studio, the header detects _DEBUG, which is defined for debug builds, to define __DEBUG_MODE__ and activate the DEBUG macros. If _DEBUG is not defined, while under Visual Studio, __RELEASE_MODE__ is assumed. Gcc doesn’t have this debug/release concept, so the header will rely on the explicit, external, definition of __DEBUG_MODE__ xor __RELEASE_MODE__ and of __TEST_MODE__.

For example, assuming either _DEBUG or __DEBUG_MODE__ is defined:

int function(int x, int y)
 {
  BEGIN_DEBUG_ONLY 
    // checks if a solution is possible
    if (x<y)
        send_to_log(__PRETTY_FUNCTION__,
                __LINE__,
                __FILE__,
                "%d<%d at %d in %d\n",
                x,y);
  ...more code to trap errors...  
   END_DEBUG_ONLY

  /* input-contract code goes here */

  /* body of function code goes here */

  TEST_ONLY( if (c>100) return E_TOOLONG; )

  /* output-contract code goes here */

  BEGIN_DEBUG_ONLY
    if (results & 0xf1415)
       send_to_log(__PRETTY_FUNCTION__,
                     __LINE__,
  ...more code to trap errors...  
  END_DEBUG_ONLY

  return result;
 }

would generate the extra code placed between the BEGIN_DEBUG_ONLY and END_DEBUG_ONLY blocks. If __DEBUG_MODE__ (and _DEBUG) is undefined, the above would disable the same code. In other words, in __RELEASE_MODE__, no extra code
whatsoever would be generated. This, of course, is unaffected by the various switches you can pass your compiler. On Gcc, for example, using -O3 does not imply that __RELEASE_MODE__ will be defined; would __DEBUG_MODE__ be defined, it would remain so and activate the corresponding code.

(Yes, I know. You’re thinking but how can this help when the code behavior changes when the code is compiled with all the fancy optimizations?. Well, it doesn’t. It doesn’t help much, at any rate. And this problem is a serious and complex one, and there’s not much you can do except build better debug-able code. When the behavior changes between non-optimized builds and fully-optimized builds, it’s almost always related to bad pointer arithmetic or out-of-bounds array accesses. In debug mode, at least on Visual Studio, many things changes. For example, allocated memory contains fence bytes that precede and follows the allocated memory to trap (if enabled) out-of-bound writes. Basically, the memory allocation manager fills the safeguards with a known pattern; allowing the heap to detect out of bound writes when you free the memory. For example, Visual Studio uses CCCCCCCC for uninitialized stack variables, FDFDFDFD for the heap memory guards, CDCDCDCD and FEFEFEFE for uninitialized and freed heap memory, respectively (ref). With debug fences out of bound reads and writes do not immediately lead to crashes, lulling you into a false sense of security about the correctness of the code—especially reads as they are not detected by the debug heap manager.

In release mode, there is no fence, so you can jump down the ravine just fine. That is, out of bound writes and reads are more likely to be performed on unallocated memory, or even on memory that doesn’t even belong to your process, causing an immediate crash.)

(Also, using __DEBUG_MODE__ (and especially __TEST_MODE__) will let you insert extra code that may help you make better, deeper, tests for your code. For example, loops that may be infinite would befinit from extra code enabled by __TEST_MODE__ that detects excessive looping. Rings a bell?)

(Also, the X_ONLY macro have been defined that way because of a GNU CPP bug around symbol concatenation operator, ##. While z ## z yields the expected result, zz, / ## / yields / / (not //) and an error. The older version of the header—still working fine with Visual Studio—would include:

#if !defined(__DEBUG_MODE__)
 #define DEBUG_ONLY COMMENT_SLASH(/)
 #define COMMENT_SLASH(z) /##z
#else
 #define DEBUG_ONLY
#endif

…which would, of course, yield the expected //.)

2 Responses to __MODULE_DEBUG_TOOLS__

  1. […] my case, I have a series of macros that control conditional compilation (such as described in a previous post). Suppose I have set of macros BEGIN_UNIT_TEST_ONLY, END_UNIT_TEST_ONLY and UNIT_TEST_ONLY that […]

  2. […] Access. In a previous entry I described how we can use the CPP to conditionally activate pieces of code depending on whether we […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: