diff options
-rw-r--r-- | include/sanitizer/common_interface_defs.h | 6 | ||||
-rw-r--r-- | lib/asan/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lib/asan/asan_allocator.cc | 7 | ||||
-rw-r--r-- | lib/asan/asan_allocator.h | 2 | ||||
-rw-r--r-- | lib/asan/asan_memory_profile.cc | 100 | ||||
-rw-r--r-- | test/asan/TestCases/Linux/print_memory_profile_test.cc | 25 |
6 files changed, 139 insertions, 2 deletions
diff --git a/include/sanitizer/common_interface_defs.h b/include/sanitizer/common_interface_defs.h index 0cd6e9791..1bf3c1c96 100644 --- a/include/sanitizer/common_interface_defs.h +++ b/include/sanitizer/common_interface_defs.h @@ -133,6 +133,12 @@ extern "C" { const char *s2, size_t n, int result); void __sanitizer_weak_hook_strcmp(void *called_pc, const char *s1, const char *s2, int result); + + // Prints stack traces for all live heap allocations ordered by total + // allocation size until `top_percent` of total live heap is shown. + // `top_percent` should be between 1 and 100. + // Experimental feature currently available only with asan on Linux. + void __sanitizer_print_memory_profile(size_t top_percent); #ifdef __cplusplus } // extern "C" #endif diff --git a/lib/asan/CMakeLists.txt b/lib/asan/CMakeLists.txt index 57ac56f8c..07d623242 100644 --- a/lib/asan/CMakeLists.txt +++ b/lib/asan/CMakeLists.txt @@ -13,6 +13,7 @@ set(ASAN_SOURCES asan_malloc_linux.cc asan_malloc_mac.cc asan_malloc_win.cc + asan_memory_profile.cc asan_poisoning.cc asan_posix.cc asan_report.cc diff --git a/lib/asan/asan_allocator.cc b/lib/asan/asan_allocator.cc index 41b1689d9..fb433bb4c 100644 --- a/lib/asan/asan_allocator.cc +++ b/lib/asan/asan_allocator.cc @@ -681,12 +681,15 @@ static StackTrace GetStackTraceFromId(u32 id) { return res; } +u32 AsanChunkView::GetAllocStackId() { return chunk_->alloc_context_id; } +u32 AsanChunkView::GetFreeStackId() { return chunk_->free_context_id; } + StackTrace AsanChunkView::GetAllocStack() { - return GetStackTraceFromId(chunk_->alloc_context_id); + return GetStackTraceFromId(GetAllocStackId()); } StackTrace AsanChunkView::GetFreeStack() { - return GetStackTraceFromId(chunk_->free_context_id); + return GetStackTraceFromId(GetFreeStackId()); } void InitializeAllocator(const AllocatorOptions &options) { diff --git a/lib/asan/asan_allocator.h b/lib/asan/asan_allocator.h index 9807d1bd5..2f9f7aaf8 100644 --- a/lib/asan/asan_allocator.h +++ b/lib/asan/asan_allocator.h @@ -58,6 +58,8 @@ class AsanChunkView { uptr AllocTid(); uptr FreeTid(); bool Eq(const AsanChunkView &c) const { return chunk_ == c.chunk_; } + u32 GetAllocStackId(); + u32 GetFreeStackId(); StackTrace GetAllocStack(); StackTrace GetFreeStack(); bool AddrIsInside(uptr addr, uptr access_size, sptr *offset) { diff --git a/lib/asan/asan_memory_profile.cc b/lib/asan/asan_memory_profile.cc new file mode 100644 index 000000000..e87e6c8e7 --- /dev/null +++ b/lib/asan/asan_memory_profile.cc @@ -0,0 +1,100 @@ +//===-- asan_memory_profile.cc.cc -----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of AddressSanitizer, an address sanity checker. +// +// This file implements __sanitizer_print_memory_profile. +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_stackdepot.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "sanitizer_common/sanitizer_stoptheworld.h" +#include "lsan/lsan_common.h" +#include "asan/asan_allocator.h" + +#if SANITIZER_LINUX // StopTheWorld is currently linux-only. + +namespace __asan { + +struct AllocationSite { + u32 id; + uptr total_size; + uptr count; +}; + +class HeapProfile { + public: + HeapProfile() : allocations_(1024) {} + void Insert(u32 id, uptr size) { + total_allocated_ += size; + total_count_++; + // Linear lookup will be good enough for most cases (although not all). + for (uptr i = 0; i < allocations_.size(); i++) { + if (allocations_[i].id == id) { + allocations_[i].total_size += size; + allocations_[i].count++; + return; + } + } + allocations_.push_back({id, size, 1}); + } + + void Print(uptr top_percent) { + InternalSort(&allocations_, allocations_.size(), + [](const AllocationSite &a, const AllocationSite &b) { + return a.total_size > b.total_size; + }); + CHECK(total_allocated_); + uptr total_shown = 0; + Printf("Live Heap Allocations: %zd bytes from %zd allocations; " + "showing top %zd%%\n", total_allocated_, total_count_, top_percent); + for (uptr i = 0; i < allocations_.size(); i++) { + auto &a = allocations_[i]; + Printf("%zd byte(s) (%zd%%) in %zd allocation(s)\n", a.total_size, + a.total_size * 100 / total_allocated_, a.count); + StackDepotGet(a.id).Print(); + total_shown += a.total_size; + if (total_shown * 100 / total_allocated_ > top_percent) + break; + } + } + + private: + uptr total_allocated_ = 0; + uptr total_count_ = 0; + InternalMmapVector<AllocationSite> allocations_; +}; + +static void ChunkCallback(uptr chunk, void *arg) { + HeapProfile *hp = reinterpret_cast<HeapProfile*>(arg); + AsanChunkView cv = FindHeapChunkByAddress(chunk); + if (!cv.IsAllocated()) return; + u32 id = cv.GetAllocStackId(); + if (!id) return; + hp->Insert(id, cv.UsedSize()); +} + +static void MemoryProfileCB(const SuspendedThreadsList &suspended_threads_list, + void *argument) { + HeapProfile hp; + __lsan::ForEachChunk(ChunkCallback, &hp); + hp.Print(reinterpret_cast<uptr>(argument)); +} + +} // namespace __asan + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE +void __sanitizer_print_memory_profile(uptr top_percent) { + __sanitizer::StopTheWorld(__asan::MemoryProfileCB, (void*)top_percent); +} +} // extern "C" + +#endif // SANITIZER_LINUX diff --git a/test/asan/TestCases/Linux/print_memory_profile_test.cc b/test/asan/TestCases/Linux/print_memory_profile_test.cc new file mode 100644 index 000000000..6bf78efc1 --- /dev/null +++ b/test/asan/TestCases/Linux/print_memory_profile_test.cc @@ -0,0 +1,25 @@ +// RUN: %clangxx_asan %s -o %t +// RUN: %t 2>&1 | FileCheck %s +#include <sanitizer/common_interface_defs.h> + +#include <stdio.h> + +char *sink[1000]; + +int main() { + int idx = 0; + for (int i = 0; i < 17; i++) + sink[idx++] = new char[131]; + for (int i = 0; i < 42; i++) + sink[idx++] = new char[24]; + + __sanitizer_print_memory_profile(100); + __sanitizer_print_memory_profile(50); +} + +// CHECK: Live Heap Allocations: {{.*}}; showing top 100% +// CHECK: 2227 byte(s) ({{.*}}%) in 17 allocation(s) +// CHECK: 1008 byte(s) ({{.*}}%) in 42 allocation(s) +// CHECK: Live Heap Allocations: {{.*}}; showing top 50% +// CHECK: 2227 byte(s) ({{.*}}%) in 17 allocation(s) +// CHECK-NOT: 1008 byte |