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
diff --git a/libc/malloc_debug/PointerData.cpp b/libc/malloc_debug/PointerData.cpp
index 85139e6..b0e2fc8 100644
--- a/libc/malloc_debug/PointerData.cpp
+++ b/libc/malloc_debug/PointerData.cpp
@@ -43,6 +43,7 @@
#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
+#include <demangle.h>
#include <private/bionic_macros.h>
#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::unordered_map<FrameKeyType, size_t> PointerData::key_to_index_ GUARDED_BY(
PointerData::frame_mutex_);
std::unordered_map<size_t, FrameInfoType> PointerData::frames_ GUARDED_BY(PointerData::frame_mutex_);
+std::unordered_map<size_t, std::vector<unwindstack::LocalFrameData>> 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 @@
}
size_t PointerData::AddBacktrace(size_t num_frames) {
- std::vector<uintptr_t> frames(num_frames);
- num_frames = backtrace_get(frames.data(), frames.size());
- if (num_frames == 0) {
- return kBacktraceEmptyIndex;
+ std::vector<uintptr_t> frames;
+ std::vector<unwindstack::LocalFrameData> 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 @@
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 @@
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 @@
return max_frames;
}
+void PointerData::LogBacktrace(size_t hash_index) {
+ std::lock_guard<std::mutex> 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<uint8_t*>(info.pointer);
@@ -242,13 +278,8 @@
}
if (info.hash_index > kBacktraceEmptyIndex) {
- std::lock_guard<std::mutex> 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 @@
return;
}
- std::lock_guard<std::mutex> 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 @@
REQUIRES(pointer_mutex_, frame_mutex_) {
for (const auto& entry : pointers_) {
FrameInfoType* frame_info = nullptr;
+ std::vector<unwindstack::LocalFrameData>* 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 @@
}
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 @@
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 @@
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");
+ }
}
}