|
|
聊聊 Linux 设备驱动调试方法的一点经验
做 Linux 设备驱动,最容易踩的坑不是代码写不出来,而是问题出现时不知道从哪里下手。驱动问题通常不像应用程序那样直接报个异常就结束,它可能表现为设备没反应、偶发超时、系统卡死、内核 panic,甚至只是“偶尔不稳定”。所以我一直觉得,驱动调试最重要的不是工具多,而是先把思路理清楚。
最基础的还是 printk 和 dmesg。很多人嫌它土,但真到板子刚起来、调试环境不完整的时候,它往往是最可靠的办法。不过 printk 不能乱打,尤其是中断、定时器、频繁调用路径里,打印太多会改变时序,甚至把问题掩盖掉。我的习惯是打印关键状态:寄存器值、返回码、状态机变化、超时点,而不是每走一行都打印。
如果驱动已经比较稳定,但需要定位某个分支或流程,dynamic debug 很好用。它可以在不重新编译内核的情况下打开某些文件、某些函数里的调试信息,比到处加 printk 干净很多。配合 trace_printk、ftrace、trace-cmd 这类工具,可以看函数调用路径和耗时,对定位性能问题、调用顺序问题特别有帮助。
硬件相关驱动一定不要只盯着软件日志。比如 I2C、SPI、UART、GPIO、中断线这些,逻辑分析仪和示波器经常比内核日志更诚实。代码里写了发送,不代表线上真的发了;中断处理函数进了,不代表中断边沿符合芯片手册要求。遇到“驱动看起来没错但设备就是不动”的情况,我一般会先抓波形,再回头看代码。
内核自带的调试选项也值得打开,比如 lockdep、KASAN、KCSAN、kmemleak。很多驱动 bug 表面上是随机崩溃,实际是越界、重复释放、竞态或者锁顺序问题。这类问题靠肉眼看代码很难发现,打开这些检查项后,虽然性能会下降,但能节省大量时间。调试阶段不要怕内核“吵”,它吵出来的信息往往就是线索。
对于崩溃类问题,保存现场很关键。串口日志、完整 oops、调用栈、寄存器、触发步骤都要留。条件允许的话可以配置 kdump,拿到 vmcore 后用 crash 分析,比只看最后几行 panic 信息靠谱得多。很多时候真正的出错点不在 panic 的地方,而是在更早之前内存已经被破坏了。
另外,sysfs、debugfs、procfs 这些接口也可以为驱动调试服务。适当暴露一些内部状态,比如当前模式、计数器、错误码、寄存器快照,可以避免频繁改代码重编译。但要注意调试接口不要随手留到正式产品里,尤其是能直接改寄存器或控制电源的接口,后期很容易变成安全隐患。
最后我想说,驱动调试不要迷信单一工具。一个有效的流程通常是:先复现问题,再缩小范围,然后用日志确认软件路径,用波形确认硬件行为,用内核调试工具查内存和并发问题。很多“玄学问题”其实都是信息不够导致的。把证据收集完整,问题往往就没那么玄了。 |
|