目录
1.3.2 record_allocs[=TOTAL_ENTRIES]
1.2.3 record_allocs_file[=FILE_NAME]
1.4.1 front_guard[=SIZE_BYTES]
1.5.2 backtrace_enable_on_signal[=MAX_FRAMES]
1.6.1 fill_on_alloc[=MAX_FILLED_BYTES]
1.6.2 fill_on_free[=MAX_FILLED_BYTES]
1.6.4 expand_alloc[=EXPADN_BYTES]
1.7.1 free_track[=ALLOCATION_COUNT]
1.7.2 free_track_backtrace_num_frames[=MAX_FRAMES]
一、malloc_debug工具使用
malloc_debug作用:用于调试单个native进程内存问题,如检测内存损坏、内存泄漏、内存访问越界、内存释放再使用等。
1.1 打开malloc_debug开关
adb shell setprop libc.debug.malloc.program app_process(proc name) ---指定 app_process,即malloc_debug 只针对 app_process 这个进程生效
adb shell setprop libc.debug.malloc.options "\"backtrace front_guard=16 rear_guard=16 backtrace_dump_prefix=/sdcard/Download/heap"\"
1.2 malloc_debug 的 options
// bionic/libc/malloc_debug/Config.h
constexpr uint64_t FRONT_GUARD = 0x1; // 设置前置保护区,用于检测内存越界访问
constexpr uint64_t REAR_GUARD = 0x2; // 设置后置保护区,用于检测内存越界访问
constexpr uint64_t BACKTRACE = 0x4; // 打印堆栈信息
constexpr uint64_t FILL_ON_ALLOC = 0x8; // 内存分配时填充0xeb
constexpr uint64_t FILL_ON_FREE = 0x10; // 内存释放时填充0xef
constexpr uint64_t EXPAND_ALLOC = 0x20; // 增加额外的内存
constexpr uint64_t FREE_TRACK = 0x40; // 记录内存释放信息
constexpr uint64_t TRACK_ALLOCS = 0x80; // 记录内存分配信息
constexpr uint64_t LEAK_TRACK = 0x100; // 记录内存泄漏信息
constexpr uint64_t RECORD_ALLOCS = 0x200; // 记录线程的内存分配与释放信息
constexpr uint64_t BACKTRACE_FULL = 0x400; // 以Java frames展开
constexpr uint64_t ABORT_ON_ERROR = 0x800;
constexpr uint64_t VERBOSE = 0x1000;
1.3 内存分配与释放检测
1.3.1 mem leak
内存泄漏检测,如果options选项包含backtrace,在发生mem leak时,会将相关信息输出到logcat.
日志如下:
04-15 12:35:33.304 7412 7412 E malloc_debug: +++ APP leaked block of size 100 at 0x2be3b0b0 (leak 1 of 2)
04-15 12:35:33.304 7412 7412 E malloc_debug: Backtrace at time of allocation:
04-15 12:35:33.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
04-15 12:35:33.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
04-15 12:35:33.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
04-15 12:35:33.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
04-15 12:35:33.305 7412 7412 E malloc_debug: +++ APP leaked block of size 24 at 0x7be32380 (leak 2 of 2)
04-15 12:35:33.305 7412 7412 E malloc_debug: Backtrace at time of allocation:
04-15 12:35:33.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
04-15 12:35:33.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
04-15 12:35:33.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
04-15 12:35:33.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
mem leak监控实现原理详见2.4.
1.3.2 record_allocs[=TOTAL_ENTRIES]
记录每个线程的每一次 分配 / 释放 的 track,当收到信号 SIGRTMAX - 18时,将这些 track dump 到一个文件中。
TOTAL_ENTRIES 表示 分配 /释放 的记录总数。如果达到了 TOTAL_ENTRIES,后面的 分配 / 释放将不再记录。默认值为 8,000,000,最大值可以设到 50,000,000。当收到信号时,所有的记录都会写到文件中,所有的记录也会被删除。
文件格式,如下:
Threadid: action pointer size
186: malloc 0xb6038060 20
186: free 0xb6038060
186: calloc 0xb609f080 32 4
186: realloc 0xb609f080 0xb603e9a0 12
186: memalign 0x85423660 16 104
186: memalign 0x85423660 4096 112
186: memalign 0x85423660 4096 8192
1.2.3 record_allocs_file[=FILE_NAME]
只有在 record_allocs 这个option 使用时生效,用以指定记录dump 的文件名。
1.2.4 verify_pointers
检测已释放的内存是否被一个指针指向或使用。
如下:
04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 UNKNOWN POINTER (free)
04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace at time of failure:
04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
1.2.5 abort_on_error
当malloc debug 检测到error 时,在发送错误 log 消息之后终止。
注意,如果 lead_track 被使能,当进程退出时检测出泄漏时不会产生 abort。
什么情况下会发生error ?
1.2.6 Use After Free
1) after free 之后指针被用来进行了realloc 操作
04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 USED AFTER FREE (realloc)
2)after free 之后又进行了 free
04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 USED AFTER FREE (free)
04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace of original free:
04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace at time of failure:
04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
1.2.7 Invalid Tag
04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 HAS INVALID TAG 1ee7d000 (malloc_usable_size)
04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace at time of failure:
04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
malloc_usable_size 这个函数被一个指针调用,而这个指针不是用来分配内存,或指针指向的内存已经损坏。
malloc_usable_size 这个函数被一个指针调用,而这个指针不是用来分配内存,或指针指向的内存已经损坏。
1.4 内存越界访问检测
1.4.1 front_guard[=SIZE_BYTES]
设置前置保护区, front guard 空间会被写上一个特定模式的数据(0xaa)。当分配的内存被释放,front guard 空间会被用来check 是否已经被修改。如果 front guard 空间任意地方被修改,将会在 log 中产生一个 error 来表示哪些 bytes 被修改。
如下:
setprop libc.debug.malloc.options front_guard=16
char *ptr = (char*) malloc(1 * 1024 * 1024); //申请 1M 空间
memset(ptr, 0, 1* 1024* 1024); //给这1M空间初始化
printf("*(ptr-16) = %d\n", *(ptr-16)); //将front guard打印一下,应该是0xaa
*(ptr - 16) = 0xee; //这个时候将值修改了
free(ptr); //free的时候malloc_debug会verify,确认是否损坏
04-10 12:00:45.621 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 SIZE 100 HAS A CORRUPTED FRONT GUARD
04-10 12:00:45.622 7412 7412 E malloc_debug: allocation[-32] = 0x00 (expected 0xaa)
04-10 12:00:45.622 7412 7412 E malloc_debug: allocation[-15] = 0x02 (expected 0xaa)
1.4.2 rear_guard[=SIZE_BYTES]
后置保护区,同 front_guard,不过是在待分配内存的尾部加上一个小的buffer,在rear guard 空间写上特定模式的数据(0xbb)。
日志如下:
04-10 12:00:45.621 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 SIZE 100 HAS A CORRUPTED REAR GUARD
04-10 12:00:45.622 7412 7412 E malloc_debug: allocation[130] = 0xbf (expected 0xbb)
04-10 12:00:45.622 7412 7412 E malloc_debug: allocation[131] = 0x00 (expected 0xbb)
1.4.3 guard[=SIZE_BYTES]
同时分配 front guard 和 rear guard。
1.5 使能调用栈功能
1.5.1 backtrace[=MAX_FRAMES]
使能抓取每一个分配点的调用栈信息的功能。
MAX_FRAMES:表示抓取的调用栈最大数目,默认值为 16,最大值为 256.
当进程收到信号SIGRTMAX-17时会dump 堆数据到一个文件。dump 下来的数据格式与运行 am dumpheap -n 的数据格式相同。默认dump 到文件 /data/local/tmp/backtrace_head.**PID**.txt 中。这对于不是从 zygote 进程fork 出来的native 进程是很有用的。
需要注意的是,当信号收到时,head 数据不是立即dump,而是等到下一次的 malloc/free 发生。
1.5.2 backtrace_enable_on_signal[=MAX_FRAMES]
当进程收到信号 SIGRTMAX-19时触发抓取。当这个 option 单独使用的时候,调用栈抓取默认是不开启的,在接受到 signal 之后才开始。如果与backtrace 选项一起设置,那么调用栈抓取在收到 signal之后使能。
1.5.3 backtrace_dump_on_exit
从 P 版本开始,当backtrace 选项使能,这个option 会导致在进程退出才将dump 的堆信息dump 到一个文件。当backtrace 选项没有使能,这个option 不做任何事情。
dump 的默认文件路径是:/data/local/tmp/backtrace_head.**PID**.exit.txt
文件路径可以使用 backtrace_dump_prefix 选项修改,见第 1.5.4.
1.5.4 backtrace_dump_prefix
从 P 版本开始,当一个 backtrace 的选项使能,这个前缀会在 SIGRTMAX-17 信号收到或backtrace_dump_on_exit 设置时使用。
默认的前缀是:/data/local/tmp/backtrace_head
设置该prefix 路径之后,原来backtrace 的路径将变为:backtrace_dump_prefix.**PID**.txt 或 backtrace_dump_prefix.**PID**.exit.txt
例如,
setprop libc.debug.malloc.options backtrace_dump_prefix=/sdcard/log
那么调用栈将会被存到/sdcard/log.**PID**.txt 或 /sdcard/log.**PID**.exit.txt 中
1.5.5 backtrace_full
从 Q 版本开始,调用栈无论何时抓到,都会用另一个不同的算法能够以Java 帧展开。这将比正常的调用栈更慢。
核心代码,如下:
// bionic/libc/malloc_debug/malloc_debug.cpp
void BacktraceAndLog() {
if (g_debug->config().options() & BACKTRACE_FULL) {
// frames 中的每个元素代表一个函数在内存中的地址
std::vector<uintptr_t> frames;
std::vector<unwindstack::LocalFrameData> frames_info;
if (!Unwind(&frames, &frames_info, 256)) {
error_log(" Backtrace failed to get any frames.");
} else {
UnwindLog(frames_info);
}
} else {
std::vector<uintptr_t> frames(256);
size_t num_frames = backtrace_get(frames.data(), frames.size());
if (num_frames == 0) {
error_log(" Backtrace failed to get any frames.");
} else {
backtrace_log(frames.data(), num_frames);
}
}
}
当options 中设置了backtrace_full,会调用unwind()。
1.6 填充分配空间
1.6.1 fill_on_alloc[=MAX_FILLED_BYTES]
除了 calloc函数,其他的常规的分配,都会使用0xeb 填充分配空间。当使用realloc 分配一个更大的空间,扩大的空间也会填充 0xeb。
如果 MAX_FILLED_BYTES 指定,那么按照该值只填充指定空间大小。默认填充整个分配空间。
1.6.2 fill_on_free[=MAX_FILLED_BYTES]
当分配空间被释放的时候,用 0xef 填充需要释放的空间。
如果 MAX_FILLED_BYTES 指定,那么按照该值只填充指定空间大小。默认填充整个分配空间。
1.6.3 fill[=MAX_FILLED_BYTES]
同时使能 fill_on_alloc 和 fill_on_free
1.6.4 expand_alloc[=EXPADN_BYTES]
为每一次分配扩充额外数量的空间。如果EXPAND_BYTES 设定,将按照该值扩充分配空间,默认值为 16字节,最大为16384 字节。
1.7 释放内存空间
1.7.1 free_track[=ALLOCATION_COUNT]
如果使用该option,当一个指针被释放时,不会立即释放内存,而是将其添加到一个列表,该列表存放已被释放的allocation。除了添加到列表之外,整个分配空间将被 0xef 填充(相当于使能fill_on_free),free 时候的调用栈也会被记录。这里调用栈记录是完全独立于backtrace option,也是进行自动记录。默认会记录最大到 16 frames 的调用找,但这个值可以使用 free_track_backtrace_num_frames 进行更改。
当然也可以将 free_track_backtrace_num_frames 设为0 来禁用该功能。
当列表满了的时候,一个 allocation 将从列表中移除,且会确认该空间的内容从添加到列表时就已经被更改。当进程结束的时候,在列表中所有的分配空间都会被核实。
ALLOCATION_COUNT 如果被设定,代表列表中总的 allocation 的个数,默认记录100 个已释放的 allocation 数,最大值可以设到 16384.
日志如下:
04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 USED AFTER FREE
04-15 12:00:31.305 7412 7412 E malloc_debug: allocation[20] = 0xaf (expected 0xef)
04-15 12:00:31.305 7412 7412 E malloc_debug: allocation[99] = 0x12 (expected 0xef)
04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace at time of free:
04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
通过这样的方式,可以检测填充为 0xef 的空间是否在被 freed 之后还在使用。
1.7.2 free_track_backtrace_num_frames[=MAX_FRAMES]
这个option 只针对上面 free_track 被设定时生效。表示当一个分配空间被释放,有多少个 frames 的调用栈可以抓取。MAX_FRAMES 表示被抓取的 frames 的数量,如果设为0, 表示在分配空间被释放时,不会抓取任何调用栈。默认值为16,最大可以设置到 256.
二、malloc_debug实现原理
2.1 malloc_debug 初始化
2.1.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();
}
// 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_globals 初始化
__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.
// 当loaded或unloaded so库时,通知HWASan运行库更新shadow memory
__libc_shared_globals()->load_hook = __hwasan_library_loaded;
__libc_shared_globals()->unload_hook = __hwasan_library_unloaded;
#endif
netdClientInit();
}
__libc_preinit() 在main 函数执行前执行,因为它有 __attribute__((constructor(1))),通过这个constructor 对此程序所连接的 libc.so 进行 preinit。
( __attribute__((constructor)) 是 GCC 和 Clang 编译器提供的函数属性,用来标记一个函数为构造函数。构造函数是在程序运行时,在 main() 函数执行之前自动执行的函数。而括号中的数字可以用来指定构造函数的优先级,数字越小,优先级越高,默认为0 )
__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);
}
// 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;
};
// 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;
// 检查工具是否enable
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();
}
}
CheckLoadMallocDebug():
// bionic/libc/bionic/malloc_common_dynamic.cpp
static bool CheckLoadMallocDebug(char** options) {
// If kDebugMallocEnvOptions is set then it overrides the system properties.
// 如果通过设备环境和系统属性都没有设置options,返回false
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.
// 检查 prop libc.debug.malloc.program 是否设定 program name
char program[PROP_VALUE_MAX];
if (__system_property_get(kDebugPropertyProgram, program) != 0 &&
strstr(getprogname(), program) == nullptr) {
return false;
}
} else {
*options = env;
}
return true;
}
InstallHooks():
// 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():
LoadSharedLibrary() 中通过 dlopen() 动态加载 libc_malloc_debug.so,接着通过 InitSharedLibrary() 函数查找如下names 数组中的 symbol,将查找到的 symbol 保存在全局数组变量 gfunctions 中。注意查找的函数都会加上 debug_ 前缀。
// bionic/libc/bionic/malloc_common_dynamic.cpp
void* LoadSharedLibrary(const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {
void* impl_handle = nullptr;
// Try to load the libc_malloc_* libs from the "runtime" namespace and then
// fall back to dlopen() to load them from the default namespace.
//
// The libraries are packaged in the runtime APEX together with libc.so.
// However, since the libc.so is searched via the symlink in the system
// partition (/system/lib/libc.so -> /apex/com.android.runtime/bionic/libc.so)
// libc.so is loaded into the default namespace. If we just dlopen() here, the
// linker will load the libs found in /system/lib which might be incompatible
// with libc.so in the runtime APEX. Use android_dlopen_ext to explicitly load
// the ones in the runtime APEX.
struct android_namespace_t* runtime_ns = android_get_exported_namespace("com_android_runtime");
if (runtime_ns != nullptr) {
const android_dlextinfo dlextinfo = {
.flags = ANDROID_DLEXT_USE_NAMESPACE,
.library_namespace = runtime_ns,
};
impl_handle = android_dlopen_ext(shared_lib, RTLD_NOW | RTLD_LOCAL, &dlextinfo);
}
if (impl_handle == nullptr) {
impl_handle = dlopen(shared_lib, RTLD_NOW | RTLD_LOCAL);
}
if (impl_handle == nullptr) {
error_log("%s: Unable to open shared library %s: %s", getprogname(), shared_lib, dlerror());
return nullptr;
}
if (!InitSharedLibrary(impl_handle, shared_lib, prefix, dispatch_table)) {
dlclose(impl_handle);
impl_handle = nullptr;
}
return impl_handle;
}
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)));
指定这些函数的目的是什么呢?详细可以查看第 2.2 中的malloc() 函数调用。
FinishInstallHooks():
// 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;
}
该函数大致做了三件事情:
1)调用 malloc_debug 中的 debug_initialize(),对 malloc_debug 内存进行初始化工作,例如其中的关键变量 g_dispatch 和 g_debug,注意参数 prev_dispatch 是默认dispatch,最开始默认为 NULL,用 NativeAllocatorDispatch() 进行创建;
// bionic/libc/malloc_debug/malloc_debug.cpp
bool debug_initialize(const MallocDispatch* malloc_dispatch, bool* zygote_child, const char* options) {
if (zygote_child == nullptr || options == nullptr) {
return false;
}
if (__asan_init != 0) {
error_log("malloc debug cannot be enabled alongside ASAN");
return false;
}
InitAtfork();
g_zygote_child = zygote_child;
g_dispatch = malloc_dispatch;
if (!DebugDisableInitialize()) {
return false;
}
DebugData* debug = new DebugData();
// g_debug 在初始化
if (!debug->Initialize(options) || !Unreachable::Initialize(debug->config())) {
delete debug;
DebugDisableFinalize();
return false;
}
g_debug = debug;
// Always enable the backtrace code since we will use it in a number// of different error cases.backtrace_startup();
if (g_debug->config().options() & VERBOSE) {
info_log("%s: malloc debug enabled", getprogname());
}
ScopedConcurrentLock::Init();
return true;
}
// 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;
}
2)设置 __libc_globals 对象中的 libc_globals.default_dispatch_table 和 current_dispatch_table 指向 malloc_dispatch_table,以后在 malloc 库函数里都会通过 GetDispatchTable(),这个函数就是返回的 current_dispatch_table 指针;
// bionic/libc/bionic/malloc_common.h
static inline const MallocDispatch* GetDispatchTable() {
return atomic_load_explicit(&__libc_globals->current_dispatch_table, memory_order_acquire);
}
3)通过 __cxa_atexit() 调用,注册 MallocFiniImpl(),注册的此函数将在进程 exit 时(例如调用 exit()函数) 进行回调:
// 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() 进行检查,例如是否有内存泄漏,如果有,会将调用栈打印出来。详见2.4.
注:
__cxa_atexit() 注册的 MallocFiniImpl(),是需要进程主动调用 exit() 才会调用 debug_finalize() 去检查,如果进程收到 fatal signal 而导致被 kernel 强制 exit,此时进程不会调用 exit(),也就不会调用 debug_finalize() 进行检查了。
2.2 malloc函数入口
// 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.
如果使能了 malloc_debug 时,就会调用 dispatch_table->malloc(),这里的 malloc 函数就是之前MallocDispatch 里面的函数指针。这里最终调用的就是 malloc_debug 中的 debug_malloc() 函数。
2.3 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;
// malloc_debug内存分配核心函数
void* pointer = InternalMalloc(size);
// 内存首地址存到g_debug
if (g_debug->config().options() & RECORD_ALLOCS) {
g_debug->record->AddEntry(new MallocEntry(pointer, size));
}
return pointer;
}
FinishInstallHooks() 函数会对 malloc_debug 进行初始化,其中就包括 malloc_debug 中的关键变量 g_debug,options 的解析也是在 g_debug 的 Initialize() 中完成。
2.3.1 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 {
// android p之后直接调用原生的系统内存分配机制,一般用scudo
pointer = g_dispatch->malloc(real_size);
}
if (pointer != nullptr) {
// 如果打开track,将内存首地址存入PointerData
if (g_debug->TrackPointers()) {
PointerData::Add(pointer, size);
}
// options的FILL_ON_ALLOC选项打开,在内存分配时进行数据0xeb填充
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().
通过 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),
};
小结:在原来size基础上新增加了一些检测相关的东西,最终调用原生的malloc进行内存分配(一般采用scudo内存分配机制),通过mmap syscall方式向内核请求分配内存。
2.4 mem leak监控原理
内存泄漏的检测原理:维护一个记录内存申请和释放的列表,每当申请内存时列表成员+1,内存释放时列表成员-1,程序退出时列表中还存在的成员即内存泄漏的成员。
1)内存申请
在 PointData 里维护了一个全局的 pointers_ map,每次申请内存时调用 Add 函数增加 pointers_ 成员,释放内存时调用 Remove 函数移除 pointers_ 成员。
// malloc_debug.cpp
static TimedResult InternalMalloc(size_t size) {
......
if (pointer != nullptr) {
if (g_debug->TrackPointers()) {
// 内存申请时调用 Add 函数增加 pointers_ 成员
PointerData::Add(pointer, size);
}
......
}
return result;
}
2)内存释放
// malloc_debug.cpp
static TimedResult InternalFree(void* pointer) {
...
if (g_debug->TrackPointers()) {
// 释放内存时调用 Remove 函数移除 pointers_ 成员
PointerData::Remove(pointer);
}
...
return result;
}
3)内存泄漏信息打印
程序退出时调用 debug_finalize() 打印内存泄漏并保存dump 文件,调用 LogLeaks() 将内存泄漏信息在log 打印,将dump 文件写入设备存储。
// bionic/libc/malloc_debug/malloc_debug.cpp
void debug_finalize() {
......
if (g_debug->config().options() & LEAK_TRACK) {
PointerData::LogLeaks();
}
......
}
LogLeaks():
LogLeaks() 内部调用 GetList 函数获得 pointers_ 成员,打印相关backtrace信息。
// bionic/libc/malloc_debug/PointerData.cpp
void PointerData::LogLeaks() {
std::vector<ListInfoType> list;
std::lock_guard<std::mutex> pointer_guard(pointer_mutex_);
std::lock_guard<std::mutex> frame_guard(frame_mutex_);
GetList(&list, false);
size_t track_count = 0;
for (const auto& list_info : list) {
error_log("+++ %s leaked block of size %zu at 0x%" PRIxPTR " (leak %zu of %zu)", getprogname(),
list_info.size, list_info.pointer, ++track_count, list.size());
if (list_info.backtrace_info != nullptr) {
error_log("Backtrace at time of allocation:");
UnwindLog(*list_info.backtrace_info);
} else if (list_info.frame_info != nullptr) {
error_log("Backtrace at time of allocation:");
backtrace_log(list_info.frame_info->frames.data(), list_info.frame_info->frames.size());
}
// Do not bother to free the pointers, we are about to exit any way.
}
}