23、日志系统与 Bug 定位:logcat 过滤、dumpsys mediaserver、tombstone 分析
做多媒体开发,说白了就是跟各种奇怪的崩溃打交道。
我刚开始接触 MediaServer 那会儿,最怕的就是线上反馈说「播放器闪退了」。你想想看,没有日志,没有现场,光靠猜,那真是大海捞针。后来我慢慢摸清了门道——Android 的日志系统,其实给了我们三把利器:logcat、dumpsys 和 tombstone。用好它们,Bug 定位的效率能翻好几倍。
核心思路:MediaServer 的崩溃,通常不是应用层直接抛异常,而是 native 层挂掉了。这时候 logcat 只能看到「MediaServer died」这种模糊信息,真正的线索藏在 tombstone 和 dumpsys 里。
23.1 logcat 过滤:从海量日志里捞出关键信息
logcat 的日志量有多大?我举个例子——你打开一个视频 App,随便播几分钟,logcat 里可能就刷出上万行。如果不加过滤,眼睛都能看花。
我个人习惯,先按 进程 ID 过滤。MediaServer 的进程名是 mediaserver,但它的 PID 每次重启都会变。所以我会先用 ps | grep mediaserver 拿到当前的 PID,然后:
adb logcat --pid=1234 -v threadtime
这样只显示这个进程的日志,干净多了。
如果不知道 PID,也可以直接用 -s 参数按 tag 过滤。MediaServer 相关的 tag 主要有:
| Tag | 说明 |
|---|---|
| MediaServer | 主进程日志,启动、注册服务等 |
| NuPlayer | 播放器引擎,解码、渲染相关 |
| ACodec | 编解码器,OMX 调用相关 |
| OMXNode | OMX 节点状态,经常在这里看到错误 |
| AudioFlinger | 音频混音器,音频输出相关 |
adb logcat -s NuPlayer:V ACodec:V OMXNode:V
嗯,这里要注意:日志级别也很关键。我一般先用 E(错误)级别扫一遍,如果没有明显线索,再降到 W(警告)或 I(信息)。
小技巧:如果崩溃发生在启动阶段,可以用 adb logcat -b all 查看所有缓冲区(main、system、events、crash)。events 缓冲区里经常有 AMS 的崩溃记录,能帮你确认是不是 MediaServer 挂了。
23.2 dumpsys mediaserver:抓取运行时快照
logcat 是流水账,dumpsys 则是「拍照片」。它能把你当前 MediaServer 内部的状态全部 dump 出来,包括:
- 注册了哪些服务(MediaPlayerService、AudioFlinger、CameraService 等)
- 每个服务有多少客户端连接
- 编解码器的使用情况
- 音频输出的设备信息
用法很简单:
adb shell dumpsys media.audio_policy
adb shell dumpsys media.player
adb shell dumpsys media.camera
但更常用的是直接 dump 整个 mediaserver:
adb shell dumpsys media
输出内容很长,我一般重点关注这几块:
- Clients 列表:看看有没有异常多的客户端连接,或者某个客户端长时间没释放。
- Codec 状态:如果某个编解码器卡在
OMX_StateExecuting一直不退出,那多半是死锁了。 - AudioFlinger 的线程状态:音频线程如果卡住,会导致整个音频通路阻塞。
我曾经遇到过一个线上问题:播放器切歌时偶尔卡死。logcat 里没有任何错误,但 dumpsys 一看,发现 AudioFlinger 的 MixerThread 里有一个 pending 的 track 一直没被处理。顺着这个线索,最终定位到是上层应用在切歌时没有正确释放音频焦点,导致底层线程死锁。
注意:dumpsys 是「有副作用的」。某些服务在 dump 时可能会短暂阻塞,所以线上环境慎用。我一般只在测试机或线下复现时用。
23.3 tombstone 分析:读懂 native 崩溃的「尸检报告」
如果 MediaServer 直接挂了,logcat 里只会留下一句:
F/libc ( 1234): Fatal signal 11 (SIGSEGV), code 1, fault addr 0xdeadbeef in tid 5678 (Binder:1234_3)
然后系统会在 /data/tombstones/ 目录下生成一个 tombstone 文件。这就是崩溃现场的「尸检报告」。
拉取 tombstone:
adb pull /data/tombstones/tombstone_XX .
打开后,你会看到类似这样的结构:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'xxx'
Revision: '0'
ABI: 'arm64'
Timestamp: 2024-01-15 10:23:45
pid: 1234, tid: 5678, name: Binder:1234_3
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdeadbeef
x0 0000000000000000 x1 0000007f8c123456 ...
pc 0000007f8a123456 /system/lib64/libmediaplayer.so
lr 0000007f8a123789 /system/lib64/libmediaplayer.so
backtrace:
#00 pc 0000000000123456 /system/lib64/libmediaplayer.so (SomeClass::someMethod+123)
#01 pc 0000000000123789 /system/lib64/libmediaplayer.so (SomeClass::handleMessage+45)
#02 pc 0000000000456789 /system/lib64/libbinder.so (android::BBinder::transact+234)
#03 pc 0000000000567890 /system/lib64/libbinder.so (android::IPCThreadState::executeCommand+567)
#04 pc 0000000000567891 /system/lib64/libbinder.so (android::IPCThreadState::joinThreadPool+89)
#05 pc 0000000000678901 /system/lib64/libbinder.so (android::PoolThread::threadLoop+12)
#06 pc 0000000000789012 /system/lib64/libutils.so (android::Thread::_threadLoop+34)
#07 pc 0000000000890123 /system/lib64/libc.so (__pthread_start(void*)+67)
#08 pc 0000000000901234 /system/lib64/libc.so (__start_thread+23)
怎么看?我一般按这个顺序:
- 看 signal 和 fault addr:
SIGSEGV是段错误,SEGV_MAPERR表示访问了未映射的内存。如果 fault addr 是0x0或很小的值,那多半是空指针解引用。 - 看 pc 和 backtrace:pc 是崩溃时的指令地址,backtrace 是调用栈。重点关注最上面几帧,尤其是
#00和#01。 - 看寄存器:
x0通常是 this 指针,如果它是0x0,那基本可以确定是某个对象为 null 时调用了它的方法。
举个例子,上面这个 tombstone 里,#00 在 libmediaplayer.so 的 SomeClass::someMethod 里崩了。结合 x0 = 0,我就能猜到是 this 指针为空。然后去源码里看 someMethod 的调用处,发现是某个回调没有判空。
实用工具:光看地址不够直观,可以用 addr2line 把地址翻译成源码行号:
arm-linux-androideabi-addr2line -e out/target/product/xxx/symbols/system/lib64/libmediaplayer.so 0x123456
注意要用带符号表的 .so 文件,一般在 out/target/product/xxx/symbols/ 目录下。
23.4 实战:一次 MediaServer 崩溃的完整定位过程
说个我印象很深的案例。有一次,某个视频 App 在播放 HDR 视频时,偶现黑屏然后闪退。logcat 里只有一句:
E/MediaServer( 1234): MediaServer process died.
我立刻去拉 tombstone:
adb pull /data/tombstones/tombstone_07 .
打开一看,backtrace 指向了 libstagefright.so 里的 OMXCodec::read 函数。pc 地址附近是 mOMXNode 的调用。我再用 addr2line 翻译,定位到源码中 OMXCodec.cpp 的第 2345 行:
status_t err = mOMXNode->fillBuffer(...);
嗯,mOMXNode 是空指针。为什么它会为空?我回头看 logcat,发现在崩溃前几秒,有一条 W/OMXNode( 1234): component is not in OMX_StateLoaded 的警告。这说明编解码器在状态不对的时候被调用了。
最终原因:HDR 视频切换时,上层应用没有等待解码器完全释放就启动了新的解码 session,导致 mOMXNode 被置空后又被访问。修复方案就是在切换前加一个状态检查。
总结一下我的定位流程:
- logcat 确认是 MediaServer 崩溃,记录时间点。
- 拉取 tombstone,看 backtrace 和寄存器。
- 用 addr2line 翻译到源码行号。
- 结合 logcat 中崩溃前的警告/错误日志,还原现场。
- 如果 tombstone 信息不够,用 dumpsys 抓取崩溃前的状态(需要提前在测试中复现)。
说白了,日志系统就是你的「眼睛」。logcat 让你看到发生了什么,dumpsys 让你看到当前的状态,tombstone 则告诉你到底是怎么死的。三样工具配合使用,大部分 MediaServer 的 Bug 都能在半小时内定位到根因。
最后提醒一句:线上环境不要随便 dump,但测试环境一定要养成习惯——每次复现完 Bug,第一时间拉 tombstone 和 dumpsys。等你想起来再拉,现场早就没了。
公众号:蓝海资料掘金营,微信deep3321