3

One of the issues with dynamic memory allocation is that one may delete/free a block of memory and still have pointers pointing into it. When one dereferences one of these pointers, chances are that things may "work" but leave one vulnerable to memory corruptions etc.

In order to help with these issues some platforms make delete / free write garbage (something like DEDEDEDE) into the freed heap cell before releasing it as a freed cell. This means that when one tries to now dereference a pointer to a freed cell, one can more or less always expect a data_abort exception which should cause the program to crash. This will when using the debug library. The release library does not do this because of performance reasons.

Could someone tell me if one can get this kind of behavior on standard Linux platforms using glibc or how to perform some simple operation to do this. I think it will help me find some bugs a lot more easily.

I would like to add that it should be trivial to enable or disable this behavior for different builds. The closest thing I can think of is malloc hooks, unfortunately free does not take the cell size as a parameter.

doron
  • 27,972
  • 12
  • 65
  • 103
  • 11
    why don't you tackle the underlying problem? Use a suitable tool, Purify/Valgrind and that will tell you all you need... – Nim Nov 23 '10 at 23:11
  • A debug allocator already does this, see what's available in your CRT. – Hans Passant Nov 23 '10 at 23:12
  • 1
    Are you programming in C, or are you programming in C++? They aren't the same. – GManNickG Nov 23 '10 at 23:25
  • @Hans - and how does one get to use the default allocator on linux. – doron Nov 24 '10 at 00:02
  • 1
    @Nim, because while Valgrind may be great for finding memory leaks it is way too slow to run all my test code in. – doron Nov 24 '10 at 00:03
  • If you are not worried about the performance then why not use the debug build that would give you this. – Greg Domjan Nov 24 '10 at 00:20
  • 1
    Trial-and-error is a really bad way to go about programming. If you can't avoid using already-freed memory and you don't have a damn good excuse like obscure race conditions in multithreaded code, you should either stick to languages that manage memory for you (which could include C++ with shared pointers) or at least minimize the use of dynamic allocations in your C code. Most dynamic allocation by novice programmers is actually a mistake, a result of wanting to copy data that could just as well have been used in-place. – R.. GitHub STOP HELPING ICE Nov 24 '10 at 02:15
  • @doron, surely if you know you have memory issues, why is *performance* your highest priority? – Nim Nov 24 '10 at 08:39
  • @Nim I am not worrying about known problems, it is the unknown problems that I am worried about. When creating a build for test, I would like something that has the greatest likelyhood of blowing up if there are memory bugs. Writing garbage to freed memory should help in this regard. – doron Nov 24 '10 at 11:45
  • @R: While it's true that trial-and-error is a bad primary approach, even the most experienced among us makes mistakes. Tools that help us catch and undo those mistakes are almost as valuable as language features that help to minimize them. – nmichaels Nov 24 '10 at 15:06

7 Answers7

3

For C++, you can do this fairly portably: replace global operators ::new and ::delete:

#include <cstdlib>
#include <stdexcept>

// value must be at least as big as sizeof(size_t),
// and a multiple of the maximum alignment required for any
// type by this implementation
#define MAX_ALIGN 8

size_t &stored_size(char *p) {
    return *reinterpret_cast<size_t*>(p);
}

void *operator new(size_t n) {
    char *p = static_cast<char*>(std::malloc(n+MAX_ALIGN));
    if (!p) throw std::bad_alloc();
    stored_size(p) = n;
    char *base = p + MAX_ALIGN;
    // set base with n bytes of eye-catchers for uninitialized memory
    return base;
}

void operator delete(void *ptr) {
    if (!ptr) return;
    char *base = static_cast<char*>(ptr);
    char *p = base - MAX_ALIGN;
    size_t n = stored_size(p);
    // set base with n bytes of eye-catchers for freed memory,
    // and make sure your compiler isn't clever enough to optimize that away.
    std::free(p);
}

If your program registers new handlers, then you'll want to call them from ::new.

To catch malloc/free, you have to do linux-specific things as in other answers, but the same trick could solve your problem that you don't have the size in the free hook, assuming that you don't want to hunt around for the size that was stored by the real malloc.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
2

As a last resort, you can fiddle with my non-intrusive heap debugger. It will not prevent you from dereferencing a dangling pointer, but it will detect double deletes and other common errors.

Community
  • 1
  • 1
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
2

From the Linux malloc man page:

   Recent  versions  of  Linux  libc  (later  than 5.4.23) and glibc

(2.x) include a malloc() implementation which is tunable via environment variables. When MALLOC_CHECK_ is set, a special (less efficient) implementation is used which is designed to be tolerant against simple errors, such as double calls of free() with the same argument, or over- runs of a single byte (off-by-one bugs). Not all such errors can be protected against, however, and memory leaks can result. If MAL- LOC_CHECK_ is set to 0, any detected heap corruption is silently ignored; if set to 1, a diagnostic message is printed on stderr; if set to 2, abort(3) is called immediately; if set to 3, a diagnostic message is printed on stderr and the program is aborted. Using a nonzero MAL- LOC_CHECK_ value can be useful because otherwise a crash may happen much later, and the true cause for the problem is then very hard to track down.

nmichaels
  • 49,466
  • 12
  • 107
  • 135
  • I think this is useful for checking memory leaks by probably does write garbage to freed memory. – doron Nov 25 '10 at 12:22
1

The following code does exactly what I want:

#include <malloc.h>

typedef void (*free_hook_t)(void*, const void*);

static free_hook_t system_free_hook;

static void my_free_hook (void *ptr, const void *caller)
     {
       __free_hook = system_free_hook;
       int size = malloc_usable_size(ptr);
       memset(ptr,0xDE, size);
       free (ptr);
       __free_hook = my_free_hook;
     }

static void init_free_hook()
     {
     system_free_hook = __free_hook;
      __free_hook = my_free_hook;
     }

/* Override initializing hook from the C library. */
void (*__malloc_initialize_hook) (void) = init_free_hook;

It is totally stand alone so technically can be included or not as required. The bit I was missing was the malloc_usable_size function.

Testing on Ubuntu 10.10, this also works in C++ where one is using new and delete

doron
  • 27,972
  • 12
  • 65
  • 103
0

Not in C++. Never heard of smart pointers? Dangling pointers are a problem of the past. You can obtain high-quality smart pointers from boost and the Standard library. Use them wisely, and you will never leak memory or try to access an object that no longer exists.

As for free not taking the cell size, it's my understanding that most implementations allocate a block and then have a block header, then return the memory, e.g.

void* malloc(int size) {
    blockheader b; // fill out the structure with e.g. blocksize
    void* mem = allocate_memory(size + sizeof(blockheader));
    memcpy(mem, &b, sizeof(blockheader)); // or memcpy(&b, mem, ...), I can't recall
    return (void*)((char*)mem + sizeof(blockheader)); // return the actual memory
}

Then in free(), they just access the blockheader again to obtain the size. You could thusly use a malloc hook.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • I guess you have never had to manage legacy code which just so happens to be a mix of both C and C++. Besides shared_prt and weak_ptr have not even made the standard yet. – doron Nov 24 '10 at 12:13
  • @doron: Boost? Plus, I admit, this isn't for legacy code management- that's why I added the blockheader idea. – Puppy Nov 24 '10 at 14:54
0

You can always just wrap free in something that does take the size of the block to be deallocated as a parameter, and use macros to switch to production mode:

#ifdef DEBUG
#define FANCY_FREE(ptr, size) do {memset(ptr, 0xDE, size); free(ptr);} while (0)
#else
#define FANCY_FREE(ptr, size) free(ptr)
#endif

It's also a good idea to null out your pointers after calling free on them. It doesn't catch pointer aliasing related bugs, but it will catch some double-frees.

nmichaels
  • 49,466
  • 12
  • 107
  • 135
  • @and how do I genericallyknow what size to use for fancy free without doing my own book keeping? – doron Nov 24 '10 at 00:06
  • @doron: You don't. Most heap allocators stick the size in memory right before your pointer, but that's awfully specific... – nmichaels Nov 24 '10 at 00:17
0

In c++, once you delete the object pointed by a pointer, every further access to the pointer is UB. Besides, in c++ raw pointers should be rarely used.

BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 2
    I know that, I just want a simple way to make that UB tend to blow up rather than fail silently. – doron Nov 24 '10 at 00:04
  • @doron Then you probably know that such problems are easily solved by having good unit tests. – BЈовић Nov 24 '10 at 07:58
  • except I want to maximize the chances of my unit tests detecting the problem. – doron Nov 24 '10 at 12:14
  • @doron In that case you are not testing good. Run your unit tests under valgrind (or similar tool), and it will report you all memory problems. – BЈовић Nov 24 '10 at 12:16