Android native ASAN 排查内存泄漏

文章介绍了在Android平台上使用ASAN(AddressSanitizer)进行C/C++内存问题调试的困难,特别是Android11以后弃用arm64ASan转向HWASan带来的挑战。作者分享了在没有Pixel手机且需要使用HWASan时的高成本,以及在Android12和13设备上遇到的JNIjstring传递报错问题。为解决这个问题,作者提出使用armeabi-v7a的SO库进行调试,待问题解决后再转为arm64。文章还提到了官方文档和第三方博客作为参考资料。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

android 对native - c/c++ 的调试和排查是比较难受的一件事。我看周遭做window , linux 甚至ios的调试排查起c的代码都比较方便。习惯了app开发去熟悉native是各种痛苦,最主要是排查问题上。后续有时间打算整理下native 的错误排查使用:address2line,ndk-stack,bugreport。该篇主要想写下ASAN排查内存问题。
ASAN是谷歌的一个项目,支持不少平台:https://github.com/google/sanitizers/wiki/AddressSanitizer 网上blog也很多,讲原理的和讲搭建的都有。但是跑起来就是各种问题,跑起来直接不能使用的也多。其中比较有效的可以参考这篇blog:
https://blog.csdn.net/taohongtaohuyiwei/article/details/123117882
该篇主要想介绍几个搭建环境的时候死活跑不通的坑。

二、环境搭建

环境搭建比较简单,可以直接参见Android官网:https://developer.android.com/ndk/guides/asan?hl=zh-cn 就是搭建出来的大概率没法使用。
可以参考:[https://blog.csdn.net/taohongtaohuyiwei/article/details/123117882]这篇blog里的demo

三、Android-ASAN的大坑

3.1、jni jstring传递报错

Android官网打开第一行:

警告:在 Android 11 之后的 AOSP master 中,弃用了 arm64 上的平台开发 ASan,改为使用 HWASan。

1、没错的谷歌现在推荐64位的使用HWASan ,那问题来了,HWASan要自己刷系统还要是pixel手机。这接入成本就有点大,我没有pixel手机所以没法尝试。

2、使用Android12 和Android13的手机去跑64位的带ASAN的包,包是可以编译出来,也可以运行。但是使用JNI 传参jstring 会报内存只读直接没法往下走了【ASAN 是识别到内存有问题就会直接crash 然后打印对应堆栈】

3、折中解决:因为手头上的手机版本都比较高,我们编译的库都是arm64的,目前只想到了使用arm7的so带asan,使用arm7来排查问题。解决了问题再编译arm64
【使用android8.0的手机直接跑arm64是可以的】

<think>我们正在处理关于检测FreeType引起的内存泄漏问题。根据引用[2],FreeType历史上存在一些内存泄漏漏洞(CVE),因此检测内存泄漏非常重要。以下是系统化的检测方法: ### 一、检测工具推荐(按优先级排序) | 工具名称 | 适用平台 | 检测原理 | 使用示例 | |----------------|-------------|------------------------|------------------------------| | **Valgrind** | Linux/macOS | 二进制插桩 | `valgrind --leak-check=full ./your_app` | | **AddressSanitizer (ASan)** | Linux/macOS/Android | 编译时插桩 | `gcc -fsanitize=address -g your_code.c` | | **Dr. Memory** | Windows | 二进制插桩 | `drmemory.exe your_app.exe` | | **LeakTracer** | 跨平台 | 重载`new/delete` | 需链接特定库 | > **工具选择公式**: > 检测精度 = 工具覆盖率 × 漏洞可重现性 > 推荐优先使用Valgrind或ASan[^2] --- ### 二、Valgrind检测FreeType泄漏的完整流程 #### 步骤1:编译调试版本 ```bash # 确保FreeType库和你的程序都包含调试符号 gcc -g -o your_program your_program.c `pkg-config --cflags --libs freetype2` ``` #### 步骤2:运行Valgrind ```bash valgrind --tool=memcheck \ --leak-check=full \ --show-leak-kinds=all \ --track-origins=yes \ --log-file=valgrind.log \ ./your_program ``` #### 步骤3:分析泄漏报告 查看`valgrind.log`,重点关注以下部分: ```log ==12345== 320 (16 direct, 304 indirect) bytes in 1 blocks are definitely lost in loss record 100 of 150 ==12345== at 0x483AB65: malloc (vg_replace_malloc.c:380) ==12345== by 0x485F2A1: FT_Alloc (in /usr/lib/x86_64-linux-gnu/libfreetype.so.6.17.1) ==12345== by 0x486D5BB: ??? (in /usr/lib/x86_64-linux-gnu/libfreetype.so.6.17.1) ==12345== by 0x486DB01: FT_Load_Glyph (in /usr/lib/x86_64-linux-gnu/libfreetype.so.6.17.1) ==12345== by 0x109234: your_function (your_code.c:42) <-- 关键调用栈 ``` #### 泄漏类型解读: | 泄漏类型 | 含义 | 危险等级 | |----------------|-------------------------------|----------| | Definitely lost | 程序完全丢失内存指针 | ★★★★★ | | Indirectly lost | 通过其他泄漏指针间接丢失 | ★★★★☆ | | Possibly lost | 指针指向内存中间地址 | ★★★☆☆ | --- ### 三、AddressSanitizer (ASan) 快速检测 #### 步骤1:使用ASan编译 ```bash gcc -fsanitize=address -g your_program.c `pkg-config --cflags --libs freetype2` -o your_program ``` #### 步骤2:设置输出环境变量 ```bash export ASAN_OPTIONS=detect_leaks=1:log_path=./asan.log ``` #### 步骤3:运行程序 ```bash ./your_program ``` #### 步骤4:查看泄漏报告 `asan.log`中会明确标注: ```log ================================================================= ==12345==ERROR: LeakSanitizer: detected memory leaks Direct leak of 16 byte(s) in 1 object(s) allocated from: #0 0x7f5a1b2b5b88 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10db88) #1 0x7f5a1a3f12a1 (/usr/lib/libfreetype.so.6+0x1d2a1) <-- FreeType内部泄漏 #2 0x7f5a1a3f55bb (/usr/lib/libfreetype.so.6+0x215bb) #3 0x55d1c8 in your_function your_code.c:42:5 ``` --- ### 四、FreeType特有泄漏场景排查 根据引用[2]中FreeType的CVE分析,重点关注: 1. **字体面未释放**(最常见) ```c FT_Face face; FT_New_Face(library, "font.ttf", 0, &face); // 忘记调用 FT_Done_Face(face); // [^1] ``` 2. **复合字形处理泄漏**(CVE-2020-15999) 当加载包含复合字形的TTF时,需确保调用: ```c FT_Glyph glyph; FT_Get_Glyph(face->glyph, &glyph); // 使用后必须释放 FT_Done_Glyph(glyph); // 缺失导致泄漏[^2] ``` 3. **缓存管理泄漏** 使用`FTC_Manager`时,必须正确调用: ```c FTC_Manager_Done(manager); // 释放所有缓存 ``` --- ### 五、Android平台特殊检测(结合引用[1][3]) 在Android中可通过以下方式检测: #### 方法1:Android Studio Profiler 1. 打开Profiler > Memory 2. 运行应用并触发FreeType操作 3. 观察Native内存持续增长(即使GC后) #### 方法2:Logcat中捕捉线索 ```log D/native: free_type: Loading font /system/fonts/Roboto.ttf ... E/libc: malloc(512) leaked by thread 12345 <-- 可疑泄漏 ``` #### 方法3:集成ASan到APK ```gradle android { defaultConfig { externalNativeBuild { cmake { arguments "-DANDROID_STL=c++_shared" cppFlags "-fsanitize=address -fno-omit-frame-pointer" } } } } ``` --- ### 六、调试技巧:定位泄漏点 当工具无法精确定位时,使用**自定义内存追踪**: ```c // 重载FreeType内存管理 FT_MemoryRec_ memory_interface = { .user = NULL, .alloc = my_alloc, // 记录分配位置 .free = my_free, // 记录释放位置 .realloc = my_realloc }; // 在初始化时注入 FT_Init_FreeType(&library); FT_Property_Set(library, "memory", &memory_interface); ``` 记录分配/释放日志示例: ```log [ALLOC] 0x7f8a1b000 512 bytes @ your_code.c:42 [FREE] 0x7f8a1b000 @ your_code.c:58 <-- 匹配则正常 [LEAK] 0x7f8a1c000 256 bytes @ freetype_glyph.c:123 <-- 未释放 ``` --- ### 结论 1. **优先使用Valgrind/ASan**进行自动化检测 2. 重点检查**字体面释放**、**字形释放**和**缓存清理** 3. Android平台需结合**Profiler**和**ASan** 4. 顽固泄漏可通过**自定义内存追踪**精确定位 通过系统化检测,可有效解决FreeType引起的内存泄漏问题[^1][^2]。 §§相关问题§§ 1. 如何避免 FreeType 在多线程环境下的资源泄漏? 2. FreeType 与 OpenCV 结合使用时有哪些内存管理技巧?[^5] 3. 嵌入式设备中如何优化 FreeType 的内存使用?[^2]</think>### 检测和调试 FreeType 内存泄漏的完整指南 检测 FreeType 内存泄漏需要结合工具和代码审查,以下是系统化的解决方案: --- #### 一、核心检测工具 ```mermaid graph TD A[检测工具] --> B[Valgrind] A --> C[AddressSanitizer ASan] A --> D[自定义内存跟踪] A --> E[Android Profiler] ``` 1. **Valgrind(Linux/macOS首选)** ```bash # 编译带调试信息的程序 gcc -g your_program.c -lfreetype -o program # 运行内存检测 valgrind --leak-check=full \ --show-leak-kinds=all \ --track-origins=yes \ ./program ``` **报告关键部分**: ``` ==12345== 320 bytes in 1 blocks are definitely lost ==12345== at 0x483AB65: malloc (vg_replace_malloc.c:380) ==12345== by 0x485F2A1: FT_Alloc (in /usr/lib/libfreetype.so) ==12345== by 0x486D5BB: TT_Load_Composite_Glyph (truetype.c:1234) ==12345== by 0x109234: render_text (your_code.c:42) <-- 你的调用栈 ``` 2. **AddressSanitizer(ASan)** ```bash # 编译时启用ASan gcc -fsanitize=address -g your_program.c -lfreetype -o program # 运行程序 ASAN_OPTIONS=detect_leaks=1 ./program ``` **输出示例**: ```log == LeakSanitizer: detected memory leaks Direct leak of 128 byte(s) in 1 object(s) allocated from: #0 0x7f1a2b in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10db2b) #1 0x485f2a1 in FT_Alloc (/usr/lib/libfreetype.so.6+0x1d2a1) #2 0x486d5bb in TT_Load_Composite_Glyph (/usr/lib/libfreetype.so.6+0x2b5bb) ``` 3. **Android平台检测** - 使用 Android Studio Profiler 监控 Native 内存增长 - 在 `AndroidManifest.xml` 中启用 `android:debuggable="true"` - 通过 `adb shell dumpsys meminfo <package_name>` 观察内存变化[^1] --- #### 二、FreeType 特有泄漏点排查 根据引用[2]中FreeType的CVE分析,重点关注: 1. **未释放的字体面(FT_Face)** ```c FT_Face face; FT_New_Face(library, "font.ttf", 0, &face); /* 使用后必须调用 */ FT_Done_Face(face); // 缺失将导致严重泄漏[^2] ``` 2. **复合字形处理泄漏**(CVE-2020-15999) ```c FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); /* 处理复合字形需额外清理 */ if (face->glyph->format == FT_GLYPH_FORMAT_COMPOSITE) { FT_Done_Glyph(glyph); // 特殊字形需要单独释放 } ``` 3. **缓存管理器泄漏** ```c FTC_Manager* manager; FTC_Manager_New(library, max_faces, max_sizes, 0, my_alloc, NULL, &manager); /* 使用后必须销毁 */ FTC_Manager_Done(manager); // 常被遗漏的API ``` --- #### 三、调试技巧:定位泄漏源 1. **资源计数法**(简单有效) ```c static int face_count = 0; void load_font(FT_Library lib, const char* path) { FT_Face face; FT_New_Face(lib, path, 0, &face); face_count++; // 增加计数 // ... } void release_font(FT_Face face) { FT_Done_Face(face); face_count--; // 减少计数 } /* 程序退出时检查 face_count 应为0 */ ``` 2. **内存断点(GDB)** ```bash gdb ./your_program (gdb) break FT_Alloc # 在FreeType分配函数设断点 (gdb) watch *0x12345678 # 监控未释放的内存地址 ``` 3. **日志追踪** ```c // 重载内存管理函数 void* my_alloc(FT_Memory memory, long size) { void* p = malloc(size); log_alloc(p, size, __LINE__); // 记录分配位置 return p; } ``` --- #### 四、预防内存泄漏的最佳实践 1. **RAII封装**(C++示例) ```cpp class ScopedFTFace { public: ScopedFTFace(FT_Library lib, const char* path) { FT_New_Face(lib, path, 0, &face_); } ~ScopedFTFace() { FT_Done_Face(face_); } // 自动释放 private: FT_Face face_; }; ``` 2. **生命周期管理规则**: ```mermaid graph LR 初始化 --> 创建字体面 --> 加载字形 --> 释放字形 --> 释放字体面 --> 释放库 ``` 3. **静态检测配置**: - 在 `.clang-tidy` 中添加检查项: ```yaml Checks: > -*,clang-analyzer-unix.Malloc -*,clang-analyzer-cplusplus.NewDeleteLeaks ``` --- ### 结论 通过 Valgrind/ASan + 重点 API 检查 + 资源计数法,可高效定位 FreeType 泄漏点。关键点: 1. **必须成对调用**:`FT_New_Face` ⇄ `FT_Done_Face`,`FT_Get_Glyph` ⇄ `FT_Done_Glyph` 2. **优先检测复合字形**处理(历史漏洞高发区)[^2] 3. Android 平台使用 `adb shell dumpsys meminfo` 监控 Native 内存[^1] > **内存安全公式**: > 泄漏风险 = 未释放资源数 × 调用频率 > 通过工具可将风险降至 0
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值