ARTICLE DETAIL

资讯详情

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

嵌入式工程师都是柯南:从串口日志到ftrace的完整解BUG方法论

嵌入式工程师都是柯南:从串口日志到ftrace的完整解BUG方法论 1. 为什么说嵌入式工程师的日常就是一场推理剧干嵌入式这行超过五年的人大概都有一种共同的肌肉记忆看到一块板子上电后没反应第一反应不是慌而是像柯南一样蹲下来盯着那颗电源指示灯脑子里飞速过一遍现场证据——电流多少、晶振起没起振、复位脚电平对不对、串口有没有打印。这套动作和侦探破案几乎一模一样先封锁现场再收集线索然后排除法锁定嫌疑人最后还原作案过程。标题说嵌入式工程师都是柯南这不是玩梗而是对这个职业最精准的概括。嵌入式开发和纯软件最大的区别在于你面对的是一个物理世界和数字世界交界的黑盒。应用层代码写错了编译器会告诉你但一块板子跑不起来可能是硬件、可能是时钟树、可能是启动流程、可能是内存映射、可能是缓存一致性甚至可能只是某个电容焊反了。没有任何一个报错信息会直接告诉你答案你只能靠推理。这篇文章想聊的就是嵌入式工程师破案的完整方法论。从拿到一块问题板子开始怎么建立证据链、怎么用工具固定现场、怎么用排除法缩小范围、怎么避免被假线索带偏。关键词里的嵌入式、Linux、解BUG、嵌入式工程师本质上都指向同一件事在信息不完整的情况下用逻辑和工具把问题逼到墙角。不管你是刚入行的新手还是做了几年还在被各种玄学问题折磨的老兵这套思路都能直接拿去用。我见过太多人解BUG的方式是改一改试试看改完好了就以为解决了结果过两天换个环境又复现。这不是破案这是碰运气。真正的柯南式排查核心在于每一步操作都要能排除掉一部分可能性而不是随机试错。下面我把这套方法拆开讲。2. 案发现场的第一手证据上电前后的信息采集2.1 别急着上电先做静态检查很多新手拿到问题板子第一件事就是插电看现象。这是大忌。柯南到案发现场第一件事是拉警戒线不是冲进去乱翻。嵌入式排查也一样上电之前有一堆静态信息必须先确认否则你可能在用一个错误的硬件状态去推断软件问题方向从一开始就歪了。静态检查我一般按这个顺序走目视检查焊接重点看BGA封装芯片周围、电源芯片、晶振、连接器。虚焊和连锡是嵌入式最经典的嫌疑人尤其是手工焊接的样板。我遇到过一块板子跑十分钟就死机查了三天软件最后发现是DDR某颗颗粒的一个电源脚虚焊温度一上来就接触不良。万用表测关键电源对地阻值在断电状态下测3.3V、1.8V、1.2V等主要电源轨对地的电阻。如果阻值接近0说明有短路这时候上电就是烧板子。正常情况应该有几百欧到几K欧的阻值具体看电路设计。确认晶振和复位电路晶振有没有起振电容、复位芯片的复位阈值对不对、复位脚有没有被意外拉低。这些是上电没反应类问题的头号嫌疑。核对启动模式引脚很多SoC有BOOT_MODE引脚决定从eMMC、SD卡还是串口启动。引脚拉错芯片根本不会去读你的固件现象就是完全没反应。提示静态检查阶段不要嫌麻烦这一步花二十分钟可能省掉后面三天的瞎折腾。我现在的习惯是每块新板子到手先花半小时把电源、时钟、复位、启动模式这四样确认一遍再上电。2.2 上电瞬间要盯住的三个量静态检查没问题可以上电了。但上电不是插上电就完事你要在通电的瞬间盯住三个关键量这三个量构成了最基础的证据链。第一个是电流。用带电流显示的稳压电源供电观察上电瞬间的电流变化。正常板子上电会有几个台阶先是静态电流几十mA然后内核启动电流上升最后稳定在某个工作电流。如果电流直接冲到限流值说明有短路如果电流纹丝不动说明芯片根本没启动如果电流反复跳动可能是复位在反复触发或者电源不稳。电流曲线是判断板子到底活没活最直接的证据。第二个是时钟。用示波器测主晶振和各个PLL输出。晶振不起振整个系统就是死的。我习惯先测主晶振确认有波形且频率正确再测SoC输出的各路时钟。这里有个坑有些晶振用示波器探头一碰就停振因为探头电容影响了振荡电路这时候要用高阻探头或者测时钟输出脚而不是晶振本身。第三个是复位和启动打印。如果板子有调试串口上电后应该能看到BootROM的打印。看不到打印先确认串口线序TX/RX有没有接反、波特率常见115200、串口电平TTL还是RS232。这些基础项确认完还是没打印才往芯片启动流程去查。2.3 建立正常基线比找问题更重要这是我想强调的一个反直觉的点排查问题的前提是你知道正常是什么样。很多新手解BUG效率低是因为他手里没有一块能正常工作的同型号板子做对比。有对比板你可以做差分正常板电流多少、时钟什么波形、串口打印什么内容、某个测试点电压多少。然后拿问题板一项项对比差异点就是线索。没有对比板怎么办那就靠数据手册建立理论基线。比如SoC的启动流程数据手册里会写清楚上电后先跑BootROMBootROM会去读启动模式引脚然后从对应介质加载固件。你把这个流程画出来每一步对应一个可观测的现象问题板卡在哪一步范围就缩小到那一步对应的硬件或配置。我自己的习惯是给每个项目建一个基线文档记录正常板子的关键测量值。这个文档在后期排查时价值极高相当于案发现场的标准照片。3. 从串口打印到内核日志软件侧的证据链怎么串3.1 串口打印是嵌入式工程师的监控录像硬件层面确认板子活着之后软件侧的第一手证据就是串口打印。BootROM的打印、Bootloader的打印、内核启动日志、文件系统挂载信息这一整条链路就是系统的监控录像。柯南破案靠监控嵌入式排查靠日志。但看日志有个技巧不要只看最后报错的那一行。很多人看到内核panic就盯着panic信息看其实真正的线索往往在前面几十行。比如一个Unable to mount root fs的错误根因可能是前面某一行mmc0: error -110 whilst initialising SD card说明SD卡初始化就失败了根因在存储驱动而不是文件系统。我一般会把完整启动日志保存下来然后用关键词过滤。Linux下常用的过滤命令# 保存完整启动日志 dmesg boot.log # 过滤错误和警告 dmesg | grep -iE error|fail|warn|timeout # 查看特定驱动 dmesg | grep -i mmc\|sdhci # 查看内核启动参数 cat /proc/cmdline3.2 内核日志的级别和常见假线索Linux内核日志有级别划分从0到7数字越小越严重。理解级别能帮你快速定位问题严重程度级别名称含义典型场景0EMERG系统不可用内核崩溃1ALERT必须立即处理硬件故障2CRIT严重错误驱动致命错误3ERR错误设备初始化失败4WARN警告可恢复的异常5NOTICE正常但重要设备注册成功6INFO信息常规启动信息7DEBUG调试详细调试输出这里有个大坑WARN级别的日志经常是假线索。很多驱动在正常工作时也会打WARN比如某些时钟没有正确释放、某个可选功能不支持。如果你一看到WARN就以为是问题会被带偏。判断一个WARN是不是真问题要看它后面有没有导致实际功能失败。没有导致失败的WARN先记下来别急着改。3.3 用ftrace和动态调试锁定运行时问题启动阶段的问题靠日志能解决大半但运行时的问题比如跑着跑着死机、某个外设偶发不工作日志往往不够用。这时候要上动态调试工具。ftrace是内核自带的跟踪工具能跟踪函数调用、中断、调度等。比如你怀疑某个驱动有竞态可以跟踪它的函数调用序列# 挂载debugfs mount -t debugfs none /sys/kernel/debug # 查看可用跟踪器 cat /sys/kernel/debug/tracing/available_tracers # 跟踪特定函数 echo function /sys/kernel/debug/tracing/current_tracer echo your_driver_func /sys/kernel/debug/tracing/set_ftrace_filter echo 1 /sys/kernel/debug/tracing/tracing_on # ... 复现问题 ... echo 0 /sys/kernel/debug/tracing/tracing_on cat /sys/kernel/debug/tracing/tracedynamic debug是另一个利器能动态打开内核里pr_debug/dev_dbg的输出不用重新编译内核# 打开某个文件的调试输出 echo file drivers/mmc/* p /sys/kernel/debug/dynamic_debug/control # 打开某个函数的调试输出 echo func sdhci_send_command p /sys/kernel/debug/dynamic_debug/control这两个工具的价值在于它们让你能在不修改代码、不重新烧录的情况下把系统的内部行为录下来。这就是柯南调监控的能力。4. 硬件与软件的边界那些最容易误判的交叉问题4.1 内存映射和缓存一致性嵌入式最经典的玄学关键词里提到了深入解析omap-l137 dsp内存映射与c674x缓存架构这其实指向嵌入式里最折磨人的一类问题内存映射和缓存一致性。这类问题的典型现象是代码逻辑明明是对的但读到的数据就是不对或者加了一句printf就好了去掉又坏或者换个编译优化等级现象就变了。这类问题的本质是CPU看到的地址和实际物理内存的对应关系以及缓存和主存之间的数据同步。举个生活化的类比缓存就像你办公桌上的便签主存是档案柜。你改了便签上的内容但档案柜里还是旧的别人去档案柜拿资料拿到的就是过时的。缓存一致性问题就是便签和档案柜对不上。在嵌入式里这类问题常见于几个场景DMA传输DMA直接访问物理内存不经过CPU缓存。如果CPU把数据写在缓存里还没回写DMA读到的就是旧数据。解决方法是DMA传输前后做cache flush/invalidate。多核共享内存两个核访问同一块内存各自的缓存可能不一致。需要用内存屏障或者非缓存映射。外设寄存器映射外设寄存器必须用volatile或者非缓存映射访问否则编译器优化和缓存都会导致读写异常。排查这类问题的关键是先确认地址映射对不对再确认缓存属性对不对。Linux下可以看/proc/iomem确认物理地址映射看设备树里的reg属性确认寄存器地址看dma-ranges确认DMA地址转换。4.2 时钟和电源看不见的凶手时钟和电源问题是最难查的因为它们往往没有直接报错现象是偶发或者特定条件下才出现。我遇到过一块板子常温下跑得好好的一到高温就死机。查了两周最后发现是某个电源的纹波在高温下变大导致DDR时序裕量不够。排查时钟问题示波器是必备的。重点看几个指标频率对不对、占空比是否接近50%、上升沿是否干净、有没有过冲和振铃。时钟质量差会导致各种莫名其妙的错误而且这些错误往往表现为软件问题让你在代码里白找半天。电源问题更隐蔽。除了电压值还要看纹波、上电时序、掉电时序。很多SoC对电源上电顺序有严格要求顺序错了可能不启动或者损坏芯片。数据手册里的Power Sequencing章节一定要仔细看。注意时钟和电源问题的一个典型特征是换一块板子就好了或者重新焊一下就好了。如果你遇到这种玄学现象优先怀疑硬件连接和电源质量别在软件里死磕。4.3 用示波器和逻辑分析仪固定现场软件工程师解BUG靠日志嵌入式工程师还得靠示波器和逻辑分析仪。这两个工具能把电信号变成可视化的波形相当于给硬件世界装了监控。逻辑分析仪特别适合看总线协议I2C、SPI、UART、CAN。比如你怀疑I2C通信失败用逻辑分析仪抓一下波形能直接看到起始条件有没有、地址对不对、ACK有没有、数据对不对。这比在代码里加一堆打印高效得多。示波器适合看模拟信号和时序关系电源纹波、时钟质量、复位时序、信号完整性。我排查DDR问题的时候会用示波器看DQS和DQ的时序关系确认建立保持时间够不够。这两个工具的使用心得是先想清楚要抓什么再设置触发条件。逻辑分析仪可以设协议触发比如当地址等于0x50时触发这样能精准抓到出问题的那一帧。盲目抓一大堆数据反而看不出问题。5. 排除法实战一个偶发死机问题的完整排查链路5.1 问题描述和初步现象讲一个我实际处理过的案例把上面的方法论串起来。现象是一块基于ARMLinux的工业控制板运行几个小时到几天不等会死机死机时串口无输出看门狗复位后能恢复。这个问题折磨了团队两周因为复现周期太长而且现象不固定。第一步我做的不是改代码而是建立复现条件和证据采集机制。既然几小时到几天才复现那就让板子一直跑同时开启所有能开的日志和监控串口日志全程记录到文件用脚本每分钟记录一次CPU温度、内存使用、关键进程状态打开内核的hung task检测和soft lockup检测配置看门狗复位后保留上一次的日志用pstore或者预留内存5.2 缩小范围从死机到哪一类死机死机是个笼统的描述首先要分类。死机可能是内核panic、内核hang死锁、硬件挂死、电源问题。区分方法是看复位后的日志和现场。我们配置了pstore复位后能读到上一次崩溃前的内核日志。第一次抓到日志时发现最后几行是某个驱动的打印然后就没有了。这说明是内核在某个驱动里hang住了不是硬件突然断电。有了这个线索范围从整个系统缩小到某个驱动。接下来用排除法把非必要的驱动一个个禁用看问题是否还复现。这个过程很耗时但很有效。最终定位到是一个自定义的SPI驱动在特定时序下会死等一个永远不会来的中断。5.3 根因定位中断丢失的完整推理定位到SPI驱动后继续深挖。为什么中断会丢我们用了ftrace跟踪中断处理# 跟踪中断相关事件 echo 1 /sys/kernel/debug/tracing/events/irq/enable echo 1 /sys/kernel/debug/tracing/events/spi/enable跟踪结果显示在死机前SPI控制器发出了中断但CPU没有进入中断处理函数。进一步查发现这个SPI中断和另一个外设共享中断线而另一个外设的中断处理函数在某些情况下会长时间关中断导致SPI中断被延迟到超时。根因是共享中断线上的一个驱动关中断时间过长导致另一个驱动的中断丢失。解决方案有两个一是把SPI中断改成独立中断线硬件改板二是优化那个长关中断的驱动软件修改。我们选了软件方案把长关中断的操作改成下半部处理。5.4 验证和回归怎么确认真的修好了修完不是就完了柯南破案还要有完整证据链。验证分三步定向验证构造能触发原问题的测试条件跑够原来复现的时间确认不再复现。压力验证加大负载、提高温度、反复开关外设看是否引入新问题。回归验证把修改后的驱动跑完整的系统测试用例确认没有影响其他功能。这个案例前后花了三周但收获很大我们后来把共享中断关中断时间加进了代码review checklist类似问题再没出现过。6. 把破案能力变成肌肉记忆日常习惯和工具链6.1 每个项目都要留现场保护机制柯南能破案是因为案发现场有监控、有物证。嵌入式排查也一样你得在系统正常的时候就埋好监控。我现在的习惯是每个项目默认开启这些pstore/ramoops崩溃时把内核日志存到预留内存复位后能读出来。这是排查偶发死机的神器。看门狗复位原因记录复位后能知道是看门狗复位、电源复位还是软件复位。关键状态定期快照用脚本定期记录温度、电压、内存、进程状态出问题时能看历史趋势。完整启动日志归档每次固件更新都保存一份启动日志方便对比。这些机制在项目正常时看起来没用但一旦出问题它们就是你的监控录像。6.2 工具链的熟练度决定排查速度嵌入式排查涉及的工具很多熟练度和排查速度直接相关。我列一下自己常用的工具和场景工具主要用途关键技巧示波器时钟、电源、信号完整性用高阻探头测晶振避免探头电容影响逻辑分析仪I2C/SPI/UART/CAN协议用协议触发精准抓问题帧JTAG调试器单步、断点、寄存器查看死机时连上读PC和栈回溯ftrace内核函数跟踪配合set_ftrace_filter缩小范围dynamic debug动态开日志不用重编译运行时开关perf性能分析看热点函数和调用栈strace系统调用跟踪看应用层卡在哪个系统调用gdb gdbserver应用层调试远程调试嵌入式应用这些工具不需要每个都精通但至少要知道什么问题该用什么工具。比如应用层卡住用strace内核hang用ftrace硬件时序用逻辑分析仪。6.3 经验沉淀把每次破案变成团队资产最后想说的是嵌入式排查能力不是靠天赋是靠积累。每解决一个问题都应该沉淀下来问题现象、排查过程、根因、解决方案、如何预防。我自己的习惯是维护一个问题库按现象分类比如上电无反应偶发死机外设不工作性能不达标。这个库的价值在于下次遇到类似现象你能快速找到排查方向而不是从零开始。团队里新人遇到问题也能先查库减少重复劳动。嵌入式工程师都是柯南这句话的另一层意思是破案能力是可以训练的。你不需要天生聪明你需要的是系统的方法、熟练的工具、和不断积累的经验。每次解BUG都是一次推理训练做得多了看到现象就能条件反射地列出几个嫌疑方向这就是老手和新手的区别。我个人的体会是排查问题最忌讳的就是急。越急越想乱试越试越乱。正确的做法是慢下来先把现场固定住把证据收集全然后一步步排除。柯南从来不急着指认凶手他先把所有线索摆出来让逻辑自己指向答案。嵌入式排查也是这个道理。
返回列表