ARTICLE DETAIL

资讯详情

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

UDS 36服务(TransferData)实战拆解:从ISO-TP到ECU刷写全流程

UDS 36服务(TransferData)实战拆解:从ISO-TP到ECU刷写全流程 做UDS开发这几年我觉得有一个服务很容易被轻视但实际工程里几乎每次刷写都离不开它那就是36服务TransferData数据传输。很多人把刷写流程挂在嘴边聊起来都是34请求下载、37退出传输可真正在总线上把几十万字节固件一块一块“喂”给ECU的恰恰是36服务。它不像19服务读DTC那样可以静态分析也不像31服务那样只要发一个命令就完事它拼的是机制理解、时序控制和异常处理。这篇文章我想把36服务拆开讲透包括它的数据块组织方式、与ISO-TP传输层的联动、在一次完整刷写流程里的实际协作以及那些文档里不会写的超时参数和踩坑点。适合正在做UDS诊断协议栈、刷写工具或者ECU底层Bootloader的朋友也适合刚接触诊断协议、想搞懂36服务为什么这么设计的测试工程师。1. 36服务在诊断刷写链路中的角色定位1.1 为什么刷写流程的核心是36服务一次完整的UDS刷写通常由一串服务串联起来10服务切换会话27服务做安全访问34服务声明要写入的内存地址和长度36服务真正搬运数据37服务收尾最后还可能用11服务复位ECU。整套流程里34服务干的是“签约”的活36服务才是“履约”的那个人。我把这组服务的关系类比成快递柜投递34服务告诉柜子“我准备投一个包裹目标格口是0x00080000包裹大小是256KB分块投递每个块最多256字节”36服务则一次一次地把包裹内容塞进格口每个块带上序号等柜子确认37服务最后说“包裹投完了关闭格口”。如果没有36服务34和37之间就是空的固件数据根本进不了ECU的Flash。所以看一个刷写工具或者Bootloader实现得专不专业关键就看36服务这段数据块怎么分包、要不要等流控帧、块序号怎么维护、超时时间怎么设。这些细节一旦处理不好轻则刷写超时重试重则ECU进入不可恢复的编程异常状态。1.2 36服务的数据块组织方式36服务最核心的机制是“分块确认”。假设一次刷写要传输128KB固件ECU在34响应中给出了支持的最大块长度maxNumberOfBlockLength比如256字节。那Tester就要把整个固件切成若干块每块单独发一个36请求并且按顺序等待ECU逐块确认。每个36请求里必须携带两个关键信息块序号blockSequenceCounter后面简称BS和数据参数dataParameter。块序号从0x01开始每发一块就加1到达0xFF后回绕到0x01继续循环。ECU通过这个序号来检查数据是否按顺序到达如果同一个序号收到两次或者序号跳变ECU通常会回复一个负码拒绝接收。这里有一个非常容易误解的点34响应里协商的maxNumberOfBlockLength是指一个36请求里“数据参数”的最大字节数并不包含SID0x36和BS这两个字节。我见过不少新手拿这个块长度去算CAN报文总长度结果每个请求都多算了3个字节正好卡在ISO-TP多帧边界上导致效率降低甚至触发ECU长度校验失败。实际计算时36请求的整体长度 2字节SIDBS 数据块长度。1.3 ISO-TP传输层对36服务的直接约束36服务跑在ISO-TPISO 15765-2之上所以它的真实传输效率要看底层用的是经典CAN还是CAN FD。经典CAN单帧最多8字节去掉ISO-TP的PCI协议控制信息1字节后一个单帧最多承载7字节诊断消息CAN FD单帧可以达到64字节有效载荷宽裕很多。如果固件数据块长度超过单帧承载能力ISO-TP就要把36请求拆成多帧发送首帧FF带着整个诊断消息的总长度连续帧CF按顺序把剩余数据发过去。接收方通过流控帧FC告诉发送方“我能收多少、多久收一次”。这就是为什么刷写时经常能看到大量连续帧喷涌而出那通常就是一条36请求被拆成的多个CAN帧。所以36服务的数据传输机制实际上有两层嵌套的分块逻辑上层是36服务的“块”由BS管理下层是ISO-TP的“帧”由流控管理。这两层经常被混在一起说但它们的职责完全不同——36服务的块对应的是业务逻辑上的数据段ISO-TP的帧对应的是物理总线上的传输单元。2. 36服务请求与响应的逐字节拆解2.1 请求报文格式从SID到数据参数的编排36服务的请求格式非常简洁标准定义如下字节1SID固定为0x36 字节2blockSequenceCounterBS块序号 字节3-末尾dataParameter[]本块携带的固件数据举个例子如果34响应中的最大块长度是256字节那么第一个36请求会是36 01 256字节数据第二个请求36 02 256字节数据以此类推。当BS到达0xFF时下一块回绕为0x01。这里有个隐藏要求在整个传输过程中除了最后一个块可以小于协商的块长度其余每个块的dataParameter长度都应尽量等于协商的最大块长度。如果ECU配置得严格一点前面某个块长度不满也可能直接报0x31requestOutOfRange。实际写协议栈的时候我习惯把BS的初始化和自增逻辑单独封装成一个接口因为刷写一个1MB的固件可能要发几千次36请求手写自增代码很容易在0xFF回绕处栽跟头。正确做法是定义一个发送函数每次调用时先取当前BS发送发送成功后BS自增如果大于0xFF就重新赋值为0x01。2.2 响应格式与正码条件ECU对36服务请求的正码响应格式也很简单字节1SID 0x40即0x76 字节2blockSequenceCounter表示下一次期望收到的块序号也就是说Tester发36 01如果ECU正常接收并完成了Flash写入会回76 02意思是“下一个请发02”。这个设计其实兼作确认和握手机制Tester可以拿响应里的BS和本地自增的BS做对比如果发现ECU回的是76 03而本地发的是76 02的下一个36 02说明中途出现异常要么是网络丢帧要么是ECU侧逻辑跳块了。需要注意ECU返回0x76并不代表数据一定已经写入非易失存储。很多Bootloader的实现是先把数据存到RAM缓冲区等收到37服务或者某个31服务触发“编程校验”时才真正把整个Buffer刷入Flash。所以正码只代表“这一块我收下了且校验通过”不代表“已经固化”。2.3 负码响应哪些NRC会在36服务中出现36服务可能遇到的负码不算多但在工程现场命中率很高我把它们整理成一张速查表NRC名称触发场景0x13incorrectMessageLengthOrInvalidFormat请求长度与格式不符例如dataParameter长度超过协商块长度0x22conditionsNotCorrectECU当前条件不满足传输要求比如发动机转速过高、KL15掉电、安全锁定状态0x31requestOutOfRangeBS序号跳变、内存地址或长度超出下载范围0x33securityAccessDenied未通过27服务安全访问或安全访问已超时失效0x36serviceNotSupportedInActiveSession当前诊断会话不允许执行36服务比如默认会话0x37requestSequenceError请求顺序错误典型场景是没发34直接发360x72generalProgrammingFailure一般编程失败Flash写入异常、Buffer溢出等0x78requestCorrectlyReceived-ResponsePending请求已收到需要更多处理时间稍后会回最终响应这里最容易被忽略的是0x22。很多ECU对刷写条件做了约束比如必须满足充电状态、必须挂P挡、必须在一定时间窗口内完成。如果Tester在刷写前没有把这些条件搞定36服务就会一直撞0x22。这种情况下不是协议栈的问题而是整车上位策略的问题需要从诊断之外去排查。3. 实战走一遍完整的36服务刷写流程3.1 握手阶段会话切换与安全解锁实际刷写之前Tester和ECU之间要先建立“可编程”的环境。我把这个阶段的操作拆成几个标准步骤第一步切换诊断会话。发送10 02请求进入编程会话Programming SessionECU回50 02。这一步的作用是让ECU从正常报文处理模式切换到刷写模式很多动态控制报文在这个会话下会停止发送避免刷写过程中出现电驱、刹车等控制器误动作。第二步做安全访问。发送27 01请求种子ECU回67 01带上种子值Tester用厂商算法计算密钥发送27 02ECU校验通过后回67 02。如果没有这一步后面的36服务大概率会收到0x33。安全访问的种子和密钥算法不同厂商差异很大有的用AES有的用自定义多项式但测试时要保证刷写工具和ECU侧算法版本一致否则就是“种子拿到了密钥对不上”的尴尬局面。第三步是检查刷写前置条件。部分ECU要求Tester先发31服务例程控制来检查ECU是否满足电压、温度、整车状态等条件。虽然这一步不是UDS标准强制项但整车厂的刷写规范里基本都会加建议不要省略。3.2 传输阶段34协商与36循环发送条件就绪后Tester先发送34服务声明下载意图。一个典型的34请求是Tester - ECU: 34 00 44 00 08 00 00 00 00 04 00 00拆解一下0x34是SID0x00是dataFormatIdentifier通常为00x44表示“4字节地址 4字节长度”后面的地址是0x00080000长度是0x00040000256KB。ECU收到后如果接受这个下载请求会回一个74响应ECU - Tester: 74 00 20 01 000x20表示“最大块长度用2字节表示”0x0100即256字节。也就是告诉Tester你就按每块256字节给我发。接下来进入36服务的循环发送环节。假设固件是256KB按256字节一块总共要发1024块。每块都需要一个独立的36请求Tester - ECU: 36 01 第1块256字节 ECU - Tester: 76 02 Tester - ECU: 36 02 第2块256字节 ECU - Tester: 76 03 ... Tester - ECU: 36 00 FF 这里只假设一个很小长度的示例实际最后一块长度可能不足256字节由于一块256字节的数据在经典CAN上必然走多帧总线上看到的现象会是一条36请求被拆成首帧和若干连续帧发送完后等待ECU的76响应。从总线上抓包看就是一个一个的“多帧包组 单帧响应”组合。这里有一个经典的分包效率优化点如果ECU支持CAN FD36请求可以承载更大的数据块推荐的典型块长度可以到4096字节甚至更多。块长度变大之后同样大小的固件可以减少36请求次数刷写时间大幅缩短。实际取值要结合ECU的RAM缓冲区和Flash写入粒度来定不是越大越好——ECU要先把整块数据放入RAM缓冲区如果RAM不够块太久照样写不了。3.3 收尾阶段37退出传输与ECU复位当最后一个36请求发送完成ECU回正码后Tester发送37服务Tester - ECU: 37 00请求退出数据传输。ECU收到后回77 00表示整个传输阶段结束。这个动作很重要因为ECU需要关闭下载通道释放缓冲区并进入“可校验”状态。接下来的校验动作通常在37之后做不归36管但和它强相关有的ECU会要求Tester发送31服务请求执行Flash块校验比如31 01 02 02 00这类例程校验通过后ECU才算真正完成了编程。校验通过后Tester一般会发11服务ECU复位使ECU退出编程会话并重启到应用程序。如果校验失败这时候还能重新走34和36流程再刷一遍不用断电。4. 刷写链路中的时间参数与超时控制4.1 影响36服务的关键计时器P2/P2*/S3搞懂36服务的时序比搞懂它的报文格式更值钱。UDS协议里有几个计时器直接决定刷写的成败计时器默认值作用P2Server通常50msECU处理一次请求并回复的最大时间P2*Server通常5000ms如果处理时间超出P2ECU先回0x78之后在P2*内回复最终响应S3Server通常5000ms会话超时时间任何服务都不发超时后返回默认会话在36服务的刷写过程中最常遇到的是P2超时。ECU收到一块数据后需要执行Flash写入这个操作可能耗时几十毫秒到几百毫秒不等超过P2很正常。规范的ECU实现会在P2内先回0x78占住总线让Tester知道“我正在写别急”等Flash写完后在P2内回真正的76响应。这里有个测试工具层面的坑很多工具默认P2*超时时间很短比如只有500ms但刷写ECU时Flash编程时间恰好在500ms边缘。结果就是ECU还在写FlashTester已经判定超时然后重发36请求两个请求撞在一起ECU报0x31或0x37刷写直接失败。所以刷写工具的超时配置一定要开放可调至少留到5秒以上。另外要注意S3的作用诊断会话是有寿命的如果Tester在两个诊断请求之间间隔超过S3时间ECU会回到默认会话安全访问立即失效。此时再发36就会收到0x36服务不支持当前会话或者0x33安全访问被拒绝。刷写大文件时如果传输比较慢需要防范这种情况可以在刷写期间周期性发送一些轻量服务比如3ETesterPresent保持会话但千万别和36请求抢总线时序——最安全的做法是确保按规划的传输周期推进不要人为加长间隔。4.2 多帧连续帧间隔与流控帧的关系36请求在多帧发送时连续帧之间的时间间隔由ECU下发的流控帧决定。ISO-TP流控帧里有几个参数BlockSizeBS、STminSeparation Time minimum连续帧的最小间隔值。如果STmin设的是0理论上Tester只要总线空闲就可以连续猛发但实际会让ECU接收缓冲区瞬间被打满尤其是在经典CAN上运行速度不太高的场景。如果STmin设得偏大比如10ms那么一条36请求如果有40个连续帧光发送就要400ms刷写速度明显变慢。实践中的经验是Tester侧要充分尊重ECU的流控参数但也要在工具里提供“延迟注入”能力因为有些ECU对流控帧STmin的实际处理有Bug收得太快丢帧收得太慢又超时。做兼容性测试时我会把STmin的最小值、常见值0、1ms、2ms、5ms都跑一遍观察哪组参数最稳定。4.3 大文件刷写时的分包策略与进度管理处理大文件时建议在传输开始前对固件做一次规划和计数。比如一个1MB的固件每块256字节一共4096块。发送进度可以用“当前块序号/总块数”来计算。由于块序号会回绕不能单纯看BS大小来算进度必须在Tester侧维护一个独立的计数器BS只用来做链路同步不要拿来做业务进度统计。分包时还需要处理最后一块。如果固件长度刚好是块长度的整数倍最后一块仍然是满块如果不是最后一块只包含剩余字节。部分ECU要求最后一块即使不满也必须填满到协商块长度不足部分用0xFF填充。具体是否需要填充看ECU的刷写规范和Bootloader实现不要想当然。我在实际项目中就遇到过一家供应商对最后一块不满长度的情况直接报0x72补了填充字节之后问题消失。另外重传逻辑一定要做细。36服务不是面向连接的可靠传输底层总线丢帧是难以完全避免的。如果Tester发了36请求但没有收到任何响应常见的策略是重发同一个BS的36请求一次。但如果重发后收到0x31或0x37说明ECU内部状态已经前进或出错了不能盲目继续重发应该中止刷写流程通过19服务读故障码或者读取ECU状态来确定下一步。5. 常见问题排查与踩坑记录5.1 典型负码与实战场景对照工作中我整理过一张36服务疑难杂症的对照表后来调试刷写工具时帮了大忙。现象可能原因排查方向发送36立即收到0x33未做安全访问或安全状态超时检查27服务种子密钥流程检查会话是否还处于编程会话发送36立即收到0x36当前会话不是允许刷写的会话排查会话切换是否成功检查ECU是否支持该服务发送36收到0x37没有先发34或34状态被重置检查是否漏发34或ECU刚复位过发送36收到0x31块长度不匹配、地址越界、BS跳变核对34协商地址和长度、块序号连续性发送36长时间无响应P2*超时配置太短或ECU卡死延长P2*时间抓包看是否有0x78收到0x72Flash编程失败或Buffer异常读取ECU日志/DTC检查供电、电压稳定性最典型的场景是“安全访问已过但刷写打到一半忽然开始报0x33”。这通常不是安全访问算法问题而是S3超时导致ECU已经退出编程会话安全状态被清除。此时回看时间线多半是两次36请求之间隔了超过5秒。5.2 四个容易踩的坑第一个坑是BS回绕误判。块序号到0xFF后回绕到0x01如果Tester侧把BS当成单调递增的密钥或进度值就会出错。特别是在日志分析里看到36 FF之后又出现36 01不要急着认为是序号跳变先算一下总块数是否超过255块。第二个坑是34块长度协商后没有遵守。有些工具在34响应里读到256字节块长但实现时分包器写死了512字节结果ECU每次收到前两个36请求正常第三个开始报0x31。排查时先看34响应中的maxNumberOfBlockLength到底是多少再看看工具实际每个36请求多长。别用“大概”来判断直接抓包数长度。第三个坑是0x78处理不当。有的自定义诊断仪把0x78当作异常负码直接显示“请求失败”实际上它是“正在处理”。正确的处理是收到0x78后重置内部定时器等待后续最终响应而不是立即判定失败并发重传。第四个坑是核心下载地址和长度格式。34服务的addressAndLengthFormatIdentifier不是随便写的前半个字节表示地址长度字节数后半个字节表示长度字段字节数。0x44表示地址和长度都是4字节。如果底层ECU实际上用的是0x242字节地址4字节长度Tester按4字节地址去组包地址直接错位36发出去自然越界。做多车型兼容时务必把这张格式表做成配置项。5.3 排查工具箱与实战思路排查36服务问题最基本的工具是一台能抓CAN/CAN FD报文的分析仪加上一个能解析UDS/ISO-TP的软件。市面上常用的有CANoe、PCAN-Explorer也有不少开源的Wireshark配合CAN接口方案。重点不是工具本身而是抓包时的“对照法”同时抓取Tester侧和ECU侧的总线报文对比同一时刻两侧对36请求的处理。我个人的习惯是每跑一次刷写就存一份完整日志按时间戳把34、36、37、31、11的服务交互过程画成时间线。如果某个36请求没有收到任何响应就在时间线上往前找看上一个服务是否成功是否出现过0x78是否有总线错误帧。大多数情况下问题都能在时间线的前后几百毫秒内找到踪迹。还有一个小技巧在测试阶段把36请求的数据区前几个字节打上一个固定模式比如每块的前4字节写入该块的偏移值。ECU侧如果支持可以在写入异常时回读这些标记字节快速判断出是哪一块数据出错、偏移是不是对得上。这个做法在定位Flash写入错误时特别好用。6. 安全视角36服务面临的威胁与防御思路6.1 为什么36服务是攻击者的重点目标36服务在诊断协议里算是“高危接口”因为它的本质是任意数据写入能力。如果攻击者绕过了安全访问就可以直接向ECU内存中写入数据这不仅仅是修改个参数的问题甚至可能覆盖Bootloader区或者植入恶意固件。常见的攻击入口有两个一个是物理接口攻击者直接接到OBD诊断口上对总线发起诊断请求另一个是远程攻击链比如通过车机联网模块、蓝牙接口或无线诊断口拿到总线访问权限后向ECU发起刷写。后者在现代智能网联汽车上越来越被重视。所以看待36服务的安全问题不能光看协议层还要看整车网络架构的纵深防御。哪怕27服务安全算法再强如果攻击者能直接在总线上伪造诊断仪身份并且成功解锁那后面就是一路畅通。这也是为什么很多新平台把刷写钥匙从简单的种子密钥升级成带数字签名、带防重放机制的安全凭证。6.2 常见攻击思路与防御手段从攻击链角度看和36服务密切相关的威胁主要有这几类攻击方式攻击行为典型防御手段未授权刷写跳过27安全访问直接发36强制安全访问、会话状态与安全状态绑定重放攻击抓取合法36数据流后重放使用加密传输或防重放计数器每次刷写种子随机化数据篡改修改36服务中的数据区固件整体签名ECU写入后校验签名降级攻击刷写旧版本固件版本号防回滚一次性OTP记录最低版本拒绝服务高频发送36请求干扰ECU速率限制、异常诊断请求检测防御不能只压在ECU端。我在实际做安全方案时习惯把防御分成三层第一层是通道防护如果ECU支持SecOC或安全诊断通道36服务优先跑在加密通道上第二层是访问控制安全访问的密钥算法要够强种子长度不能太短重试次数要做限制第三层是刷写校验不是ECU回一个76就算数必须在37之后跑完整的Bootloader校验和固件签名验证。另外整车层面的入侵检测系统IDS也开始关注36服务流量比如在非刷写售后模式下检测到高频36请求、在默认会话下出现36请求、或者安全访问失败后紧接着出现36请求都应该作为异常诊断事件上报。这些规则不需要很复杂但对识别早期攻击行为很有用。从我个人做协议栈和刷写诊断的经验来说36服务最大的难点不在代码量在于把它放到整个刷写时序里看什么时候发34、什么时候等0x78、什么时候允许超时重试、什么时候必须无条件中断这些边界条件才是真正考验一个诊断工程师功底的地方。调试时多留日志、多抓包、多复现异常场景比背标准条文管用得多。希望这篇实战拆解能让你在下次排查36服务问题时少走几个弯。
返回列表