Some of the most common failures that result in customer calls are misuses of the memory allocation routines, malloc, calloc, realloc, valloc, memalign and free. There are many ways in which you can misuse these routines and the data that they return and the resulting failures often occur within the routines even though the problem is with the calling program.

I'm not going to discuss here all the ways you can abuse these routines but look at a particular type abuse. The double free. When you allocate memory using these routines it is your responsibility to free it again so that the memory does not “leak”. However you must only free the memory once. Freeing it more than once is a bug and the results of that are undefined.

This very simple code has a double free:

#include void doit(int n, char *x) { if (n-- == 0) free(x); else doit(n,x); } int main(int argc, char **argv) { char *x; char *y; x = malloc(100000); doit(3, x); doit(10, x); } and if you compile and run that program all appears well;




However a more realistic program could go on to fail in interesting ways leaving you with the difficult task of finding the culprit. It is for that reason the libumem has good checking for double frees:




: exdev.eu FSS 26 $; LD_PRELOAD=libumem.so.1 /home/cg13442/lang/c/double_free Abort(coredump) : exdev.eu FSS 27 $; mdb core Loading modules: [ libumem.so.1 libc.so.1 ld.so.1 ] > ::status debugging core file of double_free (64-bit) from exdev file: /home/cg13442/lang/c/double_free initial argv: /home/cg13442/lang/c/double_free threading model: native threads status: process terminated by SIGABRT (Abort), pid=18108 uid=14442 code=-1 > ::umem_status Status: ready and active Concurrency: 16 Logs: (inactive) Message buffer: free(e53650): double-free or invalid buffer stack trace: libumem.so.1'umem_err_recoverable+0xa6 libumem.so.1'process_free+0x17e libumem.so.1'free+0x16 double_free'doit+0x3a double_free'doit+0x4d double_free'doit+0x4d double_free'doit+0x4d double_free'doit+0x4d double_free'doit+0x4d double_free'doit+0x4d double_free'doit+0x4d double_free'doit+0x4d double_free'doit+0x4d double_free'doit+0x4d double_free'main+0x100 double_free'_start+0x6c > Good though this is there are situations when libumem is not used and others where it can't be used1. In those cases it is useful to be able to use dtrace to do this and any way it is always nice to have more than one arrow in your quiver:




: exdev.eu FSS 54 $; me/cg13442/lang/c/double_free 2> /dev/null < /usr/sbin/dtrace -qs doublefree.d -c /home/cg13442/lang/c/double_free 2> /dev/null Hit Control-C to stop tracing double free? Address: 0xe53650 Previous free at: 2009 Jun 23 12:23:22, LWP -1 This free at: 2009 Jun 23 12:23:22, LWP -1 Frees 42663 nsec apart Allocated 64474 nsec ago by LWP -1 libumem.so.1`free double_free`doit+0x3a double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d : exdev.eu FSS 56 $; If run as root you can get the the real LWP values that did the allocation and the frees:

: exdev.eu FSS 63 $; pfexec /usr/sbin/dtrace -qs doublefree.d -c /home/cg1344> Hit Control-C to stop tracing double free? Address: 0xe53650 Previous free at: 2009 Jun 23 14:21:29, LWP 1 This free at: 2009 Jun 23 14:21:29, LWP 1 Frees 27543 nsec apart Allocated 39366 nsec ago by LWP 1 libumem.so.1`free double_free`doit+0x3a double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d double_free`doit+0x4d : exdev.eu FSS 64 $; Here is the script in all it's glory.

#!/usr/sbin/dtrace -qs BEGIN { printf("Hit Control-C to stop tracing\n"); } ERROR / arg4 == DTRACEFLT_KPRIV || arg4 == DTRACEFLT_UPRIV / { lwp = -1; } pid$target::realloc:entry, pid$target::free:entry { self->addr = arg0; self->recurse++; } pid$target::realloc:return, pid$target::free:return / self->recurse / { self->recurse--; self->addr = 0; } pid$target::malloc:entry, pid$target::memalign:entry, pid$target::valloc:entry, pid$target::calloc:entry, pid$target::realloc:entry, pid$target::realloc:entry, pid$target::free:entry / lwp != -1 && self->lwp == 0 / { self->lwp = curlwpsinfo->pr_lwpid; } pid$target::malloc:entry, pid$target::calloc:entry, pid$target::realloc:entry, pid$target::memalign:entry, pid$target::valloc:entry, pid$target::free:entry / self->lwp == 0 / { self->lwp = lwp; } pid$target::malloc:return, pid$target::calloc:return, pid$target::realloc:return, pid$target::memalign:return, pid$target::valloc:return { alloc_time[arg1] = timestamp; allocated[arg1] = 1; free_walltime[arg1] = 0LL; free_time[arg1] = 0LL; free_lwpid[arg1] = 0; alloc_lwpid[arg1] = self->lwp; self->lwp = 0; } pid$target::realloc:entry, pid$target::free:entry / self->recurse == 1 && alloc_time[arg0] && allocated[arg0] == 0 / { printf("double free?\n"); printf("\tAddress: 0x%p\n", arg0); printf("\tPrevious free at: %Y, LWP %d\n", free_walltime[arg0], free_lwpid[arg0]); printf("\tThis free at: %Y, LWP %d\n", walltimestamp, self->lwp); printf("\tFrees %d nsec apart\n", timestamp - free_time[arg0]); printf("\tAllocated %d nsec ago by LWP %d\n", timestamp - alloc_time[arg0], alloc_lwpid[arg0]); ustack(10); } pid$target::realloc:entry, pid$target::free:entry / self->recurse == 1 && alloc_time[arg0] && allocated[arg0] == 1 / { free_walltime[arg0] = walltimestamp; free_time[arg0] = timestamp; free_lwpid[arg0] = self->lwp; allocated[arg0] = 0; } pid$target::free:entry /self->lwp && self->recurse == 0/ { self->lwp = 0; } 1Most of the cases it “can't” be used is because it finds fatal problems early on in the start up of applications. Then the application writers make bizarre claims that this is a problem with libumem and will tell you it is not supported with their app. In fact the problem is with the application.




More...