no message
This commit is contained in:
300
cocos/base/memory/CallStack.cpp
Normal file
300
cocos/base/memory/CallStack.cpp
Normal file
@@ -0,0 +1,300 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "CallStack.h"
|
||||
#if USE_MEMORY_LEAK_DETECTOR
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#define __GNU_SOURCE
|
||||
#include <cxxabi.h>
|
||||
#include <dlfcn.h>
|
||||
#include <pthread.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS
|
||||
#include <execinfo.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <DbgHelp.h>
|
||||
#include <Windows.h>
|
||||
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace cc {
|
||||
|
||||
ccstd::string StackFrame::toString() {
|
||||
static ccstd::string unknown("unknown");
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
std::stringstream stream;
|
||||
stream << "\tmodule: " << (module.empty() ? unknown : module)
|
||||
<< "\tfunction: " << (function.empty() ? unknown : function);
|
||||
|
||||
return stream.str();
|
||||
|
||||
#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS
|
||||
std::stringstream stream;
|
||||
stream << "\tfile: " << (file.empty() ? unknown : file);
|
||||
|
||||
return stream.str();
|
||||
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
std::stringstream stream;
|
||||
stream << "\tmodule: " << (module.empty() ? unknown : module)
|
||||
<< "\tfile: " << (file.empty() ? unknown : file)
|
||||
<< "\tfunction: " << (function.empty() ? unknown : function)
|
||||
<< "\tline: " << line;
|
||||
|
||||
return stream.str();
|
||||
|
||||
#else
|
||||
return unknown;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
|
||||
struct ThreadStack {
|
||||
void *stack[MAX_STACK_FRAMES];
|
||||
int current;
|
||||
int overflow;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static pthread_once_t s_once = PTHREAD_ONCE_INIT;
|
||||
static pthread_key_t s_threadStackKey = 0;
|
||||
|
||||
static void __attribute__((no_instrument_function))
|
||||
init_once(void) {
|
||||
pthread_key_create(&s_threadStackKey, NULL);
|
||||
}
|
||||
|
||||
static ThreadStack *__attribute__((no_instrument_function))
|
||||
getThreadStack() {
|
||||
ThreadStack *ptr = (ThreadStack *)pthread_getspecific(s_threadStackKey);
|
||||
if (ptr) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
ptr = (ThreadStack *)calloc(1, sizeof(ThreadStack));
|
||||
ptr->current = 0;
|
||||
ptr->overflow = 0;
|
||||
pthread_setspecific(s_threadStackKey, ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void __attribute__((no_instrument_function))
|
||||
__cyg_profile_func_enter(void *this_fn, void *call_site) {
|
||||
pthread_once(&s_once, init_once);
|
||||
ThreadStack *ptr = getThreadStack();
|
||||
if (ptr->current < MAX_STACK_FRAMES) {
|
||||
ptr->stack[ptr->current++] = this_fn;
|
||||
ptr->overflow = 0;
|
||||
} else {
|
||||
ptr->overflow++;
|
||||
}
|
||||
}
|
||||
|
||||
void __attribute__((no_instrument_function))
|
||||
__cyg_profile_func_exit(void *this_fn, void *call_site) {
|
||||
pthread_once(&s_once, init_once);
|
||||
ThreadStack *ptr = getThreadStack();
|
||||
|
||||
if (ptr->overflow == 0 && ptr->current > 0) {
|
||||
ptr->current--;
|
||||
}
|
||||
|
||||
if (ptr->overflow > 0) {
|
||||
ptr->overflow--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
ccstd::string CallStack::basename(const ccstd::string &path) {
|
||||
size_t found = path.find_last_of("/\\");
|
||||
|
||||
if (ccstd::string::npos != found) {
|
||||
return path.substr(found + 1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<void *> CallStack::backtrace() {
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
ccstd::vector<void *> callstack;
|
||||
callstack.reserve(MAX_STACK_FRAMES);
|
||||
|
||||
pthread_once(&s_once, init_once);
|
||||
ThreadStack *ptr = getThreadStack();
|
||||
for (int i = ptr->current - 1; i >= 0; i--) {
|
||||
callstack.push_back(ptr->stack[i]);
|
||||
}
|
||||
|
||||
return callstack;
|
||||
|
||||
#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS
|
||||
ccstd::vector<void *> callstack;
|
||||
callstack.reserve(MAX_STACK_FRAMES);
|
||||
|
||||
void *array[MAX_STACK_FRAMES];
|
||||
int count = ::backtrace(array, MAX_STACK_FRAMES);
|
||||
for (auto i = 0; i < count; i++) {
|
||||
callstack.push_back(array[i]);
|
||||
}
|
||||
return callstack;
|
||||
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
ccstd::vector<void *> callstack;
|
||||
callstack.reserve(MAX_STACK_FRAMES);
|
||||
|
||||
void *array[MAX_STACK_FRAMES];
|
||||
int count = CaptureStackBackTrace(0, MAX_STACK_FRAMES, array, NULL);
|
||||
for (auto i = 0; i < count; i++) {
|
||||
callstack.push_back(array[i]);
|
||||
}
|
||||
return callstack;
|
||||
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
ccstd::vector<StackFrame> CallStack::backtraceSymbols(const ccstd::vector<void *> &callstack) {
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
ccstd::vector<StackFrame> frames;
|
||||
size_t size = callstack.size();
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
Dl_info info;
|
||||
StackFrame frame;
|
||||
if (dladdr(callstack[i], &info)) {
|
||||
if (info.dli_fname && strlen(info.dli_fname) > 0) {
|
||||
frame.module = basename(info.dli_fname);
|
||||
}
|
||||
|
||||
if (info.dli_sname && strlen(info.dli_sname) > 0) {
|
||||
char *real_name = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, nullptr);
|
||||
if (real_name) {
|
||||
frame.function = real_name;
|
||||
free(real_name);
|
||||
} else {
|
||||
frame.function = info.dli_sname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frames.push_back(std::move(frame));
|
||||
}
|
||||
return frames;
|
||||
|
||||
#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS
|
||||
size_t size = callstack.size();
|
||||
if (size == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ccstd::vector<StackFrame> frames;
|
||||
char **strs = ::backtrace_symbols(&callstack[0], size);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
StackFrame frame;
|
||||
frame.file = strs[i];
|
||||
frames.push_back(std::move(frame));
|
||||
}
|
||||
|
||||
return frames;
|
||||
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
ccstd::vector<StackFrame> frames;
|
||||
|
||||
#if _WIN64
|
||||
using PTR_DWORD = DWORD64;
|
||||
#else
|
||||
using PTR_DWORD = DWORD;
|
||||
#endif
|
||||
|
||||
size_t size = callstack.size();
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
StackFrame frame;
|
||||
PTR_DWORD address = reinterpret_cast<PTR_DWORD>(callstack[i]);
|
||||
|
||||
char moduelName[MAX_PATH];
|
||||
#if _WIN64
|
||||
PTR_DWORD moduleBase = SymGetModuleBase64(_process, address);
|
||||
#else
|
||||
PTR_DWORD moduleBase = SymGetModuleBase(_process, address);
|
||||
#endif
|
||||
if (moduleBase && GetModuleFileNameA((HINSTANCE)moduleBase, moduelName, MAX_PATH)) {
|
||||
frame.module = basename(moduelName);
|
||||
}
|
||||
|
||||
DWORD64 offset = 0;
|
||||
char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYMBOL_LENGTH] = {0};
|
||||
PSYMBOL_INFO symbol = (PSYMBOL_INFO)symbolBuffer;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = MAX_SYMBOL_LENGTH - 1;
|
||||
|
||||
if (SymFromAddr(_process, address, &offset, symbol)) {
|
||||
frame.function = symbol->Name;
|
||||
}
|
||||
|
||||
IMAGEHLP_LINE line;
|
||||
line.SizeOfStruct = sizeof(IMAGEHLP_LINE);
|
||||
DWORD offset_ln = 0;
|
||||
|
||||
if (SymGetLineFromAddr(_process, address, &offset_ln, &line)) {
|
||||
frame.file = line.FileName;
|
||||
frame.line = line.LineNumber;
|
||||
}
|
||||
|
||||
frames.push_back(std::move(frame));
|
||||
}
|
||||
|
||||
return frames;
|
||||
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
void CallStack::initSym() {
|
||||
_process = GetCurrentProcess();
|
||||
if (SymInitialize(_process, nullptr, true) == false) {
|
||||
CC_ABORT();
|
||||
}
|
||||
SymSetOptions(SYMOPT_LOAD_LINES);
|
||||
}
|
||||
|
||||
void CallStack::cleanupSym() {
|
||||
SymCleanup(_process);
|
||||
}
|
||||
|
||||
HANDLE CallStack::_process = 0;
|
||||
#endif
|
||||
|
||||
} // namespace cc
|
||||
|
||||
#endif
|
||||
76
cocos/base/memory/CallStack.h
Normal file
76
cocos/base/memory/CallStack.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Config.h"
|
||||
#if USE_MEMORY_LEAK_DETECTOR
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include <cstdint>
|
||||
#include "../Macros.h"
|
||||
#include "base/std/container/string.h"
|
||||
#include "base/std/container/vector.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
#define MAX_STACK_FRAMES 64
|
||||
#define MAX_SYMBOL_LENGTH 255
|
||||
|
||||
/**
|
||||
* A single frame of callstack.
|
||||
*/
|
||||
struct CC_DLL StackFrame {
|
||||
ccstd::string module;
|
||||
ccstd::string file;
|
||||
ccstd::string function;
|
||||
uint32_t line{0};
|
||||
|
||||
ccstd::string toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* An utility class used to backtrace callstack.
|
||||
*/
|
||||
class CC_DLL CallStack {
|
||||
public:
|
||||
static ccstd::string basename(const ccstd::string &path);
|
||||
|
||||
static ccstd::vector<void *> backtrace();
|
||||
static ccstd::vector<StackFrame> backtraceSymbols(const ccstd::vector<void *> &callstack);
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
static void initSym();
|
||||
static void cleanupSym();
|
||||
|
||||
private:
|
||||
static HANDLE _process;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
|
||||
#endif
|
||||
131
cocos/base/memory/Memory.h
Normal file
131
cocos/base/memory/Memory.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if (CC_PLATFORM == CC_PLATFORM_IOS)
|
||||
#include <Availability.h>
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <malloc.h>
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#include <new> // std::nothrow
|
||||
|
||||
#include "base/Macros.h"
|
||||
|
||||
namespace cc {
|
||||
class MemoryAllocDealloc final {
|
||||
public:
|
||||
inline static void *allocateBytesAligned(size_t alignment, size_t count) {
|
||||
#ifdef _MSC_VER
|
||||
void *ptr = _aligned_malloc(count, alignment);
|
||||
#else
|
||||
// alignment is not multiple of sizeof(void*)
|
||||
CC_ASSERT_ZERO(alignment % sizeof(void *));
|
||||
void *ptr = nullptr;
|
||||
posix_memalign(&ptr, alignment, count);
|
||||
#endif
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline static void deallocateBytesAligned(void *ptr) {
|
||||
#ifdef _MSC_VER
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
free(ptr);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
} // namespace cc
|
||||
|
||||
#define ccnew new (std::nothrow) //NOLINT(readability-identifier-naming)
|
||||
#define ccnew_placement(...) new (__VA_ARGS__) //NOLINT(readability-identifier-naming)
|
||||
|
||||
#define CC_SAFE_DELETE(ptr) \
|
||||
if (ptr) { \
|
||||
delete ptr; \
|
||||
(ptr) = nullptr; \
|
||||
}
|
||||
|
||||
#define CC_SAFE_DELETE_ARRAY(ptr) \
|
||||
if (ptr) { \
|
||||
delete[] ptr; \
|
||||
(ptr) = nullptr; \
|
||||
}
|
||||
|
||||
#define CC_MALLOC(bytes) malloc(bytes)
|
||||
#define CC_MALLOC_ALIGN(bytes, align) ::cc::MemoryAllocDealloc::allocateBytesAligned(align, bytes)
|
||||
#define CC_REALLOC(ptr, bytes) realloc(ptr, bytes)
|
||||
#define CC_FREE(ptr) free((void *)ptr)
|
||||
#define CC_FREE_ALIGN(ptr) ::cc::MemoryAllocDealloc::deallocateBytesAligned(ptr)
|
||||
|
||||
#define CC_SAFE_FREE(ptr) \
|
||||
if (ptr) { \
|
||||
CC_FREE(ptr); \
|
||||
(ptr) = nullptr; \
|
||||
}
|
||||
|
||||
#define CC_SAFE_DESTROY(ptr) \
|
||||
if (ptr) { \
|
||||
(ptr)->destroy(); \
|
||||
}
|
||||
|
||||
#define CC_SAFE_DESTROY_AND_DELETE(ptr) \
|
||||
if (ptr) { \
|
||||
(ptr)->destroy(); \
|
||||
delete ptr; \
|
||||
(ptr) = nullptr; \
|
||||
}
|
||||
|
||||
#define CC_SAFE_DESTROY_NULL(ptr) \
|
||||
if (ptr) { \
|
||||
(ptr)->destroy(); \
|
||||
(ptr) = nullptr; \
|
||||
}
|
||||
|
||||
#define CC_SAFE_RELEASE(p) \
|
||||
if (p) { \
|
||||
(p)->release(); \
|
||||
}
|
||||
|
||||
#define CC_SAFE_RELEASE_NULL(p) \
|
||||
if (p) { \
|
||||
(p)->release(); \
|
||||
(p) = nullptr; \
|
||||
}
|
||||
|
||||
#define CC_SAFE_ADD_REF(p) \
|
||||
if (p) { \
|
||||
(p)->addRef(); \
|
||||
}
|
||||
|
||||
#if ((CC_PLATFORM == CC_PLATFORM_IOS) && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0)) || SWIGCOCOS
|
||||
#define ALIGNAS(x)
|
||||
#else
|
||||
#define ALIGNAS(x) alignas(x)
|
||||
#endif
|
||||
338
cocos/base/memory/MemoryHook.cpp
Normal file
338
cocos/base/memory/MemoryHook.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "MemoryHook.h"
|
||||
#include "CallStack.h"
|
||||
#if USE_MEMORY_LEAK_DETECTOR
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#define __GNU_SOURCE
|
||||
#include <android/log.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
static NewHookType g_new_hooker = nullptr;
|
||||
static DeleteHookType g_delete_hooker = nullptr;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void *malloc(size_t size) __attribute__((weak));
|
||||
void free(void *ptr) __attribute__((weak));
|
||||
|
||||
// Use strong symbol to overwrite the weak one.
|
||||
void *malloc(size_t size) {
|
||||
static MallocType system_malloc = nullptr;
|
||||
if (CC_PREDICT_FALSE(system_malloc == nullptr)) {
|
||||
system_malloc = (MallocType)dlsym(RTLD_NEXT, "malloc");
|
||||
}
|
||||
|
||||
void *ptr = system_malloc(size);
|
||||
if (CC_PREDICT_TRUE(g_new_hooker != nullptr)) {
|
||||
g_new_hooker(ptr, size);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void free(void *ptr) {
|
||||
static FreeType system_free = nullptr;
|
||||
if (CC_PREDICT_FALSE(system_free == nullptr)) {
|
||||
system_free = (FreeType)dlsym(RTLD_NEXT, "free");
|
||||
}
|
||||
|
||||
system_free(ptr);
|
||||
if (CC_PREDICT_TRUE(g_delete_hooker != nullptr)) {
|
||||
g_delete_hooker(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS
|
||||
typedef void malloc_logger_t(uint32_t aType,
|
||||
uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3,
|
||||
uintptr_t aResult, uint32_t aNumHotFramesToSkip);
|
||||
|
||||
extern malloc_logger_t *malloc_logger;
|
||||
static malloc_logger_t *g_system_malloc_logger = nullptr;
|
||||
static NewHookType g_new_hooker = nullptr;
|
||||
static DeleteHookType g_delete_hooker = nullptr;
|
||||
|
||||
static void
|
||||
cc_malloc_logger(uint32_t aType,
|
||||
uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3,
|
||||
uintptr_t aResult, uint32_t aNumHotFramesToSkip) {
|
||||
if (aResult != 0) {
|
||||
size_t new_size = reinterpret_cast<size_t>(aArg3);
|
||||
if (new_size != 0) {
|
||||
// realloc
|
||||
if (CC_PREDICT_TRUE(g_delete_hooker != nullptr)) {
|
||||
const void *ptr = reinterpret_cast<const void *>(aArg2);
|
||||
g_delete_hooker(ptr);
|
||||
}
|
||||
|
||||
if (CC_PREDICT_TRUE(g_new_hooker != nullptr)) {
|
||||
const void *new_ptr = reinterpret_cast<const void *>(aResult);
|
||||
g_new_hooker(new_ptr, new_size);
|
||||
}
|
||||
} else {
|
||||
// malloc/calloc/valloc
|
||||
if (CC_PREDICT_TRUE(g_new_hooker != nullptr)) {
|
||||
const void *ptr = reinterpret_cast<const void *>(aResult);
|
||||
size_t size = reinterpret_cast<size_t>(aArg2);
|
||||
g_new_hooker(ptr, size);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// free
|
||||
if (CC_PREDICT_TRUE(g_delete_hooker != nullptr)) {
|
||||
const void *ptr = reinterpret_cast<const void *>(aArg2);
|
||||
g_delete_hooker(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <Windows.h>
|
||||
|
||||
extern "C" {
|
||||
typedef void (*MallocHook_NewHook)(const void *ptr, size_t size);
|
||||
typedef void (*MallocHook_DeleteHook)(const void *ptr);
|
||||
|
||||
int MallocHook_AddNewHook(MallocHook_NewHook hook);
|
||||
int MallocHook_RemoveNewHook(MallocHook_NewHook hook);
|
||||
int MallocHook_AddDeleteHook(MallocHook_DeleteHook hook);
|
||||
int MallocHook_RemoveDeleteHook(MallocHook_DeleteHook hook);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace cc {
|
||||
|
||||
static void newHook(const void *ptr, size_t size) {
|
||||
uint64_t address = reinterpret_cast<uint64_t>(ptr);
|
||||
GMemoryHook.addRecord(address, size);
|
||||
}
|
||||
|
||||
static void deleteHook(const void *ptr) {
|
||||
uint64_t address = reinterpret_cast<uint64_t>(ptr);
|
||||
GMemoryHook.removeRecord(address);
|
||||
}
|
||||
|
||||
MemoryHook::MemoryHook() {
|
||||
registerAll();
|
||||
}
|
||||
|
||||
MemoryHook::~MemoryHook() {
|
||||
unRegisterAll();
|
||||
dumpMemoryLeak();
|
||||
}
|
||||
|
||||
void MemoryHook::addRecord(uint64_t address, size_t size) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
||||
if (_hooking) {
|
||||
return;
|
||||
}
|
||||
|
||||
_hooking = true;
|
||||
|
||||
// {} is necessary here to make variables being destroyed before _hooking = false
|
||||
{
|
||||
MemoryRecord record;
|
||||
record.address = address;
|
||||
record.size = size;
|
||||
record.callstack = CallStack::backtrace();
|
||||
_records.insert({address, record});
|
||||
_totalSize += size;
|
||||
}
|
||||
|
||||
_hooking = false;
|
||||
}
|
||||
|
||||
void MemoryHook::removeRecord(uint64_t address) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
||||
if (_hooking) {
|
||||
return;
|
||||
}
|
||||
|
||||
_hooking = true;
|
||||
|
||||
// {} is necessary here to make variables being destroyed before _hooking = false
|
||||
{
|
||||
auto iter = _records.find(address);
|
||||
if (iter != _records.end()) {
|
||||
_totalSize -= iter->second.size;
|
||||
_records.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
_hooking = false;
|
||||
}
|
||||
|
||||
static bool isIgnored(const StackFrame &frame) {
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
static const ccstd::vector<ccstd::string> ignoreModules = {
|
||||
"SDL2",
|
||||
"EGL",
|
||||
"GLESv2",
|
||||
"opengl32",
|
||||
"nvoglv64",
|
||||
"sqlite3",
|
||||
"libuv",
|
||||
"SogouPy"};
|
||||
|
||||
static const ccstd::vector<ccstd::string> ignoreFunctions = {
|
||||
"type_info::name"};
|
||||
|
||||
for (auto &module : ignoreModules) {
|
||||
if (frame.module.find(module) != ccstd::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &function : ignoreFunctions) {
|
||||
if (frame.function.find(function) != ccstd::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void MemoryHook::dumpMemoryLeak() {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
CallStack::initSym();
|
||||
#endif
|
||||
|
||||
std::stringstream startStream;
|
||||
startStream << std::endl;
|
||||
startStream << "---------------------------------------------------------------------------------------------------------" << std::endl;
|
||||
startStream << "--------------------------------------memory leak report start-------------------------------------------" << std::endl;
|
||||
startStream << "---------------------------------------------------------------------------------------------------------" << std::endl;
|
||||
log(startStream.str());
|
||||
|
||||
if (_records.size() == 0) {
|
||||
std::stringstream stream;
|
||||
stream << std::endl;
|
||||
stream << "Congratulations! There is no memory leak at all." << std::endl;
|
||||
log(stream.str());
|
||||
}
|
||||
|
||||
uint32_t i = 0;
|
||||
size_t skipSize = 0;
|
||||
uint32_t skipCount = 0;
|
||||
|
||||
for (const auto &iter : _records) {
|
||||
bool skip = false;
|
||||
auto frames = CallStack::backtraceSymbols(iter.second.callstack);
|
||||
for (auto &frame : frames) {
|
||||
if (isIgnored(frame)) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
skipSize += iter.second.size;
|
||||
skipCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::stringstream stream;
|
||||
int k = 0;
|
||||
|
||||
stream << std::endl;
|
||||
stream << "<" << ++i << ">:"
|
||||
<< "leak " << iter.second.size << " bytes at 0x" << std::hex << iter.second.address << std::dec << std::endl;
|
||||
stream << "\tcallstack:" << std::endl;
|
||||
|
||||
for (auto &frame : frames) {
|
||||
stream << "\t[" << ++k << "]:" << frame.toString() << std::endl;
|
||||
}
|
||||
|
||||
log(stream.str());
|
||||
}
|
||||
|
||||
std::stringstream endStream;
|
||||
endStream << std::endl
|
||||
<< "Total leak count: " << _records.size() << " with " << _totalSize << " bytes, "
|
||||
<< "Total skip count: " << skipCount << " with " << skipSize << " bytes" << std::endl;
|
||||
|
||||
endStream << "---------------------------------------------------------------------------------------------------------" << std::endl;
|
||||
endStream << "--------------------------------------memory leak report end---------------------------------------------" << std::endl;
|
||||
endStream << "---------------------------------------------------------------------------------------------------------" << std::endl;
|
||||
log(endStream.str());
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
CallStack::cleanupSym();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MemoryHook::log(const ccstd::string &msg) {
|
||||
#if (CC_PLATFORM == CC_PLATFORM_ANDROID)
|
||||
__android_log_write(ANDROID_LOG_WARN, "Cocos", msg.c_str());
|
||||
#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS
|
||||
fputs(msg.c_str(), stdout);
|
||||
#elif (CC_PLATFORM == CC_PLATFORM_WINDOWS)
|
||||
OutputDebugStringA(msg.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void MemoryHook::registerAll() {
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
g_new_hooker = newHook;
|
||||
g_delete_hooker = deleteHook;
|
||||
free(malloc(1)); // force to init system_malloc/system_free
|
||||
#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS
|
||||
g_system_malloc_logger = malloc_logger;
|
||||
malloc_logger = cc_malloc_logger;
|
||||
g_new_hooker = newHook;
|
||||
g_delete_hooker = deleteHook;
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
MallocHook_AddNewHook(&newHook);
|
||||
MallocHook_AddDeleteHook(&deleteHook);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MemoryHook::unRegisterAll() {
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
g_new_hooker = nullptr;
|
||||
g_delete_hooker = nullptr;
|
||||
#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS
|
||||
malloc_logger = g_system_malloc_logger;
|
||||
g_new_hooker = nullptr;
|
||||
g_delete_hooker = nullptr;
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
MallocHook_RemoveNewHook(&newHook);
|
||||
MallocHook_RemoveDeleteHook(&deleteHook);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
|
||||
#endif
|
||||
93
cocos/base/memory/MemoryHook.h
Normal file
93
cocos/base/memory/MemoryHook.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Config.h"
|
||||
#if USE_MEMORY_LEAK_DETECTOR
|
||||
|
||||
#include <mutex>
|
||||
#include "../Macros.h"
|
||||
#include "base/std/container/string.h"
|
||||
#include "base/std/container/unordered_map.h"
|
||||
#include "base/std/container/vector.h"
|
||||
|
||||
typedef void *(*MallocType)(size_t size);
|
||||
typedef void (*FreeType)(void *ptr);
|
||||
|
||||
typedef void (*NewHookType)(const void *ptr, size_t size);
|
||||
typedef void (*DeleteHookType)(const void *ptr);
|
||||
|
||||
namespace cc {
|
||||
|
||||
struct CC_DLL MemoryRecord {
|
||||
uint64_t address{0};
|
||||
size_t size{0};
|
||||
ccstd::vector<void *> callstack;
|
||||
};
|
||||
|
||||
class CC_DLL MemoryHook {
|
||||
public:
|
||||
MemoryHook();
|
||||
~MemoryHook();
|
||||
|
||||
/**
|
||||
* RecordMap's key is memory address.
|
||||
*/
|
||||
using RecordMap = ccstd::unordered_map<uint64_t, MemoryRecord>;
|
||||
|
||||
void addRecord(uint64_t address, size_t size);
|
||||
void removeRecord(uint64_t address);
|
||||
inline size_t getTotalSize() const { return _totalSize; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* Dump all memory leaks to output window
|
||||
*/
|
||||
void dumpMemoryLeak();
|
||||
|
||||
static void log(const ccstd::string &msg);
|
||||
|
||||
/**
|
||||
* Register all malloc hooks
|
||||
*/
|
||||
void registerAll();
|
||||
|
||||
/**
|
||||
* Unregister all malloc hooks
|
||||
*/
|
||||
void unRegisterAll();
|
||||
|
||||
private:
|
||||
std::recursive_mutex _mutex;
|
||||
bool _hooking{false};
|
||||
RecordMap _records;
|
||||
size_t _totalSize{0U};
|
||||
};
|
||||
|
||||
extern MemoryHook GMemoryHook;
|
||||
|
||||
} // namespace cc
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user