C++ Heap Map

A small malloc() and free() interceptor library (heaptrace.so, 45 lines of C++) and minimalistic TCL/TK GUI for visual C++ heap analysis — a powerful tool for disputes settlement and curiosity satisfaction.

An executable under investigation should run the following way (NEdit text editor in the example)

$ LD_PRELOAD=.../heaptrace.so .../nedit | awk '/^[+-]heap /' >.../heaptrace.log
An awk-based filter is suppressing the process's own stdout output.

Because of the preloaded interceptor library a message will be printed to the stdout on each malloc() or free() invocation. The resulting dump would look like

. . .
+heap 0x20b5040 64
+heap 0x20b5090 8176
+heap 0x20b7090 4096
+heap 0x20b80a0 32
+heap 0x20b80d0 8
+heap 0x20b80f0 568
-heap 0x20b80f0 0
+heap 0x20b80f0 2784
-heap 0x20b80f0 0
+heap 0x20b80f0 336
. . .

At the moment of interest the GUI script can be invoked on the resulting dump as

$ .../heapmap.tk .../nedit.allocs.txt
The GUI script will print some diagnostic information
reading nedit.allocs.txt
allocated memory blocks: 16278
input min address: 0x1dc1040
input max address: 0x2058aa0
aligned min address: 0x1dc1000
aligned max address: 0x2059000
page size: 0x1000 (4096)
number of pages: 0x298 (664)
max_pages_per_row=2.23606797749979
pages_per_row=2
allocations drawing done
vertical grid lines done
horizontal grid lines done
and will display the heap map heapmap.tk The map can be zoomed by GUI buttons or via left mouse button area selection and scrolled either with scroll bars or right mouse button drag. Double click on the allocated block highlights all other blocks of the same size on the map. heapmap.tk.zoom

C++ source code for the library (build instructions in the comments) is below. It is also in attachment to the post along with the GUI TCL/TK script (license)

#include <cstdio>
#include <dlfcn.h>

#define likely(x)   __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)

// g++ -pthread -m64 -fPIC -std=c++0x -O3 -Wl,-zdefs -Wl,-znow -ldl -shared -Wl,-soname,heaptrace.so -o heaptrace.so heaptrace.cc

namespace
{

/**
 * malloc() direct call
 */
inline void * libc_malloc(size_t size)
{
  typedef void* (*malloc_func_t)(size_t);
  static malloc_func_t malloc_func = (malloc_func_t) dlsym(RTLD_NEXT, "malloc");

  return malloc_func(size);
}

/**
 * free() direct call
 */
inline void * libc_free(void* ptr)
{
  typedef void (*free_func_t)(void*);
  static free_func_t free_func = (free_func_t) dlsym(RTLD_NEXT, "free");

  free_func(ptr);
}

/**
 * malloc() call recorder
 */
void record_malloc(size_t size, void* ptr)
{
  if (unlikely(ptr == 0)) return;

  char buf[64];
  size_t len = snprintf(buf, sizeof(buf) / sizeof(char), "+heap %p %lu\n", ptr, size);
  fwrite(buf, sizeof(char), len, stdout);
}

/**
 * free() call recorder
 */
void record_free(void* ptr)
{
  if (unlikely(ptr == 0)) return;

  char buf[64];
  size_t len = snprintf(buf, sizeof(buf) / sizeof(char), "-heap %p 0\n", ptr);
  fwrite(buf, sizeof(char), len, stdout);
}

} // anonymous namespace


/**
 * malloc() override
 */
extern "C" void* malloc(size_t size)
{
  void* ptr = libc_malloc(size);
  record_malloc(size, ptr);
  return ptr;
}

/**
 * free() override
 */
extern "C" void free(void *ptr)
{
  libc_free(ptr);
  record_free(ptr);
  return;
}

On Linux where address randomization may take place one would need to run setarch -R in the shell to switch it of during the test.

AttachmentSize
heapmap.tk16.96 KB
heaptrace.cc1.45 KB