
1. 先把话说在前面调试显示驱动到底在调什么做显示驱动调试这行当有个特点你说不清问题出在哪一层一半以上的时间都花在定位问题归属上。是硬件参数配错了还是内核驱动状态机没走对是用户态提交的 buffer 格式不匹配还是面板的时序参数没写进 datasheet这些问题单独拎出来都不算难但它们交错在一起的时候靠眼睛看代码是看不过来的。所以这一篇我打算把平时真正会用到的调试工具按场景梳理一遍不搞大而全只讲每个工具能解决哪一类问题、怎么用、以及我实际踩过的坑。我偏重的方向是 Linux 内核的 DRM/KMS 显示驱动和帧缓冲相关调试因为这是嵌入式开发和桌面显卡驱动最常碰到的场景最后我也会简单带一下 Windows 侧的 GPUView 和 WinDbg 思路方便两边都沾点的朋友对照着看。整体顺序大概是这样先讲怎么用日志定位内核态的问题再讲怎么用工具硬查当前显示状态然后讲怎么在状态正常但画面不对的时候追帧缓冲和面板时序最后分享一套我常用的排查工作流。如果你是刚入门显示驱动的同学这篇可以当一份工具地图来用如果已经做了几年驱动也欢迎看看第 3 节和第 5 节里我总结的排查链路说不定能帮你少走几次弯路。2. 日志先行dmesg 和 drm.debug 是两把最顺手的钥匙2.1 dmesg内核留下的事故现场几乎所有显示驱动问题第一步都是打开 dmesg。原因很简单显示驱动的初始化、模式设置modeset、热插拔事件、电源状态切换都会在内核日志里留下痕迹。驱动崩了、面板上电失败、EDID 读取异常dmesg 里通常都有对应的报错行。我常用的操作很简单# 实时跟日志开另外一个终端去触发上电/插拔/切分辨率 dmesg -w # 只过滤 drm 相关日志适合日志太吵的时候 dmesg | grep -i drm # 查看最近一次启动的完整显示相关记录 journalctl -b -k | grep -iE drm|dpu|hdmi|edid注意一个细节dmesg输出的时间戳默认是内核启动后的相对秒数我不会直接看这个值来对齐外部动作而是配合-T参数转换成可读时间或者用dmesg -w实时观察。像 HDMI 插拔这类异步事件日志里会出现hdmi hotplug event、connector 0 status updated之类的记录看到它们出现的时间点基本就能判断是驱动没收到中断还是收到了但处理出错。还有一个容易忽略的点DRM 子系统的很多日志默认没开。如果你发现 dmesg 里什么都没有别急着怀疑驱动没跑很可能只是日志级别不够。这时候就要用到 drm.debug 参数。2.2 drm.debug按位开关想细看哪里就开哪里DRM 框架的调试日志归/sys/module/drm/parameters/debug管写入的是一个位掩码。每一位对应一类日志常用的几个位值如下位值对应模块能看到什么0x01DRM_UT_CORE核心初始化、对象生命周期0x02DRM_UT_DRIVER驱动级调用如 open/close/ioctl0x04DRM_UT_KMS模式设置、connector 状态变化0x08DRM_UT_PRIMEbuffer 导入导出0x10DRM_UT_ATOMICatomic commit 的详细状态流0x20DRM_UT_VBLANKvblank 中断0x40DRM_UT_STATE状态对象分配与引用计数我遇到画面闪烁或切分辨率失败的问题时最喜欢开的是0x1f即上面五位全开既不至于刷屏又能把从 ioctl 进来到 commit 完成的关键路径全打出来echo 0x1f /sys/module/drm/parameters/debug如果是 atomic 提交异常比如老是返回-EINVAL那我就会加大到0x10单独看 atomic 的日志。你会看到 crtc、plane、connector 的状态在提交前被逐一校验哪一步不过错误就直接指向哪里。这个信息量是 dmesg 默认状态下完全看不到的基本可以理解为给内核装了放大镜。用完之后记得恢复正常值我一般直接echo 0 /sys/module/drm/parameters/debug不然线上环境日志量会很恐怖。2.3 实战体会一次连接器状态反复跳变的排查有次做一款 ARM 板卡HDMI 外接显示器时偶尔黑屏dmesg 里反复出现connector 0: status updated但没有任何 error。我当时第一反应就是开drm.debug0x1f抓完整日志果然看到了问题连接器状态在connected和disconnected之间来回跳每次切换都伴随着一次 EDID 重读。这时候我就明白了问题不是驱动逻辑而是 HPDHot Plug Detect引脚上有干扰。日志只能把现象呈现到这一步剩下的要去量硬件波形。这里顺带说一句dmesg 和 drm.debug 的价值是缩小范围不是替你完成排查。它们告诉你哪一层行为异常至于异常根源在驱动代码还是硬件电路就轮到后面这些工具出场了。3. 扒开当前状态modetest、sysfs 和 fbdev 三件套3.1 modetest摸清当前显示拓扑的瑞士军刀日志终归是过去时有时候我需要看的是硬件当前的真实状态比如现在有哪些 connector、哪些 encoder、当前分辨率和刷新率是多少、plane 的格式支持范围如何。这时候用modetest最直接。modetest 是 libdrm 自带的测试工具发行版里一般叫libdrm-tests或者直接包含在drm-utils里。装好之后先看拓扑modetest -M 你的DRM设备节点前缀 -p解释一下输出Connectors段落列出所有物理输出口每个 connector 后面会跟connected/disconnected状态以及当前模式、物理尺寸、EDID 中的监视器名字等信息。Encoders段落列出编码器及其绑定的 connector、CRTC这能帮你确认链路是否配对成功。CRTCs段落列出显示控制器能看到当前使能的分辨率、刷新率以及扫描方式。Planes段落会列举主平面、光标平面、叠加平面的格式和尺寸范围。如果怀疑模式设置有问题可以用-s参数强制指定一个 connector 和模式modetest -M DRM设备 -s connector_id:mode -v比如modetest -M imx-drm -s 32:1920x108060 -v。-v是 verbose会打印模式设置过程中的耗时和返回码。这个操作会直接改动当前显示输出可能造成短暂黑屏在远程调试的时候要小心别把唯一的显示输出给断了。3.2 sysfs不看工具直接看内核暴露的寄存器modetest 虽然方便但它走的是 DRM ioctl有些状态是它没暴露的。真正常被我翻牌子的其实是 sysfs 下的几个文件# 查看 card0 下所有子设备 ls /sys/class/drm/card0/ # 直接读取 connector 状态 cat /sys/class/drm/card0/card0-HDMI-A-1/status # 查看当前分辨率 cat /sys/class/drm/card0/card0-HDMI-A-1/modes # 读取 EDID 原始数据块十六进制 cat /sys/class/drm/card0/card0-HDMI-A-1/edid | hexdump -C | head -20sink 不支持某个分辨率导致黑屏这类问题我通常先读modes文件看内核从 EDID 里解析出了哪些候选模式。如果列表里根本没有 1080p那问题就出在 EDID 解析链路跟驱动模式设置逻辑无关如果列表里有但切过去还是黑那要去查 CRTC 和 encoder 的时钟配置。EDID 那段原始数据值得多说一句我见过不止一次面板端 EDID 里写的分辨率与实际物理像素不一致用hexdump看 EDID 的 Detailed Timing Descriptor 区域能直接确认厂商有没有填错参数。这属于硬件撒谎驱动躺枪的典型场景。3.3 fbdev 工具老接口但排查兼容性问题离不开虽然现代 Linux 显示栈几乎都走 DRM/KMS但很多嵌入式平台为了兼容老应用还是会保留/dev/fb0帧缓冲设备。排查这类兼容性问题时fbset依然是利器# 查看当前帧缓冲信息 fbset -i # 修改分辨率注意要和实际显存大小匹配 fbset -g 1920 1080 1920 1080 32fbset -i输出的geometry、rgba字段能直接反映当前 framebuffer 的位深和颜色格式如果用户态程序画出来颜色错乱先确认这里是不是32位 ARGB很多时候是位深不匹配导致的问题。要说明的是fbdev 已经是遗留接口新平台别指望在内核里找到大量 fbdev 驱动但/dev/fb0这个设备在不少带 GUI 的嵌入式产品里仍承担着系统主屏输出所以这个工具短时间不会真正退出历史舞台。4. 深水区工具链ftrace、perf 与硬件仪器4.1 ftrace想看驱动函数内部调用用它日志只能打到驱动主动 printk 的地方但有时候问题是驱动压根没走到该走的函数。比如你怀疑 panel 的prepare函数没被调用但 dmesg 里没有任何痕迹这时候直接用 ftrace 跟踪内核函数调用最干净。我的用法是# 切换到 tracing 目录 cd /sys/kernel/debug/tracing # 清空历史记录 echo trace # 设置要跟踪的函数支持通配符 echo drm_atomic_commit* set_ftrace_filter echo drm_helper* set_ftrace_filter echo function current_tracer # 开始跟踪 echo 1 tracing_on # ... 执行触发操作比如切换分辨率 ... # 停止并查看 echo 0 tracing_on cat trace | head -80输出里能看到完整的函数调用序列包括入参指针虽然不如 gdb 单步那么直观但胜在几乎不影响实时性适合在板子上复现偶发问题。我踩过的一个坑是set_ftrace_filter里的函数名如果写得太大跟踪器会瞬间被刷爆因为显示驱动涉及的热路径如 vblank 中断处理函数调用频率极高动不动每秒几千次。所以我的建议是跟踪范围宁小勿大先精确到一个怀疑的入口函数确认跟预期不符再放宽。4.2 perf显示性能问题不能靠猜显示驱动不只是能不能点亮的问题还涉及流畅度。帧率上不去、掉帧、画面撕裂这些性能类问题靠modetest看不出来我习惯用perf结合内核的drm_gpu_fence事件来定位。简单场景# 实时看 CPU 侧热点 perf top # 记录 3 秒调用栈 perf record -g -a sleep 3 perf report如果是 GPU 侧耗时高那要从 DRM 的 scheduler 和 GPU fence 等待时间去分析这种情况我会配合perf trace看进程在 ioctl 上阻塞的时间perf trace -e drm:* -p 应用进程PID如果应用进程在DRM_IOCTL_WAIT_VBLANK或 atomic commit 上耗时抖动很大再配合perf sched看是否有其他进程抢占基本能区分是驱动问题还是调度问题。这里有个容易被忽略的点显示驱动领域的性能问题往往不只在驱动代码本身而是内存带宽问题。帧缓冲在系统内存里面CPU 和 GPU 抢带宽时显示控制器取帧会变慢。perf record抓到的可能只是进程调度抖动根源却在总线仲裁。遇到这种案子我会回头去量总线带宽而不是继续在驱动代码里找问题。4.3 数据链路层面示波器和逻辑分析仪是最后底牌软件工具能覆盖的范围到驱动和内核也就停了。一旦怀疑物理层信号问题——比如 MIPI DSI 的 clock lane 时序不对、LVDS 的差分信号幅值不足、I2C 上拉电阻不够导致 EDID 读取失败——只能上硬件仪器。具体分工大概是示波器量单端信号或简单差分信号看波形幅值、上升沿斜率、是否存在反射。HPD 引脚抖动问题、PWM 背光控制波形畸变用示波器基本一眼能看出来。逻辑分析仪抓 I2C/SPI 总线协议数据确认主控端发送的 EDID 读请求地址对不对、面板端是否真的回了数据以及 MIPI DSI 命令包的头和负载是否匹配。在硬件抓波这一层我的原则是先假设软件已经对了再去怀疑波形。因为硬件信号的问题在 dmesg 里通常表现为间歇性、可复现率低、换一块板子就好而这类特征如果去驱动代码里找往往浪费数天。另外提醒一句示波器探头接地线太长的确会把高频信号测变形导致误判。这是一位硬件朋友教我的做软件的人最容易踩这个坑看到波形有异常先别急着怀疑面板换个接地方式再测一次。4.4 Windows 侧对照GPUView 与 WinDbg 的排查思路如果你也在做 Windows 显示驱动WDDM工具思路跟 Linux 很像只是具体名字不同。我在这边简单列几个常用的GPUView微软官方性能分析工具用来抓 GPU 硬件队列、DMA 包、VSync 和帧延迟。当你怀疑显示掉帧但不确定是应用问题还是驱动问题时GPUView 的Present和Graphics两条时间线能直观地区分。WinDbg内核调试器对应 Linux 的 kgdb/ftrace。通过!dmlog或!drvstate这类调试扩展命令检查显示驱动的状态对象定位模式切换失败、蓝屏挂死在显示驱动里的问题。PX Tools全称 Pixel View Tools适用于特定图形调试排查像素级错乱和格式转换异常时可以抓帧对比输入输出。两边对照的思维方式其实是一致的先把问题按照日志层—状态层—时序层三层归位再决定用什么工具深入。Windows 侧更依赖 WDDM 内置的日志机制比如 ETWLinux 侧更依赖内核打印和 sysfs但排查顺序都是先看软件层有没有明示错误再看硬件时序是否可靠。5. 一套我常用的调试工作流以及新手最容易犯的错5.1 完整链路从一个黑屏案例说起我把自己处理过的一个黑屏问题完整拆一遍展示工具是怎么一环扣一环用的。第一步复现现象并打开dmesg -w。发现没有任何新增日志但屏幕确实黑了。第二步开drm.debug0x1f再次复现日志里能看到一次 atomic commit 的完整过程但 commit 之后没有调用vblank。此时问题范围可以缩小为CRTC 没有正常工作。第三步用modetest -p看 connector 是否仍保持connected如果连接器状态丢失说明问题在链路检测环节如果连接器仍为connected继续查 CRTC 使能状态。第四步用modetest -s connector:mode强制重新设置一次模式观察是否恢复。如果强制设置能恢复那说明驱动状态机出现了假死问题大概率在内核里 CRTC 的电源域没有正确上电。第五步这时再用 ftrace 跟踪drm_helper_disable_unused_functions或平台提供的 dpumask 相关接口确认驱动在 disable/enable 之间少了哪一步。这个流程走下来90% 的黑屏问题能定位到具体函数剩下 10% 硬件问题再上示波器和逻辑分析仪也不迟。整个链路里前四步都是软件工具成本低、效率高。5.2 新手容易踩的坑这里一次性说清我见过不少刚入行的朋友在调试显示驱动时一个劲地翻驱动源码从 init 函数入口一路看到 panel 初始化最后什么都没看出来。我想说的是显示驱动的代码逻辑相对固定真正不固定的其实是外部设备面板、sink、线缆的状态。与其硬读代码不如先把环境状态摸清楚。还有一个常见误区是把modetest当成能点亮画面的测试工具。它不是它只是设置模式并验证状态机画面有没有正确内容它不保证。验证画面内容是否正确要用实际的 GUI 应用或专门的抓帧工具比如 IGT 里的kms_flip或者写一个简单的双缓冲切换程序。最后是日志工具开太猛的问题drm.debug全开之后在某些低端平台上会显著拖慢系统反而制造出本来没有的时序问题。我一般只在复现窗口内开复现完立刻关掉再拿完整日志分析。6. 按场景选择工具的速查清单如果时间紧可以直接对着这张表用问题现象首选工具补充工具预期能定位到的层次初始化失败、黑屏无日志dmesg drm.debugjournalctl -k驱动初始化链路的哪一步失败切分辨率失败、模式不支持modetest sysfs modesedid dump模式列表是如何被解析出来的画面颜色错乱fbset -iDRM plane format 列表颜色格式是否匹配画面闪烁、间歇性黑屏drm.debug 的 HOTPLUG/ATOMIC示波器抓 HPD是事件抖动还是驱动状态机问题帧率上不去、掉帧perf GPUViewftrace瓶颈在 CPU 侧还是 GPU 侧面板时序不匹配示波器/逻辑分析仪面板 datasheet 对照物理信号时序和电压应用层显示异常IGT (kms_flip)dmesg 的 fence 事件提交流程是否正常这张表不全面但覆盖了我日常工作中八成以上的场景。工具不在多关键是每个工具该在什么时候拿出来、能回答什么问题这才是经验的积累。最后分享一个长期有用的习惯我自己的做法是:每当遇到一次特别难缠的显示驱动问题,解决之后我会把当天的排查步骤和日志关键行整理成一篇小笔记,重点记录最初的现象和真正的原因之间的映射关系。时间长了你会发现,很多问题表面看起来完全不同——一个是黑屏,一个是闪屏,一个是颜色错乱——但根因往往指向同一个环节, 比如 EDID 解析超时或者某个电源域的时序不对。工具只是帮你看到的手段,真正的调试能力在于你能不能快速把现象翻译成一句哪一层出了问题的假设,然后选对工具去验证它。希望这篇工具浅析能帮你缩短这个翻译的过程。