bionic: import heaptracker as chk_malloc
This patch is a rewrite of libc.debug.malloc = 10 (chk_malloc). It provides
the same features as the original (poison freed memory, detect heap overruns
and underruns), except that it provides more debugging information whenever it
detects a problem.
In addition to the original features, the new chk_malloc() implementation
detects multiple frees within a given range of the last N allocations, N being
configurable via the system property libc.debug.malloc.backlog.
Finally, this patch keeps track of all outstanding memory allocations. On
program exit, we walk that list and report each outstanding allocation.
(There is support (not enabled) for a scanner thread periodically walks over
the list of outstanding allocations as well as the backlog of recently-freed
allocations, checking for heap-usage errors.)
Feature overview:
1) memory leaks
2) multiple frees
3) use after free
4) overrun
Implementation:
-- for each allocation, there is a:
1) stack trace at the time the allocation is made
2) if the memory is freed, there is also a stack trace at the point
3) a front and rear guard (fence)
4) the stack traces are kept together with the allocation
-- the following lists and maintained
1) all outstanding memory allocations
3) a backlog of allocations what are freed; when you call free(), instead of
actually freed, the allocation is moved to this backlog;
4) when the backlog of allocations gets full, the oldest entry gets evicted
from it; at that point, the allocation is checked for overruns or
use-after-free errors, and then actually freed.
5) when the program exits, the list of outstanding allocations and the
backlog are inspected for errors, then freed;
To use this, set the following system properties before running the process or
processes you want to inspect:
libc.malloc.debug.backlog # defaults to 100
libc.malloc.debug 10
When a problem is detected, you will see the following on logcat for a multiple
free:
E/libc ( 7233): +++ ALLOCATION 0x404b9278 SIZE 10 BYTES MULTIPLY FREED!
E/libc ( 7233): +++ ALLOCATION 0x404b9278 SIZE 10 ALLOCATED HERE:
E/libc ( 7233): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
E/libc ( 7233): #00 pc 0000c35a /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #01 pc 0000c658 /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #02 pc 00016d80 /system/lib/libc.so
E/libc ( 7233): #03 pc 4009647c /system/bin/malloctest
E/libc ( 7233): #04 pc 00016f24 /system/lib/libc.so
E/libc ( 7233): +++ ALLOCATION 0x404b9278 SIZE 10 FIRST FREED HERE:
E/libc ( 7233): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
E/libc ( 7233): #00 pc 0000c35a /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #01 pc 0000c7d2 /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #02 pc 00016d94 /system/lib/libc.so
E/libc ( 7233): #03 pc 40096490 /system/bin/malloctest
E/libc ( 7233): #04 pc 00016f24 /system/lib/libc.so
E/libc ( 7233): +++ ALLOCATION 0x404b9278 SIZE 10 NOW BEING FREED HERE:
E/libc ( 7233): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
E/libc ( 7233): #00 pc 0000c35a /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #01 pc 0000c6ac /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #02 pc 00016d94 /system/lib/libc.so
E/libc ( 7233): #03 pc 400964a0 /system/bin/malloctest
E/libc ( 7233): #04 pc 00016f24 /system/lib/libc.so
The following for a heap overrun and underrun:
E/libc ( 7233): +++ REAR GUARD MISMATCH [10, 11)
E/libc ( 7233): +++ ALLOCATION 0x404b9198 SIZE 10 HAS A CORRUPTED REAR GUARD
E/libc ( 7233): +++ ALLOCATION 0x404b9198 SIZE 10 ALLOCATED HERE:
E/libc ( 7233): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
E/libc ( 7233): #00 pc 0000c35a /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #01 pc 0000c658 /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #02 pc 00016d80 /system/lib/libc.so
E/libc ( 7233): #03 pc 40096438 /system/bin/malloctest
E/libc ( 7233): #04 pc 00016f24 /system/lib/libc.so
E/libc ( 7233): +++ ALLOCATION 0x404b9198 SIZE 10 FREED HERE:
E/libc ( 7233): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
E/libc ( 7233): #00 pc 0000c35a /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #01 pc 0000c7d2 /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #02 pc 00016d94 /system/lib/libc.so
E/libc ( 7233): #03 pc 40096462 /system/bin/malloctest
E/libc ( 7233): #04 pc 00016f24 /system/lib/libc.so
E/libc ( 7233): +++ ALLOCATION 0x404b9358 SIZE 10 HAS A CORRUPTED FRONT GUARD
E/libc ( 7233): +++ ALLOCATION 0x404b9358 SIZE 10 ALLOCATED HERE:
E/libc ( 7233): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
E/libc ( 7233): #00 pc 0000c35a /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #01 pc 0000c658 /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #02 pc 00016d80 /system/lib/libc.so
E/libc ( 7233): #03 pc 400964ba /system/bin/malloctest
E/libc ( 7233): #04 pc 00016f24 /system/lib/libc.so
E/libc ( 7233): +++ ALLOCATION 0x404b9358 SIZE 10 FREED HERE:
E/libc ( 7233): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
E/libc ( 7233): #00 pc 0000c35a /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #01 pc 0000c7d2 /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #02 pc 00016d94 /system/lib/libc.so
E/libc ( 7233): #03 pc 400964e4 /system/bin/malloctest
E/libc ( 7233): #04 pc 00016f24 /system/lib/libc.so
The following for a memory leak:
E/libc ( 7233): +++ THERE ARE 1 LEAKED ALLOCATIONS
E/libc ( 7233): +++ DELETING 4096 BYTES OF LEAKED MEMORY AT 0x404b95e8 (1 REMAINING)
E/libc ( 7233): +++ ALLOCATION 0x404b95e8 SIZE 4096 ALLOCATED HERE:
E/libc ( 7233): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
E/libc ( 7233): #00 pc 0000c35a /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #01 pc 0000c658 /system/lib/libc_malloc_debug_leak.so
E/libc ( 7233): #02 pc 00016d80 /system/lib/libc.so
E/libc ( 7233): #03 pc 0001bc94 /system/lib/libc.so
E/libc ( 7233): #04 pc 0001edf6 /system/lib/libc.so
E/libc ( 7233): #05 pc 0001b80a /system/lib/libc.so
E/libc ( 7233): #06 pc 0001c086 /system/lib/libc.so
E/libc ( 7233): #07 pc 40096402 /system/bin/malloctest
E/libc ( 7233): #08 pc 00016f24 /system/lib/libc.so
Change-Id: Ic440e9d05a01e2ea86b25e8998714e88bc2d16e0
Signed-off-by: Iliyan Malchev <malchev@google.com>
diff --git a/libc/bionic/malloc_debug_leak.c b/libc/bionic/malloc_debug_leak.c
index e0bcee9..0b18645 100644
--- a/libc/bionic/malloc_debug_leak.c
+++ b/libc/bionic/malloc_debug_leak.c
@@ -61,22 +61,11 @@
extern int gMallocLeakZygoteChild;
extern pthread_mutex_t gAllocationsMutex;
extern HashTable gHashTable;
-extern const MallocDebug __libc_malloc_default_dispatch;
-extern const MallocDebug* __libc_malloc_dispatch;
// =============================================================================
-// log functions
+// stack trace functions
// =============================================================================
-#define debug_log(format, ...) \
- __libc_android_log_print(ANDROID_LOG_DEBUG, "malloc_leak_check", (format), ##__VA_ARGS__ )
-#define error_log(format, ...) \
- __libc_android_log_print(ANDROID_LOG_ERROR, "malloc_leak_check", (format), ##__VA_ARGS__ )
-#define info_log(format, ...) \
- __libc_android_log_print(ANDROID_LOG_INFO, "malloc_leak_check", (format), ##__VA_ARGS__ )
-
-static int gTrapOnError = 1;
-
#define MALLOC_ALIGNMENT 8
#define GUARD 0x48151642
#define DEBUG 0
@@ -210,246 +199,12 @@
gHashTable.count--;
}
-
// =============================================================================
-// stack trace functions
-// =============================================================================
-
-typedef struct
-{
- size_t count;
- intptr_t* addrs;
-} stack_crawl_state_t;
-
-
-/* depends how the system includes define this */
-#ifdef HAVE_UNWIND_CONTEXT_STRUCT
-typedef struct _Unwind_Context __unwind_context;
-#else
-typedef _Unwind_Context __unwind_context;
-#endif
-
-static _Unwind_Reason_Code trace_function(__unwind_context *context, void *arg)
-{
- stack_crawl_state_t* state = (stack_crawl_state_t*)arg;
- if (state->count) {
- intptr_t ip = (intptr_t)_Unwind_GetIP(context);
- if (ip) {
- state->addrs[0] = ip;
- state->addrs++;
- state->count--;
- return _URC_NO_REASON;
- }
- }
- /*
- * If we run out of space to record the address or 0 has been seen, stop
- * unwinding the stack.
- */
- return _URC_END_OF_STACK;
-}
-
-static inline
-int get_backtrace(intptr_t* addrs, size_t max_entries)
-{
- stack_crawl_state_t state;
- state.count = max_entries;
- state.addrs = (intptr_t*)addrs;
- _Unwind_Backtrace(trace_function, (void*)&state);
- return max_entries - state.count;
-}
-
-// =============================================================================
-// malloc check functions
+// malloc fill functions
// =============================================================================
#define CHK_FILL_FREE 0xef
#define CHK_SENTINEL_VALUE (char)0xeb
-#define CHK_SENTINEL_HEAD_SIZE 16
-#define CHK_SENTINEL_TAIL_SIZE 16
-#define CHK_OVERHEAD_SIZE ( CHK_SENTINEL_HEAD_SIZE + \
- CHK_SENTINEL_TAIL_SIZE + \
- sizeof(size_t) )
-
-static void dump_stack_trace()
-{
- intptr_t addrs[20];
- int c = get_backtrace(addrs, 20);
- char buf[16];
- char tmp[16*20];
- int i;
-
- tmp[0] = 0; // Need to initialize tmp[0] for the first strcat
- for (i=0 ; i<c; i++) {
- snprintf(buf, sizeof buf, "%2d: %08x\n", i, addrs[i]);
- strlcat(tmp, buf, sizeof tmp);
- }
- __libc_android_log_print(ANDROID_LOG_ERROR, "libc", "call stack:\n%s", tmp);
-}
-
-static int is_valid_malloc_pointer(void* addr)
-{
- return 1;
-}
-
-static void assert_log_message(const char* format, ...)
-{
- va_list args;
-
- pthread_mutex_lock(&gAllocationsMutex);
- {
- const MallocDebug* current_dispatch = __libc_malloc_dispatch;
- __libc_malloc_dispatch = &__libc_malloc_default_dispatch;
- va_start(args, format);
- __libc_android_log_vprint(ANDROID_LOG_ERROR, "libc",
- format, args);
- va_end(args);
- dump_stack_trace();
- if (gTrapOnError) {
- __builtin_trap();
- }
- __libc_malloc_dispatch = current_dispatch;
- }
- pthread_mutex_unlock(&gAllocationsMutex);
-}
-
-static void assert_valid_malloc_pointer(void* mem)
-{
- if (mem && !is_valid_malloc_pointer(mem)) {
- assert_log_message(
- "*** MALLOC CHECK: buffer %p, is not a valid "
- "malloc pointer (are you mixing up new/delete "
- "and malloc/free?)", mem);
- }
-}
-
-/* Check that a given address corresponds to a guarded block,
- * and returns its original allocation size in '*allocated'.
- * 'func' is the capitalized name of the caller function.
- * Returns 0 on success, or -1 on failure.
- * NOTE: Does not return if gTrapOnError is set.
- */
-static int chk_mem_check(void* mem,
- size_t* allocated,
- const char* func)
-{
- char* buffer;
- size_t offset, bytes;
- int i;
- char* buf;
-
- /* first check the bytes in the sentinel header */
- buf = (char*)mem - CHK_SENTINEL_HEAD_SIZE;
- for (i=0 ; i<CHK_SENTINEL_HEAD_SIZE ; i++) {
- if (buf[i] != CHK_SENTINEL_VALUE) {
- assert_log_message(
- "*** %s CHECK: buffer %p "
- "corrupted %d bytes before allocation",
- func, mem, CHK_SENTINEL_HEAD_SIZE-i);
- return -1;
- }
- }
-
- /* then the ones in the sentinel trailer */
- buffer = (char*)mem - CHK_SENTINEL_HEAD_SIZE;
- offset = dlmalloc_usable_size(buffer) - sizeof(size_t);
- bytes = *(size_t *)(buffer + offset);
-
- buf = (char*)mem + bytes;
- for (i=CHK_SENTINEL_TAIL_SIZE-1 ; i>=0 ; i--) {
- if (buf[i] != CHK_SENTINEL_VALUE) {
- assert_log_message(
- "*** %s CHECK: buffer %p, size=%lu, "
- "corrupted %d bytes after allocation",
- func, buffer, bytes, i+1);
- return -1;
- }
- }
-
- *allocated = bytes;
- return 0;
-}
-
-
-void* chk_malloc(size_t bytes)
-{
- char* buffer = (char*)dlmalloc(bytes + CHK_OVERHEAD_SIZE);
- if (buffer) {
- memset(buffer, CHK_SENTINEL_VALUE, bytes + CHK_OVERHEAD_SIZE);
- size_t offset = dlmalloc_usable_size(buffer) - sizeof(size_t);
- *(size_t *)(buffer + offset) = bytes;
- buffer += CHK_SENTINEL_HEAD_SIZE;
- }
- return buffer;
-}
-
-void chk_free(void* mem)
-{
- assert_valid_malloc_pointer(mem);
- if (mem) {
- size_t size;
- char* buffer;
-
- if (chk_mem_check(mem, &size, "FREE") == 0) {
- buffer = (char*)mem - CHK_SENTINEL_HEAD_SIZE;
- memset(buffer, CHK_FILL_FREE, size + CHK_OVERHEAD_SIZE);
- dlfree(buffer);
- }
- }
-}
-
-void* chk_calloc(size_t n_elements, size_t elem_size)
-{
- size_t size;
- void* ptr;
-
- /* Fail on overflow - just to be safe even though this code runs only
- * within the debugging C library, not the production one */
- if (n_elements && MAX_SIZE_T / n_elements < elem_size) {
- return NULL;
- }
- size = n_elements * elem_size;
- ptr = chk_malloc(size);
- if (ptr != NULL) {
- memset(ptr, 0, size);
- }
- return ptr;
-}
-
-void* chk_realloc(void* mem, size_t bytes)
-{
- char* buffer;
- int ret;
- size_t old_bytes = 0;
-
- assert_valid_malloc_pointer(mem);
-
- if (mem != NULL && chk_mem_check(mem, &old_bytes, "REALLOC") < 0)
- return NULL;
-
- char* new_buffer = chk_malloc(bytes);
- if (mem == NULL) {
- return new_buffer;
- }
-
- if (new_buffer) {
- if (bytes > old_bytes)
- bytes = old_bytes;
- memcpy(new_buffer, mem, bytes);
- chk_free(mem);
- }
-
- return new_buffer;
-}
-
-void* chk_memalign(size_t alignment, size_t bytes)
-{
- // XXX: it's better to use malloc, than being wrong
- return chk_malloc(bytes);
-}
-
-// =============================================================================
-// malloc fill functions
-// =============================================================================
void* fill_malloc(size_t bytes)
{
@@ -497,6 +252,9 @@
#define MEMALIGN_GUARD ((void*)0xA1A41520)
+extern __LIBC_HIDDEN__
+int get_backtrace(intptr_t* addrs, size_t max_entries);
+
void* leak_malloc(size_t bytes)
{
// allocate enough space infront of the allocation to store the pointer for
@@ -632,12 +390,3 @@
}
return base;
}
-
-/* Initializes malloc debugging framework.
- * See comments on MallocDebugInit in malloc_debug_common.h
- */
-int malloc_debug_initialize(void)
-{
- // We don't really have anything that requires initialization here.
- return 0;
-}