内存管理-65-内存诊断-1-malloc debug


一、README.md翻译

注: 本文翻译自A12的 android/bionic/libc/malloc_debug/README.md

1. Malloc Debug

Malloc debug 是一种用于调试 native 内存问题的方法。它可以帮助检测内存破坏、内存泄漏以及 use-after-free 问题。

本文档描述如何在 Android N 及更高版本中启用该功能(见“示例”章节)。

旧版本 Android 的 malloc debug 文档见:[README_marshmallow_and_earlier.md]

启用 malloc debug 后,会通过增加一层 shim 来替换常规分配调用。被替换的调用包括:

* `malloc`
* `free`
* `calloc`
* `realloc`
* `posix_memalign` //分配地址对齐的内存buffer
* `memalign`
* `aligned_alloc`
* `malloc_usable_size`

在 32 位系统上,还会替换以下两个已弃用函数:

* `pvalloc`
* `valloc`

库检测到的任何错误都会输出到日志中。

注意:从 Android P 开始,`realloc` 的行为有一个小变化。此前,当 `realloc` 从较大尺寸缩小到较小尺寸时,不会更新与该分配相关的回溯信息。从 P 开始,每一次 `realloc` 调用都会更新该指针对应的回溯信息,无论返回指针是否发生变化。


1.1 控制 Malloc Debug 行为

Malloc debug 由独立选项控制。每个选项既可单独启用,也可与其它选项组合启用。所有选项都可以两两组合。


1.2 选项说明

(1) ### front_guard[=SIZE_BYTES]

在已分配数据之前放置一个小缓冲区(前保护区)。用于检测发生在原分配区域之前的内存破坏。首次分配时,前保护区会写入固定模式(0xaa)。释放该分配时会检查该保护区是否被修改。如果前保护区任意字节被改写,会在日志中报告错误并指出变化的字节。

如果同时启用了 backtrace 选项,错误消息还会包含分配点回溯。

若设置 `SIZE_BYTES`,表示保护区字节数。默认 32 字节,最大 16384 字节。为了保证返回地址对齐,`SIZE_BYTES` 会被补齐:在 32 位系统补齐到 8 字节倍数,在 64 位系统补齐到 16 字节倍数。

该选项会给所有分配添加一个特殊头部,包含保护区和原始分配信息。

错误示例:

    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)

(2) ### rear_guard[=SIZE_BYTES]

在已分配数据之后放置一个小缓冲区(后保护区)。用于检测发生在原分配区域之后的内存破坏。首次分配时,后保护区会写入固定模式(0xbb)。释放该分配时####会检查该保护区是否被修改。如果后保护区任意字节被改写,会在日志中报告错误并指出变化的字节。

若设置 `SIZE_BYTES`,表示保护区字节数。默认 32 字节,最大 16384 字节。

该选项会给所有分配添加一个特殊头部,包含原始分配信息。

错误示例:

    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)

(3) ### guard[=SIZE_BYTES]

为所有分配同时启用前保护区和后保护区。若设置 `SIZE_BYTES`,表示前后保护区的字节数。默认 32 字节,最大 16384 字节。

(4) ### backtrace[=MAX_FRAMES]

启用对每个分配点的回溯采集。该选项会显著降低分配性能(大约一个数量级)。如果系统变慢,可减少回溯帧数上限来提升速度。

注意:malloc backtrace 库内部产生的帧不会被记录。

若设置 `MAX_FRAMES`,表示回溯最大帧数。默认 16,最大 256。

在 P 之前,该选项会给所有分配添加特殊头部,保存回溯和原始分配信息;P 及之后不再添加该特殊头部。

从 P 开始,该选项还支持在进程收到信号 `SIGRTMAX - 17`(大多数 Android 设备上是 47)时将 backtrace heap 数据转储到文件。转储格式与 `am dumpheap -n` 相同。默认写入:`/data/local/tmp/backtrace_heap.**PID**.txt`. 这对运行时间较长且不是由 zygote 派生的纯 native 可执行进程很有用。

注意:收到信号后,不会立刻转储;会在下一次 `malloc/free` 发生时才执行转储。

(5) ### backtrace_enable_on_signal[=MAX_FRAMES]

启用分配点回溯采集,但采集状态会在进程收到 `SIGRTMAX - 19`(大多数设备上是 45)时切换。当该选项单独使用时,初始为“关闭采集”,收到信号后才开启。若同时设置 `backtrace` 与本选项,则初始为“开启采集”,收到信号后切换状态。

若设置 `MAX_FRAMES`,表示回溯最大帧数。默认 16,最大 256。

在 P 之前,该选项会给所有分配添加特殊头部;P 及之后不再添加。

(6) ### backtrace_dump_on_exit

从 P 开始,当启用了 `backtrace` 后,程序退出时会把 backtrace heap 数据转储到文件。如果未启用 `backtrace`,该选项无效。默认文件名:`/data/local/tmp/backtrace_heap.**PID**.exit.txt`

可通过 `backtrace_dump_prefix` 修改文件前缀。

(7) ### backtrace_dump_prefix

从 P 开始,当启用了任一 backtrace 相关选项后,该选项用于设置转储文件名前缀:
- 收到 `SIGRTMAX - 17` 时
- 或程序退出且设置了 `backtrace_dump_on_exit` 时

默认前缀:`/data/local/tmp/backtrace_heap`

若修改此前缀:
- 信号触发文件名为 `backtrace_dump_prefix.**PID**.txt`
- 退出触发文件名为 `backtrace_dump_prefix.**PID**.exit.txt`

(8) ### backtrace_full

从 Q 开始,只要采集回溯,就使用一种更彻底的回溯算法,可跨 Java 帧展开。该模式比普通回溯更慢。

(9) ### fill_on_alloc[=MAX_FILLED_BYTES]

除 `calloc` 外的任何分配函数,都会把分配内容填充为 `0xeb`。当 `realloc` 扩容时,超出原可用大小的新区域也会被填充为 `0xeb`。

若设置 `MAX_FILLED_BYTES`,则仅填充分配块前 N 字节。默认填充整个分配块。

(10) ### fill_on_free[=MAX_FILLED_BYTES]

释放分配时,将其内容填充为 `0xef`。

若设置 `MAX_FILLED_BYTES`,则仅填充前 N 字节。默认填充整个分配块。

(11) ### fill[=MAX_FILLED_BYTES]

同时启用 `fill_on_alloc` 与 `fill_on_free`。

若设置 `MAX_FILLED_BYTES`,则仅填充前 N 字节。默认填充整个分配块。

(12) ### expand_alloc[=EXPAND_BYTES]

每次分配都额外多分配一些字节。若设置数值,表示扩展字节数。默认 16 字节,最大 16384 字节。

(13) free_track[=ALLOCATION_COUNT]

当指针被释放时,不立即真正释放内存,而是加入“已释放分配”列表。加入列表时会:
- 将整个分配填充为 `0xef`
- 记录释放时回溯

该回溯记录独立于 `backtrace` 选项;启用本选项时会自动生效。默认最多记录 16 帧,可通过 `free_track_backtrace_num_frames` 调整,设为 0 可完全关闭。

列表满时,会移除一个旧条目并检查其内容在入表后是否被修改。程序退出时,会校验列表中剩余所有条目。

若设置 `ALLOCATION_COUNT`,表示列表最大条目数。默认记录 100 个,最大 16384。

在 P 之前,该选项会给所有分配添加特殊头部;P 及之后不再添加。

错误示例:

    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

此外,如果某个带特殊头部的分配在校验前头部已损坏,还可能出现如下错误:

    04-15 12:00:31.604  7412  7412 E malloc_debug: +++ ALLOCATION 0x12345678 HAS CORRUPTED HEADER TAG 0x1cc7dc00 AFTER FREE

(14) ### free_track_backtrace_num_frames[=MAX_FRAMES]

仅在设置了 `free_track` 时有意义。用于指定释放时采集多少回溯帧。若设置 `MAX_FRAMES`,表示采集帧数。若设为 0,则释放时不采集回溯。默认 16,最大 256。

(15) ### leak_track

跟踪所有仍存活的分配。程序退出时,会把所有存活分配输出到日志。若启用了 `backtrace`,日志还会包含泄漏分配的回溯。
该选项不适合全局启用,因为很多程序在退出前并不会释放全部内存。

在 P 之前,该选项会给所有分配添加特殊头部;P 及之后不再添加。

日志中的泄漏示例:

    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

(16) ### record_allocs[=TOTAL_ENTRIES]

跟踪所有线程上的每一次分配/释放,并在收到 `SIGRTMAX - 18`(大多数设备上是 46)时转储到文件。

若设置 `TOTAL_ENTRIES`,表示最多保留多少条分配/释放记录。达到上限后,后续分配/释放将不再记录。默认 8,000,000,最大 50,000,000。

收到信号并把当前记录写入文件后,已有记录会被清空。在转储期间发生的分配/释放会被忽略。

**注意**:该选项从 Android O 开始可用。

分配数据为可读文本格式。每行以 `gettid()` 返回的 `THREAD_ID` 开头,即执行该分配/释放操作的线程。新线程创建时不会插入特殊行;线程结束时会写入一条特殊记录。

线程结束行格式:

**THREAD_ID**: thread_done 0x0

示例:

187: thread_done 0x0

以下是不同调用在文件中的记录格式:

pointer = malloc(size)

**THREAD_ID**: malloc pointer size

示例:

186: malloc 0xb6038060 20

free(pointer)

**THREAD_ID**: free pointer

示例:

186: free 0xb6038060

pointer = calloc(nmemb, size)

**THREAD_ID**: calloc pointer nmemb size

示例:

186: calloc 0xb609f080 32 4

new_pointer = realloc(old_pointer, size)

**THREAD_ID**: realloc new_pointer old_pointer size

示例:

186: realloc 0xb609f080 0xb603e9a0 12

pointer = memalign(alignment, size)

**THREAD_ID**: memalign pointer alignment size

pointer = aligned_alloc(alignment, size)

**THREAD_ID**: memalign pointer alignment size

posix_memalign(&pointer, alignment, size)

**THREAD_ID**: memalign pointer alignment size

示例:

186: memalign 0x85423660 16 104

pointer = valloc(size)

**THREAD_ID**: memalign pointer 4096 size

示例:

186: memalign 0x85423660 4096 112

pointer = pvalloc(size)

**THREAD_ID**: memalign pointer 4096 <b>SIZE_ROUNDED_UP_TO_4096</b>

示例:

186: memalign 0x85423660 4096 8192

(17) ### record_allocs_file[=FILE_NAME]

仅在设置 `record_allocs` 时有意义。用于指定记录数据写入的文件。若设置 `FILE_NAME`,记录数据会写到该位置。

**注意**:该选项从 Android O 开始可用。

(18) ### verify_pointers

跟踪所有存活分配,用于判断是否使用了不存在的指针。这是一个轻量级校验方式,可验证传给 `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`。

**注意**:该选项从 Android P 开始可用。

(19) ### abort_on_error

当 malloc debug 检测到错误时,输出错误日志后立即 `abort`。

**注意**:如果启用了 `leak_track`,则进程退出时即使检测到泄漏也不会 abort。

(20) ### verbose

从 Android Q 开始,所有 info 级别消息默认关闭。例如在 Android P 及更早版本,启用 malloc debug 会看到:####

    08-16 15:54:16.060 26947 26947 I libc    : /system/bin/app_process64: malloc debug enabled

在 Android Q 中该消息默认不显示,因为这些 info 消息会拖慢进程启动。如果你希望重新开启这些消息,请加上 `verbose` 选项。所有 “Run XXX” 提示消息也会被静默,除非指定 `verbose`。例如下面这类消息默认不再显示:

    09-10 01:03:50.070   557   557 I malloc_debug: /system/bin/audioserver: Run: 'kill -47 557' to dump the backtrace.


1.3 附加错误

日志中还可能出现其他几类错误。

### 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`。

例如如下消息:

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

表示应用调用了 `realloc`,但传入的是已释放指针。

(21) ### 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`)收到的指针要么不是已分配内存,要么该指针关联内存已损坏。

与其他错误一样,括号中的函数名就是收到坏指针的函数。


2. Backtrace Heap Dump 格式

本节描述 backtrace heap dump 的格式。该数据由 `am dumpheap -n` 产生,或从 P 开始通过信号触发/进程退出触发产生。

文件头如下:

    Android Native Heap Dump v1.0

    Total memory: XXXX
    Allocation records: YYYY
    Backtrace size: ZZZZ

`Total memory` 是当前所有存活分配的总内存。
`Allocation records` 是分配记录总数。
`Backtrace size` 是回溯可包含的最大帧数。

头部后有两部分:第一部分是分配记录,第二部分是 map 数据。

分配记录格式:

z ZYGOTE_CHILD_ALLOC sz ALLOCATION_SIZE num NUM_ALLOCATIONS bt FRAMES

`ZYGOTE_CHILD_ALLOC` 为 0 或 1。
- 0:由 zygote 进程分配,或由非 zygote 派生进程分配
- 1:由应用在从 zygote fork 后分配

`ALLOCATION_SIZE` 为分配大小。
`NUM_ALLOCATIONS` 为同尺寸且同回溯的分配数量。
`FRAMES` 为该分配回溯的指令地址列表。

示例:

z 0 sz 400 num 1 bt 0000a230 0000b500
z 1 sz 500 num 3 bt 0000b000 0000c000

第一条记录表示:由 zygote 创建、大小 400、该回溯/尺寸组合出现 1 次,回溯为 0xa230、0xb500。
第二条记录表示:由 zygote 派生应用创建、大小 500、该回溯/尺寸组合出现 3 次,回溯为 0xb000、0xc000。

最后一部分是进程 map 数据:

MAPS
7fe9181000-7fe91a2000 rw-p 00000000 00:00 0 /system/lib/libc.so
.
.
.
END

map 数据即 `/proc/PID/maps` 输出,可用于符号化回溯帧。

该文件目前有多个版本:

Android P 产出 v1.1:

Android Native Heap Dump v1.1

v1.0 与 v1.1 的唯一区别:v1.1 中 `NUM_ALLOCATIONS` 总是准确。旧版 malloc debug 在某些情况下会写入不正确的 `NUM_ALLOCATIONS`。因此对于 v1.0,应将 `NUM_ALLOCATIONS` 视为始终为 1。

Android Q 引入 v1.2,新头部如下:

Android Native Heap Dump v1.2

Build fingerprint: 'google/taimen/taimen:8.1.0/OPM2.171026.006.C1/4769658:user/release-keys'

新增 fingerprint 行来自属性 `ro.build.fingerprint`。

新版本不再对回溯地址做前导 0 填充。
在 v1.0/v1.1 中:

z 0 sz 400 num 1 bt 0000a230 0000b500

在 v1.2 中:

z 0 sz 400 num 1 bt a230 b500

另外,当使用新选项 `backtrace_full` 时,每条回溯记录后会增加一行:

bt_info {"MAP_NAME" RELATIVE_TO_MAP_PC "FUNCTION_NAME" FUNCTION_OFFSET} ...

回溯中每个 pc 对应一组花括号元素。

`MAP_NAME`:该 pc 所在 map 名称;若无有效 map 名称则为空。
`RELATIVE_TO_MAP_PC`:相对该 map 的十六进制 pc 偏移。
`FUNCTION_NAME`:该 pc 对应函数名;若无有效函数名则为空。
`FUNCTION_OFFSET`:相对函数起始地址的十六进制偏移;若函数名为空则恒为 0。

示例:

z 0 sz 400 num 1 bt a2a0 b510
bt_info {"/system/libc.so" 2a0 "abort" 24} {"/system/libutils.so" 510 "" 0}

该示例中:
- 第一帧 pc=0xa2a0,位于 `/system/libc.so`(起始 0xa000),相对 pc=0x2a0,函数为 `abort + 0x24`。
- 第二帧 pc=0xb510,位于 `/system/libutils.so`(起始 0xb000),相对 pc=0x510,函数未知。

可使用该工具可视化数据:
[native_heapdump_viewer.py](https://android.googlesource.com/platform/development/+/master/scripts/native_heapdump_viewer.py)


3. 示例

3.1 平台开发者

为所有进程启用全部分配回溯跟踪:

    adb shell stop
    adb shell setprop libc.debug.malloc.options backtrace
    adb shell start

为指定进程(ls)启用回溯跟踪:

    adb shell setprop libc.debug.malloc.options backtrace
    adb shell setprop libc.debug.malloc.program ls
    adb shell ls

为 zygote 及其派生进程启用回溯跟踪:

    adb shell stop
    adb shell setprop libc.debug.malloc.program app_process
    adb shell setprop libc.debug.malloc.options backtrace
    adb shell start

启用多个选项(backtrace 和 guard):

    adb shell stop
    adb shell setprop libc.debug.malloc.options "\"backtrace guard\""
    adb shell start

注意:`adb shell` 命令中两层引号是必须的。外层引号用于主机 shell,确保内层引号能传到设备,从而让 `backtrace guard` 作为一个参数传入。

通过环境变量启用 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

通过环境变量启用 malloc debug(Android O 及之后):

    adb shell
    # export LIBC_DEBUG_MALLOC_OPTIONS=backtrace
    # ls

从该 shell 启动的任何进程都会启用带 backtrace 的 malloc debug。

    adb shell stop
    adb shell setprop libc.debug.malloc.options backtrace
    adb shell start
    adb shell am dumpheap -n <PID_TO_DUMP> /data/local/tmp/heap.txt

也可使用 `backtrace_enable_on_signal`,但显然必须先通过信号开启采集,否则转储文件不会包含数据。


3.2 应用开发者

应用开发者应参考 NDK 文档中的 [wrap.sh](https://developer.android.com/ndk/guides/wrap-script.html) 在 Android O 及之后的非 root 设备上使用 malloc debug。

如果设备已 root,可为指定程序/应用启用 malloc debug(Android O+):

    adb shell setprop wrap.<APP> '"LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper"'

如果需要通过该方式启用多个选项,可这样写:

    adb shell setprop wrap.<APP> '"LIBC_DEBUG_MALLOC_OPTIONS=backtrace\ leak_track\ fill logwrapper"'

例如为 Google 搜索框启用 malloc debug(Android O+):

    adb shell setprop wrap.com.google.android.googlequicksearchbox '"LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper"'
    adb shell am force-stop com.google.android.googlequicksearchbox

如果设置了多个选项但应用看起来没有正常启动,请查看 logcat 是否有如下消息:(`adb logcat -d | grep "malloc debug"`)

    08-16 15:54:16.060 26947 26947 I libc    : /system/bin/app_process64: malloc debug enabled

若未看到该消息,说明 wrap 属性设置不正确。执行:

    adb shell getprop | grep wrap

并确认空格都已正确转义。

注意:在 Android O 之前,属性名长度上限是 32。这意味着用应用名创建 wrap 属性时,可能需要截断名称。从 O 开始,属性名长度大幅提升,通常不再需要截断。

检测应用运行时泄漏:

    adb shell dumpsys meminfo --unreachable <PID_OF_APP>

若不启用 malloc debug,该命令只能告诉你是否检测到泄漏,不能指出泄漏位置。如果先为应用启用带 backtrace 的 malloc debug,再执行 dumpsys,就可以得到分配位置的回溯。

为了让应用回溯更有价值,建议保留应用共享库符号而不是 strip。这样可以直接看到泄漏位置,而无需借助 `ndk-stack` 之类工具。


3.3 分析 heap dump

分析 dumpheap 产生的数据,运行:[development/scripts/native_heapdump_viewer.py](https://android.googlesource.com/platform/development/+/master/scripts/native_heapdump_viewer.py)

为了让脚本正确符号化文件中的调用栈,请确保在构建该镜像的源码树中执行该脚本。

采集、传输并分析一次 dump:

    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

目前该脚本会按设备上 `.so` 的路径,在给定目录中查找符号。例如设备上路径是 `/data/app/.../lib/arm/libx.so`,
则按上面命令,本地需放在`/some/path/to/symbols/data/app/.../lib/arm/libx.so`。也就是说:你需要在 symbols 目录中镜像应用目录结构。


二、补充

Android 里 bionic 的 malloc debug,本质上是一个“调试版分配器 shim(垫片)”。它在运行时替换正常的 malloc()/free()/realloc()/calloc() 等接口,在每次分配和释放时额外做校验、填充、回溯记录和泄漏跟踪,用来定位 native heap 问题,而不是提升性能。

1. 它的主要作用可以概括为这几类:

(1) 检测堆越界
通过 front_guard、rear_guard、guard 在分配块前后加保护区,释放时检查是否被改写,用来发现前向/后向越界写。

(2) 检测 use-after-free 和 double free
通过 free_track、fill_on_free、verify_pointers 记录已释放块、填充固定字节模式、校验指针合法性,发现释放后继续写、重复释放、无效指针传入 free/realloc/malloc_usable_size 等问题。

(3) 检测内存泄漏
通过 leak_track 跟踪仍然存活的分配,进程退出时输出未释放块;如果同时开了 backtrace,还能看到泄漏分配点调用栈。

(4) 记录分配调用栈
通过 backtrace、backtrace_enable_on_signal 为分配点保存 native backtrace,便于把“谁分配了这块内存”定位到具体函数。

(5) 导出 native heap dump
开启 backtrace 后,可以通过信号或 am dumpheap -n 产出 native heap dump,后续再用分析脚本做符号化和聚合。

可以把它理解成:它不是一个独立“工具”,而是 Android 原生内存诊断体系里比较底层的一层能力。


2. 哪些内存检测工具/能力是基于它实现的:

严格来说,直接“基于 bionic malloc debug” 的并不多,更多是“利用它提供的数据”:

(1) am dumpheap -n

这是最典型的一个。它导出的 native heap dump 格式与 malloc debug 的 backtrace heap dump 是同一体系,尤其在启用 backtrace 后,两者直接联动。

(2) native_heapdump_viewer.py

这是官方脚本,用来分析 am dumpheap -n 或 malloc debug 导出的 native heap dump。它不是分配器本身,但依赖 malloc debug/backtrace dump 产生的数据格式。

(3) wrap.sh + LIBC_DEBUG_MALLOC_OPTIONS

这不是单独分析器,而是 app 侧启用 malloc debug 的常见封装方式。很多 native 内存问题排查流程,本质上就是通过它把 malloc debug 注入到目标进程。

(4) dumpsys meminfo --unreachable

这个不算“基于 malloc debug 实现”,它主要基于 libmemunreachable。但如果同时启用了 malloc debug 的 backtrace,你能拿到更有用的分配栈信息,所以两者常被配合使用。


3. 哪些常见工具不是基于它实现的

这部分很重要,容易混淆:

(1) heapprofd

不是基于 malloc debug。它是 Perfetto/Android profiling 体系下的独立堆分析器。

(2) simpleperf

不是基于 malloc debug。它是采样 profiler。

(3) ASan/HWASan/GWP-ASan

不是基于 malloc debug。它们是独立的内存错误检测机制。

(4) Scudo

不是 malloc debug。Scudo 是分配器/强化防护机制,和 malloc debug 属于不同层面的东西。

(5) libmemunreachable

不是基于 malloc debug,但可与其配合。


3. 小结

bionic 的 malloc debug 的作用,是在运行时接管 native 内存分配接口,为越界、UAF、double free、泄漏、坏指针、分配栈追踪提供检测能力。

直接依赖它的典型能力主要是 native heap dump 链路,比如 am dumpheap -n 和 native_heapdump_viewer.py;而 heapprofd、simpleperf、ASan/HWASan、libmemunreachable` 这些常见工具并不是建立在 malloc debug 之上的。

 

总结: 看起来malloc debug用处不大。

 

posted on 2026-04-17 16:26  Hello-World3  阅读(4)  评论(0)    收藏  举报

导航