From 93bdd6ae3ad8322b7be2be0201c8db7227631d14 Mon Sep 17 00:00:00 2001 From: Christopher Ferris Date: Thu, 5 Apr 2018 11:12:38 -0700 Subject: [PATCH] Add support for using the new unwinder. This adds a new option backtrace_full, when it is set, then it will use libunwindstack. Modify the dump to file data to dump the extra information from libunwindstack. Along with the new dump file format, change the version to v1.1. Updated document for new format of file data. Add unit tests for the new functionality. Bug: 74361929 Test: Ran unit tests. Change-Id: I40fff795f5346bba7b9d7fde2e04f269ff4eb7f1 --- libc/malloc_debug/Android.bp | 19 +- libc/malloc_debug/Config.cpp | 8 + libc/malloc_debug/Config.h | 1 + libc/malloc_debug/GuardData.cpp | 5 +- libc/malloc_debug/PointerData.cpp | 111 ++++++++---- libc/malloc_debug/PointerData.h | 5 + libc/malloc_debug/README.md | 48 ++++- libc/malloc_debug/UnwindBacktrace.cpp | 111 ++++++++++++ libc/malloc_debug/UnwindBacktrace.h | 41 +++++ libc/malloc_debug/malloc_debug.cpp | 33 +++- libc/malloc_debug/malloc_debug.h | 2 + libc/malloc_debug/tests/backtrace_fake.cpp | 30 ++++ libc/malloc_debug/tests/backtrace_fake.h | 5 + libc/malloc_debug/tests/log_fake.cpp | 10 +- .../tests/malloc_debug_config_tests.cpp | 23 +++ .../tests/malloc_debug_unit_tests.cpp | 165 +++++++++++------- 16 files changed, 507 insertions(+), 110 deletions(-) create mode 100644 libc/malloc_debug/UnwindBacktrace.cpp create mode 100644 libc/malloc_debug/UnwindBacktrace.h diff --git a/libc/malloc_debug/Android.bp b/libc/malloc_debug/Android.bp index 1a79802dc..a4ff5d412 100644 --- a/libc/malloc_debug/Android.bp +++ b/libc/malloc_debug/Android.bp @@ -14,6 +14,7 @@ cc_library_static { stl: "libc++_static", whole_static_libs: [ + "libbase", "libasync_safe", "libdemangle", ], @@ -49,6 +50,7 @@ cc_library { "malloc_debug.cpp", "PointerData.cpp", "RecordData.cpp", + "UnwindBacktrace.cpp", ], stl: "libc++_static", @@ -69,9 +71,14 @@ cc_library { static_libs: [ "libasync_safe", "libbase", + "libdemangle", "libc_malloc_debug_backtrace", ], + shared_libs: [ + "libunwindstack", + ], + multilib: { lib32: { version_script: "exported32.map", @@ -112,15 +119,21 @@ cc_test { "tests/malloc_debug_unit_tests.cpp", ], - whole_static_libs: ["libc_malloc_debug"], - local_include_dirs: ["tests"], include_dirs: [ "bionic/libc", "bionic/libc/async_safe/include", ], - shared_libs: ["libbase"], + static_libs: [ + "libc_malloc_debug", + "libdemangle", + ], + + shared_libs: [ + "libbase", + "libunwindstack", + ], cflags: [ "-Wall", diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp index 2c94fe81f..96fc27f73 100644 --- a/libc/malloc_debug/Config.cpp +++ b/libc/malloc_debug/Config.cpp @@ -93,6 +93,9 @@ const std::unordered_map Config::kOptions = { { "backtrace_dump_prefix", {0, &Config::SetBacktraceDumpPrefix}, }, + { + "backtrace_full", {BACKTRACE_FULL, &Config::VerifyValueEmpty}, + }, { "fill", {FILL_ON_ALLOC | FILL_ON_FREE, &Config::SetFill}, @@ -363,6 +366,11 @@ void Config::LogUsage() const { error_log(" backtrace_dump_prefix..final.txt."); error_log(" The default is false."); error_log(""); + error_log(" backtrace_full"); + error_log(" Any time a backtrace is acquired, use an unwinder that can"); + error_log(" display Java stack frames. This unwinder can run slower than"); + error_log(" normal unwinder."); + 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"); diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h index 3bcef0bc8..86d1ee4e0 100644 --- a/libc/malloc_debug/Config.h +++ b/libc/malloc_debug/Config.h @@ -43,6 +43,7 @@ constexpr uint64_t FREE_TRACK = 0x40; constexpr uint64_t TRACK_ALLOCS = 0x80; constexpr uint64_t LEAK_TRACK = 0x100; constexpr uint64_t RECORD_ALLOCS = 0x200; +constexpr uint64_t BACKTRACE_FULL = 0x400; // In order to guarantee posix compliance, set the minimum alignment // to 8 bytes for 32 bit systems and 16 bytes for 64 bit systems. diff --git a/libc/malloc_debug/GuardData.cpp b/libc/malloc_debug/GuardData.cpp index f9a2dcaa7..debc14e0c 100644 --- a/libc/malloc_debug/GuardData.cpp +++ b/libc/malloc_debug/GuardData.cpp @@ -62,10 +62,7 @@ void GuardData::LogFailure(const Header* header, const void* pointer, const void } error_log("Backtrace at time of failure:"); - std::vector frames(64); - size_t frame_num = backtrace_get(frames.data(), frames.size()); - frames.resize(frame_num); - backtrace_log(frames.data(), frames.size()); + BacktraceAndLog(); error_log(LOG_DIVIDER); } diff --git a/libc/malloc_debug/PointerData.cpp b/libc/malloc_debug/PointerData.cpp index 85139e607..b0e2fc849 100644 --- a/libc/malloc_debug/PointerData.cpp +++ b/libc/malloc_debug/PointerData.cpp @@ -43,6 +43,7 @@ #include #include +#include #include #include "Config.h" @@ -51,6 +52,7 @@ #include "backtrace.h" #include "debug_log.h" #include "malloc_debug.h" +#include "UnwindBacktrace.h" std::atomic_uint8_t PointerData::backtrace_enabled_; std::atomic_bool PointerData::backtrace_dump_; @@ -63,6 +65,7 @@ std::mutex PointerData::frame_mutex_; std::unordered_map PointerData::key_to_index_ GUARDED_BY( PointerData::frame_mutex_); std::unordered_map PointerData::frames_ GUARDED_BY(PointerData::frame_mutex_); +std::unordered_map> PointerData::backtraces_info_ GUARDED_BY(PointerData::frame_mutex_); constexpr size_t kBacktraceEmptyIndex = 1; size_t PointerData::cur_hash_index_ GUARDED_BY(PointerData::frame_mutex_); @@ -127,10 +130,18 @@ bool PointerData::Initialize(const Config& config) NO_THREAD_SAFETY_ANALYSIS { } size_t PointerData::AddBacktrace(size_t num_frames) { - std::vector frames(num_frames); - num_frames = backtrace_get(frames.data(), frames.size()); - if (num_frames == 0) { - return kBacktraceEmptyIndex; + std::vector frames; + std::vector frames_info; + if (g_debug->config().options() & BACKTRACE_FULL) { + if (!Unwind(&frames, &frames_info, num_frames)) { + return kBacktraceEmptyIndex; + } + } else { + frames.resize(num_frames); + num_frames = backtrace_get(frames.data(), frames.size()); + if (num_frames == 0) { + return kBacktraceEmptyIndex; + } } FrameKeyType key{.num_frames = num_frames, .frames = frames.data()}; @@ -144,6 +155,9 @@ size_t PointerData::AddBacktrace(size_t num_frames) { key_to_index_.emplace(key, hash_index); frames_.emplace(hash_index, FrameInfoType{.references = 1, .frames = std::move(frames)}); + if (g_debug->config().options() & BACKTRACE_FULL) { + backtraces_info_.emplace(hash_index, std::move(frames_info)); + } } else { hash_index = entry->second; FrameInfoType* frame_info = &frames_[hash_index]; @@ -168,6 +182,9 @@ void PointerData::RemoveBacktrace(size_t hash_index) { FrameKeyType key{.num_frames = frame_info->frames.size(), .frames = frame_info->frames.data()}; key_to_index_.erase(key); frames_.erase(hash_index); + if (g_debug->config().options() & BACKTRACE_FULL) { + backtraces_info_.erase(hash_index); + } } } @@ -230,6 +247,25 @@ size_t PointerData::GetFrames(const void* ptr, uintptr_t* frames, size_t max_fra return max_frames; } +void PointerData::LogBacktrace(size_t hash_index) { + std::lock_guard frame_guard(frame_mutex_); + if (g_debug->config().options() & BACKTRACE_FULL) { + auto backtrace_info_entry = backtraces_info_.find(hash_index); + if (backtrace_info_entry != backtraces_info_.end()) { + UnwindLog(backtrace_info_entry->second); + return; + } + } else { + auto frame_entry = frames_.find(hash_index); + if (frame_entry != frames_.end()) { + FrameInfoType* frame_info = &frame_entry->second; + backtrace_log(frame_info->frames.data(), frame_info->frames.size()); + return; + } + } + error_log(" hash_index %zu does not have matching frame data.", hash_index); +} + void PointerData::LogFreeError(const FreePointerInfoType& info, size_t usable_size) { error_log(LOG_DIVIDER); uint8_t* memory = reinterpret_cast(info.pointer); @@ -242,13 +278,8 @@ void PointerData::LogFreeError(const FreePointerInfoType& info, size_t usable_si } if (info.hash_index > kBacktraceEmptyIndex) { - std::lock_guard frame_guard(frame_mutex_); - auto frame_entry = frames_.find(info.hash_index); - if (frame_entry != frames_.end()) { - FrameInfoType* frame_info = &frame_entry->second; - error_log("Backtrace at time of free:"); - backtrace_log(frame_info->frames.data(), frame_info->frames.size()); - } + error_log("Backtrace at time of free:"); + LogBacktrace(info.hash_index); } error_log(LOG_DIVIDER); @@ -328,15 +359,8 @@ void PointerData::LogFreeBacktrace(const void* ptr) { return; } - std::lock_guard frame_guard(frame_mutex_); - auto frame_entry = frames_.find(hash_index); - if (frame_entry == frames_.end()) { - error_log("Freed pointer hash_index %zu does not have matching frame data.", hash_index); - return; - } - FrameInfoType* frame_info = &frame_entry->second; error_log("Backtrace of original free:"); - backtrace_log(frame_info->frames.data(), frame_info->frames.size()); + LogBacktrace(hash_index); } void PointerData::VerifyAllFreed() { @@ -350,18 +374,28 @@ void PointerData::GetList(std::vector* list, bool only_with_backtr REQUIRES(pointer_mutex_, frame_mutex_) { for (const auto& entry : pointers_) { FrameInfoType* frame_info = nullptr; + std::vector* backtrace_info = nullptr; size_t hash_index = entry.second.hash_index; if (hash_index > kBacktraceEmptyIndex) { - frame_info = &frames_[hash_index]; - if (frame_info->references == 0) { + auto frame_entry = frames_.find(hash_index); + if (frame_entry == frames_.end()) { // Somehow wound up with a pointer with a valid hash_index, but // no frame data. This should not be possible since adding a pointer // occurs after the hash_index and frame data have been added. // When removing a pointer, the pointer is deleted before the frame // data. - frames_.erase(hash_index); error_log("Pointer 0x%" PRIxPTR " hash_index %zu does not exist.", entry.first, hash_index); - frame_info = nullptr; + } else { + frame_info = &frame_entry->second; + } + + if (g_debug->config().options() & BACKTRACE_FULL) { + auto backtrace_entry = backtraces_info_.find(hash_index); + if (backtrace_entry == backtraces_info_.end()) { + error_log("Pointer 0x%" PRIxPTR " hash_index %zu does not exist.", entry.first, hash_index); + } else { + backtrace_info = &backtrace_entry->second; + } } } if (hash_index == 0 && only_with_backtrace) { @@ -369,7 +403,7 @@ void PointerData::GetList(std::vector* list, bool only_with_backtr } list->emplace_back(ListInfoType{entry.first, 1, entry.second.RealSize(), - entry.second.ZygoteChildAlloc(), frame_info}); + entry.second.ZygoteChildAlloc(), frame_info, backtrace_info}); } // Sort by the size of the allocation. @@ -440,7 +474,10 @@ void PointerData::LogLeaks() { for (const auto& list_info : list) { error_log("+++ %s leaked block of size %zu at 0x%" PRIxPTR " (leak %zu of %zu)", getprogname(), list_info.size, list_info.pointer, ++track_count, list.size()); - if (list_info.frame_info != nullptr) { + if (list_info.backtrace_info != nullptr) { + error_log("Backtrace at time of allocation:"); + UnwindLog(*list_info.backtrace_info); + } else if (list_info.frame_info != nullptr) { error_log("Backtrace at time of allocation:"); backtrace_log(list_info.frame_info->frames.data(), list_info.frame_info->frames.size()); } @@ -520,14 +557,28 @@ void PointerData::DumpLiveToFile(FILE* fp) { if (frame_info->frames[i] == 0) { break; } -#if defined(__LP64__) - fprintf(fp, " %016" PRIxPTR, frame_info->frames[i]); -#else - fprintf(fp, " %08" PRIxPTR, frame_info->frames[i]); -#endif + fprintf(fp, " %" PRIxPTR, frame_info->frames[i]); } } fprintf(fp, "\n"); + if (info.backtrace_info != nullptr) { + fprintf(fp, " bt_info"); + for (const auto& frame : *info.backtrace_info) { + fprintf(fp, " {"); + if (frame.map_info != nullptr && !frame.map_info->name.empty()) { + fprintf(fp, "\"%s\"", frame.map_info->name.c_str()); + } else { + fprintf(fp, "\"\""); + } + fprintf(fp, " %" PRIx64, frame.rel_pc); + if (frame.function_name.empty()) { + fprintf(fp, " \"\" 0}"); + } else { + fprintf(fp, " \"%s\" %" PRIx64 "}", demangle(frame.function_name.c_str()).c_str(), frame.function_offset); + } + } + fprintf(fp, "\n"); + } } } diff --git a/libc/malloc_debug/PointerData.h b/libc/malloc_debug/PointerData.h index 7a0b3b89e..62d4186f2 100644 --- a/libc/malloc_debug/PointerData.h +++ b/libc/malloc_debug/PointerData.h @@ -39,8 +39,10 @@ #include #include +#include #include "OptionData.h" +#include "UnwindBacktrace.h" extern int* g_malloc_zygote_child; @@ -105,6 +107,7 @@ struct ListInfoType { size_t size; bool zygote_child_alloc; FrameInfoType* frame_info; + std::vector* backtrace_info; }; class PointerData : public OptionData { @@ -160,6 +163,7 @@ class PointerData : public OptionData { private: static std::string GetHashString(uintptr_t* frames, size_t num_frames); + static void LogBacktrace(size_t hash_index); size_t alloc_offset_ = 0; std::vector cmp_mem_; @@ -174,6 +178,7 @@ class PointerData : public OptionData { static std::mutex frame_mutex_; static std::unordered_map key_to_index_; static std::unordered_map frames_; + static std::unordered_map> backtraces_info_; static size_t cur_hash_index_; static std::mutex free_pointer_mutex_; diff --git a/libc/malloc_debug/README.md b/libc/malloc_debug/README.md index 51540f841..b50de40d9 100644 --- a/libc/malloc_debug/README.md +++ b/libc/malloc_debug/README.md @@ -150,8 +150,8 @@ 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 +As of P, when one of 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. @@ -160,6 +160,11 @@ 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. +### backtrace\_full +As of P, any time that a backtrace is gathered, a different algorithm is used +that is extra thorough and can unwind through Java frames. This will run +slower than the normal backtracing function. + ### 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 @@ -490,6 +495,45 @@ The final section is the map data for the process: The map data is simply the output of /proc/PID/maps. This data can be used to decode the frames in the backtraces. +As of Android P, there is a new version of this file. The new header is: + + Android Native Heap Dump v1.1 + +The new version no longer 0 pads the backtrace addresses. In v1.0: + + z 0 sz 400 num 1 bt 0000a230 0000b500 + +While v1.1: + + z 0 sz 400 num 1 bt a230 b500 + +In addition, when the new option backtrace\_full is used, another line will +be added to every backtrace line. The line will be: + + bt_info {"MAP_NAME" RELATIVE_TO_MAP_PC "FUNCTION_NAME" FUNCTION_OFFSET} ... + +For each backtrace pc, there will be one element in braces. + +MAP\_NAME is the name of the map in which the backtrace pc exists. If there is +no valid map name, this will be empty. +RELATIVE\_TO\_MAP\_PC is the hexadecimal value of the relative pc to the map. +FUNCTION\_NAME the name of the function for this pc. If there is no valid +function name, then it will be empty. +FUNCTION\_OFFSET the hexadecimal offset from the beginning of the function. If +the FUNCTION\_NAME is empty, then this value will always be zero. + +An example of this new format: + + z 0 sz 400 num 1 bt a2a0 b510 + bt_info {"/system/libc.so" 2a0 "abort" 24} {"/system/libutils.so" 510 "" 0} + +In this example, the first backtrace frame has a pc of 0xa2a0 and is in the +map named /system/libc.so which starts at 0xa000. The relative pc is 0x2a0, +and it is in the function abort + 0x24. +The second backtrace frame has a pc of 0xb510 and is in the map named +/system/libutils.so which starts at 0xb000. The relative pc is 0x510 and +it is in an unknown function. + There is a tool to visualize this data, development/scripts/native\_heapdump\_viewer.py. Examples diff --git a/libc/malloc_debug/UnwindBacktrace.cpp b/libc/malloc_debug/UnwindBacktrace.cpp new file mode 100644 index 000000000..ddafc731f --- /dev/null +++ b/libc/malloc_debug/UnwindBacktrace.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "UnwindBacktrace.h" +#include "debug_log.h" + +#if defined(__LP64__) +#define PAD_PTR "016" PRIx64 +#else +#define PAD_PTR "08" PRIx64 +#endif + +static pthread_once_t g_setup_once = PTHREAD_ONCE_INIT; + +static unwindstack::LocalUnwinder* g_unwinder; + +static void Setup() { +#if defined(__LP64__) + std::vector skip_libraries{"/system/lib64/libunwindstack.so", "/system/lib64/libc_malloc_debug.so"}; +#else + std::vector skip_libraries{"/system/lib/libunwindstack.so", "/system/lib/libc_malloc_debug.so"}; +#endif + + g_unwinder = new unwindstack::LocalUnwinder(skip_libraries); + g_unwinder->Init(); +} + +bool Unwind(std::vector* frames, std::vector* frame_info, size_t max_frames) { + pthread_once(&g_setup_once, Setup); + + if (g_unwinder == nullptr) { + return false; + } + + if (!g_unwinder->Unwind(frame_info, max_frames)) { + frames->clear(); + frame_info->clear(); + return false; + } + + for (const auto& frame : *frame_info) { + frames->push_back(frame.pc); + } + return true; +} + +void UnwindLog(const std::vector& frame_info) { + for (size_t i = 0; i < frame_info.size(); i++) { + const unwindstack::LocalFrameData* info = &frame_info[i]; + unwindstack::MapInfo* map_info = info->map_info; + + std::string line = android::base::StringPrintf(" #%0zd pc %" PAD_PTR " ", i, info->rel_pc); + if (map_info->offset != 0) { + line += android::base::StringPrintf("(offset 0x%" PRIx64 ") ", map_info->offset); + } + + if (map_info->name.empty()) { + line += android::base::StringPrintf("", map_info->start); + } else { + line += map_info->name; + } + + if (!info->function_name.empty()) { + line += " (" + demangle(info->function_name.c_str()); + if (info->function_offset != 0) { + line += "+" + std::to_string(info->function_offset); + } + line += ")"; + } + error_log_string(line.c_str()); + } +} diff --git a/libc/malloc_debug/UnwindBacktrace.h b/libc/malloc_debug/UnwindBacktrace.h new file mode 100644 index 000000000..4c6c8d4a5 --- /dev/null +++ b/libc/malloc_debug/UnwindBacktrace.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#pragma once + +#include + +#include +#include + +#include +#include + +bool Unwind(std::vector* frames, std::vector* info, size_t max_frames); + +void UnwindLog(const std::vector& frame_info); diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp index 6f841cab5..836c33b86 100644 --- a/libc/malloc_debug/malloc_debug.cpp +++ b/libc/malloc_debug/malloc_debug.cpp @@ -47,6 +47,7 @@ #include "debug_disable.h" #include "debug_log.h" #include "malloc_debug.h" +#include "UnwindBacktrace.h" // ------------------------------------------------------------------------ // Global Data @@ -118,6 +119,26 @@ static void InitAtfork() { }); } +void BacktraceAndLog() { + if (g_debug->config().options() & BACKTRACE_FULL) { + std::vector frames; + std::vector frames_info; + if (!Unwind(&frames, &frames_info, 256)) { + error_log(" Backtrace failed to get any frames."); + } else { + UnwindLog(frames_info); + } + } else { + std::vector 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,14 +149,8 @@ static void LogError(const void* pointer, const char* error_str) { PointerData::LogFreeBacktrace(pointer); } - std::vector 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); } @@ -819,7 +834,7 @@ bool debug_dump_heap(const char* file_name) { return false; } - fprintf(fp, "Android Native Heap Dump v1.0\n\n"); + fprintf(fp, "Android Native Heap Dump v1.1\n\n"); PointerData::DumpLiveToFile(fp); diff --git a/libc/malloc_debug/malloc_debug.h b/libc/malloc_debug/malloc_debug.h index ac210e246..7280ea7b7 100644 --- a/libc/malloc_debug/malloc_debug.h +++ b/libc/malloc_debug/malloc_debug.h @@ -62,3 +62,5 @@ constexpr char LOG_DIVIDER[] = "*** *** *** *** *** *** *** *** *** *** *** *** constexpr size_t FREE_TRACK_MEM_BUFFER_SIZE = 4096; extern const MallocDispatch* g_dispatch; + +void BacktraceAndLog(); diff --git a/libc/malloc_debug/tests/backtrace_fake.cpp b/libc/malloc_debug/tests/backtrace_fake.cpp index db542e5e2..ad16c0211 100644 --- a/libc/malloc_debug/tests/backtrace_fake.cpp +++ b/libc/malloc_debug/tests/backtrace_fake.cpp @@ -20,6 +20,8 @@ #include #include +#include + #include "backtrace.h" #include "backtrace_fake.h" #include "debug_log.h" @@ -57,3 +59,31 @@ void backtrace_log(const uintptr_t* frames, size_t frame_count) { error_log(" #%02zd pc %p", i, reinterpret_cast(frames[i])); } } + +static std::deque> g_fake_local_frame_data; + +void BacktraceUnwindFakeClearAll() { + g_fake_local_frame_data.clear(); +} + +void BacktraceUnwindFake(const std::vector& frames) { + g_fake_local_frame_data.push_back(frames); +} + +bool Unwind(std::vector* frames, std::vector* info, size_t) { + if (g_fake_local_frame_data.empty()) { + return false; + } + + *info = g_fake_local_frame_data.front(); + g_fake_local_frame_data.pop_front(); + frames->clear(); + for (const auto& frame : *info) { + frames->push_back(frame.pc); + } + + return true; +} + +void UnwindLog(const std::vector& /*frame_info*/) { +} diff --git a/libc/malloc_debug/tests/backtrace_fake.h b/libc/malloc_debug/tests/backtrace_fake.h index f2aa7a0e6..a9ee97dc7 100644 --- a/libc/malloc_debug/tests/backtrace_fake.h +++ b/libc/malloc_debug/tests/backtrace_fake.h @@ -21,7 +21,12 @@ #include +#include + void backtrace_fake_clear_all(); void backtrace_fake_add(const std::vector& ips); +void BacktraceUnwindFakeClearAll(); +void BacktraceUnwindFake(const std::vector& frames); + #endif // MALLOC_DEBUG_TESTS_BACKTRACE_FAKE_H diff --git a/libc/malloc_debug/tests/log_fake.cpp b/libc/malloc_debug/tests/log_fake.cpp index fb64e3eb7..9a23ac761 100644 --- a/libc/malloc_debug/tests/log_fake.cpp +++ b/libc/malloc_debug/tests/log_fake.cpp @@ -45,9 +45,7 @@ std::string getFakeLogPrint() { } extern "C" int async_safe_format_log(int priority, const char* tag, const char* format, ...) { - g_fake_log_print += std::to_string(priority) + ' '; - g_fake_log_print += tag; - g_fake_log_print += ' '; + g_fake_log_print += std::to_string(priority) + ' ' + tag + ' '; va_list ap; va_start(ap, format); @@ -59,6 +57,12 @@ extern "C" int async_safe_format_log(int priority, const char* tag, const char* return 0; } +extern "C" int async_safe_write_log(int priority, const char* tag, const char* msg) { + g_fake_log_print += std::to_string(priority) + ' ' + tag + ' ' + msg + '\n'; + + return 0; +} + extern "C" int __android_log_buf_write(int bufId, int prio, const char* tag, const char* msg) { g_fake_log_buf += std::to_string(bufId) + ' ' + std::to_string(prio) + ' '; g_fake_log_buf += tag; diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp index 460353583..2a6e39986 100644 --- a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp @@ -83,6 +83,11 @@ std::string usage_string( "6 malloc_debug backtrace_dump_prefix..final.txt.\n" "6 malloc_debug The default is false.\n" "6 malloc_debug \n" + "6 malloc_debug backtrace_full\n" + "6 malloc_debug Any time a backtrace is acquired, use an unwinder that can\n" + "6 malloc_debug display Java stack frames. This unwinder can run slower than\n" + "6 malloc_debug normal unwinder.\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" @@ -413,6 +418,24 @@ TEST_F(MallocDebugConfigTest, backtrace_dump_prefix) { ASSERT_STREQ("", getFakeLogPrint().c_str()); } +TEST_F(MallocDebugConfigTest, backtrace_full) { + ASSERT_TRUE(InitConfig("backtrace_full")) << getFakeLogPrint(); + ASSERT_EQ(BACKTRACE_FULL, config->options()); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + ASSERT_STREQ("", getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugConfigTest, backtrace_full_fail) { + ASSERT_FALSE(InitConfig("backtrace_full=200")) << getFakeLogPrint(); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string log_msg( + "6 malloc_debug malloc_testing: value set for option 'backtrace_full' " + "which does not take a value\n"); + ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str()); +} + TEST_F(MallocDebugConfigTest, fill_on_alloc) { ASSERT_TRUE(InitConfig("fill_on_alloc=64")) << getFakeLogPrint(); ASSERT_EQ(FILL_ON_ALLOC, config->options()); diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp index 1504d06dd..0663f6a46 100644 --- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -1311,30 +1312,23 @@ void MallocDebugTest::BacktraceDumpOnSignal(bool trigger_with_alloc) { 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 50 num 1 bt 000000000000a100 000000000000b200\n" - "z 0 sz 10 num 1 bt 000000000000a000 000000000000b000\n" - "z 0 sz 5 num 1 bt 000000000000a300 000000000000b300\n" - "z 1 sz 200 num 1 bt 0000000000000500 0000000000000600\n" - "z 1 sz 100 num 1 bt 0000000000000100 0000000000000200\n" - "z 1 sz 40 num 1 bt 0000000000000300 0000000000000400\n" -#else - "z 0 sz 50 num 1 bt 0000a100 0000b200\n" - "z 0 sz 10 num 1 bt 0000a000 0000b000\n" - "z 0 sz 5 num 1 bt 0000a300 0000b300\n" - "z 1 sz 200 num 1 bt 00000500 00000600\n" - "z 1 sz 100 num 1 bt 00000100 00000200\n" - "z 1 sz 40 num 1 bt 00000300 00000400\n" -#endif - "MAPS\n" - "MAP_DATA\n" - "END\n\n"; +R"(Android Native Heap Dump v1.1 + +Total memory: 405 +Allocation records: 6 +Backtrace size: 4 + +z 0 sz 50 num 1 bt a100 b200 +z 0 sz 10 num 1 bt a000 b000 +z 0 sz 5 num 1 bt a300 b300 +z 1 sz 200 num 1 bt 500 600 +z 1 sz 100 num 1 bt 100 200 +z 1 sz 40 num 1 bt 300 400 +MAPS +MAP_DATA +END + +)"; ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual; ASSERT_STREQ("", getFakeLogBuf().c_str()); @@ -1383,24 +1377,20 @@ TEST_F(MallocDebugTest, backtrace_dump_on_exit) { 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 500 num 1 bt 000000000000a000 000000000000b000 000000000000c000\n" - "z 0 sz 400 num 1 bt 000000000000a000 000000000000b000\n" - "z 0 sz 300 num 1 bt 0000000000000100 0000000000000200\n" -#else - "z 0 sz 500 num 1 bt 0000a000 0000b000 0000c000\n" - "z 0 sz 400 num 1 bt 0000a000 0000b000\n" - "z 0 sz 300 num 1 bt 00000100 00000200\n" -#endif - "MAPS\n" - "MAP_DATA\n" - "END\n\n"; +R"(Android Native Heap Dump v1.1 + +Total memory: 1200 +Allocation records: 3 +Backtrace size: 4 + +z 0 sz 500 num 1 bt a000 b000 c000 +z 0 sz 400 num 1 bt a000 b000 +z 0 sz 300 num 1 bt 100 200 +MAPS +MAP_DATA +END + +)"; ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual; ASSERT_STREQ("", getFakeLogBuf().c_str()); @@ -1436,28 +1426,84 @@ TEST_F(MallocDebugTest, backtrace_dump_on_exit_shared_backtrace) { std::string sanitized(SanitizeHeapData(actual)); std::string expected = - "Android Native Heap Dump v1.0\n" - "\n" - "Total memory: 1000\n" - "Allocation records: 2\n" - "Backtrace size: 4\n" - "\n" -#if defined(__LP64__) - "z 0 sz 400 num 1 bt 000000000000a000 000000000000b000 000000000000c000\n" - "z 0 sz 300 num 2 bt 0000000000000100 0000000000000200\n" -#else - "z 0 sz 400 num 1 bt 0000a000 0000b000 0000c000\n" - "z 0 sz 300 num 2 bt 00000100 00000200\n" -#endif - "MAPS\n" - "MAP_DATA\n" - "END\n\n"; +R"(Android Native Heap Dump v1.1 + +Total memory: 1000 +Allocation records: 2 +Backtrace size: 4 + +z 0 sz 400 num 1 bt a000 b000 c000 +z 0 sz 300 num 2 bt 100 200 +MAPS +MAP_DATA +END + +)"; ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual; ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } +TEST_F(MallocDebugTest, backtrace_full_dump_on_exit) { + pid_t pid; + if ((pid = fork()) == 0) { + Init("backtrace=4 backtrace_full backtrace_dump_on_exit"); + BacktraceUnwindFake( + std::vector{{nullptr, 0x1100, 0x100, "fake1", 10}, + {nullptr, 0x1200, 0x200, "fake2", 20}}); + unwindstack::MapInfo map_info{0x10000, 0x20000, 0, PROT_READ | PROT_EXEC, "/data/fake.so"}; + BacktraceUnwindFake( + std::vector{{&map_info, 0x1a000, 0xa000, "level1", 0}, + {&map_info, 0x1b000, 0xb000, "level2", 10}}); + BacktraceUnwindFake( + std::vector{{nullptr, 0x1a000, 0xa000, "func1", 0}, + {nullptr, 0x1b000, 0xb000, "func2", 10}, + {nullptr, 0x1c000, 0xc000, "", 30}}); + + std::vector 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, TEMP_FAILURE_RETRY(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 = +R"(Android Native Heap Dump v1.1 + +Total memory: 1200 +Allocation records: 3 +Backtrace size: 4 + +z 0 sz 500 num 1 bt 1a000 1b000 1c000 + bt_info {"" a000 "func1" 0} {"" b000 "func2" a} {"" c000 "" 0} +z 0 sz 400 num 1 bt 1a000 1b000 + bt_info {"/data/fake.so" a000 "level1" 0} {"/data/fake.so" b000 "level2" a} +z 0 sz 300 num 1 bt 1100 1200 + bt_info {"" 100 "fake1" a} {"" 200 "fake2" 14} +MAPS +MAP_DATA +END + +)"; + ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual; + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + ASSERT_STREQ("", getFakeLogPrint().c_str()); +} TEST_F(MallocDebugTest, realloc_usable_size) { Init("front_guard"); @@ -2286,7 +2332,8 @@ TEST_F(MallocDebugTest, verify_pointers) { std::string realloc_pointer_str( android::base::StringPrintf("6 malloc_debug +++ ALLOCATION %p UNKNOWN POINTER (realloc)\n", pointer)); - std::string backtrace_str("6 malloc_debug Backtrace failed to get any frames.\n"); + std::string backtrace_str("6 malloc_debug Backtrace at time of failure:\n"); + backtrace_str += "6 malloc_debug Backtrace failed to get any frames.\n"; std::string expected_log(DIVIDER + free_pointer_str + backtrace_str + DIVIDER); expected_log += DIVIDER + usable_pointer_str + backtrace_str + DIVIDER;