diff options
-rw-r--r-- | libc/malloc_debug/BacktraceData.cpp | 29 | ||||
-rw-r--r-- | libc/malloc_debug/BacktraceData.h | 16 | ||||
-rw-r--r-- | libc/malloc_debug/Config.cpp | 45 | ||||
-rw-r--r-- | libc/malloc_debug/Config.h | 8 | ||||
-rw-r--r-- | libc/malloc_debug/README.md | 91 | ||||
-rw-r--r-- | libc/malloc_debug/TrackData.cpp | 43 | ||||
-rw-r--r-- | libc/malloc_debug/TrackData.h | 2 | ||||
-rw-r--r-- | libc/malloc_debug/exported32.map | 1 | ||||
-rw-r--r-- | libc/malloc_debug/exported64.map | 1 | ||||
-rw-r--r-- | libc/malloc_debug/malloc_debug.cpp | 93 | ||||
-rw-r--r-- | libc/malloc_debug/malloc_debug.h | 3 | ||||
-rw-r--r-- | libc/malloc_debug/tests/malloc_debug_config_tests.cpp | 55 | ||||
-rw-r--r-- | libc/malloc_debug/tests/malloc_debug_unit_tests.cpp | 231 |
13 files changed, 594 insertions, 24 deletions
diff --git a/libc/malloc_debug/BacktraceData.cpp b/libc/malloc_debug/BacktraceData.cpp index 752d50713..65ae6fafa 100644 --- a/libc/malloc_debug/BacktraceData.cpp +++ b/libc/malloc_debug/BacktraceData.cpp @@ -41,12 +41,12 @@ #include "debug_log.h" #include "malloc_debug.h" -static void EnableToggle(int, siginfo_t*, void*) { - if (g_debug->backtrace->enabled()) { - g_debug->backtrace->set_enabled(false); - } else { - g_debug->backtrace->set_enabled(true); - } +static void ToggleBacktraceEnable(int, siginfo_t*, void*) { + g_debug->backtrace->ToggleBacktraceEnabled(); +} + +static void EnableDump(int, siginfo_t*, void*) { + g_debug->backtrace->EnableDumping(); } BacktraceData::BacktraceData(DebugData* debug_data, const Config& config, size_t* offset) @@ -62,7 +62,7 @@ bool BacktraceData::Initialize(const Config& config) { struct sigaction enable_act; memset(&enable_act, 0, sizeof(enable_act)); - enable_act.sa_sigaction = EnableToggle; + enable_act.sa_sigaction = ToggleBacktraceEnable; enable_act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; sigemptyset(&enable_act.sa_mask); if (sigaction(config.backtrace_signal(), &enable_act, nullptr) != 0) { @@ -72,5 +72,20 @@ bool BacktraceData::Initialize(const Config& config) { info_log("%s: Run: 'kill -%d %d' to enable backtracing.", getprogname(), config.backtrace_signal(), getpid()); } + + struct sigaction act; + memset(&act, 0, sizeof(act)); + + act.sa_sigaction = EnableDump; + act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; + sigemptyset(&act.sa_mask); + if (sigaction(config.backtrace_dump_signal(), &act, nullptr) != 0) { + error_log("Unable to set up backtrace dump signal function: %s", strerror(errno)); + return false; + } + info_log("%s: Run: 'kill -%d %d' to dump the backtrace.", getprogname(), + config.backtrace_dump_signal(), getpid()); + + dump_ = false; return true; } diff --git a/libc/malloc_debug/BacktraceData.h b/libc/malloc_debug/BacktraceData.h index 6dee505e2..c8234dc3c 100644 --- a/libc/malloc_debug/BacktraceData.h +++ b/libc/malloc_debug/BacktraceData.h @@ -31,6 +31,8 @@ #include <stdint.h> +#include <atomic> + #include <private/bionic_macros.h> #include "OptionData.h" @@ -47,13 +49,21 @@ class BacktraceData : public OptionData { inline size_t alloc_offset() { return alloc_offset_; } - bool enabled() { return enabled_; } - void set_enabled(bool enabled) { enabled_ = enabled; } + bool ShouldBacktrace() { return enabled_ == 1; } + void ToggleBacktraceEnabled() { enabled_.fetch_xor(1); } + + void EnableDumping() { dump_ = true; } + bool ShouldDumpAndReset() { + bool expected = true; + return dump_.compare_exchange_strong(expected, false); + } private: size_t alloc_offset_ = 0; - volatile bool enabled_ = false; + std::atomic_uint8_t enabled_; + + std::atomic_bool dump_; DISALLOW_COPY_AND_ASSIGN(BacktraceData); }; diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp index df453a930..e3798ab9f 100644 --- a/libc/malloc_debug/Config.cpp +++ b/libc/malloc_debug/Config.cpp @@ -56,6 +56,7 @@ static constexpr size_t MAX_GUARD_BYTES = 16384; static constexpr size_t DEFAULT_BACKTRACE_FRAMES = 16; static constexpr size_t MAX_BACKTRACE_FRAMES = 256; +static constexpr const char DEFAULT_BACKTRACE_DUMP_PREFIX[] = "/data/local/tmp/backtrace_heap"; static constexpr size_t DEFAULT_EXPAND_BYTES = 16; static constexpr size_t MAX_EXPAND_BYTES = 16384; @@ -85,6 +86,13 @@ const std::unordered_map<std::string, Config::OptionInfo> Config::kOptions = { {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktraceEnableOnSignal}, }, + {"backtrace_dump_on_exit", + {0, &Config::SetBacktraceDumpOnExit}, + }, + {"backtrace_dump_prefix", + {0, &Config::SetBacktraceDumpPrefix}, + }, + {"fill", {FILL_ON_ALLOC | FILL_ON_FREE, &Config::SetFill}, }, @@ -236,6 +244,24 @@ bool Config::SetBacktraceEnableOnSignal(const std::string& option, const std::st &backtrace_frames_); } +bool Config::SetBacktraceDumpOnExit(const std::string& option, const std::string& value) { + if (Config::VerifyValueEmpty(option, value)) { + backtrace_dump_on_exit_ = true; + return true; + } + return false; +} + +bool Config::SetBacktraceDumpPrefix(const std::string&, const std::string& value) { + if (value.empty()) { + backtrace_dump_prefix_ = DEFAULT_BACKTRACE_DUMP_PREFIX; + } else { + backtrace_dump_prefix_ = value; + } + return true; +} + + bool Config::SetExpandAlloc(const std::string& option, const std::string& value) { return ParseValue(option, value, DEFAULT_EXPAND_BYTES, 1, MAX_EXPAND_BYTES, &expand_alloc_bytes_); } @@ -309,6 +335,9 @@ void Config::LogUsage() const { error_log(" backtrace[=XX]"); error_log(" Enable capturing the backtrace at the point of allocation."); error_log(" If XX is set it sets the number of backtrace frames."); + error_log(" This option also enables dumping the backtrace heap data"); + error_log(" when a signal is received. The data is dumped to the file"); + error_log(" backtrace_dump_prefix.<PID>.txt."); error_log(" The default is %zu frames, the max number of frames is %zu.", DEFAULT_BACKTRACE_FRAMES, MAX_BACKTRACE_FRAMES); error_log(""); @@ -319,6 +348,19 @@ void Config::LogUsage() const { error_log(" frames. The default is %zu frames, the max number of frames is %zu.", DEFAULT_BACKTRACE_FRAMES, MAX_BACKTRACE_FRAMES); error_log(""); + error_log(" backtrace_dump_prefix[=FILE]"); + error_log(" This option only has meaning if the backtrace option has been specified."); + error_log(" This is the prefix of the name of the file to which backtrace heap"); + error_log(" data will be dumped. The file will be named backtrace_dump_prefix.<PID>.txt."); + error_log(" The default is %s.", DEFAULT_BACKTRACE_DUMP_PREFIX); + + error_log(""); + error_log(" backtrace_dump_on_exit"); + error_log(" This option only has meaning if the backtrace option has been specified."); + error_log(" This will cause all live allocations to be dumped to the file"); + error_log(" backtrace_dump_prefix.<PID>.final.txt."); + error_log(" The default is false."); + error_log(""); error_log(" fill_on_alloc[=XX]"); error_log(" On first allocation, fill with the value 0x%02x.", DEFAULT_FILL_ALLOC_VALUE); error_log(" If XX is set it will only fill up to XX bytes of the"); @@ -426,12 +468,15 @@ bool Config::Init(const char* options_str) { front_guard_value_ = DEFAULT_FRONT_GUARD_VALUE; rear_guard_value_ = DEFAULT_REAR_GUARD_VALUE; backtrace_signal_ = SIGRTMAX - 19; + backtrace_dump_signal_ = SIGRTMAX - 17; record_allocs_signal_ = SIGRTMAX - 18; free_track_backtrace_num_frames_ = 0; record_allocs_file_.clear(); fill_on_free_bytes_ = 0; backtrace_enable_on_signal_ = false; backtrace_enabled_ = false; + backtrace_dump_on_exit_ = false; + backtrace_dump_prefix_ = DEFAULT_BACKTRACE_DUMP_PREFIX; // Process each option name we can find. std::string option; diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h index d8a706925..349ad775d 100644 --- a/libc/malloc_debug/Config.h +++ b/libc/malloc_debug/Config.h @@ -65,9 +65,12 @@ class Config { uint64_t options() const { return options_; } int backtrace_signal() const { return backtrace_signal_; } + int backtrace_dump_signal() const { return backtrace_dump_signal_; } size_t backtrace_frames() const { return backtrace_frames_; } size_t backtrace_enabled() const { return backtrace_enabled_; } size_t backtrace_enable_on_signal() const { return backtrace_enable_on_signal_; } + bool backtrace_dump_on_exit() const { return backtrace_dump_on_exit_; } + const std::string& backtrace_dump_prefix() const { return backtrace_dump_prefix_; } size_t front_guard_bytes() const { return front_guard_bytes_; } size_t rear_guard_bytes() const { return rear_guard_bytes_; } @@ -110,6 +113,8 @@ class Config { bool SetBacktrace(const std::string& option, const std::string& value); bool SetBacktraceEnableOnSignal(const std::string& option, const std::string& value); + bool SetBacktraceDumpOnExit(const std::string& option, const std::string& value); + bool SetBacktraceDumpPrefix(const std::string& option, const std::string& value); bool SetExpandAlloc(const std::string& option, const std::string& value); @@ -130,8 +135,11 @@ class Config { bool backtrace_enable_on_signal_ = false; int backtrace_signal_ = 0; + int backtrace_dump_signal_ = 0; bool backtrace_enabled_ = false; size_t backtrace_frames_ = 0; + bool backtrace_dump_on_exit_ = false; + std::string backtrace_dump_prefix_; size_t fill_on_alloc_bytes_ = 0; size_t fill_on_free_bytes_ = 0; diff --git a/libc/malloc_debug/README.md b/libc/malloc_debug/README.md index 03a8a73e5..bb56cb8bb 100644 --- a/libc/malloc_debug/README.md +++ b/libc/malloc_debug/README.md @@ -108,6 +108,17 @@ this can be set to is 256. This option adds a special header to all allocations that contains the backtrace and information about the original allocation. +As of P, this option will also enable dumping backtrace heap data to a +file when the process receives the signal SIGRTMAX - 17 ( which is 47 on most +Android devices). The format of this dumped data is the same format as +that dumped when running am dumpheap -n. The default is to dump this data +to the file /data/local/tmp/backtrace\_heap.**PID**.txt. This is useful when +used with native only executables that run for a while since these processes +are not spawned from a zygote process. + +Note that when the signal is received, the heap is not dumped until the next +malloc/free occurs. + ### backtrace\_enable\_on\_signal[=MAX\_FRAMES] Enable capturing the backtrace of each allocation site. If the backtrace capture is toggled when the process receives the signal @@ -123,6 +134,26 @@ this can be set to is 256. This option adds a special header to all allocations that contains the backtrace and information about the original allocation. +### backtrace\_dump\_on\_exit +As of P, when the backtrace option has been enabled, this causes the backtrace +dump heap data to be dumped to a file when the program exits. If the backtrace +option has not been enabled, this does nothing. The default is to dump this +to the file named /data/local/tmp/backtrace\_heap.**PID**.exit.txt. + +The file location can be changed by setting the backtrace\_dump\_prefix +option. + +### backtrace\_dump\_prefix +As of P, when the backtrace options has been enabled, this sets the prefix +used for dumping files when the signal SIGRTMAX - 17 is received or when +the program exits and backtrace\_dump\_on\_exit is set. + +The default is /data/local/tmp/backtrace\_heap. + +When this value is changed from the default, then the filename chosen +on the signal will be backtrace\_dump\_prefix.**PID**.txt. The filename chosen +when the program exits will be backtrace\_dump\_prefix.**PID**.exit.txt. + ### fill\_on\_alloc[=MAX\_FILLED\_BYTES] Any allocation routine, other than calloc, will result in the allocation being filled with the value 0xeb. When doing a realloc to a larger size, the bytes @@ -369,6 +400,66 @@ the pointer has been corrupted. As with the other error message, the function in parenthesis is the function that was called with the bad pointer. +Backtrace Heap Dump Format +========================== + +This section describes the format of the backtrace heap dump. This data is +generated by am dumpheap -n or, as of P, by the signal or on exit. + +The data has this header: + + Android Native Heap Dump v1.0 + + Total memory: XXXX + Allocation records: YYYY + Backtrace size: ZZZZ + +Total memory is the total of all of the currently live allocations. +Allocation records is the total number of allocation records. +Backtrace size is the maximum number of backtrace frames that can be present. + +Following this header are two different sections, the first section is the +allocation records, the second section is the map data. + +The allocation record data has this format: + + z ZYGOTE_CHILD_ALLOC sz ALLOCATION_SIZE num NUM_ALLOCATIONS bt FRAMES + +ZYGOTE\_CHILD\_ALLOC is either 0 or 1. 0 means this was allocated by the +zygote process or in a process not spawned from the zygote. 1 means this +was allocated by an application after it forked off from the zygote process. + +ALLOCATION\_SIZE is the size of the allocation. +NUM\_ALLOCATIONS is the number of allocations that have this size and have the +same backtrace. +FRAMES is a list of instruction pointers that represent the backtrace of the +allocation. + +Example: + + z 0 sz 400 num 1 bt 0000a230 0000b500 + z 1 sz 500 num 3 bt 0000b000 0000c000 + +The first allocation record was created by the zygote of size 400 only one +with this backtrace/size and a backtrace of 0xa230, 0xb500. +The second allocation record was create by an application spawned from the +zygote of size 500, where there are three of these allocation with the same +backtrace/size and a backtrace of 0xb000, 0xc000. + +The final section is the map data for the process: + + MAPS + 7fe9181000-7fe91a2000 rw-p 00000000 00:00 0 /system/lib/libc.so + . + . + . + END + +The map data is simply the output of /proc/PID/maps. This data can be used to +decode the frames in the backtraces. + +There is a tool to visualize this data, development/scripts/native\_heapdump\_viewer.py. + Examples ======== diff --git a/libc/malloc_debug/TrackData.cpp b/libc/malloc_debug/TrackData.cpp index 7ce477cff..4266aa282 100644 --- a/libc/malloc_debug/TrackData.cpp +++ b/libc/malloc_debug/TrackData.cpp @@ -59,6 +59,49 @@ void TrackData::GetList(std::vector<const Header*>* list) { }); } +void TrackData::GetListBySizeThenBacktrace(std::vector<const Header*>* list, size_t* total_memory) { + if (!(debug_->config().options() & BACKTRACE)) { + return; + } + + *total_memory = 0; + for (const auto& header : headers_) { + list->push_back(header); + *total_memory += header->real_size(); + } + + // Put all zygote allocations first by size and backtrace. + // Then all zygote child allocation by size and backtrace. + std::sort(list->begin(), list->end(), [&](const Header* a, const Header* b) { + if (a->zygote_child_alloc() && !b->zygote_child_alloc()) { + return false; + } else if (!a->zygote_child_alloc() && b->zygote_child_alloc()) { + return true; + } + if (a->real_size() != b->real_size()) return a->real_size() < b->real_size(); + // If the size is the same, compare backtrace elements. + BacktraceHeader* a_back = debug_->GetAllocBacktrace(a); + BacktraceHeader* b_back = debug_->GetAllocBacktrace(b); + for (size_t i = 0; i < a_back->num_frames; i++) { + if (i > b_back->num_frames) { + // All frames equal up to this point, but a has more frames available. + return false; + } + if (a_back->frames[i] < b_back->frames[i]) { + return false; + } else if (a_back->frames[i] > b_back->frames[i]) { + return true; + } + } + if (a_back->num_frames < b_back->num_frames) { + // All frames equal up to this point, but b has more frames available. + return true; + } + return false; + }); + +} + void TrackData::Add(const Header* header, bool backtrace_found) { pthread_mutex_lock(&mutex_); if (backtrace_found) { diff --git a/libc/malloc_debug/TrackData.h b/libc/malloc_debug/TrackData.h index e4c8951e3..f7486e9d6 100644 --- a/libc/malloc_debug/TrackData.h +++ b/libc/malloc_debug/TrackData.h @@ -51,6 +51,8 @@ class TrackData : public OptionData { void GetList(std::vector<const Header*>* list); + void GetListBySizeThenBacktrace(std::vector<const Header*>* list, size_t* total_memory); + void Add(const Header* header, bool backtrace_found); void Remove(const Header* header, bool backtrace_found); diff --git a/libc/malloc_debug/exported32.map b/libc/malloc_debug/exported32.map index 59bb10283..e92a7cfc8 100644 --- a/libc/malloc_debug/exported32.map +++ b/libc/malloc_debug/exported32.map @@ -1,6 +1,7 @@ LIBC_MALLOC_DEBUG { global: debug_calloc; + debug_dump_heap; debug_finalize; debug_free; debug_free_malloc_leak_info; diff --git a/libc/malloc_debug/exported64.map b/libc/malloc_debug/exported64.map index ec9d84095..94104b0ca 100644 --- a/libc/malloc_debug/exported64.map +++ b/libc/malloc_debug/exported64.map @@ -1,6 +1,7 @@ LIBC_MALLOC_DEBUG { global: debug_calloc; + debug_dump_heap; debug_finalize; debug_free; debug_free_malloc_leak_info; diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp index 014d3855e..d890a1ccd 100644 --- a/libc/malloc_debug/malloc_debug.cpp +++ b/libc/malloc_debug/malloc_debug.cpp @@ -34,8 +34,11 @@ #include <sys/param.h> #include <unistd.h> +#include <mutex> #include <vector> +#include <android-base/file.h> +#include <android-base/stringprintf.h> #include <private/bionic_malloc_dispatch.h> #include "backtrace.h" @@ -65,6 +68,7 @@ __BEGIN_DECLS bool debug_initialize(const MallocDispatch* malloc_dispatch, int* malloc_zygote_child, const char* options); void debug_finalize(); +bool 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); @@ -138,7 +142,7 @@ static void* InitHeader(Header* header, void* orig_pointer, size_t size) { header->orig_pointer = orig_pointer; header->size = size; if (*g_malloc_zygote_child) { - header->set_zygote(); + header->set_zygote_child_alloc(); } header->usable_size = g_dispatch->malloc_usable_size(orig_pointer); if (header->usable_size == 0) { @@ -164,7 +168,7 @@ static void* InitHeader(Header* header, void* orig_pointer, size_t size) { bool backtrace_found = false; if (g_debug->config().options() & BACKTRACE) { BacktraceHeader* back_header = g_debug->GetAllocBacktrace(header); - if (g_debug->backtrace->enabled()) { + if (g_debug->backtrace->ShouldBacktrace()) { back_header->num_frames = backtrace_get( &back_header->frames[0], g_debug->config().backtrace_frames()); backtrace_found = back_header->num_frames > 0; @@ -224,6 +228,13 @@ void debug_finalize() { g_debug->track->DisplayLeaks(); } + if ((g_debug->config().options() & BACKTRACE) && g_debug->config().backtrace_dump_on_exit()) { + ScopedDisableDebugCalls disable; + debug_dump_heap( + android::base::StringPrintf("%s.%d.exit.txt", + g_debug->config().backtrace_dump_prefix().c_str(), getpid()).c_str()); + } + DebugDisableSet(true); backtrace_shutdown(); @@ -288,6 +299,13 @@ size_t debug_malloc_usable_size(void* pointer) { } static void *internal_malloc(size_t size) { + if ((g_debug->config().options() & BACKTRACE) && g_debug->backtrace->ShouldDumpAndReset()) { + debug_dump_heap( + android::base::StringPrintf("%s.%d.txt", + g_debug->config().backtrace_dump_prefix().c_str(), + getpid()).c_str()); + } + if (size == 0) { size = 1; } @@ -341,6 +359,13 @@ void* debug_malloc(size_t size) { } static void internal_free(void* pointer) { + if ((g_debug->config().options() & BACKTRACE) && g_debug->backtrace->ShouldDumpAndReset()) { + debug_dump_heap( + android::base::StringPrintf("%s.%d.txt", + g_debug->config().backtrace_dump_prefix().c_str(), + getpid()).c_str()); + } + void* free_pointer = pointer; size_t bytes; Header* header; @@ -538,7 +563,7 @@ void* debug_realloc(void* pointer, size_t bytes) { if (real_size < header->usable_size) { header->size = real_size; if (*g_malloc_zygote_child) { - header->set_zygote(); + header->set_zygote_child_alloc(); } if (g_debug->config().options() & REAR_GUARD) { // Don't bother allocating a smaller pointer in this case, simply @@ -758,3 +783,65 @@ void* debug_valloc(size_t size) { return debug_memalign(getpagesize(), size); } #endif + +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; + } + + fprintf(fp, "Android Native Heap Dump v1.0\n\n"); + + std::vector<const Header*> list; + size_t total_memory; + g_debug->track->GetListBySizeThenBacktrace(&list, &total_memory); + fprintf(fp, "Total memory: %zu\n", total_memory); + fprintf(fp, "Allocation records: %zd\n", list.size()); + fprintf(fp, "Backtrace size: %zu\n", g_debug->config().backtrace_frames()); + fprintf(fp, "\n"); + + for (const auto& header : list) { + const BacktraceHeader* back_header = g_debug->GetAllocBacktrace(header); + fprintf(fp, "z %d sz %8zu num 1 bt", (header->zygote_child_alloc()) ? 1 : 0, + header->real_size()); + for (size_t i = 0; i < back_header->num_frames; i++) { + if (back_header->frames[i] == 0) { + break; + } +#ifdef __LP64__ + fprintf(fp, " %016" PRIxPTR, back_header->frames[i]); +#else + fprintf(fp, " %08" PRIxPTR, back_header->frames[i]); +#endif + } + fprintf(fp, "\n"); + } + + fprintf(fp, "MAPS\n"); + std::string content; + if (!android::base::ReadFileToString("/proc/self/maps", &content)) { + fprintf(fp, "Could not open /proc/self/maps\n"); + } else { + fprintf(fp, "%s", content.c_str()); + } + fprintf(fp, "END\n"); + fclose(fp); + return true; +} diff --git a/libc/malloc_debug/malloc_debug.h b/libc/malloc_debug/malloc_debug.h index 347fae236..4a1e8dad1 100644 --- a/libc/malloc_debug/malloc_debug.h +++ b/libc/malloc_debug/malloc_debug.h @@ -54,7 +54,8 @@ struct Header { size_t size; size_t usable_size; size_t real_size() const { return size & ~(1U << 31); } - void set_zygote() { size |= 1U << 31; } + void set_zygote_child_alloc() { size |= 1U << 31; } + bool zygote_child_alloc() const { return size & (1U << 31); } static size_t max_size() { return (1U << 31) - 1; } } __attribute__((packed)); diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp index 77dc84842..ee8fe0691 100644 --- a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp @@ -60,6 +60,9 @@ std::string usage_string( "6 malloc_debug backtrace[=XX]\n" "6 malloc_debug Enable capturing the backtrace at the point of allocation.\n" "6 malloc_debug If XX is set it sets the number of backtrace frames.\n" + "6 malloc_debug This option also enables dumping the backtrace heap data\n" + "6 malloc_debug when a signal is received. The data is dumped to the file\n" + "6 malloc_debug backtrace_dump_prefix.<PID>.txt.\n" "6 malloc_debug The default is 16 frames, the max number of frames is 256.\n" "6 malloc_debug \n" "6 malloc_debug backtrace_enable_on_signal[=XX]\n" @@ -68,6 +71,18 @@ std::string usage_string( "6 malloc_debug receives a signal. If XX is set it sets the number of backtrace\n" "6 malloc_debug frames. The default is 16 frames, the max number of frames is 256.\n" "6 malloc_debug \n" + "6 malloc_debug backtrace_dump_prefix[=FILE]\n" + "6 malloc_debug This option only has meaning if the backtrace option has been specified.\n" + "6 malloc_debug This is the prefix of the name of the file to which backtrace heap\n" + "6 malloc_debug data will be dumped. The file will be named backtrace_dump_prefix.<PID>.txt.\n" + "6 malloc_debug The default is /data/local/tmp/backtrace_heap.\n" + "6 malloc_debug \n" + "6 malloc_debug backtrace_dump_on_exit\n" + "6 malloc_debug This option only has meaning if the backtrace option has been specified.\n" + "6 malloc_debug This will cause all live allocations to be dumped to the file\n" + "6 malloc_debug backtrace_dump_prefix.<PID>.final.txt.\n" + "6 malloc_debug The default is false.\n" + "6 malloc_debug \n" "6 malloc_debug fill_on_alloc[=XX]\n" "6 malloc_debug On first allocation, fill with the value 0xeb.\n" "6 malloc_debug If XX is set it will only fill up to XX bytes of the\n" @@ -292,12 +307,14 @@ TEST_F(MallocDebugConfigTest, backtrace) { ASSERT_EQ(64U, config->backtrace_frames()); ASSERT_TRUE(config->backtrace_enabled()); ASSERT_FALSE(config->backtrace_enable_on_signal()); + ASSERT_FALSE(config->backtrace_dump_on_exit()); ASSERT_TRUE(InitConfig("backtrace")) << getFakeLogPrint(); ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options()); ASSERT_EQ(16U, config->backtrace_frames()); ASSERT_TRUE(config->backtrace_enabled()); ASSERT_FALSE(config->backtrace_enable_on_signal()); + ASSERT_FALSE(config->backtrace_dump_on_exit()); ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); @@ -309,12 +326,14 @@ TEST_F(MallocDebugConfigTest, backtrace_enable_on_signal) { ASSERT_EQ(64U, config->backtrace_frames()); ASSERT_FALSE(config->backtrace_enabled()); ASSERT_TRUE(config->backtrace_enable_on_signal()); + ASSERT_FALSE(config->backtrace_dump_on_exit()); ASSERT_TRUE(InitConfig("backtrace_enable_on_signal")) << getFakeLogPrint(); ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options()); ASSERT_EQ(16U, config->backtrace_frames()); ASSERT_FALSE(config->backtrace_enabled()); ASSERT_TRUE(config->backtrace_enable_on_signal()); + ASSERT_FALSE(config->backtrace_dump_on_exit()); ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); @@ -326,12 +345,14 @@ TEST_F(MallocDebugConfigTest, backtrace_enable_on_signal_init) { ASSERT_EQ(64U, config->backtrace_frames()); ASSERT_FALSE(config->backtrace_enabled()); ASSERT_TRUE(config->backtrace_enable_on_signal()); + ASSERT_FALSE(config->backtrace_dump_on_exit()); ASSERT_TRUE(InitConfig("backtrace")) << getFakeLogPrint(); ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options()); ASSERT_EQ(16U, config->backtrace_frames()); ASSERT_TRUE(config->backtrace_enabled()); ASSERT_FALSE(config->backtrace_enable_on_signal()); + ASSERT_FALSE(config->backtrace_dump_on_exit()); ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); @@ -343,12 +364,46 @@ TEST_F(MallocDebugConfigTest, backtrace_enable_and_backtrace) { ASSERT_EQ(16U, config->backtrace_frames()); ASSERT_TRUE(config->backtrace_enabled()); ASSERT_TRUE(config->backtrace_enable_on_signal()); + ASSERT_FALSE(config->backtrace_dump_on_exit()); ASSERT_TRUE(InitConfig("backtrace backtrace_enable_on_signal")) << getFakeLogPrint(); ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options()); ASSERT_EQ(16U, config->backtrace_frames()); ASSERT_TRUE(config->backtrace_enabled()); ASSERT_TRUE(config->backtrace_enable_on_signal()); + ASSERT_FALSE(config->backtrace_dump_on_exit()); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + ASSERT_STREQ("", getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugConfigTest, backtrace_dump_on_exit) { + ASSERT_TRUE(InitConfig("backtrace_dump_on_exit")) << getFakeLogPrint(); + ASSERT_EQ(0U, config->options()); + ASSERT_TRUE(config->backtrace_dump_on_exit()); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + ASSERT_STREQ("", getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugConfigTest, backtrace_dump_on_exit_error) { + ASSERT_FALSE(InitConfig("backtrace_dump_on_exit=something")) << getFakeLogPrint(); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string log_msg( + "6 malloc_debug malloc_testing: value set for option 'backtrace_dump_on_exit' " + "which does not take a value\n"); + ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugConfigTest, backtrace_dump_prefix) { + ASSERT_TRUE(InitConfig("backtrace_dump_prefix")) << getFakeLogPrint(); + ASSERT_EQ(0U, config->options()); + ASSERT_EQ("/data/local/tmp/backtrace_heap", config->backtrace_dump_prefix()); + + ASSERT_TRUE(InitConfig("backtrace_dump_prefix=/fake/location")) << getFakeLogPrint(); + ASSERT_EQ(0U, config->options()); + ASSERT_EQ("/fake/location", config->backtrace_dump_prefix()); ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp index 4fdba2ef9..37d805751 100644 --- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp @@ -32,6 +32,7 @@ #include <android-base/file.h> #include <android-base/stringprintf.h> +#include <android-base/strings.h> #include <private/bionic_macros.h> #include <private/bionic_malloc_dispatch.h> @@ -82,6 +83,8 @@ static size_t get_tag_offset(uint32_t flags = 0, size_t backtrace_frames = 0) { static constexpr const char RECORD_ALLOCS_FILE[] = "/data/local/tmp/record_allocs.txt"; +static constexpr const char BACKTRACE_DUMP_PREFIX[] = "/data/local/tmp/backtrace_heap"; + class MallocDebugTest : public ::testing::Test { protected: void SetUp() override { @@ -104,6 +107,8 @@ class MallocDebugTest : public ::testing::Test { initialized = true; } + void BacktraceDumpOnSignal(bool trigger_with_alloc); + bool initialized; int zygote; @@ -132,7 +137,7 @@ MallocDispatch MallocDebugTest::dispatch = { mallopt, }; -void VerifyAllocCalls() { +void VerifyAllocCalls(bool backtrace_enabled) { size_t alloc_size = 1024; // Verify debug_malloc. @@ -186,17 +191,23 @@ void VerifyAllocCalls() { ASSERT_TRUE(pointer == nullptr); ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); + std::string expected_log; + if (backtrace_enabled) { + expected_log += android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + } + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } TEST_F(MallocDebugTest, fill_generic) { Init("fill"); - VerifyAllocCalls(); + VerifyAllocCalls(false); } TEST_F(MallocDebugTest, fill_on_alloc_generic) { Init("fill_on_alloc"); - VerifyAllocCalls(); + VerifyAllocCalls(false); } TEST_F(MallocDebugTest, fill_on_alloc_partial) { @@ -275,7 +286,7 @@ TEST_F(MallocDebugTest, free_track_partial) { TEST_F(MallocDebugTest, all_options) { Init("guard backtrace fill expand_alloc free_track leak_track"); - VerifyAllocCalls(); + VerifyAllocCalls(true); } TEST_F(MallocDebugTest, expand_alloc) { @@ -624,6 +635,9 @@ TEST_F(MallocDebugTest, leak_track_no_frees_with_backtrace) { ASSERT_STREQ("", getFakeLogBuf().c_str()); std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + expected_log += android::base::StringPrintf( "6 malloc_debug +++ malloc_testing leaked block of size 1024 at %p (leak 1 of 3)\n", pointer3); expected_log += "6 malloc_debug Backtrace at time of allocation:\n"; @@ -649,7 +663,6 @@ TEST_F(MallocDebugTest, leak_track_no_frees_with_backtrace) { expected_log += "6 malloc_debug #00 pc 0x1000\n"; expected_log += "6 malloc_debug #01 pc 0x2000\n"; expected_log += "6 malloc_debug #02 pc 0x3000\n"; - ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } @@ -1022,7 +1035,10 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_empty) { ASSERT_EQ(0U, backtrace_size); ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } TEST_F(MallocDebugTest, get_malloc_leak_info_single) { @@ -1065,7 +1081,10 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_single) { debug_free(pointer); ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } TEST_F(MallocDebugTest, get_malloc_leak_info_multi) { @@ -1144,7 +1163,10 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_multi) { debug_free(pointers[2]); ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } TEST_F(MallocDebugTest, get_malloc_leak_info_multi_skip_empty_backtrace) { @@ -1215,6 +1237,185 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_multi_skip_empty_backtrace) { debug_free(pointers[2]); ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} + +static std::string SanitizeHeapData(const std::string& data) { + // Remove the map data since it's not consistent. + std::string sanitized; + bool skip_map_data = false; + bool map_data_found = false; + for (auto& line : android::base::Split(data, "\n")) { + if (skip_map_data) { + if (line == "END") { + if (map_data_found) { + sanitized += "MAP_DATA\n"; + map_data_found = false; + } + skip_map_data = false; + } else { + map_data_found = true; + continue; + } + } + if (line == "MAPS") { + skip_map_data = true; + } + sanitized += line + '\n'; + } + return sanitized; +} + +void MallocDebugTest::BacktraceDumpOnSignal(bool trigger_with_alloc) { + Init("backtrace=4"); + + backtrace_fake_add(std::vector<uintptr_t> {0x100, 0x200}); + backtrace_fake_add(std::vector<uintptr_t> {0x300, 0x400}); + backtrace_fake_add(std::vector<uintptr_t> {0x500, 0x600}); + + backtrace_fake_add(std::vector<uintptr_t> {0xa000, 0xb000}); + backtrace_fake_add(std::vector<uintptr_t> {0xa100, 0xb200}); + backtrace_fake_add(std::vector<uintptr_t> {0xa300, 0xb300}); + + std::vector<void*> pointers; + zygote = 1; + pointers.push_back(debug_malloc(100)); + ASSERT_TRUE(pointers.back() != nullptr); + pointers.push_back(debug_malloc(40)); + ASSERT_TRUE(pointers.back() != nullptr); + pointers.push_back(debug_malloc(200)); + ASSERT_TRUE(pointers.back() != nullptr); + + zygote = 0; + pointers.push_back(debug_malloc(10)); + ASSERT_TRUE(pointers.back() != nullptr); + pointers.push_back(debug_malloc(50)); + ASSERT_TRUE(pointers.back() != nullptr); + pointers.push_back(debug_malloc(5)); + ASSERT_TRUE(pointers.back() != nullptr); + + // Dump all of the data accumulated so far. + ASSERT_TRUE(kill(getpid(), SIGRTMAX - 17) == 0); + sleep(1); + + // This triggers the dumping. + if (trigger_with_alloc) { + pointers.push_back(debug_malloc(23)); + ASSERT_TRUE(pointers.back() != nullptr); + } else { + debug_free(pointers.back()); + pointers.pop_back(); + } + + for (auto* pointer : pointers) { + debug_free(pointer); + } + + // Read all of the contents. + std::string actual; + std::string name = android::base::StringPrintf("%s.%d.txt", BACKTRACE_DUMP_PREFIX, getpid()); + ASSERT_TRUE(android::base::ReadFileToString(name, &actual)); + ASSERT_EQ(0, unlink(name.c_str())); + + std::string sanitized(SanitizeHeapData(actual)); + + std::string expected = + "Android Native Heap Dump v1.0\n" + "\n" + "Total memory: 405\n" + "Allocation records: 6\n" + "Backtrace size: 4\n" + "\n" +#if defined(__LP64__) + "z 0 sz 5 num 1 bt 000000000000a300 000000000000b300\n" + "z 0 sz 10 num 1 bt 000000000000a000 000000000000b000\n" + "z 0 sz 50 num 1 bt 000000000000a100 000000000000b200\n" + "z 1 sz 40 num 1 bt 0000000000000300 0000000000000400\n" + "z 1 sz 100 num 1 bt 0000000000000100 0000000000000200\n" + "z 1 sz 200 num 1 bt 0000000000000500 0000000000000600\n" +#else + "z 0 sz 5 num 1 bt 0000a300 0000b300\n" + "z 0 sz 10 num 1 bt 0000a000 0000b000\n" + "z 0 sz 50 num 1 bt 0000a100 0000b200\n" + "z 1 sz 40 num 1 bt 00000300 00000400\n" + "z 1 sz 100 num 1 bt 00000100 00000200\n" + "z 1 sz 200 num 1 bt 00000500 00000600\n" +#endif + "MAPS\n" + "MAP_DATA\n" + "END\n\n"; + ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual; + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + expected_log += android::base::StringPrintf( + "6 malloc_debug Dumping to file: /data/local/tmp/backtrace_heap.%d.txt\n\n", getpid()); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugTest, backtrace_dump_on_signal_by_malloc) { + BacktraceDumpOnSignal(true); +} + +TEST_F(MallocDebugTest, backtrace_dump_on_signal_by_free) { + BacktraceDumpOnSignal(false); +} + +TEST_F(MallocDebugTest, backtrace_dump_on_exit) { + pid_t pid; + if ((pid = fork()) == 0) { + Init("backtrace=4 backtrace_dump_on_exit"); + backtrace_fake_add(std::vector<uintptr_t> {0x100, 0x200}); + backtrace_fake_add(std::vector<uintptr_t> {0xa000, 0xb000}); + backtrace_fake_add(std::vector<uintptr_t> {0xa000, 0xb000, 0xc000}); + + std::vector<void*> pointers; + pointers.push_back(debug_malloc(300)); + pointers.push_back(debug_malloc(400)); + pointers.push_back(debug_malloc(500)); + + // Call the exit function manually. + debug_finalize(); + exit(0); + } + ASSERT_NE(-1, pid); + ASSERT_EQ(pid, waitpid(pid, nullptr, 0)); + + // Read all of the contents. + std::string actual; + std::string name = android::base::StringPrintf("%s.%d.exit.txt", BACKTRACE_DUMP_PREFIX, pid); + ASSERT_TRUE(android::base::ReadFileToString(name, &actual)); + ASSERT_EQ(0, unlink(name.c_str())); + + std::string sanitized(SanitizeHeapData(actual)); + + std::string expected = + "Android Native Heap Dump v1.0\n" + "\n" + "Total memory: 1200\n" + "Allocation records: 3\n" + "Backtrace size: 4\n" + "\n" +#if defined(__LP64__) + "z 0 sz 300 num 1 bt 0000000000000100 0000000000000200\n" + "z 0 sz 400 num 1 bt 000000000000a000 000000000000b000\n" + "z 0 sz 500 num 1 bt 000000000000a000 000000000000b000 000000000000c000\n" +#else + "z 0 sz 300 num 1 bt 00000100 00000200\n" + "z 0 sz 400 num 1 bt 0000a000 0000b000\n" + "z 0 sz 500 num 1 bt 0000a000 0000b000 0000c000\n" +#endif + "MAPS\n" + "MAP_DATA\n" + "END\n\n"; + ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual; + + ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } @@ -1317,6 +1518,9 @@ TEST_F(MallocDebugTest, backtrace_enable_on_signal) { std::string expected_log = android::base::StringPrintf( "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to enable backtracing.\n", SIGRTMAX - 19, getpid()); + expected_log += android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } @@ -1439,7 +1643,10 @@ TEST_F(MallocDebugTest, zygote_set) { debug_free(pointer); ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } TEST_F(MallocDebugTest, max_size) { @@ -1596,6 +1803,7 @@ void VerifyRecordAllocs() { // Read all of the contents. std::string actual; ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual)); + ASSERT_EQ(0, unlink(RECORD_ALLOCS_FILE)); ASSERT_STREQ(expected.c_str(), actual.c_str()); @@ -1653,6 +1861,7 @@ TEST_F(MallocDebugTest, record_allocs_max) { // Read all of the contents. std::string actual; ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual)); + ASSERT_EQ(0, unlink(RECORD_ALLOCS_FILE)); ASSERT_STREQ(expected.c_str(), actual.c_str()); @@ -1694,6 +1903,7 @@ TEST_F(MallocDebugTest, record_allocs_thread_done) { // Read all of the contents. std::string actual; ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual)); + ASSERT_EQ(0, unlink(RECORD_ALLOCS_FILE)); ASSERT_STREQ(expected.c_str(), actual.c_str()); @@ -1748,6 +1958,7 @@ TEST_F(MallocDebugTest, record_allocs_file_name_fail) { expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer); ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual)); + ASSERT_EQ(0, unlink(RECORD_ALLOCS_FILE)); ASSERT_STREQ(expected.c_str(), actual.c_str()); ASSERT_STREQ("", getFakeLogBuf().c_str()); |