news 2026/7/21 16:57:29

内存泄漏系列专题分析之五:使用malloc_debug定位C/C++ native heap内存泄露

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
内存泄漏系列专题分析之五:使用malloc_debug定位C/C++ native heap内存泄露

【关注我,后续持续新增专题博文,谢谢!!!】

上一篇我们讲了:

内存泄漏系列专题分析之四:Android malloc_debug工具在Camera领域使用中预览卡死的瓶颈限制问题和二次改造

这一篇我们开始讲:内存泄漏系列专题分析之五:使用malloc_debug定位C/C++ native heap内存泄露

目录

一、Malloc Debug配置说明

1.1:常见配置

1.2:malloc debug log日志

1.3:native_heapdump_viewer.py 使用

二、使用Malloc Debug

2.1:打开Malloc Debug

2.2:分析原始dump文件

2.3:native_heapdump_viewer.py 格式化

2.4:解析调用栈

2.5:分析调用栈对应代码

2.6:总结


本文主要介绍使用Google原生Android自带的malloc debug帮助定位C/C++ native heap内存泄露的方法。

一、Malloc Debug配置说明

1.1:常见配置

front_guard[=SIZE_BYTES] :每次调用 malloc ,都在分配的区域之前填充 SIZE_BYTES ,填充内容为 0xaa


rear_guard[=SIZE_BYTES] :每次调用 malloc ,都在指向内存的最后填充 SIZE_BYTES ,填充内容为 0xbb


guard[=SIZE_BYTES] :这个选项包含了 front_guard 和 rear_guard 。在指向内存的前后连 续 SIZE_BYTES 分别填充 0xaa 和 0xbb


backtrace[=MAX_FRAMES] :这 个 optipn s 会 将 内存分配的速度减慢一个数量级 ,MAX_FRAMES 最大 值 256 默认值 16 。 每次在调用 malloc 时 , malloc debug 都会记录 malloc 的调用栈 (trace).

  1. 栈的最大深度为 MAX_FRAMES , MAX_FRAMES 越大 , 对 malloc 的性能影响越大 , 也就越慢。
  2. 当进程收到信号( SIGRTMAX - 17 ) Android 通常该信号值为 47 时, 会触发 malloc debug 的 dump heap trace 功能。 默认 dump 路径在 /data/local/tmp/ backtrace_heap.PID.txt 。
  3. 给进程发信号通过 kill -s 47 PID , 进程收 到信号后并不会马上 dump backtrace , 而是会等到下次调用 malloc 或者 free 时 才会触发。所以如果发送信号后没有产生 trace 文件,请继续针对调试的进程做 测试。
  4. 执行 kill -s 47 PID 之后,系统开始进行等待用户执行 malloc 操作。 举例: setprop libc.debug.malloc.options backtrace=5 得到的 dump 文件为 $pid.txt 结尾


backtrace_enable_on_signal[=MAX_FRAMES] :使能这个选项 , 通过给进程发送信号 45 , 可以动态开启和关闭 backtrace 功能 。


backtrace_dump_on_exit:进程退出后自动 dump trace 文件,得到的 dump 文件为 $pid.exit.txt 结尾


backtrace_dump_prefix:trace dump 的路径 , 如果需要放置其他目录 , 如 /sdcard/heap, 则 dump 的文件路 径为 /sdcard/heap.$PID.txt


leak_track:程序结束后,如果有未 free 的指针, logcat 中会打印出来

12-19 14:54:32.583 7971 7971 E malloc_debug: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 12-19 14:55:02.585 7971 7971 E malloc_debug: +++ androidtest leaked block of size 3072 at 0x736e78f030 (leak 1 of 7) // 内存泄露的 log ,直到程序退出未释放的内存 12-19 14:55:02.585 7971 7971 E malloc_debug: Backtrace at time of allocation: 12-19 14:55:02.585 7971 7971 E malloc_debug: #00 pc 00000000000152b0 /apex/com.android.runtime/lib64/libc_malloc_debug.so (debug_calloc+432) 12-19 14:55:02.585 7971 7971 E malloc_debug: #01 pc 000000000000114c /system/bin/androidtest // 通过 addr2line -e symbols/system/bin/androidtest 000000000000114c 可以得到具体是哪一个指针未释放内存。 12-19 14:55:02.585 7971 7971 E malloc_debug: #02 pc 000000000007d86c /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+108) 12-19 14:55:02.585 7971 7971 E malloc_debug: #03 pc 000000000000104c /system/bin/androidtest 12-19 14:55:02.585 7971 7971 E malloc_debug: #04 pc 00000000000533f4 /apex/com.android.runtime/bin/linker64 12-19 14:55:02.585 7971 7971 E malloc_debug: +++ androidtest leaked block of size 88 at 0x736e631030 (leak 2 of 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 条

1.2:malloc debug log日志

verbose 打开 malloc debug 更多 log ,类似

08-16 15:54:16.060 26947 26947 I libc : /system/bin/app_process64: malloc debug enabled
09-10 01:03:50.070 557 557 I malloc_debug: /system/bin/audioserver: Run: 'kill -47 557' to dump the backtrace.

1.3:native_heapdump_viewer.py 使用

该工具可以将 dump trace 文件通过符号表得到当前 未释放内存的指针在代 码中的行号 。准确性依赖 trace 保存栈的最大深度。所以最好有两份该文件,分 别是不同占用内存时 dump 得到的。对比查看哪个指针嫌疑最大,缩小范围继 续排查。其中 symbols 路径要正确!

二、使用Malloc Debug

本文主要介绍使用Google原生Android自带的malloc debug帮助定位C/C++ native heap内存泄露的方法。

2.1:打开Malloc Debug

针对android.hardware.graphics.composer@2.1-service打开malloc debug

#!/bin/bash

# 需要重启上层让 property 生效
stop

# 指定 debug 参数。可以默认这样写,更多选项参考
# bionic/libc/malloc_debug/README.md
setproplibc.debug.malloc.options"backtrace=16 guard=8 fill_on_free=16"

# 指定要开malloc debug的进程
setproplibc.debug.malloc.programandroid.hardware.graphics.composer@2.1-service

# 进程重启,用libc_deubg.so替换原来的libc.SO库,mallocDebug代码生效
kill -9 $(pidof android.hardware.graphics.composer@2.1-service)
start

# 需要关闭SELinux和目录读写权限
setenforce 0
chmod 777 /data/local/tmp

# 触发Malloc Debug dump,默认会生成如下dump文件
# /data/local/tmp/backtrace\_heap.**PID**.txt
kill -47 $(pidof android.hardware.graphics.composer@2.1-service)

2.2:分析原始dump文件

原始dump展现形式:

1. 第一部分,主要包含分配内存的大小,分配的次数,分配内存的函数调用栈地址。相同的调用栈用一行表示。

z 0 sz 242952 num 1 bt 776cd9667c 776cd964a8 776d7977bc 776a16e384 776a16e6d0 776a16e4d8 776a1701bc 776a16fff0 776a188b48 776a188a0c 776a175394 776a179038 7769881260 7769878458 7769862208 776985ce4c z 0 sz 163840 num 1 bt 776cd9667c 776cd964a8 776a1937b4 776a1913d4 776a198cf8 776a199128 776a16fa80 776a1703cc 776a170328 776a188a30 776a175394 776a179038 7769881260 7769878458 7769862208 776985ce4c z 0 sz 163840 num 1 bt 776cd9667c 776cd964a8 776a19362c 776a1912e0 776a16f164 776a16e394 776a16e6d0 776a16e4d8 776a1701bc 776a16fff0 776a188b48 776a188a0c 776a175394 776a179038 7769881260 7769878458

2. 第二部分,包含进程二进制代码在进程空间内加载的地址。配合第一部分可以确认分配函数的调用栈。

7765e92000-7765e9d000 --xp 00009000 fd:08 3329 /vendor/lib64/hw/gralloc.mt6885.so 7765e9d000-7765e9e000 rw-p 00014000 fd:08 3329 /vendor/lib64/hw/gralloc.mt6885.so 7765e9e000-7765e9f000 r--p 00015000 fd:08 3329 /vendor/lib64/hw/gralloc.mt6885.so 7765e9f000-7765ea0000 rw-p 00000000 00:00 0 [anon:.bss] 7765ec4000-7765ed9000 r--p 00000000 fd:07 3965 /system/lib64/libEGL.so 7765ed9000-7765ef1000 --xp 00015000 fd:07 3965 /system/lib64/libEGL.so 7765ef1000-7765ef2000 rw-p 0002d000 fd:07 3965 /system/lib64/libEGL.so 7765ef2000-7765ef7000 r--p 0002e000 fd:07 3965 /system/lib64/libEGL.so

2.3:native_heapdump_viewer.py 格式化

原始的dump文件可读性差,可以使用 development/scripts/native_heapdump_viewer.py 格式化,转换为树状结构

转换后的dump文件会有很多分配内存的堆栈信息。这里只提取了内存泄漏最严重的一个堆栈,为了方便查看只截取了每行的前面部分:

BYTES %TOTAL %PARENT COUNT ADDR LIBRARY FUNCTION LOCATION 0 0.00% 0.00% 0 APP 181538320 100.00% 100.00% 566960 ZYGOTE 152077336 83.77% 83.77% 391966 776d8aa2cc /system/lib64/vndk-29/android.hardware.graphics.co 152077336 83.77% 100.00% 391966 776d8a9628 /system/lib64/vndk-29/android.hardware.graphics. 152077336 83.77% 100.00% 391966 776ba1a598 /vendor/lib64/hw/android.hardware.graphics.com 152077336 83.77% 100.00% 391966 776ba1d0b4 /vendor/lib64/hw/android.hardware.graphics.c 147506972 81.25% 96.99% 380182 776ba1e4f0 /vendor/lib64/hw/android.hardware.graphics 147506972 81.25% 100.00% 380182 776ba1f654 /vendor/lib64/hw/android.hardware.graphi 147506972 81.25% 100.00% 380182 776ba17704 /vendor/lib64/hw/android.hardware.grap 147506972 81.25% 100.00% 380182 7769850744 /vendor/lib64/hw/hwcomposer.mt6885.s 147506048 81.25% 100.00% 380171 776988dfd8 /vendor/lib64/hw/hwcomposer.mt6885 147505960 81.25% 100.00% 380170 7769859c3c /vendor/lib64/hw/hwcomposer.mt68 147505960 81.25% 100.00% 380170 7769862134 /vendor/lib64/hw/hwcomposer.mt 147505960 81.25% 100.00% 380170 7769875bd0 /vendor/lib64/hw/hwcomposer. 147505960 81.25% 100.00% 380170 776a176bd8 /vendor/lib64/libdpframworks 147505960 81.25% 100.00% 380170 776d7977bc /system/lib64/vndk-sp-29 147505960 81.25% 100.00% 380170 776cd964a8 /system/lib64/libc_mal

文件记录了当前进程所有使用malloc系列接口分配内存的操作,每次还没有释放的分配计数为1,是潜在的内存泄露。排在最前面的是持有内存最多的操作。可以看到composer进程中使用内存最多的是libdpframworks模块,共使用内存147505960Kb,还有380170次分配的内存没有释放,在composer所有还没有释放的分配中占比81.25%。显然这个调用栈非常可疑。

我们重点关注这一行:
147505960 81.25% 100.00% 380170 776a176bd8 /vendor/lib64/libdpframworks

2.4:解析调用栈

查看调用栈我们容易知道分配内存的地方在如下的代码中:

147505960 81.25% 100.00% 380170 776a176bd8 /vendor/lib64/libdpframework.soDpAsyncBlitStream2::createJob(unsigned int&, int&) vendor/mediatek/proprietary/hardware/dpframework/common/stream/DpAsyncBlitStream2.cpp:85

DP_STATUS_ENUM DpAsyncBlitStream2::createJob(uint32_t &jobID, int32_t &fence) { DP_STATUS_ENUM status; struct AsyncBlitJobPair *pJob; int32_t fenceFD, fenceFD2; //分配内存 pJob = new struct AsyncBlitJobPair; if (pJob == NULL) { DPLOGE("DpAsyncBlitStream2: cannot allocate job\n"); return DP_STATUS_OUT_OF_MEMORY; } }

2.5:分析调用栈对应代码

跟踪pJob的生命周期,我们最终定位到内存泄露的位置

DP_STATUS_ENUM DpAsyncBlitStream2::invalidate(struct timeval *endTime) { DP_STATUS_ENUM status; struct AsyncBlitJobPair *pJob; { ... pJob = m_jobList.front(); } ... { AutoMutex lock(m_pJobMutex); m_jobList.erase(m_jobList.begin()); //从链表取出使用后,忘记释放资源. 这里应该有delete delete pJob; }

2.6:总结

使用Malloc Debug工具,我们关心BYTES%TOTAL两个指标。如果这两个指标一直偏大,就标明可能存在内存泄露。

【关注我,后续持续新增专题博文,谢谢!!!】

下一篇讲解:内存泄漏系列专题分析之六:高通camx 内存泄漏测试的未回收问题分析

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/21 16:56:20

Reducer 是什么?多个节点如何安全更新状态

上一篇文章里,我们讨论了 Edge 和 Conditional Edge。 一个重要结论是: Edge 让流程可以分支,也可以回到前面的节点。 但一旦流程变复杂,就会遇到另一个问题: 多个节点如何安全更新同一份 State? 这就是 Re…

作者头像 李华
网站建设 2026/7/21 16:55:18

【亲测免费】 picacomic-downloader:快速下载哔咔漫画的利器

picacomic-downloader:快速下载哔咔漫画的利器 在数字化阅读的时代,漫画爱好者总希望找到一种高效的方式来收集自己喜欢的漫画资源。今天,我要向大家推荐一个开源项目——picacomic-downloader,一款能够帮助用户快速下载哔咔漫画的…

作者头像 李华
网站建设 2026/7/21 16:54:00

通义千问CLI终极指南:从命令行到智能代理的技术深度解析

通义千问CLI终极指南:从命令行到智能代理的技术深度解析 【免费下载链接】Qwen The official repo of Qwen (通义千问) chat & pretrained large language model proposed by Alibaba Cloud. 项目地址: https://gitcode.com/GitHub_Trending/qw/Qwen 在当…

作者头像 李华
网站建设 2026/7/21 16:52:05

打造个性化轮播:使用LESS自定义jQuery.Flipster主题的完整教程

打造个性化轮播:使用LESS自定义jQuery.Flipster主题的完整教程 【免费下载链接】jquery-flipster Responsive, CSS3, touch-enabled jQuery Coverflow plugin. 项目地址: https://gitcode.com/gh_mirrors/jq/jquery-flipster jQuery.Flipster是一款响应式、支…

作者头像 李华