第4章 动态检测实战:在MTK8676上部署Valgrind并分析座舱应用
好,到了最硬核的部分了。前面我们聊了静态分析,说白了那是「纸上谈兵」。真正跑起来,内存泄漏才会原形毕露。我个人习惯,拿到一块新板子,第一件事就是先把 Valgrind 怼上去。为什么?因为座舱应用一旦内存泄漏,轻则卡顿,重则黑屏重启——这可不是闹着玩的。
4.1 交叉编译 Valgrind:别踩我踩过的坑
MTK8676 是 ARM64 架构,你的 PC 是 x86。所以你不能直接 apt install valgrind,得交叉编译。我在项目中遇到过,有人直接拿 PC 版拷到板子上,跑起来直接报「Illegal instruction」——嗯,那画面太美我不敢看。
交叉编译其实不复杂,但有几个细节要注意:
# 下载源码
wget https://sourceware.org/pub/valgrind/valgrind-3.22.0.tar.bz2
tar -xjf valgrind-3.22.0.tar.bz2
cd valgrind-3.22.0
# 配置交叉编译环境
export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++
export LD=aarch64-linux-gnu-ld
# 配置
./configure --host=aarch64-linux-gnu --target=aarch64-linux-gnu \
--prefix=/opt/valgrind_arm64
# 编译
make -j8
make install
- configure 时一定要指定
--host和--target,缺一不可。否则编译出来的还是 x86 版本。 - MTK8676 的 libc 版本可能比较老。如果编译报错,试试加上
--enable-only64bit。 - 编译出来的 valgrind 大约 30MB,别嫌大,这是调试阶段的必需品。
4.2 部署到板子上:别把整个目录拷过去
编译完成后,你会得到一个 /opt/valgrind_arm64 目录。很多人直接整个目录 scp 到板子上——其实没必要。你只需要这几个东西:
| 文件/目录 | 说明 | 是否必须 |
|---|---|---|
| bin/valgrind | 主程序 | ✅ 必须 |
| lib/valgrind/*.so | 工具库(memcheck 等) | ✅ 必须 |
| libexec/valgrind/* | 辅助工具 | ✅ 必须 |
| share/doc | 文档 | ❌ 可选 |
我个人习惯,在板子上建一个 /data/local/valgrind 目录,只拷这三个必须的部分。然后设置环境变量:
export VALGRIND_LIB=/data/local/valgrind/lib/valgrind
export PATH=$PATH:/data/local/valgrind/bin
/etc/profile 里,重启后自动生效。省得每次手动 export。
4.3 跑起来!分析座舱应用的内存泄漏
好了,现在 Valgrind 已经就位。我们来分析座舱的核心应用——比如多媒体服务 media_server。这个服务负责音频、视频、蓝牙电话,一旦泄漏,整个座舱体验直接崩盘。
跑 Valgrind 的命令很简单:
valgrind --tool=memcheck --leak-check=full \
--show-leak-kinds=all \
--log-file=/data/logs/valgrind_media.log \
/system/bin/media_server
等等,这里有个问题。座舱应用通常是开机自启动的,你不能直接停掉它再手动跑。怎么办?我一般用两种方式:
- 修改 init.rc:在服务的启动命令前加上
valgrind前缀。比如把service media_server /system/bin/media_server改成service media_server /data/local/valgrind/bin/valgrind --tool=memcheck ... /system/bin/media_server。 - 用 GDB attach:先让服务跑起来,然后用 GDB attach 上去,再手动启动 Valgrind。不过这种方式比较麻烦,我很少用。
🔍 实际案例: 我在某款车型的 MTK8676 平台上,用 Valgrind 抓到了 media_server 的一个经典泄漏。每次蓝牙电话挂断后,音频路由模块会分配一个 4KB 的 buffer,但只在通话中释放。挂断后 buffer 就「悬空」了。跑了一整天,泄漏了 200MB+。嗯,找到问题后,一行代码就修好了。
4.4 解读 Valgrind 报告:别被海量信息吓到
Valgrind 跑起来后,日志文件会刷刷地增长。我第一次看的时候,说实话,有点懵。几万行输出,全是「definitely lost」「indirectly lost」「possibly lost」……
别慌。我教你一个快速定位的方法:
# 只看 definitely lost(确定泄漏)
grep "definitely lost" /data/logs/valgrind_media.log
# 统计泄漏大小
grep "definitely lost" /data/logs/valgrind_media.log | awk '{sum+=$5} END {print sum " bytes"}'
# 提取泄漏堆栈
grep -A 20 "definitely lost" /data/logs/valgrind_media.log | head -100
你想想看,一个座舱系统里,第三方应用、系统服务、HAL 层代码混在一起。Valgrind 会报告所有进程的泄漏。但我们的目标是——先抓大块头。泄漏 1KB 的可以先放一放,泄漏 1MB 的必须立刻修。
| 泄漏类型 | 严重程度 | 处理建议 |
|---|---|---|
| definitely lost | 🔴 致命 | 必须修复,100% 泄漏 |
| indirectly lost | 🟡 严重 | 通常伴随 definitely lost,一起修 |
| possibly lost | 🟢 可疑 | 可能是指针运算导致,需要人工确认 |
| still reachable | ⚪ 无害 | 程序退出时未释放,但运行中可访问 |
4.5 实战技巧:如何让 Valgrind 跑得更「聪明」
Valgrind 默认是全量检测,但座舱应用通常很大,全量检测太慢了。我一般会做几个优化:
- 只检测特定模块:用
--trace-children=yes跟踪子进程,但用--exe-path只检测你关心的可执行文件。 - 抑制已知泄漏:有些第三方库(比如 GPU 驱动)本身就有泄漏,但你不能改。用
--suppressions=my_supp.txt把这些「已知问题」过滤掉。 - 设置泄漏阈值:用
--leak-check-heuristics=all让 Valgrind 更智能地判断泄漏类型。
举个例子,我的抑制文件长这样:
{
ignore_libmali_leaks
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
obj:/vendor/lib/egl/libGLES_mali.so
}
说白了,就是告诉 Valgrind:「这个库的泄漏我已知,别报了,省得干扰我找真正的问题。」
4.6 总结一下
动态检测这件事,说白了就是「让代码在显微镜下跑一遍」。Valgrind 就是那个显微镜。但工具再好,也得会用。我见过太多人,跑完 Valgrind 看到几万行日志就放弃了——其实你只需要关注那几行「definitely lost」就够了。
下一章,我们会讲如何用 Perfetto 做性能剖析。嗯,那个工具更猛,能直接看到每个线程在干什么。到时候你就知道,为什么座舱应用会卡顿了。