diff options
Diffstat (limited to 'libc/malloc_debug/malloc_debug.cpp')
-rw-r--r-- | libc/malloc_debug/malloc_debug.cpp | 191 |
1 files changed, 152 insertions, 39 deletions
diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp index b866e54fa..53fcead01 100644 --- a/libc/malloc_debug/malloc_debug.cpp +++ b/libc/malloc_debug/malloc_debug.cpp @@ -29,6 +29,9 @@ #include <errno.h> #include <inttypes.h> #include <malloc.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include <sys/cdefs.h> #include <sys/param.h> @@ -38,8 +41,10 @@ #include <vector> #include <android-base/file.h> +#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <private/bionic_malloc_dispatch.h> +#include <private/MallocXmlElem.h> #include "Config.h" #include "DebugData.h" @@ -47,13 +52,14 @@ #include "debug_disable.h" #include "debug_log.h" #include "malloc_debug.h" +#include "UnwindBacktrace.h" // ------------------------------------------------------------------------ // Global Data // ------------------------------------------------------------------------ DebugData* g_debug; -int* g_malloc_zygote_child; +bool* g_zygote_child; const MallocDispatch* g_dispatch; // ------------------------------------------------------------------------ @@ -65,12 +71,13 @@ const MallocDispatch* g_dispatch; // ------------------------------------------------------------------------ __BEGIN_DECLS -bool debug_initialize(const MallocDispatch* malloc_dispatch, int* malloc_zygote_child, +bool debug_initialize(const MallocDispatch* malloc_dispatch, bool* malloc_zygote_child, const char* options); void debug_finalize(); -bool debug_dump_heap(const char* file_name); +void debug_dump_heap(const char* file_name); void debug_get_malloc_leak_info(uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory, size_t* backtrace_size); +bool debug_write_malloc_leak_info(FILE* fp); ssize_t debug_malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count); void debug_free_malloc_leak_info(uint8_t* info); size_t debug_malloc_usable_size(void* pointer); @@ -82,6 +89,7 @@ void* debug_realloc(void* pointer, size_t bytes); void* debug_calloc(size_t nmemb, size_t bytes); struct mallinfo debug_mallinfo(); int debug_mallopt(int param, int value); +int debug_malloc_info(int options, FILE* fp); int debug_posix_memalign(void** memptr, size_t alignment, size_t size); int debug_iterate(uintptr_t base, size_t size, void (*callback)(uintptr_t base, size_t size, void* arg), void* arg); @@ -96,6 +104,32 @@ void* debug_valloc(size_t size); __END_DECLS // ------------------------------------------------------------------------ +class ScopedConcurrentLock { + public: + ScopedConcurrentLock() { + pthread_rwlock_rdlock(&lock_); + } + ~ScopedConcurrentLock() { + pthread_rwlock_unlock(&lock_); + } + + static void Init() { + pthread_rwlockattr_t attr; + // Set the attribute so that when a write lock is pending, read locks are no + // longer granted. + pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); + pthread_rwlock_init(&lock_, &attr); + } + + static void BlockAllOperations() { + pthread_rwlock_wrlock(&lock_); + } + + private: + static pthread_rwlock_t lock_; +}; +pthread_rwlock_t ScopedConcurrentLock::lock_; + static void InitAtfork() { static pthread_once_t atfork_init = PTHREAD_ONCE_INIT; pthread_once(&atfork_init, []() { @@ -118,6 +152,26 @@ static void InitAtfork() { }); } +void BacktraceAndLog() { + if (g_debug->config().options() & BACKTRACE_FULL) { + std::vector<uintptr_t> frames; + std::vector<unwindstack::LocalFrameData> frames_info; + if (!Unwind(&frames, &frames_info, 256)) { + error_log(" Backtrace failed to get any frames."); + } else { + UnwindLog(frames_info); + } + } else { + std::vector<uintptr_t> frames(256); + size_t num_frames = backtrace_get(frames.data(), frames.size()); + if (num_frames == 0) { + error_log(" Backtrace failed to get any frames."); + } else { + backtrace_log(frames.data(), num_frames); + } + } +} + static void LogError(const void* pointer, const char* error_str) { error_log(LOG_DIVIDER); error_log("+++ ALLOCATION %p %s", pointer, error_str); @@ -128,15 +182,12 @@ static void LogError(const void* pointer, const char* error_str) { PointerData::LogFreeBacktrace(pointer); } - std::vector<uintptr_t> frames(128); - size_t num_frames = backtrace_get(frames.data(), frames.size()); - if (num_frames == 0) { - error_log("Backtrace failed to get any frames."); - } else { - error_log("Backtrace at time of failure:"); - backtrace_log(frames.data(), num_frames); - } + error_log("Backtrace at time of failure:"); + BacktraceAndLog(); error_log(LOG_DIVIDER); + if (g_debug->config().options() & ABORT_ON_ERROR) { + abort(); + } } static bool VerifyPointer(const void* pointer, const char* function_name) { @@ -201,15 +252,15 @@ static void* InitHeader(Header* header, void* orig_pointer, size_t size) { return g_debug->GetPointer(header); } -bool debug_initialize(const MallocDispatch* malloc_dispatch, int* malloc_zygote_child, +bool debug_initialize(const MallocDispatch* malloc_dispatch, bool* zygote_child, const char* options) { - if (malloc_zygote_child == nullptr || options == nullptr) { + if (zygote_child == nullptr || options == nullptr) { return false; } InitAtfork(); - g_malloc_zygote_child = malloc_zygote_child; + g_zygote_child = zygote_child; g_dispatch = malloc_dispatch; @@ -229,6 +280,12 @@ bool debug_initialize(const MallocDispatch* malloc_dispatch, int* malloc_zygote_ // of different error cases. backtrace_startup(); + if (g_debug->config().options() & VERBOSE) { + info_log("%s: malloc debug enabled", getprogname()); + } + + ScopedConcurrentLock::Init(); + return true; } @@ -237,6 +294,10 @@ void debug_finalize() { return; } + // Make sure that there are no other threads doing debug allocations + // before we kill everything. + ScopedConcurrentLock::BlockAllOperations(); + // Turn off capturing allocations calls. DebugDisableSet(true); @@ -264,10 +325,12 @@ void debug_finalize() { void debug_get_malloc_leak_info(uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory, size_t* backtrace_size) { + ScopedConcurrentLock lock; + ScopedDisableDebugCalls disable; // Verify the arguments. - if (info == nullptr || overall_size == nullptr || info_size == NULL || total_memory == nullptr || + if (info == nullptr || overall_size == nullptr || info_size == nullptr || total_memory == nullptr || backtrace_size == nullptr) { error_log("get_malloc_leak_info: At least one invalid parameter."); return; @@ -297,6 +360,7 @@ size_t debug_malloc_usable_size(void* pointer) { if (DebugCallsDisabled() || pointer == nullptr) { return g_dispatch->malloc_usable_size(pointer); } + ScopedConcurrentLock lock; ScopedDisableDebugCalls disable; if (!VerifyPointer(pointer, "malloc_usable_size")) { @@ -360,6 +424,7 @@ void* debug_malloc(size_t size) { if (DebugCallsDisabled()) { return g_dispatch->malloc(size); } + ScopedConcurrentLock lock; ScopedDisableDebugCalls disable; void* pointer = InternalMalloc(size); @@ -435,6 +500,7 @@ void debug_free(void* pointer) { if (DebugCallsDisabled() || pointer == nullptr) { return g_dispatch->free(pointer); } + ScopedConcurrentLock lock; ScopedDisableDebugCalls disable; if (g_debug->config().options() & RECORD_ALLOCS) { @@ -452,6 +518,7 @@ void* debug_memalign(size_t alignment, size_t bytes) { if (DebugCallsDisabled()) { return g_dispatch->memalign(alignment, bytes); } + ScopedConcurrentLock lock; ScopedDisableDebugCalls disable; if (bytes == 0) { @@ -530,6 +597,7 @@ void* debug_realloc(void* pointer, size_t bytes) { if (DebugCallsDisabled()) { return g_dispatch->realloc(pointer, bytes); } + ScopedConcurrentLock lock; ScopedDisableDebugCalls disable; if (pointer == nullptr) { @@ -648,6 +716,7 @@ void* debug_calloc(size_t nmemb, size_t bytes) { if (DebugCallsDisabled()) { return g_dispatch->calloc(nmemb, bytes); } + ScopedConcurrentLock lock; ScopedDisableDebugCalls disable; size_t size; @@ -705,11 +774,39 @@ int debug_mallopt(int param, int value) { return g_dispatch->mallopt(param, value); } +int debug_malloc_info(int options, FILE* fp) { + if (DebugCallsDisabled() || !g_debug->TrackPointers()) { + return g_dispatch->malloc_info(options, fp); + } + ScopedConcurrentLock lock; + ScopedDisableDebugCalls disable; + + MallocXmlElem root(fp, "malloc", "version=\"debug-malloc-1\""); + std::vector<ListInfoType> list; + PointerData::GetAllocList(&list); + + size_t alloc_num = 0; + for (size_t i = 0; i < list.size(); i++) { + MallocXmlElem alloc(fp, "allocation", "nr=\"%zu\"", alloc_num); + + size_t total = 1; + size_t size = list[i].size; + while (i < list.size() - 1 && list[i + 1].size == size) { + i++; + total++; + } + MallocXmlElem(fp, "size").Contents("%zu", list[i].size); + MallocXmlElem(fp, "total").Contents("%zu", total); + alloc_num++; + } + return 0; +} + void* debug_aligned_alloc(size_t alignment, size_t size) { if (DebugCallsDisabled()) { return g_dispatch->aligned_alloc(alignment, size); } - if (!powerof2(alignment)) { + if (!powerof2(alignment) || (size % alignment) != 0) { errno = EINVAL; return nullptr; } @@ -721,7 +818,7 @@ int debug_posix_memalign(void** memptr, size_t alignment, size_t size) { return g_dispatch->posix_memalign(memptr, alignment, size); } - if (!powerof2(alignment)) { + if (alignment < sizeof(void*) || !powerof2(alignment)) { return EINVAL; } int saved_errno = errno; @@ -732,6 +829,7 @@ int debug_posix_memalign(void** memptr, size_t alignment, size_t size) { int debug_iterate(uintptr_t base, size_t size, void (*callback)(uintptr_t, size_t, void*), void* arg) { + ScopedConcurrentLock lock; if (g_debug->TrackPointers()) { // Since malloc is disabled, don't bother acquiring any locks. for (auto it = PointerData::begin(); it != PointerData::end(); ++it) { @@ -746,6 +844,7 @@ int debug_iterate(uintptr_t base, size_t size, void (*callback)(uintptr_t, size_ } void debug_malloc_disable() { + ScopedConcurrentLock lock; g_dispatch->malloc_disable(); if (g_debug->pointer) { g_debug->pointer->PrepareFork(); @@ -753,6 +852,7 @@ void debug_malloc_disable() { } void debug_malloc_enable() { + ScopedConcurrentLock lock; if (g_debug->pointer) { g_debug->pointer->PostForkParent(); } @@ -763,6 +863,7 @@ ssize_t debug_malloc_backtrace(void* pointer, uintptr_t* frames, size_t max_fram if (DebugCallsDisabled() || pointer == nullptr) { return 0; } + ScopedConcurrentLock lock; ScopedDisableDebugCalls disable; if (!(g_debug->config().options() & BACKTRACE)) { @@ -797,28 +898,11 @@ void* debug_valloc(size_t size) { static std::mutex g_dump_lock; -bool debug_dump_heap(const char* file_name) { - ScopedDisableDebugCalls disable; - - std::lock_guard<std::mutex> guard(g_dump_lock); - - FILE* fp = fopen(file_name, "w+e"); - if (fp == nullptr) { - error_log("Unable to create file: %s", file_name); - return false; - } - error_log("Dumping to file: %s\n", file_name); - - if (!(g_debug->config().options() & BACKTRACE)) { - fprintf(fp, "Native heap dump not available. To enable, run these commands (requires root):\n"); - fprintf(fp, "# adb shell stop\n"); - fprintf(fp, "# adb shell setprop libc.debug.malloc.options backtrace\n"); - fprintf(fp, "# adb shell start\n"); - fclose(fp); - return false; - } +static void write_dump(FILE* fp) { + fprintf(fp, "Android Native Heap Dump v1.2\n\n"); - fprintf(fp, "Android Native Heap Dump v1.1\n\n"); + std::string fingerprint = android::base::GetProperty("ro.build.fingerprint", "unknown"); + fprintf(fp, "Build fingerprint: '%s'\n\n", fingerprint.c_str()); PointerData::DumpLiveToFile(fp); @@ -830,6 +914,35 @@ bool debug_dump_heap(const char* file_name) { fprintf(fp, "%s", content.c_str()); } fprintf(fp, "END\n"); - fclose(fp); +} + +bool debug_write_malloc_leak_info(FILE* fp) { + ScopedConcurrentLock lock; + ScopedDisableDebugCalls disable; + + std::lock_guard<std::mutex> guard(g_dump_lock); + + if (!(g_debug->config().options() & BACKTRACE)) { + return false; + } + + write_dump(fp); return true; } + +void debug_dump_heap(const char* file_name) { + ScopedConcurrentLock lock; + ScopedDisableDebugCalls disable; + + std::lock_guard<std::mutex> guard(g_dump_lock); + + FILE* fp = fopen(file_name, "w+e"); + if (fp == nullptr) { + error_log("Unable to create file: %s", file_name); + return; + } + + error_log("Dumping to file: %s\n", file_name); + write_dump(fp); + fclose(fp); +} |