ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

显示驱动调试实战:从内核态到用户态的分层工具详解

显示驱动调试实战:从内核态到用户态的分层工具详解 显示驱动调试这件事做过的都知道有多折腾。屏幕不亮、花屏、闪屏、分辨率不对、刷新率上不去这类问题不像纯软件逻辑错误那样能单步调试它牵扯到内核态驱动、显示控制器、时序信号、用户态合成器等多个环节问题出在哪一层往往一眼看不出来。我这几年在显示驱动上踩了不少坑从内核态到用户态把能用的调试工具基本都摸了一遍这篇就把我实际用过、觉得真正能解决问题的工具和思路梳理一下。内容主要面向做显示驱动开发、底层系统调试、以及被显示异常问题折磨的应用层同学核心是帮大家建立一套“分层定位”的调试思维同时给出每个层级最实用的工具和具体用法。1. 先理清显示驱动的分层结构才知道该用什么工具很多人拿到一个显示异常的问题第一反应就是抓logcat或者翻dmesg翻半天发现报错信息模棱两可然后就开始盲试。这个问题的根源在于没有先搞清楚显示链路的分层模型——工具选错效率必然低。1.1 显示链路的三层模型现代操作系统里的显示架构从底层到上层大致可以分成三层第一层是内核态的显示驱动和显示控制器Display Controller驱动。这一层负责和硬件打交道包括时钟配置、时序参数Porch、Sync等、像素时钟频率、内存与显示控制器的DMA通道配置等。Linux下一般是DRM/KMS框架Direct Rendering Manager / Kernel Mode SettingAndroid平台还要往下再深入一层到HWComposerHWC的HAL层。这一层出问题典型表现是屏幕完全没有信号、黑屏但系统活着、内核日志里有modeset相关的报错、或者画面撕裂tearing。第二层是用户态的图形栈包括合成与提交。Linux桌面环境下是Wayland/Weston、Xorg/DDX驱动Android平台下是SurfaceFlinger和HWComposer HAL的合作。这一层决定了一帧画面由哪些图层组成、以什么方式叠放、最终通过什么buffer提交给显示控制器。这一层出问题表现通常是画面分层顺序不对、GPU合成的图层有黑块/黑影、部分UI界面刷新闪烁、或者某个应用的黑名单/保护机制触发。第三层是应用层和中间件包括EGL/Vulkan调用、硬件视频编解码、ColorManager色彩管理等。这一层更多是逻辑问题常见的比如色彩空间设置错误导致偏色、动态帧率切换导致卡顿、HDR元数据传递出错导致高光过曝等。调试工具的选择逻辑很简单先判断问题出现在哪一层再对症下药。内核态的问题你用systrace去抓是抓不到的用户态合成顺序的问题你盯再久的寄存器也没用。1.2 调试工具选型的基本逻辑我在实际调试中习惯用“由底向上”的顺序如果问题是最基础的不显示、闪屏、花屏优先怀疑内核态的modeset和时序配置此时的调试工具以dmesg、modetest、debugfs节点为主。如果内核实模式设置看起来正常系统也能起来但UI显示有异常比如被遮挡、掉帧、图层错误那就是用户态合成链路的问题此时用SurfaceFlinger的dumpsys、gfxinfo、systrace/perfetto更有效。如果画面颜色不对、HDR不正常、帧率切换异常还要结合色彩管理相关的调试接口。这个优先级并不是固化的但按照这个顺序排查多数情况下能快速缩小范围避免在错误层里耗时间。2. 内核态调试三板斧dmesg、modetest、debugfs内核态是显示驱动调试的主战场。这里谈的“内核态”是广义的包括内核DRM子系统、硬件驱动、以及设备树/ACPI的配置。我自己的经验是内核态的调试工具不需要多花哨但一定要熟练尤其是dmesg过滤和modetest的用法。2.1 dmesg的正确打开方式别再看全文了很多内核对显示相关的日志都在DRM子系统里刷屏量极大。如果直接dmesg | grep drm可能刷出几百行其中大部分是驱动的状态切换日志。真正有用的其实是报错和警告。我常用的过滤方式是dmesg -T | grep -E drm|display|hdmi|dp|edid|modeset | grep -E error|fail|warn|timeout|abort|status加-T参数是为了显示人类可读的本地时间这在和其他日志比如logcat对齐时特别关键——显示问题的排查经常需要跨模块比时间线没有时间戳的日志根本没法定时定位。另外如果驱动在启动早期就出了问题dmesg拿不到历史日志这时候需要用内核的pstore/ramoops或者串口来抓早期日志。比如用earlycon或consolettyS0,115200这种内核参数把日志输出到串口这是调试“开机黑屏”问题的基本手段。2.2 modetest内核modeset状态的照妖镜modetest是libdrm工具集里的经典工具几乎所有做过显示驱动的人都会用。它基本功能有两个列出当前的显示设备和mode以及测试某个mode是否可用。最常用的几个操作# 列出所有DRM设备及当前状态 modetest -M imx-drm -p # 只看连接器和分辨率信息 modetest -M imx-drm -c # 强制测试某个mode输出 modetest -M imx-drm -s 42:1920x108060实操中我经常用-p参数去看当前正在使用的分辨率、刷新率以及每个plane的format和size。如果modetest显示的内容和实际屏幕不符基本可以断定是内核态mode设置的问题直接去查驱动里的时序配置。小技巧modetest的输出非常详细初看会懵我的建议是先关注三行——Connector行是否connected、Mode行当前mode是什么、Plane行当前plane的buffer大小。这三个对上了问题大概率不在内核modeset。2.3 debugfs驱动开放的“后门”内核的DRM驱动一般会在debugfs下创建一批调试节点这些是排查问题时的金矿。以Rockchip、全志这类常见平台为例# 查看framebuffer访问统计 cat /sys/kernel/debug/dri/0/framebuffer # 查看驱动当前的寄存器状态映射 cat /sys/kernel/debug/dri/0/state # 很多厂商驱动的私有调试节点 ls /sys/kernel/debug/dri/0/cat .../state这个命令很实用它会把当前所有CRTC、connector、plane的状态以原子化的方式dump出来。当你怀疑“驱动到底接没接上这个分辨率”的时候看这里比看论文级别的代码分析快得多。另一个容易被忽视的点/sys/kernel/debug/dri/0/{gem,fb,clients}这个路径可以列出当前有哪些进程在占用framebuffer。如果某个进程长期占着buffer不放可能导致显示卡顿或闪屏这里会给你一个直接的线索。3. 用户态图形栈调试从SurfaceFlinger到systrace/perfetto显示链路的第二层问题是日常最容易遇到的也是最多人“不会调”的。因为内核日志里几乎不会报错而问题却实实在在反映在屏幕上。3.1 SurfaceFlinger dump读懂图层合成状态Android平台上SurfaceFlinger是把所有应用的窗口图层合成成最终画面的核心服务。当出现图层被遮挡、显示顺序错乱、画面闪烁这类问题时第一时间应该去拿SF的当前状态adb shell dumpsys SurfaceFlinger --list | head -50该命令会把当前所有图层Layer列出来包括包名、宽高、position、layerStack等信息。如果某个应用的全屏黑色遮罩如Dialog、启动页盖住了关键内容这里一眼就能看到哪个图层在最上面、尺寸是否正确。进一步想看单个图层的细节adb shell dumpsys SurfaceFlinger --layer layer-name这条命令会给出该图层的完整状态——z轴顺序、alpha值、buffer format、当前frame number等。我在排查“应用启动时白屏/黑屏”问题时经常用这个命令确认该应用到底有没有成功提交buffer如果frame number长时间不增长说明App侧一直在请求帧但并未成功入队。3.2 gfxinfo与帧时间分析gfxinfo是分析应用渲染性能的轻量级工具虽说它偏应用层但在显示链路的调试中它能帮你区分问题到底出在应用渲染还是系统合成。adb shell dumpsys gfxinfo 包名 framestats值得注意的是framestats要求应用targetSdk高一些才能拿到全部数据并且会把每个帧的CPU/GPU耗时、提交时间、渲染间隔精确打点。我常用它去判断“掉帧”是应用主动丢帧渲染耗时高还是由于合成阻塞vsync等待时间异常。如果看到PROFILE_TOTAL很高但PROFILE_DRAW很低那问题大概率不在这个应用而在系统合成端。3.3 systrace/perfetto跨进程时间线神器写显示驱动调试文章不提systrace等于教钓鱼不教渔。它相当于对整个系统做了“时间轴上的CT扫描”。用perfetto新版systrace可以同时抓取内核态的事件、GPU渲染的slice、SurfaceFlinger的合成时间点从而找到帧在哪个环节卡住。常用命令# Samsung/Google等原生镜像上通常自带perfetto adb shell perfetto -o /data/misc/perfetto-traces/trace.file -t 10s sched freq idle gfx view res memory # 老版本系统使用systrace python systrace.py --time10 -b 8000 gfx view wm am sched freq idle实际排查花屏/掉帧时我一般会把gfx、sched、freq这三个tag全开。gfx看帧生命周期sched看关键线程的调度freq看CPU频率响应。三者对照基本能还原一帧在“App绘制→GPU渲染→SF合成→HWC呈现”整个链条里的耗时。这里有一个实操点perfetto打开的UI虽然炫酷但在命令行环境里建议直接导出info和ui格式用脚本对它做关键字搜索。我经常用grep -E BufferQueue|HWC|Present|Missed vsync trace.txt来找“vsync错过”这类典型卡顿原因。4. 动态追踪与性能剖析ftrace、perf与GPU计数器调试显示驱动只会看静态状态是远远不够的很多时候反而是“动态变化”才能暴露问题——比如某个中断频繁触发、GPU频率上不去、DMA传输超时。这一章讲动态追踪类的工具。4.1 ftrace内核函数的动态探针ftrace是内核自带的动态追踪工具功能强大且无额外依赖。平时可能用得不多但在显示驱动的疑难问题如中断风暴、锁竞争、调度延迟里它是真正的“终极武器”。最常用的两个场景场景一追踪显示中断的处理频率和时间。很多显示驱动依赖硬件vsync中断来触发帧提交。如果vsync中断处理函数耗时长或者频率异常会导致系统卡顿。# 打开ftrace追踪drm相关的函数 echo 0 /sys/kernel/debug/tracing/tracing_on echo function_graph /sys/kernel/debug/tracing/current_tracer echo drm* /sys/kernel/debug/tracing/set_ftrace_filter echo 1 /sys/kernel/debug/tracing/tracing_on # 等几秒后抓取结果 cat /sys/kernel/debug/tracing/trace /data/ftrace_log.txt场景二追踪dma_fence的等待链。帧提交通常要等GPU渲染完成这个“等待”就是dma_fence机制。当遇到“应用显示黑屏但GPU在忙”时看dma_fence有没有超时会是一个好的突破点。echo dma_fence* /sys/kernel/debug/tracing/set_ftrace_filter有一个常见误区ftrace不用一直开着因为开着的开销会改变时序行为。我的经验是先全局关掉设定过滤条件后再开抓个5-10秒就关不要当常驻工具用。4.2 perf定位调度与CPU侧的显示损耗perf是Linux性能剖析标准工具。显示驱动中的性能问题比如GPU频率没拉起来、中断导致CPU占用高、内存带宽不够都可以用perf拿到证据。常用命令# 看看CPU上的采样热点 perf top # 对某个具体进程的CPU行为进行剖析 perf record -g -p pid -o /tmp/perf.data -- sleep 10 perf report -i /tmp/perf.data但更实用的场景是使用perf的tracepoint功能去统计特定内核事件比如drm_vblank_event的触发频率perf stat -e drm:* -a sleep 3这会告诉你每秒钟vblank事件触发了多少次——如果和你期望的刷新率对不上说明vsync链路的配置有误解。这类计数器统计往往比干看代码更直观。4.3 GPU计数器与厂商工具不同GPU厂商的调试工具差异很大。高通用Snapdragon Profiler、Arm用Arm Mobile Studio、PowerVR有PVRMonitorNVIDIA则是Nsight。这类工具能给出GPU内部的占用率、带宽读、算力利用率、流水线瓶颈这些是通用的perf和ftrace触及不到的。就拿“GPU频率上不去导致卡顿”来说有时候从应用侧看不出任何耗时异常draw耗时很低但在GPU profiler上能明显看到GPU空闲时间极长、频率被锁定在低档。这个问题在通用工具里很难定位基本只能靠厂商profiler。5. 实战复盘一次花屏问题的完整排查链路理论讲再多不如记录一次真实的排查过程。下面这个案例不是特别罕见的问题但整个“工具链推理链”非常典型可以直观看到前面几类工具是怎么协作的。5.1 问题现象与初步判断设备某国产ARM平板Android系统。 现象开机一段时间后偶发花屏形成“彩条”状屏幕上半部约60%区域被撕裂的色块覆盖几分钟后可能恢复也可能一直持续。期间系统不崩溃触摸操作正常。第一反应是GPU渲染出错了。但如果GPU真有错通常系统会触发GPU recovery或者出现应用闪退但实测并没有所以问题不一定出在GPU侧更多的可能是在合成/显示输出阶段。5.2 排查路径从内核态到用户态第一步确认内核modeset是否正常。使用modetest查看连接状态modetest -M platform -p发现当前mode为1920x120060planes都绑定正常没有报错。内核modeset这一层基本排除。第二步抓dmesg看是否有时序相关的异常。使用前面提到的dmesg -T过滤发现若干条“EDID checksum error”的警告。这条信息很关键——它说明显示链路在EDID读取和校验上存在问题虽然系统仍能输出但可能引发部分硬件状态异常。第三步看SurfaceFlinger的图层状态。使用dumpsys SurfaceFlinger观察图层的排列和buffer状态发现出现花屏时有一个Layer的format是RGBA_8888但宽高和原始尺寸不匹配——这是典型的buffer size和显示控制器配置不一致的迹象。第四步用perfetto抓时序。抓取10s轨迹观察出现花屏的那个时间点SurfaceFlinger在合成该图层时有没有报QueuedBuffer的异常、有没有错过vsync。第五步启用厂商GPU profiler。最后看GPU的显存带宽和format转换单元利用率发现GPU在做RGBA--RGB格式重写时占用的带宽异常偏高。最终定位由于该Layer的buffer尺寸错误导致显示控制器在读取时跨地址访问了不连续的内存区域而GPU为了修复这次错误反复做大量格式转换通路互相影响酿成了花屏。根源修正方向是让应用申请正确尺寸的buffer并对HWC的validate流程加了防御。5.3 从案例中得到的几点教训花屏问题不等于GPU问题很大体量是“buffer尺寸不匹配”或“display控制器地址跳变”导致的。dmesg里的警告不能跳过尤其像EDID checksum这类看似开关机都能过但可能形成隐性不匹配。SurfaceFlinger的dumpsys信息要与modetest的硬件信息交叉验证单看任何一边都会错过关键线索。6. 容易被忽略的“隐形调试工具”录屏、抓包与文档最后我想单独说几个“不起眼但关键时刻救命”的工具/手段这些在一般调试手册里很少被拿出来单讲但实战价值相当高。6.1 录屏与截图是显示问题最诚实的证物很多人遇到花屏、闪屏第一反应是去查代码但忽略了一个最基本的动作录屏/截图。录屏adb shell screenrecord记录的是合成后的画面天然能区分“底层输出异常”和“应用层逻辑异常”如果录屏画面是正常的、但现场屏幕花屏说明是显示控制器或面板输出阶段的问题可直接定位内核侧如果录屏画面也是花的那问题就出在合成链路或上层可以把排查重点放到GPU/SF。这个小结论能帮你第一时间省下大量时间。截图同理可以快速检查“画面黑屏是不是因为图层透明度异常”这类纯逻辑问题。6.2 协议分析仪和HDMI/DP抓包极端情况下的“降维打击”这不是常规工具但当你做HDMI/DP这种外部接口的驱动调试时光靠软件日志是远远不够的。硬件上的时序信号、AUX channel通信、HDCP握手过程必须借助协议分析仪来抓。比如DSC显示流压缩开启后出现花屏这种问题靠寄存器对比很难定位因为压缩流的error信息几乎不会体现在软件层。这时候一个能解析DP AUX帧的分析仪能直接看到DSC参数是否正确发送、接收端有没有回复异常。这类工具贵、操作门槛也高但在排疑难杂症时确实能一剑封喉。6.3 不要小看datasheet和vendor SDK文档最后想泼一点冷水工具再强也替代不了读文档。面对一个新的显示控制器我会把下面三份资料先吃透芯片的Display Controller章节尤其是接口时序和内存带宽计算面板的datasheet尤其是电源上电时序、前后肩、时钟范围——这直接决定了初始化和mode的设定厂商提供的显示框架说明文档如新思、谱瑞等方案里面往往有调试寄存器映射和常见故障现象对照表。说真的遇到一些“要么花屏、要么闪屏、要么奇葩水波纹”的问题我去查寄存器定义比对两个版本的系统固件发现答案往往就藏在datasheet的一行注释里。显示驱动的调试工具箱跟别的领域不太一样它的核心东西其实不算多但每一个工具的适用场景、使用顺序和组合方式决定了效率差距。我现在的习惯是问题一入手先不看代码先按“内核modeset - 合成/提交 - GPU/带宽 - 时序/协议”的顺序把工具跑一遍基本能在半小时内把问题缩小到某个子系统剩下的就是细分钻研。最后再提醒一句不同厂商、不同平台的工具细节差异很大这份清单给你的是思路和主线具体接口名要以你手头那份SDK文档为准。拿不准的时候回到现场去抓trace永远比猜要好。
返回列表