The Heisenberg principle

Some of the worst bugs are those that disappear when normal debugging techniques are applied. These include behaviors such as:

To avoid these kinds of behaviors, debugging techniques should try and perturb the program as little as possible.

Unfortunately, the extended header pointer tracking technique discussed in the last section greatly perturbs the memory allocator behavior. It changes the sizes of the objects being allocated, which in turn will change the order in which memory blocks are allocated.


Warning: This section is extremely system dependent. The ideas should be generally applicable; however, the exact implementations described will need to be tailored to fit your system.


A much less intrusive solution is to store the extra debugging information about an object in a parallel structure, in memory obtained using a mechanism that does not change the memory allocator behavior at all. The debug memory obtained should be the same size as the expected maximum heap size.

Some of the techniques available to obtain memory for use without changing the behavior of the allocator are:

You will also have to be able to obtain the address of the start of the heap. This was shown in an earlier section.

Putting these ideas together:


	#include <stddef.h>
	#include <stdlib.h>
	#include <limits.h>

	struct exthdr_ptr {
	  char *	xhp_type_name;
	  unsigned	xhp_number;
	};

	#define type_name_exthdr_ptr(_xhp)	((_xhp)->xhp_type_name)
	#define number_exthdr_ptr(_xhp)		((_xhp)->xhp_number)

	/* These are setup else where */
	extern char *	base_heap_ptr;
	extern char *	base_debug_ptr;
	extern unsigned	size_debug_ptr;


	void *
      	do_xalloc(
		unsigned	size,
		char *		type_name,
		unsigned	number
		)
	{
	  char *		ptr = malloc(size);
	  unsigned		offset = ptr - base_heap_ptr;
	  struct exthdr_ptr *	hdr =
	  	(struct exthdr_ptr *)(base_debug_ptr+offset);

	  assert( ptr >= base_heap_ptr );
	  assert( offset + sizeof(struct exthdr_ptr) < size_debug_ptr );

	  type_name_exthdr_ptr(hdr) = type_name;
	  number_exthdr_ptr(hdr) = number;

	  return ptr;
	}

	int
	check_exthdr_ptr(
		void *		ptr,
		char *		type_name,
		unsigned	number
		)
	{
	  unsigned		offset = ptr - base_heap_ptr;
	  struct exthdr_ptr *	hdr =
	  	(struct exthdr_ptr *)(base_debug_ptr+offset);

	  assert( ptr >= base_heap_ptr );
	  assert( offset + sizeof(struct exthdr_ptr) < size_debug_ptr );

	  if( number != number_exthdr_ptr(hdr) ) {
	    return 0;
	  }

	  if( type_name != type_name_exthdr_ptr(hdr) ) {
	    if( strcmp(type_name, type_name_exthdr_ptr(hdr)) != 0 ) {
	      return 0;
	    }
	    type_name_exthdr_ptr(hdr) = type_name;
	  }

	  return 1;
	}

This approach establishes a structure in the debug memory parallel to each allocated object. For the case of an int_stack, the relationship looks like:


base_heap_ptr:		base_debug_ptr:
	.		.
	.		.
		
	.		.
	.		.
	__________	.
	|   25   |	.
	|________|	__________
stack-->|   16   |	| 	+---> "struct int_stack"
	|________|	|________|
	|    0   |	|UINT_MAX|
	|________|	|________|
	|        |
	|________|

We do not have to restrict ourselves to just keeping track of type and size information. For instance, if the minimum size object is 16 bytes, we might be able to also track the file name and line number where the object was allocated.


Next Prev Main Top Feedback