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 <stdlib.h>
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 used.
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;
}