一.malloc_debug简介
android 的libc中有malloc_debug的hook调用,具体android源码/bionic/libc/malloc_debug下,我们可以使用malloc_debug中的hook函数对内存分配进行跟踪加测。
malloc_debug主要包含的功能如下:
1.内存分配和释放跟踪,支持的函数如下:
When malloc debug is enabled, it works by adding a shim layer that replaces
the normal allocation calls. The replaced calls are:
* `malloc`
* `free`
* `calloc`
* `realloc`
* `posix_memalign`
* `memalign`
* `malloc_usable_size`
2 支持内存边界,可以在申请的内存头部和尾部添加guard,内存越界检查,use after free,内存崩溃检查等.
3 调用栈跟踪和打印,跟踪内存分配的同时保存内存分配的调用栈,方便内存泄漏检查.
二.malloc_debug的使用
1.stop
adb shell stop
2 开启malloc_debug
adb shell setprop libc.debug.malloc.program app_process
3 设置相关检测项
adb shell setprop libc.debug.malloc.options backtrace
4 start
adb shell start
三 malloc_debug原理
/bionic/libc/Android.bp
分析bp文件可以得知我们libc.so中包含了/bionic/libc/bionic/malloc_common.cpp
// ========================================================
// libc.a + libc.so
// ========================================================
cc_library {
defaults: ["libc_defaults"],
name: "libc",
product_variables: {
platform_sdk_version: {
asflags: ["-DPLATFORM_SDK_VERSION=%d"],
},
},
static: {
srcs: [
"bionic/dl_iterate_phdr_static.cpp",
"bionic/icu_static.cpp",
"bionic/malloc_common.cpp",
"bionic/libc_init_static.cpp",
],
cflags: ["-DLIBC_STATIC"],
whole_static_libs: ["libc_init_static", "libjemalloc"],
},
shared: {
srcs: [
"arch-common/bionic/crtbegin_so.c",
"arch-common/bionic/crtbrand.S",
"bionic/icu.cpp",
"bionic/malloc_common.cpp",
"bionic/NetdClient.cpp",
"arch-common/bionic/crtend_so.S",
],
whole_static_libs: ["libc_init_dynamic"],
},
......
}
libc在初始化时会调用malloc_init_impl判断属性来加载libc_malloc_debug.so,调用InitMallocFunctions替换掉Libc原生的内存分配和释放函数。
malloc_comon.cpp
static const char* DEBUG_SHARED_LIB = "libc_malloc_debug.so";
static const char* DEBUG_MALLOC_PROPERTY_OPTIONS = "libc.debug.malloc.options";
static const char* DEBUG_MALLOC_PROPERTY_PROGRAM = "libc.debug.malloc.program";
// __libc_init会调用__libc_init_malloc,进而调到malloc_init_impl进行初始化
// Initializes memory allocation framework.
// This routine is called from __libc_init routines in libc_init_dynamic.cpp.
__LIBC_HIDDEN__ void __libc_init_malloc(libc_globals* globals) {
malloc_init_impl(globals);
}
// Initializes memory allocation framework once per process.
static void malloc_init_impl(libc_globals* globals) {
char value[PROP_VALUE_MAX];
// If DEBUG_MALLOC_ENV_OPTIONS is set then it overrides the system properties.
const char* options = getenv(DEBUG_MALLOC_ENV_OPTIONS);
if (options == nullptr || options[0] == '\0') {
//判断libc.debug.malloc.options属性如果为空,则直接返回
if (__system_property_get(DEBUG_MALLOC_PROPERTY_OPTIONS, value) == 0 || value[0] == '\0') {
return;
}
options = value;
// Check to see if only a specific program should have debug malloc enabled.
char program[PROP_VALUE_MAX];
//判断libc.debug.malloc.program不为空,并且program为null,此时也是直接返回。
if (__system_property_get(DEBUG_MALLOC_PROPERTY_PROGRAM, program) != 0 &&
strstr(getprogname(), program) == nullptr) {
return;
}
}
//如果进行了属性设置,则将利用libc_malloc_debug.so库中的部分函数替换libc.so的相关接口。
// Load the debug malloc shared library.
void* malloc_impl_handle = dlopen(DEBUG_SHARED_LIB, RTLD_NOW | RTLD_LOCAL);
if (malloc_impl_handle == nullptr) {
error_log("%s: Unable to open debug malloc shared library %s: %s",
getprogname(), DEBUG_SHARED_LIB, dlerror());
return;
}
// Initialize malloc debugging in the loaded module.
auto init_func = reinterpret_cast<bool (*)(const MallocDispatch*, int*, const char*)>(
dlsym(malloc_impl_handle, "debug_initialize"));
if (init_func == nullptr) {
error_log("%s: debug_initialize routine not found in %s", getprogname(), DEBUG_SHARED_LIB);
dlclose(malloc_impl_handle);
return;
}
// Get the syms for the external functions.
void* finalize_sym = dlsym(malloc_impl_handle, "debug_finalize");
if (finalize_sym == nullptr) {
error_log("%s: debug_finalize routine not found in %s", getprogname(), DEBUG_SHARED_LIB);
dlclose(malloc_impl_handle);
return;
}
void* get_leak_info_sym = dlsym(malloc_impl_handle, "debug_get_malloc_leak_info");
if (get_leak_info_sym == nullptr) {
error_log("%s: debug_get_malloc_leak_info routine not found in %s", getprogname(),
DEBUG_SHARED_LIB);
dlclose(malloc_impl_handle);
return;
}
int __system_property_get(const char *name, char *value)
{
const prop_info *pi = __system_property_find(name);
if(pi != 0) {
return __system_property_read(pi, 0, value);
} else {
value[0] = 0;
return 0;
}
}
四 malloc_debug option选项
1 内存边界检查
front_guard[=SIZE_BYTES]Enables a small buffer placed before the allocated data.
rear_guard[=SIZE_BYTES] Enables a small buffer placed after the allocated data.
guard[=SIZE_BYTES] Enables both a front guard and a rear guard on all allocations.
主要原理是在分配内存的头部和尾部添加一段数据,作为边界,头部初始化为0xaa,尾部初始化为0xbb。
2 调用栈功能
backtrace[=MAX_FRAMES]
backtrace_enable_on_signal[=MAX_FRAMES]
backtrace_dump_on_exit
backtrace_dump_prefix
backtrace_full
设置保存的调用栈个数,在信号量或者退出时打印调用栈
3 malloc内存默认值
fill_on_alloc[=MAX_FILLED_BYTES] size will be set to 0xeb.
fill_on_free[=MAX_FILLED_BYTES] When an allocation is freed, fill it with 0xef.
fill[=MAX_FILLED_BYTES] This enables both the fill_on_alloc option and the fill_on_free option.
expand_alloc[=EXPAND_BYTES] Add an extra amount to allocate for every allocation.
4 释放内存存档
free_track[=ALLOCATION_COUNT] 默认值是100,最大值是16384
free_track_backtrace_num_frames[=MAX_FRAMES]
5 分配释放检测
leak_track 在进程退出时,执行finalize函数,打印当前分配的内存
record_allocs[=TOTAL_ENTRIES] 记录alloc操作,The default value is 8,000,000 and the maximum value this can be set to is 50,000,000.
record_allocs_file[=FILE_NAME] 设置record_allocs保存地址
verify_pointers free/malloc_usable_size/realloc 有效检查
abort_on_error When malloc debug detects an error, abort after sending the error log message.内存泄漏检测不在此处,只在进程退出时检测
6 verbose 开启debug info log,如果要看更多的信息,建议开启此选项
五 常见用法
1 内存泄漏检测
在shell命令下执行 #setprop libc.debug.malloc.options “backtrace leak_track verbose”
这样开启后在进程退出时会打印leak信息,在发送kill -47时会打印当前内存申请
2 内存崩溃检查
在shell命令中添加guard #setprop libc.debug.malloc.options “backtrace leak_track verbose guard”
这样会检测内存覆盖等检测
3 verify_pointers 开启可以检测use after free和double free等操作
六.malloc问题
1.申请后多次释放 (double free)
2 释放后又去使用 (used after free)
3 使用越界 (比如申请了50节内存,结果在使用时多用了8字节的内存,这样就把后面的内存的内容踩坏,引起堆结构异常)
4 释放时传给free()的地址不是malloc()申请的地址,比如:p = malloc(10); free(p + 5);
5 内存泄露:申请内存后,忘记释放或某些代码路径没有释放。
七.实际运用
1.front_guard[=SIZE_BYTES]
每次调用malloc,都在分配的区域之前填充SIZE_BYTES,填充内容为0xaa
例子:setprop libc.debug.malloc.options front_guard=16
2.backtrace[=MAX_FRAMES]
MAX_FRAMES 最大值256 默认值16
每次在调用malloc时,malloc debug 都会记录malloc的调用栈(trace),栈的最大深度为MAX_FRAMES,MAX_FRAMES 越大,对malloc的性能影响越大。当进程收到信号(SIGRTMAX - 17)Android通常该信号值为47时,会触发malloc debug 的dump heap trace功能。默认dump路径在/data/local/tmp/ backtrace_heap.PID.txt。
给进程发信号通过kill -s 47 PID,进程收到信号后并不会马上dump backtrace,二是会等到下次调用malloc 或者 free时才会触发。所以如果发送信号后没有产生trace文件,请继续针对调试的进程做测试。
举例:
setprop libc.debug.malloc.options backtrace=5
3.backtrace_enable_on_signal[=MAX_FRAMES]
使能这个选项,通过给进程发送信号 45,可以动态开启和关闭backtrace 功能。
4.backtrace_dump_on_exit
进程退出后自动dump trace 文件,得到的dump文件为 $pid.exit.txt 结尾
5.backtrace_dump_prefix
trace dump的路径,如果需要放置其他目录,如/sdcard/heap, 则dump的文件路径为/sdcard/heap.$PID.txt
6.leak_track
程序结束后,如果有未free的指针,logcat中会打印出来
7.record_allocs[=TOTAL_ENTRIES],该选项很占内存,建议不开启
对进程中使用malloc, calloc, realloc 的地方进行记录,打印的格式如下
Threadid: action pointer size
471: malloc 0x72e00330c0 6
471: realloc 0x72e0005220 0x72e00330c0 12
471: free 0x72e012fcc0
471: free 0x72e0005220
471: malloc 0x72e01ade40 56
471: malloc 0x72e00330c0 6
注意,最大记录8,000,000 条