开发者

Writing Garbage on free / delete

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 perfo开发者_如何学运维rm 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.


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.


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.


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.


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


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜