版本基于:Android R
在上一篇博文《Android 中malloc_debug 原理详解》中详细剖析了 malloc_debug 的原理,本文将在此基础上详细描述 malloc_debug 的使用方法。
在Android O 之前:
adb shell
# setprop libc.debug.malloc.env_enabled 1
# setprop libc.debug.malloc.options backtrace
# export LIBC_DEBUG_MALLOC_ENABLE=1
# ls
在Android O 及之后:
adb shell
# export LIBC_DEBUG_MALLOC_OPTIONS=backtrace
# ls
adb shell stop
adb shell setprop libc.debug.malloc.options backtrace
adb shell start
adb shell stop
adb shell setprop libc.debug.malloc.program app_process
adb shell setprop libc.debug.malloc.options backtrace
adb shell start
当没有配置环境变量的时候,也可以通过 prop 来启动 malloc_debug。即libc.debug.malloc.options 这个 prop。
另一个 prop libc.debug.malloc.program 是在 libc.debug.malloc.options 指定的前提下,限制 malloc_debug 只针对一些特殊的进程,例如上面指定 app_process,即malloc_debug 只针对 app_process 这个进程生效。
adb shell stop
adb shell setprop libc.debug.malloc.program malloc_debug_test
adb shell setprop libc.debug.malloc.options "\"backtrace front_guard=16 rear_guard=16 backtrace_dump_prefix=/sdcard/Download/heap"\"
adb shell start
对于adb shell 命令来说,两层双引号是必须的,第一层的双引号是为了 host 端使用 shell,用以确保引号里面的数据能传入设备达到 'backtrace front_guard=16 ...' 的效果,这样可以作为设备端的单一的参数。
在介绍 options 之前先来看下 malloc_debug 在代码中的标识值:
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;
constexpr uint64_t FILL_ON_FREE = 0x10;
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;
constexpr uint64_t ABORT_ON_ERROR = 0x800;
constexpr uint64_t VERBOSE = 0x1000;
malloc_debug 的options 解析起源是从环境变量或 prop libc.debug.malloc.options 获取,以字符串的形式配置,而在代码中以对应的标识值与其对应。
用以使能一个小的 buffer 放置在待申请空间之前。这是在原始分配之前的一个查找内存损坏发生的尝试。原文:
Enables a small buffer placed before the allocated data. This is an attempt
to find memory corruption occuring to a region before the original allocation.
在第一个分配内存时,这个 front guard 空间会被写上一个特定模式的数据(0xaa)。当分配的内存被释放,front guard 空间会被用来check 是否已经被修改。如果 front guard 空间任意地方被修改,将会在 log 中产生一个 error 来表示哪些 bytes 被修改。
如果 backtrace 这个选项也被使能,那么 error 信息会包含分配点的调用栈信息。
SIZE_BYTES 表示front guard 空间的字节数,默认是 32 bytes,最大为16384 bytes。这个 SIZE_BYTES 最终会被以 8 字节(32位系统) 或 16 字节(64位系统)填充,以确保分配内存返回时是对齐的。
这个选项会在所有的分配空间前加个特殊的头,包含front guard 和待分配空间的信息。
出错示例:
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)
举例:
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,确认是否损坏
同 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)
同时分配 front guard 和 rear guard。
SIZE_BYTES 代表两个 guard 空间大小,默认值为32 bytes,最大为16384 字节。
使能抓取每一个分配点的调用栈信息的功能。
这个选项使能,将分配速度减慢一个数量级。如果系统因为这个option 使能而运行太慢,将MAX_FRAMES 减小。
需要注意的是,malloc 内部的库函数的调用栈信息是不会被记录的。
MAX_FRAMES 表示抓取的调用栈最大数目,默认值为 16,最大值为 256.
在 Android P 之前如果使能这个option,会在待分配的空间前加上一个特殊的header,包含调用栈和原始分配的信息。在之后这个 option 不会在添加特殊header。
从 P 版本开始,如果这个 option 使能,当进程收到信号 SIGRTMAX-17(大部分android 设备是 47)时会dump 堆数据到一个文件。dump 下来的数据格式与运行 am dumpheap -n 的数据格式相同。默认dump 到文件 /data/local/tmp/backtrace_head.**PID**.txt 中。这对于 不是从 zygote 进程fork 出来的native 进程是很有用的。
需要注意的是,当信号收到时,head 数据不是立即dump,而是等到下一次的 malloc/free 发生。
使能抓取每一个分配点的调用栈信息的功能。
当进程收到信号 SIGRTMAX-19(大部分android 设备是45)时触发抓取。当这个 option 单独使用的时候,调用栈抓取默认是不开启的,在接受到 signal 之后才开始。如果与backtrace 选项一起设置,那么调用栈抓取在收到 signal之后使能。
MAX_FRAMES 表示抓取的调用栈最大数目,默认值为 16,最大值为 256.
在 Android P 之前如果使能这个option,会在待分配的空间前加上一个特殊的header,包含调用栈和原始分配的信息。在之后这个 option 不会在添加特殊header。
从 P 版本开始,当backtrace 选项使能,这个option 会导致在进程退出才将dump 的堆信息dump 到一个文件。当backtrace 选项没有使能,这个option 不做任何事情。
dump 的默认文件路径是:/data/local/tmp/backtrace_head.**PID**.exit.txt
文件路径可以使用 backtrace_dump_prefix 选项修改,见第 2.2.4 节。
从 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 中
从 Q 版本开始,调用栈无论何时抓到,都会用另一个不同的算法能够以Java 帧展开。这将比正常的调用栈更慢。
通过代码可知:
bionic/libc/malloc_debug/malloc_debug.cpp
void BacktraceAndLog() {
if (g_debug->config().options() & BACKTRACE_FULL) {
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()。
除了 calloc函数,其他的常规的分配,都会使用0xeb 填充分配空间。当使用realloc 分配一个更大的空间,扩大的空间也会填充 0xeb。
如果 MAX_FILLED_BYTES 指定,那么按照该值只填充指定空间大小。默认填充整个分配空间。
当分配空间被释放的时候,用 0xef 填充需要释放的空间。
如果 MAX_FILLED_BYTES 指定,那么按照该值只填充指定空间大小。默认填充整个分配空间。
同时使能 fill_on_alloc 和 fill_on_free
为每一次分配扩充额外数量的空间。如果EXPAND_BYTES 设定,将按照该值扩充分配空间,默认值为 16字节,最大为16384 字节。
如果使用该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.
在 Android P 之前如果使能这个option,会在待分配的空间前加上一个特殊的header,包含调用栈和原始分配的信息。在之后这个 option 不会在添加特殊header。
错误示例:
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 之后还在使用。
另外,log 中可能发现另外一种错误消息。如果分配空间有个特殊的 header,而在verfiy 之前,头信息被破坏了,会在log 中出现下面的错误信息:
04-15 12:00:31.604 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 HAS CORRUPTED HEADER TAG 0x1cc7dc00 AFTER FREE
这个option 只针对上面 free_track 被设定时生效。表示当一个分配空间被释放,有多少个 frames 的调用栈可以抓取。
MAX_FRAMES 表示被抓取的 frames 的数量,如果设为0, 表示在分配空间被释放时,不会抓取任何调用栈。默认值为16,最大可以设置到 256.
泄漏检测,在程序结束的时候,会将所有还活着的分配空间的dump 到 log 中。如果 backtrace 的option 使能的话,log 中将包含泄漏分配空间的调用栈。
在 Android P 之前如果使能这个option,会在待分配的空间前加上一个特殊的header,包含调用栈和原始分配的信息。在之后这个 option 不会在添加特殊header。
错误示例:
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
保留每个线程的每一次 分配 / 释放 的 track,当收到信号 SIGRTMAX - 18(大部分android 设备是46) 时,将这些 track dump 到一个文件中。
TOTAL_ENTRIES 表示 分配 /释放 的记录总数。如果达到了 TOTAL_ENTRIES,后面的 分配 / 释放将不再记录。默认值为 8,000,000,最大值可以设到 50,000,000。
当收到信号时,所有的记录都会写到文件中,所有的记录也会被删除。而dump 期间的 分配 / 释放将会被忽略。
这个太影响性能,一般不建议使用。打印格式如下:
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
注意,这个option 用以Android O 及以后版本。
只有在 record_allocs 这个option 使用时生效,用以指定记录dump 的文件名。
注意,这个option 用以Android O 及以后版本。
跟踪所有活着的 allocations 确定是否一个指针不存在了还在被使用。这个option 是一个轻量级的方式,用以核实所有的 free/malloc_usable_size/realloc 调用使用的有效的指针。
错误示例:
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
只有三个函数做这样的核实,free、malloc_usable_size、realloc
注意,这个option 用以Android P 及以后版本。
当malloc debug 检测到error 时,在发送错误 log 消息之后终止。
注意,如果 lead_track 被使能,当进程退出时检测出泄漏时不会产生 abort。
从 Q 版本开始,所有malloc debug 信息将会关掉,例如在android P 中,使能 malloc debug 会在log 中产生这样的消息:
08-16 15:54:16.060 26947 26947 I libc : /system/bin/app_process64: malloc debug enabled
在Android Q 中,这种消息将不会显示,因为这些 info 消息会减慢进程的启动。但是如果希望在log 中看到这样的消息,使用 verbose 这个option。所有的 "Run XXX" 消息也不会显示,除非使用 verbose 这个option。例如:
09-10 01:03:50.070 557 557 I malloc_debug: /system/bin/audioserver: Run: 'kill -47 557' to dump the backtrace.
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
注意圆括号里面表示的操作,这里是free,表示 after free 之后又进行了 free。
04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 USED AFTER FREE (realloc)
这种表示 after free 之后指针被用来进行了realloc 操作。
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 这个函数被一个指针调用,而这个指针不是用来分配内存,或指针指向的内存已经损坏。
圆括号里面的函数被一个坏的指针调用。
该工具可以将dump trace文件通过符号表得到当前未释放内存的指针在代码中的行号。准确性依赖trace保存栈的最大深度。所以最好有两份该文件,分别是不同占用内存时dump 得到的。对比查看哪个指针嫌疑最大,缩小范围继续排查。
python native_heapdump_viewer.py backtrace_heap.7971.exit.txt --symbols ~/Download/symbols --reverse > backtrace_heap.7971.exit.txt.heapout
或者
adb shell am dumpheap -n <PID_TO_DUMP> /data/local/tmp/heap.txt
adb shell pull /data/local/tmp/heap.txt .
python development/scripts/native_heapdump_viewer.py --symbols /some/path/to/symbols/ heap.txt > heap_info.txt
这种情况下py 脚本会从给定的目录中查找 symbols,例如 so 文件在设备中 /system/lib/libxx.so,编译工程应该在 out/target/product/xxx/symbols/system/lib/ 下。
相关博文: