Android 中malloc_debug 使用详解

版本基于:Android R

0. 前言

在上一篇博文《Android 中malloc_debug 原理详解》中详细剖析了 malloc_debug 的原理,本文将在此基础上详细描述 malloc_debug 的使用方法。

1.  使能 malloc_debug

1.1 使用环境变量 

在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

1.2 使用prop

    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 这个进程生效。

1.2.1 多个选项

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 ...' 的效果,这样可以作为设备端的单一的参数。

2. malloc_debug 的 options

在介绍 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 获取,以字符串的形式配置,而在代码中以对应的标识值与其对应。

2.1 内存边界核查

2.1.1 front_guard[=SIZE_BYTES]

用以使能一个小的 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,确认是否损坏

2.1.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)

2.1.3 guard[=SIZE_BYTES]

同时分配 front guard 和 rear guard。

SIZE_BYTES 代表两个 guard 空间大小,默认值为32 bytes,最大为16384 字节。

2.2 使能调用栈功能

2.2.1 backtrace[=MAX_FRAMES]

使能抓取每一个分配点的调用栈信息的功能。

这个选项使能,将分配速度减慢一个数量级。如果系统因为这个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 发生。

2.2.2 backtrace_enable_on_signal[=MAX_FRAMES]

使能抓取每一个分配点的调用栈信息的功能。

当进程收到信号 SIGRTMAX-19(大部分android 设备是45)时触发抓取。当这个 option 单独使用的时候,调用栈抓取默认是不开启的,在接受到 signal 之后才开始。如果与backtrace 选项一起设置,那么调用栈抓取在收到 signal之后使能。

MAX_FRAMES 表示抓取的调用栈最大数目,默认值为 16,最大值为 256.

在 Android P 之前如果使能这个option,会在待分配的空间前加上一个特殊的header,包含调用栈和原始分配的信息。在之后这个 option 不会在添加特殊header。

2.2.3 backtrace_dump_on_exit

从 P 版本开始,当backtrace 选项使能,这个option 会导致在进程退出才将dump 的堆信息dump 到一个文件。当backtrace 选项没有使能,这个option 不做任何事情。

dump 的默认文件路径是:/data/local/tmp/backtrace_head.**PID**.exit.txt 

文件路径可以使用 backtrace_dump_prefix 选项修改,见第 2.2.4 节。

2.2.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 中

2.2.5 backtrace_full

从 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()。

2.3 填充分配空间

2.3.1 fill_on_alloc[=MAX_FILLED_BYTES]

除了 calloc函数,其他的常规的分配,都会使用0xeb 填充分配空间。当使用realloc 分配一个更大的空间,扩大的空间也会填充 0xeb

如果 MAX_FILLED_BYTES 指定,那么按照该值只填充指定空间大小。默认填充整个分配空间。

2.3.2 fill_on_free[=MAX_FILLED_BYTES]

当分配空间被释放的时候,用 0xef 填充需要释放的空间。

如果 MAX_FILLED_BYTES 指定,那么按照该值只填充指定空间大小。默认填充整个分配空间。

2.3.3 fill[=MAX_FILLED_BYTES]

同时使能 fill_on_alloc 和 fill_on_free

2.3.4 expand_alloc[=EXPADN_BYTES]

为每一次分配扩充额外数量的空间。如果EXPAND_BYTES 设定,将按照该值扩充分配空间,默认值为 16字节,最大为16384 字节。

2.4 释放内存空间

2.4.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.

在 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

2.4.2 free_track_backtrace_num_frames[=MAX_FRAMES] 

这个option 只针对上面 free_track 被设定时生效。表示当一个分配空间被释放,有多少个 frames 的调用栈可以抓取。

MAX_FRAMES 表示被抓取的 frames 的数量,如果设为0, 表示在分配空间被释放时,不会抓取任何调用栈。默认值为16,最大可以设置到 256.

2.5 分配释放检测

2.5.1 leak_track

泄漏检测,在程序结束的时候,会将所有还活着的分配空间的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

2.5.2 record_allocs[=TOTAL_ENTRIES]

保留每个线程的每一次 分配 / 释放 的 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 及以后版本。

2.5.3 record_allocs_file[=FILE_NAME]

只有在 record_allocs 这个option 使用时生效,用以指定记录dump 的文件名。

注意,这个option 用以Android O 及以后版本。

2.5.4 verify_pointers

跟踪所有活着的 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 及以后版本。

2.5.5 abort_on_error

当malloc debug 检测到error 时,在发送错误 log 消息之后终止。

注意,如果 lead_track 被使能,当进程退出时检测出泄漏时不会产生 abort。

2.6 verbose

从 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.

3. 其他错误日志

3.1 Use After 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

注意圆括号里面表示的操作,这里是free,表示 after free 之后又进行了 free。

    04-15 12:00:31.304  7412  7412 E malloc_debug: +++ ALLOCATION 0x12345678 USED AFTER FREE (realloc)

这种表示 after free 之后指针被用来进行了realloc 操作。

3.2 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 这个函数被一个指针调用,而这个指针不是用来分配内存,或指针指向的内存已经损坏。

圆括号里面的函数被一个坏的指针调用。

4. native_heapdump_viewer.py 使用

该工具可以将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/ 下。

 相关博文:

《Android 中malloc_debug 原理详解》

猜你喜欢

转载自blog.csdn.net/jingerppp/article/details/129205767