23、日志系统与 Bug 定位:logcat 过滤、dumpsys mediaserver、tombstone 分析

做多媒体开发,说白了就是跟各种奇怪的崩溃打交道。

我刚开始接触 MediaServer 那会儿,最怕的就是线上反馈说「播放器闪退了」。你想想看,没有日志,没有现场,光靠猜,那真是大海捞针。后来我慢慢摸清了门道——Android 的日志系统,其实给了我们三把利器:logcatdumpsystombstone。用好它们,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 一看,发现 AudioFlingerMixerThread 里有一个 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)

怎么看?我一般按这个顺序:

  1. 看 signal 和 fault addrSIGSEGV 是段错误,SEGV_MAPERR 表示访问了未映射的内存。如果 fault addr 是 0x0 或很小的值,那多半是空指针解引用。
  2. 看 pc 和 backtrace:pc 是崩溃时的指令地址,backtrace 是调用栈。重点关注最上面几帧,尤其是 #00#01
  3. 看寄存器x0 通常是 this 指针,如果它是 0x0,那基本可以确定是某个对象为 null 时调用了它的方法。

举个例子,上面这个 tombstone 里,#00libmediaplayer.soSomeClass::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 被置空后又被访问。修复方案就是在切换前加一个状态检查。

总结一下我的定位流程:

  1. logcat 确认是 MediaServer 崩溃,记录时间点。
  2. 拉取 tombstone,看 backtrace 和寄存器。
  3. 用 addr2line 翻译到源码行号。
  4. 结合 logcat 中崩溃前的警告/错误日志,还原现场。
  5. 如果 tombstone 信息不够,用 dumpsys 抓取崩溃前的状态(需要提前在测试中复现)。
MediaServer Bug 定位流程 logcat 过滤 按 PID / Tag / 级别 dumpsys mediaserver 运行时快照 / 状态检查 tombstone 分析 backtrace / 寄存器 / addr2line 是否定位到具体原因? 修复并验证 修改源码 / 添加判空 / 状态检查 增加日志 / 复现 添加 ALOGV / 缩小复现步骤 最终修复

说白了,日志系统就是你的「眼睛」。logcat 让你看到发生了什么,dumpsys 让你看到当前的状态,tombstone 则告诉你到底是怎么死的。三样工具配合使用,大部分 MediaServer 的 Bug 都能在半小时内定位到根因。

最后提醒一句:线上环境不要随便 dump,但测试环境一定要养成习惯——每次复现完 Bug,第一时间拉 tombstone 和 dumpsys。等你想起来再拉,现场早就没了。


公众号:蓝海资料掘金营,微信deep3321