Detecting Memory Corruption with Dog Tags

A simple marker can reveal a broad class of storage overwrite problems.A simple marker can reveal a broad class of storage overwrite problems.


May 01, 1998
URL:http://www.drdobbs.com/detecting-memory-corruption-with-dog-tag/184403504

May 1998/Detecting Memory Corruption with Dog Tags/Listing 1

Listing 1: Class DogTag

#include <assert.h>

#define DECL_DOG_TAG(dogTag) DogTag dogTag;
#define CHECK_DOG_TAG(dogTag) \
   assert(dogTag.isValid())

class DogTag {
public:
   DogTag(): _this(this) {}
   DogTag(const DogTag& rhs): _this(this)
      {assert(rhs.isValid());}
  ~DogTag() {assert(isValid()); _this=0;}

   DogTag& operator=(const DogTag& rhs)
      {
         assert(isValid());
         assert(rhs.isValid());

         return *this;
      }

   bool isValid() const
      {return _this == this;}

private:
   const DogTag *_this;
};

/* End of File */

May 1998/Detecting Memory Corruption with Dog Tags

Detecting Memory Corruption with Dog Tags

Jim Williams

A simple marker can reveal a broad class of storage overwrite problems.


One of the few systematic techniques for finding bugs related to memory corruption uses debugging versions of malloc and free. When asked for a block of memory, such a malloc implementation allocates an extra byte at the beginning and end of the block it returns. These bytes, known as "dog tags" or "guard words," are initialized by malloc to some known value before returning. Since they are hidden from the calling program, their contents should not change during the lifetime of the block. This allows free to check each block it receives for changes, treating any found as errors. This technique can detect memory overwrites, multiple frees of the same pointer, and frees of pointers that do not address dynamically-allocated memory.

This article describes a C++ class, DogTag, that uses the same idea. Simply adding a DogTag as a member of a class enables that DogTag to detect the above problems when they occur in objects of the class. Adding a few more lines to the same class enables its DogTags to detect uses of objects after they've been destroyed, and uses of objects that were never constructed. DogTags also have some advantages over debugging malloc libraries. One is the ability to find problems in objects of any storage type: heap, stack, or static.

Implementation

Listing 1 shows a conceptual implementation of the DogTag class. The full source is available online (see page 3 for downloading instructions). As shown, DogTags own a single data member named _this. During the lifetime of the object, _this holds the same value as the object's this pointer, initialized to that value at construction. Notice that the copy constructor and assignment operator do not copy the member from their argument, since all DogTags must have a unique _this member. Only in the destructor does the pointer finally change, being set there to null.

Controlling the member pointer in this way allows the isValid method to detect whether or not a problem has occurred. If isValid finds _this not equal to the object's address (this), it returns false. In such a case, one of the problems listed above has probably occurred. The destructor calls isValid within an assertion before setting _this to null. Thus, as objects owning DogTags go in and out of scope, or are allocated and deleted, they automatically check themselves for problems.

Using DogTags

The obvious way to use a DogTag is to add one to any class whose objects are getting corrupted. However, an even better way is to add them to your code from the beginning, ignoring them until they bark, much like assertions. Adding DogTags to a few, commonly-used classes is a good way to passively find these problems throughout development.

Instead of explicitly declaring DogTags or calling isValid, I have implemented two macros that accomplish the same thing. Both are conditionally defined with preprocessor directives (not shown here) that allow you to control whether they expand to the code shown or to nothing. To add a DogTag to a class, insert a call to the DECL_DOG_TAG macro in its body as follows:

class MyClass {
public:
   ...
   void method();

private:
   ...
   DECL_DOG_TAG(_dogTag)  //added
};

Note that no semicolon should be used in this macro call; otherwise, when the macro expands to nothing (as in a production build) it will leave a lone semicolon hanging in your class.

You can find problems earlier, plus additional types of problems, by inserting calls to CHECK_DOG_TAG into the methods of the class. CHECK_DOG_TAG conditionally expands to a call to isValid within an assertion or to nothing. Note that semicolons are needed after calls to this macro.

Summary

DogTags offer a way to automatically find many of the costliest bugs that exist in C++ software. They cannot find all such bugs, only those that corrupt the DogTag bytes. Nor are DogTags meant to replace more powerful commercial debugging tools. Even so, DogTags helped us find some subtle compiler bugs we were unable to find any other way. They are a worthy addition to the programmer's toolbox. o

Jim Williams received his education at Texas A&M University, where he earned a BS and MS in mechanical engineering. He currently writes software for United Space Alliance in support of space shuttle and space station robotics. His interests include object-oriented programming, classical mechanics, control systems, and numerical methods. He can be reached at [email protected].

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.