版本基于:Android R
关联博文:
最近上项目中遇到一个native 可能内存泄漏的问题,曾考虑使用HWASAN,但这个工具是针对整个系统,运行代价还是很高的。而笔者遇到的问题大致有所方向,能指定到某一个进程,针对单个进程是否有检测的功能呢?答案是肯定的,也就是本文需要分析的 malloc_debug。
malloc_debug 是调试native 内存问题的一个工具,能够帮助我们检测内存损坏、内存泄漏、释放再使用等问题。详细的细节可以查看:bionic/libc/malloc_debug/README.md 文件,该文件主要总结Android N 及之后版本中的使用方法,而Android N 之前的malloc_debug 使用方法可以查看README_marshmallow_and_earlier.md 这个文件。
在分析 malloc_debug 初始化之前,需要记住 malloc_debug 在libc 中的几个重要常量:
bionic/libc/bionic/malloc_common_dynamic.cpp
static constexpr char kDebugSharedLib[] = "libc_malloc_debug.so";
static constexpr char kDebugPrefix[] = "debug";
static constexpr char kDebugPropertyOptions[] = "libc.debug.malloc.options";
static constexpr char kDebugPropertyProgram[] = "libc.debug.malloc.program";
static constexpr char kDebugEnvOptions[] = "LIBC_DEBUG_MALLOC_OPTIONS";
这里,指定了动态加载malloc_debug 的so 名称,也指定了 malloc_debug 配置的prop 名称和环境变量名称。
在程序加载 libc.so 的时候会调用 __libc_preinit():
bionic/libc/bionic/libc_init_dynamic.cpp
// We flag the __libc_preinit function as a constructor to ensure that
// its address is listed in libc.so's .init_array section.
// This ensures that the function is called by the dynamic linker as
// soon as the shared library is loaded.
// We give this constructor priority 1 because we want libc's constructor
// to run before any others (such as the jemalloc constructor), and lower
// is better (http://b/68046352).
__attribute__((constructor(1))) static void __libc_preinit() {
// The linker has initialized its copy of the global stack_chk_guard, and filled in the main
// thread's TLS slot with that value. Initialize the local global stack guard with its value.
__stack_chk_guard = reinterpret_cast<uintptr_t>(__get_tls()[TLS_SLOT_STACK_GUARD]);
__libc_preinit_impl();
}
__libc_preinit() 在main 函数执行前执行,因为它有 __attribute__((constructor(1))),通过这个constructor 对此程序所连接的 libc.so 进行 preinit。
该函数的核心处理函数是 __libc_preinit_impl():
bionic/libc/bionic/libc_init_dynamic.cpp
// We need a helper function for __libc_preinit because compiling with LTO may
// inline functions requiring a stack protector check, but __stack_chk_guard is
// not initialized at the start of __libc_preinit. __libc_preinit_impl will run
// after __stack_chk_guard is initialized and therefore can safely have a stack
// protector.
__attribute__((noinline))
static void __libc_preinit_impl() {
#if defined(__i386__)
__libc_init_sysinfo();
#endif
// Register libc.so's copy of the TLS generation variable so the linker can
// update it when it loads or unloads a shared object.
TlsModules& tls_modules = __libc_shared_globals()->tls_modules;
tls_modules.generation_libc_so = &__libc_tls_generation_copy;
__libc_tls_generation_copy = tls_modules.generation;
__libc_init_globals();
__libc_init_common();
// Hooks for various libraries to let them know that we're starting up.
__libc_globals.mutate(__libc_init_malloc);
// Install reserved signal handlers for assisting the platform's profilers.
__libc_init_profiling_handlers();
__libc_init_fork_handler();
#if __has_feature(hwaddress_sanitizer)
// Notify the HWASan runtime library whenever a library is loaded or unloaded
// so that it can update its shadow memory.
__libc_shared_globals()->load_hook = __hwasan_library_loaded;
__libc_shared_globals()->unload_hook = __hwasan_library_unloaded;
#endif
netdClientInit();
}
这里需要注意三点:
bionic/libc/private/bionic_globals.h
__LIBC_HIDDEN__ extern WriteProtected<libc_globals> __libc_globals;
bioni/libc/bionic/malloc_common_dynamic.cpp
// Initializes memory allocation framework.
// This routine is called from __libc_init routines in libc_init_dynamic.cpp.
__BIONIC_WEAK_FOR_NATIVE_BRIDGE
__LIBC_HIDDEN__ void __libc_init_malloc(libc_globals* globals) {
MallocInitImpl(globals);
}
参数 globals 指向的是 __libc_globals 对象的 contents 里的 value,即 libc_globals 对象。这里顺便来看下 libc_globals:
bionic/libc/private/bionic_globals.h
struct libc_globals {
vdso_entry vdso[VDSO_END];
long setjmp_cookie;
uintptr_t heap_pointer_tag;
_Atomic(const MallocDispatch*) current_dispatch_table;
_Atomic(const MallocDispatch*) default_dispatch_table;
MallocDispatch malloc_dispatch_table;
};
malloc_dispatch_table 就是后来用来存放,libc_malloc_debug.so 中的函数 symbol,详细看第 2.4.1 节。
回到函数,该函数最终调用的 MallocInitImpl(),该函数详细看第 2.3 节。
bionic/libc/bionic/malloc_common_dynamic.cpp
static void MallocInitImpl(libc_globals* globals) {
char prop[PROP_VALUE_MAX];
char* options = prop;
MaybeInitGwpAsanFromLibc(globals);
// Prefer malloc debug since it existed first and is a more complete
// malloc interceptor than the hooks.
bool hook_installed = false;
if (CheckLoadMallocDebug(&options)) {
hook_installed = InstallHooks(globals, options, kDebugPrefix, kDebugSharedLib);
} else if (CheckLoadMallocHooks(&options)) {
hook_installed = InstallHooks(globals, options, kHooksPrefix, kHooksSharedLib);
}
if (!hook_installed) {
if (HeapprofdShouldLoad()) {
HeapprofdInstallHooksAtInit(globals);
}
} else {
// Record the fact that incompatible hooks are active, to skip any later
// heapprofd signal handler invocations.
HeapprofdRememberHookConflict();
}
}
针对 malloc_debug() 会通过 CheckLoadMallocDebug() 来确认该工具是否 enabled:
bionic/libc/bionic/malloc_common_dynamic.cpp
static bool CheckLoadMallocDebug(char** options) {
// If kDebugMallocEnvOptions is set then it overrides the system properties.
char* env = getenv(kDebugEnvOptions);
if (env == nullptr || env[0] == '\0') {
if (__system_property_get(kDebugPropertyOptions, *options) == 0 || *options[0] == '\0') {
return false;
}
// Check to see if only a specific program should have debug malloc enabled.
char program[PROP_VALUE_MAX];
if (__system_property_get(kDebugPropertyProgram, program) != 0 &&
strstr(getprogname(), program) == nullptr) {
return false;
}
} else {
*options = env;
}
return true;
}
通过该函数主要有两种方式使能 malloc_debug:
回到 MallocInitImpl() 函数,当确认 malloc_debug 使能时,会调用 InstallHooks() 进行动态加载 libc_malloc_debug.so,该函数的参数除了传入 globals 和 options,还指定了库的使用前缀和库名,这里分别是“debug” 和 libc_malloc_debug.so。函数 InstallHooks() 详细看 2.4 节。
bionic/libc/bionic/malloc_common_dynamic.cpp
static bool InstallHooks(libc_globals* globals, const char* options, const char* prefix,
const char* shared_lib) {
void* impl_handle = LoadSharedLibrary(shared_lib, prefix, &globals->malloc_dispatch_table);
if (impl_handle == nullptr) {
return false;
}
if (!FinishInstallHooks(globals, options, prefix)) {
dlclose(impl_handle);
return false;
}
return true;
}
这里主要做了两件事情:
在 LoadSharedLibrary() 中通过 dlopen() 动态加载 libc_malloc_debug.so,接着通过 InitSharedLibrary() 函数查找如下names 数组中的 symbol,将查找到的 symbol 保存在全局数组变量 gfunctions 中。注意查找的函数都会加上 debug_ 前缀。
bionic/libc/bionic/malloc_common_dynamic.cpp
bool InitSharedLibrary(void* impl_handle, const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {
static constexpr const char* names[] = {
"initialize",
"finalize",
"get_malloc_leak_info",
"free_malloc_leak_info",
"malloc_backtrace",
"write_malloc_leak_info",
};
for (size_t i = 0; i < FUNC_LAST; i++) {
char symbol[128];
snprintf(symbol, sizeof(symbol), "%s_%s", prefix, names[i]);
gFunctions[i] = dlsym(impl_handle, symbol);
if (gFunctions[i] == nullptr) {
error_log("%s: %s routine not found in %s", getprogname(), symbol, shared_lib);
ClearGlobalFunctions();
return false;
}
}
if (!InitMallocFunctions(impl_handle, dispatch_table, prefix)) {
ClearGlobalFunctions();
return false;
}
return true;
}
然后通过 InitMallocFuntions() 在这个so里继续查找 malloc_debug 其他系列函数:
bionic/libc/malloc_debug/malloc_debug.cpp
debug_free()
debug_calloc()
debug_mallinfo()
debug_malloc()
debug_realloc()
...
并将查找到的 symbol 都存放到 MallocDispatch 对应的函数指针,这个 MallocDispatch 就是最开始 LoadSharedLibrary() 的第三个参数,也就是 globals->malloc_dispatch_table:
bionic/libc/private/bionic_malloc_dispatch.h
struct MallocDispatch {
MallocCalloc calloc;
MallocFree free;
MallocMallinfo mallinfo;
MallocMalloc malloc;
MallocMallocUsableSize malloc_usable_size;
MallocMemalign memalign;
MallocPosixMemalign posix_memalign;
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
MallocPvalloc pvalloc;
#endif
MallocRealloc realloc;
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
MallocValloc valloc;
#endif
MallocIterate malloc_iterate;
MallocMallocDisable malloc_disable;
MallocMallocEnable malloc_enable;
MallocMallopt mallopt;
MallocAlignedAlloc aligned_alloc;
MallocMallocInfo malloc_info;
} __attribute__((aligned(32)));
指定这些函数的目的是什么呢?
详细可以查看第 3 节的malloc() 函数调用。
bionic/libc/bionic/malloc_common_dynamic.cpp
bool FinishInstallHooks(libc_globals* globals, const char* options, const char* prefix) {
init_func_t init_func = reinterpret_cast<init_func_t>(gFunctions[FUNC_INITIALIZE]);
// If GWP-ASan was initialised, we should use it as the dispatch table for
// heapprofd/malloc_debug/malloc_debug.
const MallocDispatch* prev_dispatch = GetDefaultDispatchTable();
if (prev_dispatch == nullptr) {
prev_dispatch = NativeAllocatorDispatch();
}
if (!init_func(prev_dispatch, &gZygoteChild, options)) {
error_log("%s: failed to enable malloc %s", getprogname(), prefix);
ClearGlobalFunctions();
return false;
}
// Do a pointer swap so that all of the functions become valid at once to
// avoid any initialization order problems.
atomic_store(&globals->default_dispatch_table, &globals->malloc_dispatch_table);
if (!MallocLimitInstalled()) {
atomic_store(&globals->current_dispatch_table, &globals->malloc_dispatch_table);
}
// Use atexit to trigger the cleanup function. This avoids a problem
// where another atexit function is used to cleanup allocated memory,
// but the finalize function was already called. This particular error
// seems to be triggered by a zygote spawned process calling exit.
int ret_value = __cxa_atexit(MallocFiniImpl, nullptr, nullptr);
if (ret_value != 0) {
// We don't consider this a fatal error.
warning_log("failed to set atexit cleanup function: %d", ret_value);
}
return true;
}
该函数大致做了三件事情:
bionic/libc/bionic/malloc_common_dynamic.cpp
static void MallocFiniImpl(void*) {
// Our BSD stdio implementation doesn't close the standard streams,
// it only flushes them. Other unclosed FILE*s will show up as
// malloc leaks, but to avoid the standard streams showing up in
// leak reports, close them here.
fclose(stdin);
fclose(stdout);
fclose(stderr);
reinterpret_cast<finalize_func_t>(gFunctions[FUNC_FINALIZE])();
}
其实最终调用的是 malloc_debug 中的debug_finalize() 进行检查,例如是否有内存泄漏,如果有,会将 callstack 打印出来:
bionic/libc/malloc_debug/malloc_debug.cpp
void debug_finalize() {
if (g_debug == nullptr) {
return;
}
// Make sure that there are no other threads doing debug allocations
// before we kill everything.
ScopedConcurrentLock::BlockAllOperations();
// Turn off capturing allocations calls.
DebugDisableSet(true);
if (g_debug->config().options() & FREE_TRACK) {
PointerData::VerifyAllFreed();
}
if (g_debug->config().options() & LEAK_TRACK) {
PointerData::LogLeaks();
}
if ((g_debug->config().options() & BACKTRACE) && g_debug->config().backtrace_dump_on_exit()) {
debug_dump_heap(android::base::StringPrintf("%s.%d.exit.txt",
g_debug->config().backtrace_dump_prefix().c_str(),
getpid()).c_str());
}
backtrace_shutdown();
delete g_debug;
g_debug = nullptr;
DebugDisableFinalize();
}
例如options 里指定的 FREE_TRACK、LEAK_TRACK、BACKTRACE,都是在这里进行检查。
注意:
__cxa_atexit() 注册的 MallocFiniImpl(),是需要进程主动调用 exit() 才会调用 debug_finalize() 去检查,如果进程收到 fatal signal 而导致被 kernel 强制 exit,此时进程不会调用 exit(),也就不会调用 debug_finalize() 进行检查了。
在上面一节将 malloc_debug 初始化的过程基本是分析完了,其中关键点总结有如下几个方面:
本节来看下 malloc() 接口,通过该接口进一步了解 malloc_debug 的使用:
bionic/libc/bionic/malloc_common.cpp
extern "C" void* malloc(size_t bytes) {
auto dispatch_table = GetDispatchTable();
void *result;
if (__predict_false(dispatch_table != nullptr)) {
result = dispatch_table->malloc(bytes);
} else {
result = Malloc(malloc)(bytes);
}
if (__predict_false(result == nullptr)) {
warning_log("malloc(%zu) failed: returning null pointer", bytes);
return nullptr;
}
return MaybeTagPointer(result);
}
如果没有使能 malloc_debug 时,dispatch_table 为 nullptr,则会进入 Malloc() 调用,即原生的 malloc 函数。这里的 dispatch_table 就是加载 libc_malloc_debug.so 之后初始化全局变量 __libc_globals 中的 current_dispatch_table,详细看上面第 2.4.2 节。
如果使能了 malloc_debug时,就会调用 dispatch_table->malloc(),这里的malloc 函数就是之前第 2.4.1 节中所解析的 MallocDispatch 里面的函数指针。这里最终调用的就是 malloc_debug 中的 debug_malloc() 函数:
bionic/libc/malloc_debug/malloc_debug.cpp
void* debug_malloc(size_t size) {
if (DebugCallsDisabled()) {
return g_dispatch->malloc(size);
}
ScopedConcurrentLock lock;
ScopedDisableDebugCalls disable;
ScopedBacktraceSignalBlocker blocked;
void* pointer = InternalMalloc(size);
if (g_debug->config().options() & RECORD_ALLOCS) {
g_debug->record->AddEntry(new MallocEntry(pointer, size));
}
return pointer;
}
我们在之前的第 2.4.2 节中 FinishInstallHooks() 函数会对 malloc_debug 进行初始化,其中就包括 malloc_debug 中的关键变量 g_debug,options 的解析也是在 g_debug的 Initialize() 中完成。
介绍完 g_debug,再来看下 debug_malloc() 的核心处理函数在 InternalMalloc(),参数为需要申请的内存大小。
bionic/libc/malloc_debug/malloc_debug.cpp
static void* InternalMalloc(size_t size) {
if ((g_debug->config().options() & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) {
debug_dump_heap(android::base::StringPrintf(
"%s.%d.txt", g_debug->config().backtrace_dump_prefix().c_str(), getpid())
.c_str());
}
if (size == 0) {
size = 1;
}
//g_debug在初始化的时候,会根据options解析 extra_bytes_
size_t real_size = size + g_debug->extra_bytes();
if (real_size < size) {
// Overflow.
errno = ENOMEM;
return nullptr;
}
if (size > PointerInfoType::MaxSize()) {
errno = ENOMEM;
return nullptr;
}
void* pointer;
//创建 header,real_size 按照16字节或8字节对齐,64位系统按16字节
if (g_debug->HeaderEnabled()) {
Header* header =
reinterpret_cast<Header*>(g_dispatch->memalign(MINIMUM_ALIGNMENT_BYTES, real_size));
if (header == nullptr) {
return nullptr;
}
pointer = InitHeader(header, header, size);
} else {
pointer = g_dispatch->malloc(real_size);
}
if (pointer != nullptr) {
if (g_debug->TrackPointers()) {
PointerData::Add(pointer, size);
}
if (g_debug->config().options() & FILL_ON_ALLOC) {
size_t bytes = InternalMallocUsableSize(pointer);
size_t fill_bytes = g_debug->config().fill_on_alloc_bytes();
bytes = (bytes < fill_bytes) ? bytes : fill_bytes;
memset(pointer, g_debug->config().fill_alloc_value(), bytes);
}
}
return pointer;
}
来看下 real_size 是在size 基础上又加上了 g_debug->extra_bytes(),在g_debug 在初始化时根据 options 指定计算出增加的空间保存在 g_debug->extra_bytes_ 中,详细可以查看 g_debug->Initialize():
bionic/libc/malloc_debug/DebugData.cpp
bool DebugData::Initialize(const char* options) {
if (config_.options() & HEADER_OPTIONS) {
// Initialize all of the static header offsets.
pointer_offset_ = __BIONIC_ALIGN(sizeof(Header), MINIMUM_ALIGNMENT_BYTES);
if (config_.options() & FRONT_GUARD) {
front_guard.reset(new FrontGuardData(this, config_, &pointer_offset_));
}
extra_bytes_ = pointer_offset_;
// Initialize all of the non-header data.
if (config_.options() & REAR_GUARD) {
rear_guard.reset(new RearGuardData(this, config_));
extra_bytes_ += config_.rear_guard_bytes();
}
}
...
if (config_.options() & EXPAND_ALLOC) {
extra_bytes_ += config_.expand_alloc_bytes();
}
return true;
}
回到 InternalMalloc(), 当 options 设置 HEADER_OPTIONS 时,HeaderEnabled() 返回true,此时创建 header 对象,以real_size 申请空间,并调用 InitHeader() 对header 进行初始化。
否则,通过 g_dispatch->malloc() 通过原生的 malloc() 申请 real_size 空间:
bionic/libc/bionic/malloc_common.cpp
static constexpr MallocDispatch __libc_malloc_default_dispatch __attribute__((unused)) = {
Malloc(calloc),
Malloc(free),
Malloc(mallinfo),
Malloc(malloc),
Malloc(malloc_usable_size),
Malloc(memalign),
Malloc(posix_memalign),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
Malloc(pvalloc),
#endif
Malloc(realloc),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
Malloc(valloc),
#endif
Malloc(malloc_iterate),
Malloc(malloc_disable),
Malloc(malloc_enable),
Malloc(mallopt),
Malloc(aligned_alloc),
Malloc(malloc_info),
};
说到底,最终会使用原生的 malloc() 进行创建,只不过在原来的 size 基础上多了一些东西,而这些东西就是 malloc_debug 的核心。
至此,malloc_debug 原理已经基本分析完。
有什么遗漏的地方,欢迎留言提醒,笔者会在后面尽快更新!!
具体malloc_debug 的使用可以查看:Android 中malloc_debug 使用详解