[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

8.4.3 How It Works

Here are some technical details about the functioning of the memory debugger. This can help you understand better how it works, and I think this can help you use the debugger in a more productive fashion.

In each `new' or `delete' call, we check whenever we have been initialized. If not, we call the initialization function that parses the `.map' file, sets the debugging options and so on. When program exits the memory debugger checks for unfreed memory block, displays statistics and so on. This is done by declaring a dummy static variable at the very end of memory debugger module and the shutdown function being called from its destructor.

The only trick used in memory debugger is used to get the address from where `new' and `delete' have been called. This is a processor-dependent issue, alas we can't do it in a platform-independent fashion. To support doing this on different platforms we define a macro called GET_CALL_ADDRESS(). We pass the first argument of the procedure we're invoking GET_CALL_ADDRESS() from and we get the address from were we were called in a variable called `addr'.

Here are details on how we're doing it on different platforms:

Intel x86
These processors use a stack for passing arguments, and the same stack is used to push the return address. Since all machines uses a top-to-bottom stack (i.e. stack increases in the direction of lower addresses), and also since ANSI C standard requires that the addresses of function arguments increase (that is, if you're defining a function such as `void some(int a, int b)' you can be absolutely sure that address of `b' is higher than address of `a'. It is a good guess to suppose that on most computers as of today upon the invocation of the `new' and `delete' operators the stack will look like this:

 
+-----------------+ higher addresses
|       arg2      |        ^
+-----------------+        |
|       arg1      |        |
+-----------------+        |
|  return address |        |
+-----------------+ lower addresses

This means that if we take the address of first argument of the procedure and go one machine word back, we'll get the address of the function's return address word. Now we peek it from there and we have the return address. This is embedded into a macro that looks like this:

 
#define GET_CALL_ADDRESS(firstarg) \
  address addr = ((address*)&firstarg) [-1];

Where `address' is a shortcut for type `void*'.

An Almost Cross-Platform Solution
Fortunately for us, GCC versions above 2.8.0 have a extremely useful built-in function called __builtin_return_address(). If we invoke it with the argument zero, it returns just what we need; the address from where our procedure was called. Thus, an almost-cross-platform GET_CALL_ADDRESS() is implemented this way:

 
#if (__GNUC__ >= 2) && (__GNUC_MINOR__ >= 8)
#  define GET_CALL_ADDRESS(firstarg) \
     address addr = (address)__builtin_return_address(0);
#endif

Thus, if you can't get `memdbg.sh' to work, you should find a newer GCC; this should always help.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

This document was generated using texi2html