blob: e64e488534a545a983e9cc4ff787740b157e0632 [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// Custom memory allocators
#include <cstdio>
#include <cstdlib>
#include <unordered_map>
// A regular ThreadLock cannot be used as its ctor would call WP2Malloc(),
// which uses this 'thread_lock', which calls WP2Malloc() in its ctor etc.
// std::mutex might be forbidden in the library, but here it's for debugging.
#include <mutex> // NOLINT
#if defined(WP2_HAVE_BOOST)
#include <boost/stacktrace.hpp>
#include <sstream>
#elif defined(WP2_HAVE_BACKTRACE)
#include <execinfo.h>
#endif
#include "./example_utils.h"
static std::unordered_map<void*, size_t> all_blocks;
static size_t mem_allocated = 0;
static size_t total_allocation = 0;
static size_t high_water_mark = 0;
static int num_malloc_calls = 0;
static int num_free_calls = 0;
static std::mutex mutex;
namespace WP2 {
// Get the stack trace.
static std::string GetTrace() {
#if defined(WP2_HAVE_BOOST)
std::stringstream ss;
ss << boost::stacktrace::stacktrace();
return ss.str();
#elif defined(WP2_HAVE_BACKTRACE)
// glibc solution with mangled names.
static constexpr uint32_t kMaxSize = 1024;
void* array[kMaxSize];
const size_t size = backtrace(array, kMaxSize);
char** strings = backtrace_symbols(array, size);
assert(strings != NULL);
std::string trace;
for (size_t i = 0; i < size; i++) {
trace += strings[i];
trace += "\n";
}
free(strings);
return trace;
#else
return "";
#endif
}
bool WP2MemoryFailNow(size_t size) {
fprintf(stderr, "=== FAILING to allocate %u more bytes NOW ===\n",
(uint32_t)size);
return false;
}
MemoryTrace::~MemoryTrace() {
if (print_info || show_traffic || memory_limit > 0 || countdown_to_fail > 0 ||
mem_allocated > 0 || top_stacktrace_num > 0) {
std::lock_guard<std::mutex> lock(mutex);
fprintf(stderr, "\n=== MEMORY INFO ===\n");
fprintf(stderr, "num calls to: malloc = %4d\n", num_malloc_calls);
fprintf(stderr, " free = %4d\n", num_free_calls);
fprintf(stderr, "final memory: %u%s\n",
(uint32_t)mem_allocated,
(mem_allocated == 0) ? "" : " !!! ERROR");
fprintf(stderr, "total allocation: %u\n", (uint32_t)total_allocation);
fprintf(stderr, "high-water mark: %u\n", (uint32_t)high_water_mark);
if (show_traffic && mem_allocated > 0) {
fprintf(stderr, "Still allocated:\n");
for (auto it : all_blocks) {
fprintf(stderr, "ptr = %p size = %7u\n",
it.first, (uint32_t)it.second);
}
}
all_blocks.clear();
if (top_stacktrace_num > 0) {
// Compute the most frequent stack traces.
typedef std::pair<std::string, uint32_t> Pair;
std::vector<Pair> counts;
for (const auto& iter : counts_per_trace) counts.push_back(iter);
std::sort(
counts.begin(), counts.end(),
[](const Pair& p1, const Pair& p2) { return p1.second > p2.second; });
fprintf(stderr, "\n");
for (uint32_t i = 0;
i < std::min(top_stacktrace_num, (uint32_t)counts.size()); ++i) {
fprintf(stderr,
"Most called %d/%d: %d %s (might be a duplicate)\n\n", i + 1,
top_stacktrace_num, counts[i].second,
top_alloc_size ? "bytes" : "calls");
fprintf(stderr, "%s\n\n", counts[i].first.c_str());
}
}
}
}
bool MemoryTrace::Register(void* ptr, size_t size) {
if (ptr == nullptr) return true;
std::lock_guard<std::mutex> lock(mutex);
// don't call Unregister() when failing here! This would dead-lock...
if (countdown_to_fail >= 1) {
if (countdown_to_fail == 1) {
fprintf(stderr, "WARNING! We already FAILED! Do you have any delayed "
"malloc checks (in constructors e.g.) ??\n");
return false;
}
if (--countdown_to_fail == 1) {
return WP2MemoryFailNow(size);
}
}
if (memory_limit > 0 && mem_allocated + size > memory_limit) {
if (show_traffic) {
fprintf(stderr, "FAILING to allocate %u bytes over the %u limit\n",
(uint32_t)size, (uint32_t)memory_limit);
}
return false;
}
++num_malloc_calls;
all_blocks[ptr] = size;
mem_allocated += size;
total_allocation += size;
if (mem_allocated > high_water_mark) high_water_mark = mem_allocated;
if (top_stacktrace_num > 0) {
counts_per_trace[GetTrace()] += top_alloc_size ? size : 1;
}
if (show_traffic) {
if (countdown_to_fail > 0) {
fprintf(stderr, "fail-count: %5d [mem=%5u \t(+%7u) ptr=%p]\n",
countdown_to_fail, (uint32_t)mem_allocated, (uint32_t)size, ptr);
} else {
fprintf(stderr, "Alloc #%5u: %u (+%u)\n",
(uint32_t)num_malloc_calls, (uint32_t)mem_allocated,
(uint32_t)size);
}
}
return true;
}
void MemoryTrace::Unregister(void* ptr) {
if (ptr == nullptr) return;
std::lock_guard<std::mutex> lock(mutex);
++num_free_calls;
if (all_blocks.find(ptr) == all_blocks.end()) {
fprintf(stderr, "Invalid pointer free! (%p)\n", ptr);
abort(); // Critical error. Just can't happen.
}
const size_t size = all_blocks[ptr];
all_blocks.erase(ptr);
mem_allocated -= size;
if (show_traffic) {
fprintf(stderr, "Free %u (-%u)\n", (uint32_t)mem_allocated,
(uint32_t)size);
}
}
MemoryTrace kMemoryTrace;
//------------------------------------------------------------------------------
void ProgramOptions::AddMemoryOptionSection() {
#if !defined(NDEBUG)
// these options are always available, but are pretty arcane. We don't want to
// confuse the regular user with these. Hence the !NDEBUG in the help message.
Add("Memory options:");
Add("-mem_info", "report global memory usage information");
Add("-mem_limit <int>", "sets the maximum allowed total memory usage");
Add("-mem_fail <int>", "sets a maximum number of allocations");
Add("-mem_traffic", "report all malloc/free traffic (verbose!)");
// these two tracing modes are mutually exclusive:
Add("-mem_top <int>", "report top malloc/free stack traces");
Add("-mem_top_alloc <int>", "report top allocation stack traces");
#endif
}
bool ProgramOptions::ParseMemoryOptions(char* arg[], int argc, int& skip) {
bool error = false;
if (!strcmp(arg[0], "-mem_traffic")) {
skip = 1;
kMemoryTrace.show_traffic = true;
} else if (!strcmp(arg[0], "-mem_info")) {
kMemoryTrace.print_info = true;
skip = 1;
} else if (!strcmp(arg[0], "-mem_limit")) {
if (argc < 2) return false; // not enough args
kMemoryTrace.memory_limit = ExUtilGetUInt(arg[1], &error);
if (error) return false;
skip = 2;
} else if (!strcmp(arg[0], "-mem_fail")) {
if (argc < 2) return false; // not enough args
kMemoryTrace.countdown_to_fail = 1 + ExUtilGetUInt(arg[1], &error);
if (error) return false;
skip = 2;
#if !defined(_MSC_VER)
} else if (!strcmp(arg[0], "-mem_top") || !strcmp(arg[0], "-mem_top_alloc")) {
if (argc < 2) return false; // not enough args
kMemoryTrace.top_alloc_size = !strcmp(arg[0], "-mem_top_alloc");
kMemoryTrace.top_stacktrace_num = ExUtilGetUInt(arg[1], &error);
if (error) return false;
skip = 2;
#endif
} else {
return false;
}
WP2SetMemoryHook(&kMemoryTrace);
return true;
}
} // namespace WP2