Next: , Previous: Overwrites and underwrites, Up: Using mpatrol


7.5 Using with a debugger

If you would like to use mpatrol to pause at a specific memory allocation, reallocation or deallocation in a debugger then this section will describe how to go about it. Unfortunately, debuggers vary widely in function and usage and are normally very system-dependent. The example below will use gdb as the debugger, but as long as you know how to set a breakpoint within a debugger, any one will do.

First of all, decide where you would like the mpatrol library to pause when running your program within the debugger. You can choose one allocation index to break at using the ALLOCSTOP option, or you can choose to break at a specific reallocation of that allocation by also using the REALLOCSTOP option. If you use REALLOCSTOP without using ALLOCSTOP then you will break at the first memory allocation that has been reallocated the specified number of times. You can also choose to break at the point in your program that frees a specific allocation index by using the FREESTOP option.

The normal process for determining where you would like to pause your program in the debugger is by using the LOGALL option and examining the log file produced by mpatrol. If your program crashed then you should look at the last entry in the log file to see what the allocation index (and possibly also the reallocation index) of the last successful call was. You can then decide which of the above options to use. Note that the debugger will break at a point before any work is done by the mpatrol library for that allocation index so that you can see if it was the last successful operation that caused the damage.

Having decided which combination of mpatrol options to use, you should set them in the MPATROL_OPTIONS environment variable before running the debugger on your program. Alternatively, your debugger may have a command that allows you to modify your environment during debugging, but you're just as well setting the environment variable before you run the debugger as it shouldn't make any difference1.

After you get to the debugger command prompt, you should set a breakpoint at the __mp_trap() function. This is the function that gets called when the specified allocation index and/or reallocation index appears and so when you run your program under the debugger the mpatrol library will call __mp_trap() and the debugger will stop at that point. If you are not running your program within a debugger, or if you haven't set the breakpoint, then __mp_trap() will still be called, but it won't do anything. Note that there may be some naming issues on some platforms where the visible name of a global function gets an underscore prepended to it. You may have to take that into account when setting the breakpoint on such systems.

Now that you have set the MPATROL_OPTIONS environment variable and have set the debugger to break at __mp_trap(), all that is required is for you to run your program. Hopefully, the debugger should stop at __mp_trap(). If it doesn't then you may have to check your environment variable settings to ensure that they are the same as when you ran the program outwith the debugger, although obviously with the addition of ALLOCSTOP, etc. Once the program has been halted by the debugger, you can then single-step through your code until you see where it goes wrong. If this is near the end of your program then you'll have saved yourself a lot of time by using this method.

The following example will be used to illustrate the steps involved in using the ALLOCSTOP, REALLOCSTOP and FREESTOP options. However, it is only for tutorial purposes and the same effect could easily be achieved by breaking at line 18 in a debugger because in this case it is obvious from the code and the mpatrol log file where it is going wrong. In real programs this is hardly ever the case2.

      1  /*
      2   * Allocates 1000 blocks of 16 bytes, freeing each block immediately
      3   * after it is allocated, and freeing the last block twice.
      4   */
     
     
      7  #include "mpatrol.h"
     
     
     10  int main(void)
     11  {
     12      void *p;
     13      int i;
     
     15      for (i = 0; i < 1000; i++)
     16          if (p = malloc(16))
     17              free(p);
     18      free(p);
     19      return EXIT_SUCCESS;
     20  }

Compile this example code with debugging information enabled and link it with the mpatrol library, then set MPATROL_OPTIONS to LOGALL and run the resulting program. If you examine mpatrol.log you will see the following near the bottom of the file.

     ...
     
     ALLOC: malloc (1000, 16 bytes, 4 bytes) [main|test.c|16]
             0x08049449 main+57
             0x4007C9CB __libc_start_main+255
             0x08049381 _start+33
     
     returns 0x080620E8
     
     FREE: free (0x080620E8) [main|test.c|17]
             0x08049470 main+96
             0x4007C9CB __libc_start_main+255
             0x08049381 _start+33
     
         0x080620E8 (16 bytes) {malloc:1000:0} [main|test.c|16]
             0x08049449 main+57
             0x4007C9CB __libc_start_main+255
             0x08049381 _start+33
     
     FREE: free (0x080620E8) [main|test.c|18]
             0x08049491 main+129
             0x4007C9CB __libc_start_main+255
             0x08049381 _start+33
     
     ERROR: [NOTALL]: free: 0x080620E8 has not been allocated
     
     ...

In this example, we'll want to use ALLOCSTOP to halt the program at the 1000th memory allocation so that we can step through it with a debugger. So, set MPATROL_OPTIONS to ALLOCSTOP=1000 and load the program into the debugger. If you are using gdb you can now do the following steps, but if you are not you will have to use the equivalent commands in your debugger. Note that `(gdb)' is the debugger command prompt and so anything that appears on that line after that should be typed as a command.

     (gdb) break __mp_trap
     Breakpoint 1 at 0x804ee83
     (gdb) run
     Starting program: a.out
     Breakpoint 1, 0x804ee83 in __mp_trap()
     (gdb) backtrace
     #0  0x804ee83 in __mp_trap()
     #1  0x804c61b in __mp_getmemory()
     #2  0x8049894 in __mp_alloc()
     #3  0x8049449 in main() at test.c:16
     (gdb) finish
     Run till exit from #0  0x804ee83 in __mp_trap()
     0x804c61b in __mp_getmemory()
     (gdb) finish
     Run till exit from #0  0x804c61b in __mp_getmemory()
     0x8049894 in __mp_alloc()
     (gdb) finish
     Run till exit from #0  0x8049894 in __mp_alloc()
     0x8049449 in main() at test.c:16
     16              if (p = malloc(16))
     (gdb) step
     17                  free(p);
     (gdb) step
     15          for (i = 0; i < 1000; i++)
     (gdb) step
     18          free(p);
     (gdb) quit
     The program is running.  Exit anyway? (y or n) y

After setting the breakpoint and running the program, the debugger halts at __mp_trap(). Because __mp_trap() is a function within the mpatrol library, you don't want to bother stepping through any of the library functions, and in this case you can't since the mpatrol library was not compiled with debugging information enabled. So, after returning from all of the library functions, the source line becomes line 16 because that was the location of the 1000th memory allocation. Single-stepping twice gets us to line 18 which is our destination. Note that the file extra/.gdbinit included in the mpatrol distribution contains predefined commands which make setting the allocation index to stop at much easier.

Sometimes it is useful to be able to see information about a memory allocation whilst running a program from within a debugger. The __mp_printinfo() function is provided for that purpose and takes a heap address as its only argument. Using the above example, it would have been possible to print out information about the pointer `p' at line 17 from within gdb:

     (gdb) call __mp_printinfo(p)
     address 0x080620E8 located in allocated block:
         start of block:     0x080620E8
         size of block:      16 bytes
         stored type:        <unknown>
         stored type size:   <unknown>
         user data:          0x00000000
         allocated by:       malloc
         allocation index:   1000
         reallocation index: 0
         modification event: 1999
         flags:              none
         calling function:   main
         called from file:   test.c
         called at line:     16
         function call stack:
             0x08049449 main
             0x4007C9CB __libc_start_main
             0x08049381 _start

Some debuggers, such as gdb, also allow you to define your own commands for use in a debugging session. The following example defines a new gdb command called `printalloc' which calls __mp_printinfo()3:

     (gdb) define printalloc
     Type commands for definition of "printalloc".
     End with a line saying just "end".
     >call __mp_printinfo($arg0)
     >end
     (gdb) document printalloc
     Type documentation for "printalloc".
     End with a line saying just "end".
     >Displays information about an address in the heap.
     >end

Footnotes

[1] Unless you've linked the debugger with the mpatrol library.

[2] The other reason that this program is simple is because a proper example would generally involve crashing the program, but on AmigaOS and Netware that would also involve crashing the system — not something you'd want to do whilst trying this out.

[3] A sample GDB command file for use with mpatrol can be found in extra/.gdbinit.