blob: 573f7e8a9b1bc4d67cf8e03d48875e12d91ac38e [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file implements a memory leak detector. It is vastly inferior to what
// msan, or even asan can do, but do work in Fuchsia today. This must be
// replaced when msan or asan with leak detection when it is available on the
// platform.
//
// The principle is to run an application under ASAN and override new and
// delete. This code will then keep track of unrelease memory and when the
// number of unrelease memory reach a given threasold, find the stack trace that
// has is the origin for the most number of unallocated memory and displaying it
// to the user.
#if __has_feature(address_sanitizer)
#include <sanitizer/asan_interface.h>
#include <array>
#include <cstdint>
#include <map>
#include <mutex>
namespace {
// Maximum number of allocation to keep track.
constexpr size_t kKeepAlloc = 10000;
// Tracks elements to be able to find out which allocation are still valid.
// Constructing and using this container must not allocate memory, as it will be
// used in new and delete overrides.
//
// MaxSize: The maximum number of element in the container.
// A: The type of element in the container.
template <size_t MaxSize = kKeepAlloc, typename A = void *>
struct ElementTracker {
public:
// Inserts the given value in the container, returns false if the container is
// full.
bool Insert(A value) {
std::lock_guard<std::mutex> lg(m_);
if (size_ == MaxSize) {
return false;
}
elements_[size_] = value;
size_++;
return true;
}
// Removes the given value from the container, returns false if the element is
// not present.
bool Remove(A value) {
std::lock_guard<std::mutex> lg(m_);
for (size_t i = 0; i < size_; ++i) {
if (elements_[i] == value) {
elements_[i] = elements_[size_ - 1];
size_--;
return true;
}
}
return false;
}
// Returns the ith element of this container.
A operator[](size_t i) const {
std::lock_guard<std::mutex> lg(m_);
return elements_[i];
}
// Returns the number of elements in the container.
size_t size() const { return size_; };
private:
size_t size_ = 0;
std::array<A, MaxSize> elements_;
mutable std::mutex m_;
};
// Gets the global allocation tracker to use.
ElementTracker<> &GetAllocationTracker() {
static ElementTracker<> *sAllocationSet = [] {
void *memory reinterpret_cast<ElementTracker<> *>(
malloc(sizeof(ElementTracker<>)));
return new (memory) ElementTracker<>();
}();
return *sAllocationSet;
}
// Returns a signature of the stack trace that allocated the memory in |a|.
uintptr_t GetSignature(void *a) {
void *stack[50];
int thread_id;
size_t nb_element = __asan_get_alloc_stack(a, stack, 50, &thread_id);
uintptr_t result = thread_id;
for (size_t i = 0; i < nb_element; ++i) {
result = result ^ reinterpret_cast<uintptr_t>(stack[i]);
}
return result;
}
// Returns whether the memory in |a| is still allocated.
bool IsPointerAlive(void *a) {
void *stack;
int thread_id;
return __asan_get_free_stack(a, &stack, 1, &thread_id) == 0;
}
// Removes all elements in the global allocation tracker that are not allocated
// anymore.
//
// Because of a bug, some allocation are done through new but freed by malloc.
// As this code only overrides new and delete, these allocations seem to be
// leaking while it is not the case. As asan keeps track of free and malloc, it
// is used to remove these spurious addresses.
void SanitizeSet() {
size_t i = 0;
while (i < GetAllocationTracker().size()) {
void *p = GetAllocationTracker()[i];
if (IsPointerAlive(p)) {
++i;
} else {
GetAllocationTracker().Remove(p);
}
}
}
// Wraps all allocations. This keeps track of all allocation. When the
// allocation tracker is full, this finds the stack that has the most allocated
// elements and uses ASAN to show it to the user.
void *WrapAlloc(void *p) {
// |done| is true, once a leak has been found. This will short-circuit this
// method so that allocation can be safely done when showing diagnostic
// information.
static bool done = false;
if (done) {
return p;
}
if (!GetAllocationTracker().Insert(p)) {
// The tracker is full, but might contain adress that haven been created
// with new and released with free (see |SanitizeSet|). Remove all of these
// from the tracker, and check again.
SanitizeSet();
if (!GetAllocationTracker().Insert(p)) {
// The tracker is now full of unreleased memory.
done = true;
// Count for each stack that did allocate memory, how many times it did
// so.
std::map<uintptr_t, size_t> counts;
for (size_t i = 0; i < GetAllocationTracker().size(); ++i) {
counts[GetSignature(GetAllocationTracker()[i])]++;
}
// Find the stack that allocated memory the most time.
size_t max_count = 0;
uintptr_t signature;
for (const auto [s, c] : counts) {
if (c > max_count) {
max_count = c;
signature = s;
}
}
// Find an allocation allocated by the stack that allocated the most
// memory.
for (size_t i = 0; i < GetAllocationTracker().size(); ++i) {
void *p = GetAllocationTracker()[i];
if (GetSignature(p) == signature) {
assert(IsPointerAlive(p));
// Double free the pointer to trigger asan and get information about
// the leak.
free(p);
free(p);
assert(false);
}
}
}
}
return p;
}
// Wraps all deallocations to remove these from the tracker.
void *WrapDealloc(void *p) {
GetAllocationTracker().Remove(p);
return p;
}
} // namespace
#define INTERFACE __attribute__((visibility("default")))
INTERFACE void *operator new(size_t size) { return WrapAlloc(malloc(size)); }
INTERFACE void *operator new[](size_t size) { return WrapAlloc(malloc(size)); }
INTERFACE void *operator new(size_t size, std::nothrow_t const &) noexcept {
return WrapAlloc(malloc(size));
}
INTERFACE void *operator new[](size_t size, std::nothrow_t const &) noexcept {
return WrapAlloc(malloc(size));
}
INTERFACE void *operator new(size_t size, std::align_val_t align) {
return WrapAlloc(malloc(size));
}
INTERFACE void *operator new[](size_t size, std::align_val_t align) {
return WrapAlloc(malloc(size));
}
INTERFACE void *operator new(size_t size, std::align_val_t align,
std::nothrow_t const &) noexcept {
return WrapAlloc(malloc(size));
}
INTERFACE void *operator new[](size_t size, std::align_val_t align,
std::nothrow_t const &) noexcept {
return WrapAlloc(malloc(size));
}
INTERFACE void operator delete(void *ptr) noexcept { free(WrapDealloc(ptr)); }
INTERFACE void operator delete[](void *ptr) noexcept { free(WrapDealloc(ptr)); }
INTERFACE void operator delete(void *ptr, std::nothrow_t const &)noexcept {
free(WrapDealloc(ptr));
}
INTERFACE void operator delete[](void *ptr, std::nothrow_t const &) noexcept {
free(WrapDealloc(ptr));
}
INTERFACE void operator delete(void *ptr, size_t size) noexcept {
free(WrapDealloc(ptr));
}
INTERFACE void operator delete[](void *ptr, size_t size) noexcept {
free(WrapDealloc(ptr));
}
INTERFACE void operator delete(void *ptr, std::align_val_t align) noexcept {
free(WrapDealloc(ptr));
}
INTERFACE void operator delete[](void *ptr, std::align_val_t align) noexcept {
free(WrapDealloc(ptr));
}
INTERFACE void operator delete(void *ptr, std::align_val_t align,
std::nothrow_t const &)noexcept {
free(WrapDealloc(ptr));
}
INTERFACE void operator delete[](void *ptr, std::align_val_t align,
std::nothrow_t const &) noexcept {
free(WrapDealloc(ptr));
}
INTERFACE void operator delete(void *ptr, size_t size,
std::align_val_t align) noexcept {
free(WrapDealloc(ptr));
}
INTERFACE void operator delete[](void *ptr, size_t size,
std::align_val_t align) noexcept {
free(WrapDealloc(ptr));
}
#endif