四川万景建设工程有限公司网站网站排名优化怎样做
版本基于:Android R
0. 前言
最近上项目中遇到一个native 可能内存泄漏的问题,曾考虑使用HWASAN,但这个工具是针对整个系统,运行代价还是很高的。而笔者遇到的问题大致有所方向,能指定到某一个进程,针对单个进程是否有检测的功能呢?答案是肯定的,也就是本文需要分析的 malloc_debug。
1. malloc_debug简介
malloc_debug 是调试native 内存问题的一个工具,能够帮助我们检测内存损坏、内存泄漏、释放再使用等问题。详细的细节可以查看:bionic/libc/malloc_debug/README.md 文件,该文件主要总结Android N 及之后版本中的使用方法,而Android N 之前的malloc_debug 使用方法可以查看README_marshmallow_and_earlier.md 这个文件。
2. malloc_debug 初始化
在分析 malloc_debug 初始化之前,需要记住 malloc_debug 在libc 中的几个重要常量:
bionic/libc/bionic/malloc_common_dynamic.cppstatic 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 名称和环境变量名称。
2.1 加载lib.so 时进行preinit
在程序加载 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;
#endifnetdClientInit();
}
这里需要注意三点:
- 全局变量 __libc_globals:
bionic/libc/private/bionic_globals.h__LIBC_HIDDEN__ extern WriteProtected<libc_globals> __libc_globals;
- __libc_init_globals() 对__libc_globals 初始化:
- __libc_globals.mutate(__libc_init_malloc); 为每个进程注册通知
2.2 入口函数 __libc_init_malloc()
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.hstruct 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 节。
2.3 MallocInitImpl()
bionic/libc/bionic/malloc_common_dynamic.cppstatic 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.cppstatic 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:
- 通过环境变量 LIBC_DEBUG_MALLOC_OPTIONS 指定options
- 通过prop libc.debug.malloc.options 指定 options
回到 MallocInitImpl() 函数,当确认 malloc_debug 使能时,会调用 InstallHooks() 进行动态加载 libc_malloc_debug.so,该函数的参数除了传入 globals 和 options,还指定了库的使用前缀和库名,这里分别是“debug” 和 libc_malloc_debug.so。函数 InstallHooks() 详细看 2.4 节。
2.4 InstallHooks()
bionic/libc/bionic/malloc_common_dynamic.cppstatic 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(),注意这里的第三个参数 malloc_dispatch_table;
- FinishInstallHooks(),进行malloc_debug 初始化工作,并注册回调函数 MallocFiniImpl() 进行收尾;
2.4.1 LoadSharedLibrary()
在 LoadSharedLibrary() 中通过 dlopen() 动态加载 libc_malloc_debug.so,接着通过 InitSharedLibrary() 函数查找如下names 数组中的 symbol,将查找到的 symbol 保存在全局数组变量 gfunctions 中。注意查找的函数都会加上 debug_ 前缀。
bionic/libc/bionic/malloc_common_dynamic.cppbool 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.cppdebug_free()
debug_calloc()
debug_mallinfo()
debug_malloc()
debug_realloc()
...
并将查找到的 symbol 都存放到 MallocDispatch 对应的函数指针,这个 MallocDispatch 就是最开始 LoadSharedLibrary() 的第三个参数,也就是 globals->malloc_dispatch_table:
bionic/libc/private/bionic_malloc_dispatch.hstruct 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;
#endifMallocRealloc realloc;
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)MallocValloc valloc;
#endifMallocIterate malloc_iterate;MallocMallocDisable malloc_disable;MallocMallocEnable malloc_enable;MallocMallopt mallopt;MallocAlignedAlloc aligned_alloc;MallocMallocInfo malloc_info;
} __attribute__((aligned(32)));
指定这些函数的目的是什么呢?
详细可以查看第 3 节的malloc() 函数调用。
2.4.2 FinishInstallHooks()
bionic/libc/bionic/malloc_common_dynamic.cppbool 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;
}
该函数大致做了三件事情:
- 调用 malloc_debug 中的 debug_initialize(),对malloc_debug 内存进行初始化工作,例如其中的关键变量 g_dispatch 和 g_debug,注意参数prev_dispatch 是默认dispatch,最开始默认为NULL,用 NativeAllocatorDispatch() 进行创建;
- 设置 __libc_globals 对象中的 libc_globals.default_dispatch_table 和 current_dispatch_table 指向 malloc_dispatch_table,以后在 malloc 库函数里都会通过 GetDispatchTable(),这个函数就是返回的 current_dispatch_table 指针;
- 通过 __cxa_atexit() 调用,注册 MallocFiniImpl(),注册的此函数将在进程 exit 时(例如调用 exit()函数) 进行回调:
bionic/libc/bionic/malloc_common_dynamic.cppstatic 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.cppvoid 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() 进行检查了。
3. malloc()
在上面一节将 malloc_debug 初始化的过程基本是分析完了,其中关键点总结有如下几个方面:
- 通过 dlopen() 动态加载 libc_malloc_debug.so,并将几个重要接口函数保存在 gfunctions 数组中;
- 通过 InitMallocFuntions() 在这个so里继续查找 malloc_debug 其他系列函数,并保存在全局变量 __libc_globals 对象里 libc_globals.malloc_dispatch_table 变量指定的函数指针;
- 注册 exit() 的回调函数 MallocFiniImpl(),最终调用 malloc_debug 中的 debug_finalize() 接口进行检查;
本节来看下 malloc() 接口,通过该接口进一步了解 malloc_debug 的使用:
bionic/libc/bionic/malloc_common.cppextern "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.cppvoid* 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(),参数为需要申请的内存大小。
3.1 InternalMalloc()
bionic/libc/malloc_debug/malloc_debug.cppstatic 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.cppbool 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.cppstatic 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),
#endifMalloc(realloc),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)Malloc(valloc),
#endifMalloc(malloc_iterate),Malloc(malloc_disable),Malloc(malloc_enable),Malloc(mallopt),Malloc(aligned_alloc),Malloc(malloc_info),
};
说到底,最终会使用原生的 malloc() 进行创建,只不过在原来的 size 基础上多了一些东西,而这些东西就是 malloc_debug 的核心。
至此,malloc_debug 原理已经基本分析完。
有什么遗漏的地方,欢迎留言提醒,笔者会在后面尽快更新!!