尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

开源UDS刷写日志分析工具:从CAN帧还原到NRC定位

开源UDS刷写日志分析工具:从CAN帧还原到NRC定位 1. 刷写日志分析这件事为什么值得单独做一个开源工具大概两年前的这个时候我在客户现场排查一次批量刷写失败。现象很典型产线上十台ECU里有三四台中途报错Bootloader没跑完整包数据只写了一半。客户甩给我一份几十MB的CANoe ASC日志让我帮忙看看什么问题。那半天我干了一件特别原始的事情——用文本编辑器打开日志肉眼在十六进制帧里翻0x7F。找到负响应之后还要反查它是回应谁的、发生在哪个刷写阶段、当时传的是哪一块数据。几十MB的日志人工翻下来眼睛都快瞎了。最后定位到的原因其实非常低级某一帧0x34请求下载里声明的memorySize比实际文件长度少了64字节Bootloader按声明长度接收完数据之后做校验发现Flash里多了一段不该有的内容直接抛了0x72。这个错误放在几万条总线帧里人工几乎不可能一眼看穿。从那天起我就明确了UDS/ISO-TP刷写日志的离线分析不是一个偶尔手工看一下的活儿它值得被做成一个正规工具。因为刷写场景的数据量实在太大了一条完整的刷写流程光是0x36传输数据的连续帧就有几千帧再加上多 ECU 并发、多通道CAN纯靠人眼永远只能看到局部。而刷写出问题的场景又偏偏集中在全局视角才能看出的异常某个阶段超时了、某条会话窗口期丢了帧、某个NRC出现位置的上下文不对、块传输的有效吞吐率骤降。市面上不是没有商业工具能看日志但要么贵、要么绑定特定硬件平台、要么对非标UDS服务支持不友好。自研Bootloader的团队、做产线刷写工具的团队、售后分析工程师其实都需要一个免费、跨平台、能自己改的逻辑分析层。这就是我开源这个工具的直接动机。这个工具不是什么CANoe杀手它做三件事把CAN ISOTP帧还原成完整的诊断服务会话自动切分出刷写阶段并统计各阶段耗时与错误输出一份人能看懂、CI能解析的报告。定位清晰使用简单不需要安装复杂的运行时命令行和库两种方式都支持。适合Bootloader开发、诊断测试、产线工具集成、售后日志排查这四类人群。2. 解析内核从CAN总线帧还原成诊断会话工具的核心价值在于它把底层的、混乱的CAN帧序列变成一套结构化的诊断服务记录。这个还原过程分三层每层都踩过不少坑。2.1 输入层不只有CANoe日志做工具之前我第一件纠结的事就是输入格式。明明都是刷写日志Vector和PCAN导出的文件、ZLG上位机另存的CSV、自研刷写工具打印的TXT字段差异大到让人崩溃。时间戳单位有的用秒、有的用毫秒、有的用微秒CAN ID有的写十六进制、有的写十进制数据列有的叫Data、有的叫Data[0]、有的干脆是一串不带空格的十六进制字符串。所以输入层必须做归一化。工具统一把各种形式转成内部的一种中间表示我叫它UDLRUniversal Diagnostic Log Representation本质上就是一条条带全局时间戳、通道号、帧方向、CAN ID、数据字节的归一化帧序列。解析器在读取阶段就完成单位和进制识别后面所有分析逻辑都在UDLR上做不再关心原始日志是哪种格式。目前内置支持的格式包括CANoe的ASC/BLF、PCAN的TXT/CSV、ZLG USBCAN的CSV以及各公司自研工具的通用行文本格式。遇到实在不认识的格式可以写一个几十行的解析器把日志转换成CSV喂进来接口留得比较宽。另外归一化过程中有个容易忽略的隐患有的日志软件会在两行帧之间插入类似ErrorFrame、OverloadFrame的行或者带-号的总线错误标记。解析器如果直接跳过这些行可能丢掉错误上下文。我选择把它们也转成UDLR里的特殊事件帧在后续分析的超时判断和错误统计里会用到。2.2 状态机ISO-TP 重组不能只按CAN ID粗暴聚合ISO-TPISO 15765-2做了什么事它把一包可能几百字节的诊断请求拆成若干CAN帧单帧、首帧、连续帧、流控帧靠PCI字段区分。很多人在自己写解析脚本时想当然地认为只要按CAN ID把帧放在一起拼起来就是完整消息。这个思路在绝大多数场景下能跑但在真正复杂的刷写日志里会翻车。翻车的典型场景有两个。第一个是多会话并发现在的ECU刷写时诊断仪可能同时维持两条逻辑会话例如一条用于传输数据的物理寻址会话一条用于并行读取信息的会话它们的ISO-TP消息交叉出现在同一条物理CAN ID上。如果只按ID聚合A会话的连续帧会混进B会话的流控状态机里直接导致解析错乱。第二个是功能寻址。刷写前的网络唤醒、预编程阶段经常用功能寻址发送0x85控制DTC设置这类广播请求多个ECU会同时回响应。这时候一个请求会对应多条响应流每条流的连续帧都交叉在一起。状态机必须区分请求-多条响应的关系不能简单地把同一个ID的帧归成一条流。我的做法是给每条诊断会话维护独立的ISO-TP状态机状态机由(通道, 发送方ID, 接收方ID, 寻址类型)四元组标识。首帧来时创建会话状态记录总长度、流控参数BS、STmin连续帧按序号校验连续性序号回绕、丢帧、重复帧都记录成事件流控帧单独处理并跟踪它对应的首帧单帧则直接解析成一条短消息。状态机处理完之后输出的就不再是CAN帧而是一组组完整的ISO-TP消息每条消息带有首帧时间、结束时间和总字节数。这个状态机看起来不复杂但边界情况极多。比如首帧刚发出去对方回了一个流控帧(WAIT)然后又重复了一个首帧这时候应该以哪个首帧为准再比如连续帧序号明明应该是0x0F但来了0x01是丢帧了还是总线上的其他干扰这些场景我都做了保守处理宁可标记为异常事件也不去强行猜测因为猜测的结果会直接污染后续的阶段识别数据。2.3 UDS 服务语义解析正响应、负响应、带参数的复杂服务ISO-TP还原的是消息消息本身还得解析成UDS服务。UDSISO 14229的规则大家都很熟请求SID是0x10正响应就是0x50负响应的第一个字节永远是0x7F后面跟着请求SID和NRC负响应码。真正麻烦的是不同服务携带的参数语义完全不同不能只解析SID就完事。举例来说0x22按ID读数据后面跟着的是DID0x27安全访问后面跟着的是子功能和种子/密钥0x31例程控制后面跟着的是子功能和RID0x34请求下载后面对齐的是地址长度格式、内存地址和大小0x36传输数据后面是块序列号加数据块。刷写日志分析如果不解析这些参数就完全无法回答到底是哪一个RID擦除失败下载的地址启动地址是多少哪个DID读取超时了这类问题。工具内置了常用服务与子功能的解析字典比如0x10的会话类型、0x27的子功能序列、0x31的例程ID、0x34的地址格式标识、0x19的DTC掩码、0x14清除DTC、0x11复位方式等。每个解析出的字段会作为一个带名的结构挂到服务记录上因参数非法导致的NRC比如0x34的addressAndLengthFormatIdentifier不是标准里的0x44或者0x36的块序不连续工具会自动在原文上下文里标出来。对于非标诊断服务比如各OEM自己定义的私有服务、扩展的RID工具提供一份服务字典配置文件用户在配置里写明service_id、parameter_layout就能让工具按自定义规则解析。对这个开源项目来说非标服务一定存在你不可能预测所有厂商的私有定义所以配置能力比预置知识库更关键。3. 刷写阶段识别与错误定位工具真正能帮上忙的地方解析完成、还原出服务会话之后才轮到工具最有价值的功能把刷写流程自动分段并且把错误定位到具体环节。3.1 阶段切分不能只靠0x10会话切换刷写流程直觉上可以分为预编程、编程会话、下载、校验、复位这几段。但实际日志里阶段边界远没有教科书上那么干净。一个简单的例子有的Bootloader在进入编程会话之前还会先发几个握手报文、读几个版本号DID这些报文都发生在默认会话阶段而0x10 02进入编程会话之后可能又因为安全访问的种子交换反复请求多次。如果工具只靠0x10 02这一条记录来切分阶段那么预编程和编程的临界点就永远差着一段安全访问之前的关键交互。我的实现是综合多个信号来判定阶段边界而不是依赖单一服务0x10的会话切换记录作为一级线索标记大致区间关键操作序列作为二级线索比如0x27的种子/密钥交换标志着安全访问阶段的开始0x31 01 RID标志着擦除/例程阶段的开始0x34标志着下载阶段的开始如果日志里出现了0x11ECU复位之后又进入默认会话基本可以判定整个刷写流程已经结束。阶段切分的结果会以时间线的形式展示每个阶段下面用树形结构挂该阶段服务记录。某条服务超时了工具会自动算出来超时发生在哪个阶段以及该阶段内同类型服务的平均耗时是多少。这样一份几十MB的日志最终形成的就是一张三五行能看懂的阶段时间表。3.2 NRC 定位的粒度要精确到它是应答谁的刷写日志分析里最容易被误导的就是负响应。很多工程师一看到0x7F 31 31就以为例程控制执行失败赶紧去查Flash算法其实这帧的完整上下文可能是例程控制返回0x31超出范围因为例程参数里的block counter不对。工具处理NRC时会把负响应消息和它对应的请求消息关联起来并提取出请求参数。比如一条0x34 00 44 00 00 01 00 00 00 FF FF的请求如果ECU回了0x7F 34 31工具不只是打印NRC 0x31 (requestOutOfRange)它会把请求的地址0x00000100、大小0xFFFF、以及上一帧0x22读到的Flash起始地址一起标在报告里。有了这些上下文你就能直接判断是地址写错了、大小溢出了还是Driver本身有问题根本不用回到原始日志里再对一遍十六进制。这里我也对NRC做了分级分类。像0x22条件不正确、0x31超出范围、0x33安全访问被拒绝、0x35无效密钥、0x72通用编程失败属于硬错误一旦出现基本就是刷写失败0x78请求已接收、正在处理属于临时状态正常刷写中出现过几次并不算异常0x7E服务不支持和0x7F子功能不支持属于配置类问题常见原因是服务字典对不上或者诊断仪和ECU版本不匹配。报告里不同级别用不同标记区分避免一竿子把所有NRC都当故障。3.3 超时应答检测以及吞吐率和水线刷写日志里除了负响应最常见的失败是没有响应。诊断仪发出一条请求CAN总线上始终没有对应SID的正响应或负响应出现这种情况在日志里表现为一段空白没有任何总线活动。工具无法直接知道ECU当时在想什么但它能做合理的超时判定一条请求发出后如果在预设的时间阈值内没有任何来自目标地址的帧无论是不是响应则标记为超时事件并统计超时前的重试次数、连续超时次数。除此之外工具会按阶段统计块传输的指标。比如0x36传数据块的有效时间、块与块之间的间隔、以及整包数据的平均吞吐率。这类指标对排查刷写慢但不失败的问题很有用。我曾经见过一个案例整体刷写耗时从正常的30秒涨到3分钟日志里没有任何错误帧人眼看半天看不出哪里不对劲但工具的块间隔统计一下就把问题暴露了每次传输数据块之间都有一次800ms的空档再一查是诊断仪侧在等待某个应用报文的周期性发送整条总线的调度优先级配错了。提示超时判定一定要可配置。不同ECU的Bootloader性能差异很大有的下一条请求间隔200ms都算正常有的超过50ms就是问题。默认阈值按100ms/500ms两级做了保守设置但生产环境里建议按具体ECU的Bootloader文档调一次。4. 实现中的真实踩坑时间戳、性能、还有协议细节工具从原型到能用中间绕了不少弯路。有几件事如果一开始就想明白能省掉很多返工。4.1 时间戳精度与多通道日志对齐第一个绕不过去的坑是时间戳。不同工具导出的时间戳精度参差不齐CANoe ASC 默认到秒的小数位BLF内部按微秒记录有些自研工具的TXT日志则只有毫秒级分辨率。如果解析内核统一把时间内部转成微秒存储毫秒级日志按0填充虽然在单条记录层面差别不大但一旦做多通道对齐、超时分析毫秒级颗粒度会让整个时间线失真。多通道日志对齐是另一个隐性难点。使用双通道CAN卡刷写时诊断仪在通道0上发0x10 02ECU在通道1上回0x50 02两路日志文件如果分开导出时间基准可能不是同一个原点。工具的做法是尝试从日志头部解析全局时间起点如果两个通道的时间起点不一致就利用日志里同步发出的一对报文的相对位移做时间校正。这个校正不保证完全精确但能让跨通道分析的误差从秒级降到百微秒内。最后如果你拿到的日志完全没有时间戳——确实有这种生产情况某些老产线控制器只打印帧序列不带时间——工具会降级为按序分析模式。这种模式下功能依旧可用但超时判定、阶段耗时统计会变成N/ANRC定位只能靠帧序号关联不能靠时间窗口关联。4.2 大日志的内存与性能设计很早的版本我用的是全部读进内存再分析的思路处理10MB以内的日志非常爽但一上真实场景就露馅了。客户给的日志经常是几百MBBLF格式解出来甚至能到1GB以上如果用列表把所有帧对象加载进内存GC能把整个进程卡死。现在的实现改成了流式解析 二级索引第一遍扫描逐行读取原始日志边读边构建UDLR帧同时把每帧的字节偏移和时间戳写入一个轻量索引文件第二遍分析需要随机访问帧内容时通过索引里的偏移去mmap映射的日志文件里取数据不会一次性把全部帧读入内存聚合统计过程是允许丢失大部分细节的只保留每个阶段的统计量、异常事件和代表性帧。这个设计让工具能够流畅处理数GB级别的日志内存占用保持在几百MB以内。代价是实现复杂度上去了不少但换来的是实打实的大日志分析能力。4.3 展示层里的0x7F 22还是0x7F 0x22以及VIN这类ASCII字段还有一个看着小但特别影响使用体验的问题日志展示层的表述风格不统一。有的工具把所有字节统一写成0x7F 22 02这样的两字节形式有的写成7f 22 02还有的会把负响应里的SID和NRC合并成一个含义字符串。工具在输出报告时统一采用0x7F SID NRC的格式但在搜索和命令行过滤里同时兼容7F2213、7f 22 13、0x7f 0x22 0x13三种写法避免工程师从同事群里复制的片段直接查不到结果。除此之外刷写日志里经常能碰到ASCII可打印字段比如VIN码、软件版本号、Bootloader版本。工具在解析0x22响应、0x31例程结果这类服务时如果发现字段内容是可见ASCII字符会额外渲染一列人类可读的值。这个功能实现起来很简单但现场排查时能省掉大量把十六进制转回字符串再比对的时间。5. 命令行集成与二次开发怎么在自己的工作流里用起来工具设计之初就把嵌入其他工作流当成一等公民因此提供了命令行和Python API两层接口。5.1 最常用的几个命令# 解析CANoe ASC日志输出结构化分析报告 udslog analyze flash_log.asc --format asc --output report.html # 只提取刷写下载阶段过滤0x36块传输明细 udslog analyze flash_log.blf --format blf --phase download --filter sid0x36 # 导出JSON格式结果供Jenkins/GitLab CI进一步处理 udslog analyze flash_log.asc --format asc --export-json result.json # 做NRC分布统计快速知道失败集中在哪个服务上 udslog nrc-stat flash_log.asc --format asc --top 20日常排查时我习惯先用nrc-stat跑一遍看到底哪些服务在报错再按NRC过滤出具体报文关联上下文。整个过程不用打开GUI纯终端操作在客户现场的Linux笔记本上跑特别顺手。5.2 Python API 与自定义插件如果命令行满足不了你的业务逻辑可以直接在自己的测试脚本里import这个工具的核心库from udslog import Parser, Analyzer # 解析BLF日志 frames Parser.parse(integration_test.blf, formatblf) analyzer Analyzer(frames) # 获取刷写阶段 phases analyzer.get_phases() for phase in phases: print(phase.name, phase.start, phase.end, phase.duration) # 找出所有NRC nrcs analyzer.find_nrc(min_levelerror) for nrc in nrcs: print(nrc.request.sid, nrc.code, nrc.request.param_summary)API层还留了几个插件接口比如自定义超时判定策略、自定义阶段边界识别规则、自定义报告模板。我的使用经验是与其让工具大而全地支持所有奇奇怪怪的私有流程不如把解析能力和判定骨架暴露出来让每个团队填自己那层逻辑。5.3 关于开源协议与协作方式这个项目选用了Apache-2.0许可不要求衍生作品开源。选它而不是GPL是希望有团队把它集成进自己的内部产线工具后不需要担心合规问题。如果你是想完全自由地使用MIT/Apache这类宽松许可对汽车电子行业更友好如果你想借开源项目构建社区生态GPL会更有吸引力但会劝退一部分企业内部集成场景。项目目前长期需要三方面的贡献更多日志格式的解析器特别是各家自研上位机的私有格式、更多ECU平台的非标UDS服务字典、以及离线分析之外的可视化界面比如基于Web的时间轴视图。如果你在搭建刷写测试台架给这个项目提交一个自定义格式解析器可能后面很多同行都能省下踩同一个坑的时间。6. 从这一版看项目还能往哪走工具当前的核心链路是日志进 → 结构化数据 → 阶段识别 → 报告出。这个链路解决的是事后分析问题但实际工作中刷写日志分析还有一个高频场景是实时监控——在产线和台架上刷写失败后能立刻给出失败原因而不是等到日志拷下来再分析。下一阶段我计划在现有内核上包一层实时的订阅接口让工具既能离线吃日志也能接CAN硬件在线看复用同一套解析逻辑。这个想法目前还停留在设计阶段所以我架构上特意把解析内核和前端UI彻底分离就是为了后续扩展这个方向不伤筋动骨。另外和自动测试体系的对接也值得做深。现在工具可以导出JUnit XML格式的结果刷写中的每个阶段对应一个testcaseNRC和超时自动映射成fail原因。这样刷写测试不光是过了挂了而是能直接挂到CI流水线里让每次代码合入后的刷写验证结果带上一份结构化报告。实测下来把工具接进流水线之后测试团队排查问题的平均时间从半小时降到了几分钟大部分问题在合入阶段就能暴露。最后分享一个实际使用中的小建议无论工具多智能拿到一份刷写失败日志第一件事永远是先看整体阶段时间线别急着扎进NRC明细。阶段时间线会告诉你问题到底出在哪一段——是根本没进编程会话还是擦除阶段就炸了还是下载中途断了。先有全局再看局部排查速度会快得多。这也是这个工具想帮所有人省下的第一步时间。
返回列表