android 如何分析应用的内存(九)
接上文,在前面文章中,介绍了bionic库提供的各种功能,其中包括:
- 自定义的malloc
- malloc hook
- malloc debug
接下来,介绍的是bionic库提供的libc回调功能,它可以通过代码获得所有的内存分配情况 。
前情提要:在使用libc回调的时候,依然需要打开malloc的调试功能,见上一篇文章中的如何启用malloc debug
android 如何分析应用的内存(八)
libc回调
无论是Android 7.0之前,还是Android 7.0之后,bionic库都提供了一个回调接口,用于获取对应的分配信息。如下:
extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory, size_t* backtrace_size);
其中,info包含所有的分配信息。overall_size是info的大小。而info_size是表示info中的每个项的大小,如果为0,则表示没有内存被跟踪。total_memory是当前时刻所有已经分配的memory大小,它不会包含libc库自己使用的内存大小。backtrace_size是最大的栈帧数。
info的格式如下:
size_t size_of_original_allocation ## 原始分配的大小,注意,这个数值的31位为1时,表示是从zygote进程的子进程分配的。比如应用程序
size_t num_allocations ## 分配了多少个,即具有相同调用栈和分配大小的个数。在android 7.0时,这个值被错误的设置成了堆栈指针的个数。
uintptr_t pc1 ## 分配时对应堆栈的pc指针
uintptr_t pc2
uintptr_t pc3
.
.
.
有多少个分配,则可以通过overall_size除以info_size获得。pc1,pc2,pc3的个数则由backtrace_size决定。如果堆栈指针个数小于最大值,剩下的位置为0.
注意:对于32位系统,size_t和uintptr_t 都是4个字节。而64位系统则为8个字节
使用完get_malloc_leak_info之后,需要调用
extern "C" void free_malloc_leak_info(uint8_t* info);
将返回的info进行释放。
libc回调举例
首先在必要的位置,声明函数如下:
extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory, size_t* backtrace_size);
extern "C" void free_malloc_leak_info(uint8_t* info);
然后在必要的地方,调用对应的函数,如下:
uint8_t* info = nullptr;
size_t overall_size = 0;
size_t info_size = 0;
size_t total_memory= 0;
size_t backtrace_size = 0;get_malloc_leak_info(&info,&overall_size,&info_size,&total_memory,&backtrace_size);
需要注意的是,上面两个函数的定义在libc库中,因此在编译的时候,需要链接libc库。针对Cmake和Android.mk增加如下代码:
## cmake代码
set(LINK_FLAGS "-lc")
set(CMAKE_SHARED_LINKER_FLAGS "${LINK_FLAGS}")
## makefile代码
LOCAL_LDLIBS := -lc
在运行的时候,可能提示,没有libc++_shared.so库,因此将这个标准库放入即可。如下图
然后将info中的数据输出。如下:
ALOGD("overall_size %zu info_size %zu",overall_size,info_size);
for(size_t i=0;i<overall_size/info_size ;i++){//获取第i个分配项auto buffer = info + info_size*i;//获取正确的大小,处理掉第31位auto size = *(size_t *)buffer & ~(0x1 << 31);ALOGD("#-------这是第%zu个分配,原始分配大小%zu,分配个数%zu",i,size,*(size_t *)(buffer+sizeof(size_t)));//获取堆栈头指针auto pc = (uintptr_t *)(buffer+sizeof(size_t)*2);//定义一个缓存位置,并初始化char out[1024*backtrace_size];for(size_t l=0;l<1024*backtrace_size;++l){out[l] = '\0';}//解析堆栈指针,并将解析结果放入out中dumpBacktraceIndex(out,(intptr_t *)pc,backtrace_size);//输出解析结果到log中std::ostringstream tempString;for(size_t k = 0;k<1024*backtrace_size;k++){if(out[k] != '$'){tempString << out[k];}else{ALOGD("%s",tempString.str().c_str());tempString.str("");tempString.clear();}}ALOGD("#-------这是第%zu个分配,完成",i);
}
其中,dumpBacktraceIndex函数,即我们在
android 如何分析应用的内存(六)——自定义malloc中提及的Debug类中的函数。
如下:
void dumpBacktraceIndex(char *out, intptr_t *buffer, size_t count) {for (size_t idx = 0; idx < count; ++idx) {intptr_t addr = buffer[idx];const char *symbol = " ";const char *dlfile = " ";void * baseA = NULL;char temp[50];Dl_info info;info.dli_fbase = NULL;if (dladdr((void *) addr, &info)) {if (info.dli_sname) {symbol = info.dli_sname;}if (info.dli_fname) {dlfile = info.dli_fname;}if(info.dli_fbase){baseA = info.dli_fbase;}} else {strcat(out, "# ");strcat(out,"$");continue;}memset(temp, 0, sizeof(temp));sprintf(temp, "%zu", idx);strcat(out, "#");strcat(out, temp);strcat(out, ": ");memset(temp, 0, sizeof(temp));sprintf(temp, "%p", (void *) addr);strcat(out, temp);strcat(out, " ");memset(temp, 0, sizeof(temp));sprintf(temp, "%p", (void *) ((long)addr - (long )baseA));strcat(out, temp);strcat(out, " ");strcat(out, symbol);strcat(out, " ");strcat(out, dlfile);strcat(out, "$");}
}
libc回调举例
为了测试libc是否能够正常工作,在适当的地方故意做如下操作:
auto *p = new int[41024*4];
p[1] = 12354;
auto *p1 = new int;
*p1 = 2345678;
然后触发libc回调,得到如下的log。如下图
除此之外,还能看到其他的分配,如下图
实际使用中,可通过监控total_memory来判断是否存在内存泄漏。然后再分析info中的所有分配,来查看具体的泄漏点。同时,可以对分配的数据,进行大小排序,而达到查看的目的。
事实上,部分Android的内存分析工具,就是通过libc回调实现的。
注意:上诉所有例子,都是在Android 8.1上测试。同时,存在部分国内的Android版本,无法使用malloc调试和libc回调。此时,若要进行内存分析,可更换Android 设备。
至此,libc回调介绍完毕。
除了通过libc回调收集内存分配信息以外,还可以通过malloc info收集以及libmemunreachable收集,将会在下一小节中介绍