第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

等等,这里有个问题。座舱应用通常是开机自启动的,你不能直接停掉它再手动跑。怎么办?我一般用两种方式:

  1. 修改 init.rc:在服务的启动命令前加上 valgrind 前缀。比如把 service media_server /system/bin/media_server 改成 service media_server /data/local/valgrind/bin/valgrind --tool=memcheck ... /system/bin/media_server
  2. 用 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 ⚪ 无害 程序退出时未释放,但运行中可访问
⚠️ 注意: Valgrind 在 MTK8676 上跑,性能会下降 5-10 倍。也就是说,原来 60fps 的动画,跑 Valgrind 后可能只有 6fps。所以建议在开发板上跑,别在量产车上跑——我曾经试过,结果车主一上车,中控卡成 PPT,差点被投诉。

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 做性能剖析。嗯,那个工具更猛,能直接看到每个线程在干什么。到时候你就知道,为什么座舱应用会卡顿了。