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

📅 2026/7/21 16:57:31
内存泄漏系列专题分析之五:使用malloc_debug定位C/C++ native heap内存泄露
【关注我后续持续新增专题博文谢谢】上一篇我们讲了内存泄漏系列专题分析之四Android malloc_debug工具在Camera领域使用中预览卡死的瓶颈限制问题和二次改造这一篇我们开始讲内存泄漏系列专题分析之五使用malloc_debug定位C/C native heap内存泄露目录一、Malloc Debug配置说明1.1常见配置1.2malloc debug log日志1.3native_heapdump_viewer.py 使用二、使用Malloc Debug2.1打开Malloc Debug2.2分析原始dump文件2.3native_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 ,填充内容为 0xaarear_guard[SIZE_BYTES] 每次调用 malloc ,都在指向内存的最后填充 SIZE_BYTES ,填充内容为 0xbbguard[SIZE_BYTES] 这个选项包含了 front_guard 和 rear_guard 。在指向内存的前后连 续 SIZE_BYTES 分别填充 0xaa 和 0xbbbacktrace[MAX_FRAMES] 这 个 optipn s 会 将 内存分配的速度减慢一个数量级 ,MAX_FRAMES 最大 值 256 默认值 16 。 每次在调用 malloc 时 , malloc debug 都会记录 malloc 的调用栈 (trace).栈的最大深度为 MAX_FRAMES , MAX_FRAMES 越大 , 对 malloc 的性能影响越大 , 也就越慢。当进程收到信号 SIGRTMAX - 17 Android 通常该信号值为 47 时, 会触发 malloc debug 的 dump heap trace 功能。 默认 dump 路径在 /data/local/tmp/ backtrace_heap.PID.txt 。给进程发信号通过 kill -s 47 PID , 进程收 到信号后并不会马上 dump backtrace , 而是会等到下次调用 malloc 或者 free 时 才会触发。所以如果发送信号后没有产生 trace 文件,请继续针对调试的进程做 测试。执行 kill -s 47 PID 之后,系统开始进行等待用户执行 malloc 操作。 举例 setprop libc.debug.malloc.options backtrace5 得到的 dump 文件为 $pid.txt 结尾backtrace_enable_on_signal[MAX_FRAMES] :使能这个选项 , 通过给进程发送信号 45 , 可以动态开启和关闭 backtrace 功能 。backtrace_dump_on_exit进程退出后自动 dump trace 文件,得到的 dump 文件为 $pid.exit.txt 结尾backtrace_dump_prefixtrace dump 的路径 , 如果需要放置其他目录 , 如 /sdcard/heap, 则 dump 的文件路 径为 /sdcard/heap.$PID.txtleak_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_calloc432) 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_init108) 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.2malloc debug log日志verbose 打开 malloc debug 更多 log ,类似08-16 15:54:16.060 26947 26947 I libc : /system/bin/app_process64: malloc debug enabled09-10 01:03:50.070 557 557 I malloc_debug: /system/bin/audioserver: Run: kill -47 557 to dump the backtrace.1.3native_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.composer2.1-service打开malloc debug#!/bin/bash# 需要重启上层让 property 生效stop# 指定 debug 参数。可以默认这样写更多选项参考# bionic/libc/malloc_debug/README.mdsetproplibc.debug.malloc.optionsbacktrace16 guard8 fill_on_free16# 指定要开malloc debug的进程setproplibc.debug.malloc.programandroid.hardware.graphics.composer2.1-service# 进程重启用libc_deubg.so替换原来的libc.SO库mallocDebug代码生效kill -9 $(pidof android.hardware.graphics.composer2.1-service)start# 需要关闭SELinux和目录读写权限setenforce 0chmod 777 /data/local/tmp# 触发Malloc Debug dump默认会生成如下dump文件# /data/local/tmp/backtrace\_heap.**PID**.txtkill -47 $(pidof android.hardware.graphics.composer2.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 77698784582. 第二部分包含进程二进制代码在进程空间内加载的地址。配合第一部分可以确认分配函数的调用栈。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.so2.3native_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/libdpframworks2.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:85DP_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 内存泄漏测试的未回收问题分析