邢臺市政建設集團股份有限公司網站百度云網盤入口
版本基于:Android R
0. 前言
最近上項目中遇到一個native 可能內存泄漏的問題,曾考慮使用HWASAN,但這個工具是針對整個系統(tǒng),運行代價還是很高的。而筆者遇到的問題大致有所方向,能指定到某一個進程,針對單個進程是否有檢測的功能呢?答案是肯定的,也就是本文需要分析的 malloc_debug。
1. malloc_debug簡介
malloc_debug 是調試native 內存問題的一個工具,能夠幫助我們檢測內存損壞、內存泄漏、釋放再使用等問題。詳細的細節(jié)可以查看: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";
這里,指定了動態(tài)加載malloc_debug 的so 名稱,也指定了 malloc_debug 配置的prop 名稱和環(huán)境變量名稱。?
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 函數執(zhí)行前執(zhí)行,因為它有?__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 節(jié)。
回到函數,該函數最終調用的 MallocInitImpl(),該函數詳細看第 2.3 節(jié)。
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:
- 通過環(huán)境變量 LIBC_DEBUG_MALLOC_OPTIONS 指定options
- 通過prop libc.debug.malloc.options 指定 options
回到?MallocInitImpl() 函數,當確認 malloc_debug 使能時,會調用 InstallHooks() 進行動態(tài)加載 libc_malloc_debug.so,該函數的參數除了傳入 globals 和 options,還指定了庫的使用前綴和庫名,這里分別是“debug” 和 libc_malloc_debug.so。函數 InstallHooks() 詳細看 2.4 節(jié)。
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() 動態(tài)加載 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里繼續(xù)查找 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 節(jié)的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() 進行創(chuàng)建;
- 設置 __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()
在上面一節(jié)將 malloc_debug 初始化的過程基本是分析完了,其中關鍵點總結有如下幾個方面:
- 通過 dlopen() 動態(tài)加載 libc_malloc_debug.so,并將幾個重要接口函數保存在?gfunctions 數組中;
- 通過 InitMallocFuntions() 在這個so里繼續(xù)查找 malloc_debug 其他系列函數,并保存在全局變量 __libc_globals 對象里 libc_globals.malloc_dispatch_table 變量指定的函數指針;
- 注冊 exit() 的回調函數 MallocFiniImpl(),最終調用 malloc_debug 中的 debug_finalize() 接口進行檢查;
本節(jié)來看下 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 節(jié)。
如果使能了 malloc_debug時,就會調用 dispatch_table->malloc(),這里的malloc 函數就是之前第 2.4.1 節(jié)中所解析的?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?節(jié)中?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;//創(chuàng)建 header,real_size 按照16字節(jié)或8字節(jié)對齊,64位系統(tǒng)按16字節(jié)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,此時創(chuàng)建 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() 進行創(chuàng)建,只不過在原來的 size 基礎上多了一些東西,而這些東西就是 malloc_debug 的核心。
?
至此,malloc_debug 原理已經基本分析完。
有什么遺漏的地方,歡迎留言提醒,筆者會在后面盡快更新!!