diff --git a/dtracetool/README.md b/dtracetool/README.md new file mode 100644 index 0000000..005be94 --- /dev/null +++ b/dtracetool/README.md @@ -0,0 +1,15 @@ +DTrace Memory Tracer +==================== + +This is a DTrace tool which aims to create an ltrace compatible output for +heap management functions which can be visualised by villoc. + +Tested only on OS X. Other supported platforms may require modificatons. + +Usage +----- + +```shell +$ sudo ./memtrace.d -c | ./villoc.py - out.html +``` + diff --git a/dtracetool/memtrace.d b/dtracetool/memtrace.d new file mode 100755 index 0000000..1403cee --- /dev/null +++ b/dtracetool/memtrace.d @@ -0,0 +1,149 @@ +#!/usr/sbin/dtrace -s + +/* +memtrace.d - DTrace-based memory allocation tracer +Andrzej Dyjak + +Based on ltrace output style. Compatible with villoc [1]. + +$ sudo dtrace ./memtrace.d -p +$ sudo dtrace ./memtrace.d -c + +NOTE: For bigger programs you may stumble upon data drops. This can be mitigated +to a certain degree via DTrace tuning [2] [3]. + +[1] https://github.com/wapiflapi/villoc +[2] https://wikis.oracle.com/display/DTrace/Buffers+and+Buffering +[3] https://wikis.oracle.com/display/DTrace/Options+and+Tunables +*/ + +/* Re-enable to see potential data drops */ +#pragma D option quiet + +/* Globals for alloc functions args. Explicitly typed to avoid errors. */ +size_t malloc_size; +size_t valloc_size; +size_t calloc_count; +size_t calloc_size; +int64_t realloc_addr; +size_t realloc_size; +int64_t reallocf_addr; +size_t reallocf_size; +int64_t free_addr; + +BEGIN +{ + /* Flags for failure inside of the functions */ + /* However, we're using them also for predicates. This is a hack. */ + malloc_fail = 0; + valloc_fail = 0; + calloc_fail = 0; + realloc_fail = 0; + reallocf_fail = 0; + free_fail = 0; +} + + +pid$target::malloc:entry +{ + malloc_size = arg0; + malloc_fail = 1; +} + +pid$target::malloc:return +/malloc_fail/ +{ + printf("malloc(%d) = %#p\n", malloc_size, arg1); + malloc_fail = 0; +} + +pid$target::valloc:entry +{ + valloc_size = arg0; + valloc_fail = 1; +} + +pid$target::valloc:return +/valloc_fail/ +{ + printf("valloc(%d) = %#p\n", valloc_size, arg1); + valloc_fail = 0; +} + +pid$target::calloc:entry +{ + calloc_count = arg0; + calloc_size = arg1; + calloc_fail = 1; +} + +pid$target::calloc:return +/calloc_fail/ +{ + printf("calloc(%d, %d) = %#p\n", calloc_count, calloc_size, arg1); + calloc_fail = 0; +} + +pid$target::realloc:entry +{ + realloc_addr = arg0; + realloc_size = arg1; + realloc_fail = 1; +} + +pid$target::realloc:return +/realloc_fail/ +{ + printf("realloc(%#p, %d) = %#p\n", realloc_addr, realloc_size, arg1); + realloc_fail = 0; +} + +pid$target::reallocf:entry +{ + reallocf_addr = arg0; + reallocf_size = arg1; + reallocf_fail = 1; +} + +pid$target::reallocf:return +/reallocf_fail/ +{ + printf("reallocf(%#p, %d) = %#p\n", reallocf_addr, reallocf_size, arg1); + reallocf_fail = 0; +} + +pid$target::free:entry +{ + free_addr = arg0; + printf("free(%#p) = \n", free_addr); +} + +END +/malloc_fail/ +{ + printf("malloc(%d) = \n", malloc_size); +} + +END +/valloc_fail/ +{ + printf("valloc(%d) = \n", valloc_size); +} + +END +/calloc_fail/ +{ + printf("calloc(%d, %d) = \n", calloc_count, calloc_size); +} + +END +/realloc_fail/ +{ + printf("realloc(%#p, %d) = \n", realloc_addr, realloc_size); +} + +END +/reallocf_fail/ +{ + printf("realloc(%#p, %d) = \n", reallocf_addr, reallocf_size); +} diff --git a/dtracetool/tests/Makefile b/dtracetool/tests/Makefile new file mode 100644 index 0000000..f67c104 --- /dev/null +++ b/dtracetool/tests/Makefile @@ -0,0 +1,17 @@ +CC = clang + +all: test1 test2 test3 + +test1: test1.c + $(CC) -o test1 test1.c + +test2: test2.c + $(CC) -o test2 test2.c + +test3: test3.c + $(CC) -o test3 test3.c + +clean: + rm test1 + rm test2 + rm test3 \ No newline at end of file diff --git a/dtracetool/tests/test1.c b/dtracetool/tests/test1.c new file mode 100644 index 0000000..1291bdb --- /dev/null +++ b/dtracetool/tests/test1.c @@ -0,0 +1,39 @@ +/* Testing for each alloc function from libc */ + +#include +#include + +#define DEBUG 0 + +int +main(void) +{ + int *cptr, *mptr, *vptr; + + cptr = calloc(8, 32); + if(DEBUG) + printf("cptr = %p\n", cptr); + + mptr = malloc(64); + if(DEBUG) + printf("mptr = %p\n", mptr); + + mptr = realloc(mptr, 128); + if(DEBUG) + printf("mptr = %p\n", mptr); + + vptr = valloc(64); + if(DEBUG) + printf("vptr = %p\n", vptr); + + vptr = reallocf(vptr, 128); + if(DEBUG) + printf("vptr = %p\n", vptr); + + free(cptr); + free(mptr); + free(vptr); + + return 0; +} + diff --git a/dtracetool/tests/test2.c b/dtracetool/tests/test2.c new file mode 100644 index 0000000..b56d7dd --- /dev/null +++ b/dtracetool/tests/test2.c @@ -0,0 +1,69 @@ +/* Testing for fail/success of alloc functions */ + +#include +#include + +#define DEBUG 0 + +int +main(void) +{ + int *cptr, *mptr, *vptr, *rptr, *rfptr; + + /* Failing calloc */ + cptr = calloc(1337, 0xFFFFFFFFFFFF); + if(DEBUG) + printf("cptr = %p\n", cptr); + + /* Succeeding calloc */ + cptr = calloc(8, 32); + if(DEBUG) + printf("cptr = %p\n", cptr); + + /* Failing malloc */ + mptr = malloc(0xFFFFFFFFFFFFFF); + if(DEBUG) + printf("mptr = %p\n", mptr); + + /* Succeeding malloc */ + mptr = malloc(64); + if(DEBUG) + printf("mptr = %p\n", mptr); + + /* Failing realloc, we want to test if mptr is still valid */ + rptr = realloc(mptr, 0xFFFFFFFFFFFF); + if(DEBUG) + printf("mptr = %p\n", mptr); + + /* Succeeding realloc */ + mptr = realloc(mptr, 128); + if(DEBUG) + printf("mptr = %p\n", mptr); + + /* Failing valloc */ + vptr = valloc(0xFFFFFFFFFFFF); + if(DEBUG) + printf("vptr = %p\n", vptr); + + /* Succeeding valloc */ + vptr = valloc(64); + if(DEBUG) + printf("vptr = %p\n", vptr); + + /* Succeeding reallocf */ + vptr = reallocf(vptr, 128); + if(DEBUG) + printf("vptr = %p\n", vptr); + + /* Failing reallocf, we want to test if vptr is still valid */ + rfptr = reallocf(vptr, 0xFFFFFFFFFFFF); + if(DEBUG) + printf("vptr = %p\n", vptr); + + free(cptr); + free(mptr); +// free(vptr); + + return 0; +} + diff --git a/dtracetool/tests/test3.c b/dtracetool/tests/test3.c new file mode 100644 index 0000000..d9c0bff --- /dev/null +++ b/dtracetool/tests/test3.c @@ -0,0 +1,34 @@ +/* Testing for a crash inside of the malloc() */ + +#include +#include +#include + +int main(int argc, char **argv) +{ + (void) argc, (void) argv; + + void *a, *b, *c; + + a = malloc(48); + b = malloc(48); + c = malloc(48); + + free(a); + + // Let's say we have an underflow on c. This + // is far from the only way to trigger a crash. + memset(c-128, 0xff, 0x200); + + printf("A crash in malloc will be triggered *after* this print.\n"); + malloc(48); + + printf("This shouldn't be reached but it is on OS X (unlike Ubuntu)\n"); + malloc(48); + + printf("However, this isn't reached\n"); + malloc(1337); + + return 0; +} + diff --git a/villoc.py b/villoc.py index 67629a8..bb184a7 100755 --- a/villoc.py +++ b/villoc.py @@ -159,6 +159,10 @@ def malloc(state, ret, size): state.append(Block(ret, size)) +def valloc(state, ret, size): + malloc(state, ret, size) + + def calloc(state, ret, nmemb, size): malloc(state, ret, nmemb * size) @@ -183,6 +187,8 @@ def realloc(state, ret, ptr, size): if not ptr: return malloc(state, ret, size) + elif not ret: + return malloc(state, ret, size) elif not size: return free(state, ret, ptr) @@ -195,19 +201,30 @@ def realloc(state, ret, ptr, size): state[s].error = True else: state[s] = Block(ret, size, color=match.color) + + +# This is just an empty stub for reallocf(). This is because internally +# reallocf() calls realloc() so we catch it there. +# However, for the sake of completness it's good to have it in the output. +def reallocf(state, ret, ptr, size): + return operations = { 'free': free, 'malloc': malloc, + 'valloc': valloc, 'calloc': calloc, 'realloc': realloc, + 'reallocf': reallocf, } def sanitize(x): if x is None: return None + if x == "": + return None if x == "": return 0 return int(x, 0)