做ECU测试这几年,我遇到过太多因为会话切换没搞明白导致的刷写失败、服务被拒。UDS诊断里,会话Session是比任何功能服务都靠前的一道门:门没开对,后面所有动作都白搭。这篇文章就用实际报文来拆解UDS诊断中最常被问到的会话切换,也就是0x10服务(DiagnosticSessionControl),从报文格式、状态机规则一直讲到刷写中的完整切换链路,适合刚接触诊断协议的同学,也适合已经在做台架测试但总被NRC卡住的朋友。
1. 先理解UDS会话:ECU并不会时刻开放全部权限
1.1 会话机制到底解决什么问题
UDS(Unified Diagnostic Services)是ISO 14229定义的一套汽车诊断协议,跑在CAN、CAN FD、DoIP或者LIN上,应用层通过SID(Service ID)区分功能。0x10这个SID负责的就是会话控制。
会话机制的本质,是给ECU内部划分了几个不同权限的运行模式。ECU在出厂、售后、产线、刷写、标定、防盗匹配这些不同场景下,开放给诊断仪的功能范围完全不同。如果一上电就把编程、安全访问、DTC写入这些高权限能力全部开放,那整车的网络安全风险会非常大。会话切换就是一把钥匙,把ECU从低权限模式切换到高权限模式。
常见的会话定义四个:
- 0x01 默认会话:上电后通常停在这里,只有基础读操作,比如读版本、读DTC状态
- 0x02 编程会话:允许使用0x34请求下载、0x36数据转移这类刷写服务
- 0x03 扩展会话:允许标定、复杂例程控制、写入请求,也是进入编程会话前必经的中间态
- 0x04 安全系统诊断会话:部分平台用于安全气囊、主动安全这类系统的特殊诊断
一个粗暴但好用的类比是门禁:默认会话就像大厅,扩展会话是办公区,编程会话是机房。没有刷卡(切会话)就硬闯机房,轻则被保安拦下(NRC否定响应),重则触发整车安全保护。
1.2 别被Web开发里的Session带跑偏
搜索UDS会话相关问题时,很容易看到一些Web领域的词条,比如“there is no session with id”“cookie和session的区别”。这里需要明确一点:UDS里的Session是ECU内部诊断状态机,和浏览器会话没有一点关系。UDS上下文里出现“session”字样,指的就是0x10服务控制的诊断会话状态。
容易混淆的还有0x29服务。0x29在ISO 14229里是Authentication(安全认证)服务,负责基于证书、挑战码的认证流程。0x29和会话切换相关,但执行时不能把它当成切会话的入口。切会话永远是0x10,0x29只是帮你在高权限会话里做身份确认。不少人以为“29服务”能切会话,实际是乌龙。
2. 会话切换的入口:0x10服务与应答逻辑
2.1 请求报文怎么组
0x10服务请求格式非常固定:
0x10 + Sub-functionSub-function就是目标会话ID,常见值如下表格所示。
| Sub-function | 会话名称 | 典型用途 |
|---|---|---|
| 0x01 | defaultSession | 默认会话,上电初始态 |
| 0x02 | programmingSession | 编程会话,刷写固件 |
| 0x03 | extendedDiagnosticSession | 扩展诊断会话,标定/高级读写 |
| 0x04 | safetySystemDiagnosticSession | 安全系统诊断会话 |
| 0x40-0x7F | OEM自定义 | 厂家扩展会话,比如bootloader特殊态 |
这里有一个细节:Sub-function的bit7是suppressPosRspMsgIndicationBit,也就是抑制肯定响应位。比如你要切到扩展会话,但不想让ECU回肯定响应,可以发0x10 0x83而不是0x10 0x03。
请求1:10 03 // 切扩展会话,正常回正响应 请求2:10 83 // 切扩展会话,抑制正响应抑制位生效后,ECU不会回50 03,但如果切换失败,否定响应7F 10 xx还是会回的。这个行为需要特别注意:很多上位机脚本只看有没有响应帧判断成功,结果把否定响应也当成成功,看着像是切过去了,实际状态没变。
2.2 肯定响应与两个关键定时器
0x10服务成功后,肯定响应格式是:
50 + Sub-function + P2_Server_max(2字节) + S3_Server_max(2字节)举个例子,实际抓包经常看到:
TX: 10 03 RX: 50 03 00 32 13 88展开解读:
50是肯定响应SID,等于请求SID+0x4003表示当前已进入扩展会话00 32表示P2_Server_max,单位ms,换算后是50ms13 88表示S3_Server_max,单位ms,换算后是5000ms
P2_Server_max的含义是:非默认会话下,ECU处理新请求后,需要在50ms内给出首次响应;如果处理时间超过50ms,必须先回一帧7F xx 78(ResponsePending),让诊断仪知道请求还在处理,防止诊断仪超时误判。
S3_Server_max的含义是:在这个会话下,如果超过5000ms没有收到任何新的诊断请求,ECU会自动跳回默认会话。这就是诊断协议里保活的根源:切到扩展会话后,工具必须周期性发送诊断请求来维持会话状态,否则ECU就悄悄退出了。
P2和S3的具体值在不同ECU上不一样,但通常符合ISO 14229的默认推荐:非默认会话P2=50ms,S3=5000ms。有些OEM自定义值可能不同,要以车型规范为准。
2.3 否定响应:被拒了要知道为什么
切换失败时,ECU回的是:
7F + 10 + NRC常用NRC整理如下:
| NRC | 名称 | 含义与常见场景 |
|---|---|---|
| 0x12 | subFunctionNotSupported | 子功能不支持,比如直接发10 02但ECU不支持 |
| 0x13 | incorrectMessageLengthOrInvalidFormat | 报文长度或格式错误,比如发了10 03 01多一个字节 |
| 0x22 | conditionsNotCorrect | 条件不满足,比如整车状态不允许编程 |
| 0x33 | securityAccessDenied | 安全访问被拒,通常对应编程会话要求先解锁 |
| 0x34 | authenticationRequired | 要求先走0x29认证,新一代安全架构常见 |
| 0x78 | requestCorrectlyReceivedResponsePending | 不是错误,是ECU还在处理,稍后会再补响应 |
| 0x7E | subFunctionNotSupportedInActiveSession | 当前会话不支持该子功能切换,常见于部分特殊会话 |
遇到7F 10 33,基本说明你试图切入的会话需要更高安全等级;遇到7F 10 22,则说明ECU认为当前整车条件不满足,比如车速不为零、挡位不在P挡、点火开关不在ON挡,都会导致条件不满足。
3. 会话状态机:谁可以切到谁是有规矩的
3.1 标准切换规则与工程实践
ISO 14229里会话切换不是一个任意矩阵,标准Annex A定义了会话状态图,工程实现上一般遵循这么几条不成文但很常见的规则。
| 当前会话 | 目标会话 | 普遍行为 |
|---|---|---|
| 默认会话0x01 | 扩展会话0x03 | 允许,不需要解锁 |
| 默认会话0x01 | 编程会话0x02 | 多数ECU拒绝,NRC 0x22或0x33 |
| 默认会话0x01 | 安全系统诊断0x04 | 需要特定条件,通常拒绝 |
| 扩展会话0x03 | 默认会话0x01 | 允许 |
| 扩展会话0x03 | 编程会话0x02 | 允许,但通常需要先通过0x27安全访问或0x29认证 |
| 扩展会话0x03 | 扩展会话0x03 | 多数ECU直接成功,相当于重置S3定时器 |
| 编程会话0x02 | 默认会话0x01 | 允许 |
| 编程会话0x02 | 扩展会话0x03 | 允许,部分ECU需要先回默认再切 |
| 编程会话0x02 | 编程会话0x02 | 多数成功,相当于重置定时器 |
关键点在于:默认会话直接切编程会话,在国产车和合资车的实际标定中大多走不通。如果你在工程规范里看到“programming session shall only be entered from extended session”,意思是编程会话只能从扩展会话进入,那就必须先10 03再10 02。
顺带一提,很多ECU进入编程会话后会短暂屏蔽部分常规诊断服务,甚至有些ECU在编程会话下会自动让出总线调度,避免刷写过程被干扰。测试时如果发现进入编程会话后,之前能用的服务突然回NRC 0x11(serviceNotSupported),不要惊讶,这是正常的权限收窄。
3.2 S3超时回退:ECU的“自动锁门”
S3定时器的存在让很多新手栽跟头。CANoe或PCAN里做了很久的标定,中间停下来喝口水,回来继续发请求发现响应全部变成NRC 0x11。原因很简单:扩展会话5秒超时已经回默认会话了,默认会话根本不支持标定服务。
这个“自动锁门”是ISO强制的安全设计,防止诊断仪异常掉线后ECU一直暴露在高权限状态。刷写过程中,一段数据转移完成后,如果上位机处理文件需要几秒钟,而ECU已经回默认,后续下载流程立刻断链,整个刷写包都要从头来过。
维护会话的办法有两种:
- 周期性发送任意受支持的服务,比如
10 03重发切扩展会话,或者22 F1 90读软件版本号之类的无副作用请求 - 上位机内置S3心跳线程,保证请求间隔小于S3值,通常保守做法是间隔1000ms到2000ms发一次心跳
实测经验:不要卡着4500ms去发心跳,网络抖动、CAN总线繁忙都可能让你错过窗口。宁可每1000ms发一个轻量读服务。
3.3 条件不满足与会话切换的关联
前面提到的NRC 0x22,往往不是单纯“你权限不够”,而是外部条件没达标。比如:
- 整车电源模式不在KL15 ON
- 车速不满足安全条件
- 挡位不在允许范围
- 发动机转速不为零(刷写时不允许发动机运行)
- 安全访问未完成(部分ECU把未解锁也归到0x22而不是0x33)
所以排查0x22时,不能只盯诊断层,还要看ECU输入状态。用CANoe里增加环境变量或者读取ECU的电源模式、挡位信号,能快速定位。
此外,部分ECU有“引脚拉低进入特殊模式”的机制。例如某些MCU的Bootloader里,编程会话可能还要求物理引脚状态配合。这时候单纯发0x10 02进不去是正常的,需要硬件搭接。这是厂家特定的行为,操作前一定先看Bootloader规范。
4. 完整实例:从默认会话一路切到编程会话
4.1 场景设定
假设我现在要给一块VCU刷写Bootloader,工具用的是CANoe + CANcaseXL,ECU地址按标准设置。刷写前置流程是:
默认会话 -> 扩展会话 -> 安全访问解锁 -> 编程会话 -> 传输数据这个过程里,每一步的报文我都从日志里截出来,带着解释过一遍。
4.2 从默认会话切到扩展会话
第一步发送:
TX: 10 03ECU响应:
RX: 50 03 00 32 13 88说明:50 03表示进入扩展会话成功。00 32是P2_Server_max=50ms,13 88是S3_Server_max=5000ms。响应回来后,ECU已经把S3定时器启动,接下来5秒内必须维持请求。
这里我习惯在做完切换后立刻用一个22服务读版本号,一方面是确认会话确实生效,另一方面也能顺带保活。比如:
TX: 22 F1 90 RX: 62 F1 90 01 02 03能正常读到版本号,说明当前确实在扩展会话下,22服务是被允许的。如果还在默认会话,22服务很多ECU是不可用的,会回7F 22 11。
4.3 安全访问:进编程会话前的必经之路
扩展会话不代表你能刷写。0x10 03只打开了扩展诊断的门,编程会话还需要安全访问解锁。标准流程是seed-key机制,用0x27服务。
日志如下:
TX: 27 01 // 请求seed(level 01) RX: 67 01 11 22 33 44 // 返回seed 0x11223344 TX: 27 02 55 66 77 88 // 发送通过算法计算出的key RX: 67 02 // 解锁成功如果key算错了,ECU会回:
RX: 7F 27 35NRC 0x35表示invalidKey。连续多次错误key还会触发延时锁定机制,锁定时长从几十毫秒到几秒不等,这是防止暴力破解seed-key的既定设计。
安全访问解锁和会话切换是两件事,但要注意顺序。多数ECU要求先在扩展会话下做安全访问,解锁成功后才允许切编程会话。如果你在默认会话直接发27服务,大概率被拒,因为默认会话根本不开放安全访问服务。
还有一点:安全访问解锁状态不跟随会话切换而永久保留。部分ECU设计成一旦退出到默认会话,安全解锁状态也随之清除,重新进入扩展后需要再次解锁。我测试中遇到不少这样的实现,所以刷写脚本里“10 03 -> 27 -> 10 02”的顺序必须严格串行,不能提前切默认会话。
4.4 从扩展会话切编程会话
安全访问解锁成功后,发送:
TX: 10 02正常响应:
RX: 50 02 00 32 13 88这说明已经进入编程会话,P2_Server_max=50ms,S3_Server_max=5000ms。此时0x34、0x36、0x37这类传输服务才被允许执行。
如果在未解锁时强行发送10 02,会遇到两种典型回报:
TX: 10 02 RX: 7F 10 33 // securityAccessDenied或者某些ECU统一归为条件不满足:
RX: 7F 10 22 // conditionsNotCorrect这两种都是“没有资格进入编程会话”的信号,区别只是NRC分类策略不同。遇到0x22时尤其先确认自己的安全访问状态。
进入编程会话后,刷写基础链路一般是这样:
10 02 -> 34 00 44 ... -> 36 01 data -> 36 02 data -> 37 -> 31 01 FF 0034请求下载、36数据传输、37请求退出传输、31例程控制做刷写完成校验。整套动作里所有请求都必须卡在编程会话有效期内,一旦S3超时回到默认,后面所有SID都会被打回。
4.5 会话切换时序上的一个坑
有些ECU对于“扩展会话->安全访问->编程会话”这一串动作有时间限制。比如规范里会写:安全访问解锁成功后,必须在例如200ms内发起编程会话切换,否则解锁状态自动失效。
这个限制在一些老式Bootloader里非常常见,因为Bootloader本身不跑完整操作系统,内存里的解锁状态是靠RAM保持的,掉电或者看门狗复位都会清掉。如果是这种ECU,你的上位机在解锁后不能有任何多余的延时代码,必须在最短时间内把10 02发出去。
实测中我还遇到过一个ECU,编程会话切换成功后会自动执行一个内部复位,复位期间总线上会有几百ms的静默。上位机如果没有处理好这个静默窗口,会误以为ECU掉线了,然后主动断开连接。正确的做法是在切编程会话成功后,等待一段明确的静默时间,再发送第一个刷写请求。
5. 会话切换常见问题与排查经验
5.1 NRC速查与排查优先级
会话切换失败,先看NRC,再结合整车条件查,效率最高。我整理了一张实际排查顺序表:
| 现象 | 优先排查方向 |
|---|---|
| 7F 10 12 | 目标子功能根本不存在,查规范中的会话定义 |
| 7F 10 13 | 请求长度或者格式错了,比如多发了字节 |
| 7F 10 22 | 整车条件不满足,查电源模式、车速、挡位、安全状态 |
| 7F 10 33 | 安全访问未解锁,先做27服务,再切会话 |
| 7F 10 34 | 需要0x29认证流程,查证书和挑战码流程 |
| 7F 10 11 | 目标服务在当前会话下不支持,注意是否已回默认 |
实际排查时,我经常先怀疑是不是S3超时偷偷退了会话,再怀疑条件不满足。因为日志里可能几十秒前还是扩展会话,但是中间没有任何请求,等再次发请求时ECU已经回默认。这种问题不是诊断逻辑错了,是保活没做。
5.2 案例:刷写流程中段突然NRC 0x11
一次项目里,刷写脚本每次跑到第4包数据就失败,日志如下:
TX: 10 02 RX: 50 02 00 32 13 88 TX: 27 01 ... TX: 34 ... RX: 74 ... TX: 36 01 ... RX: 76 ... TX: 36 02 ... RX: 7F 36 11跑到36第二包就回NRC 0x11。最初怀疑是36报文格式问题,但检查发现36请求格式完全正确。后来把每条请求的时间戳拉出来看,才发现34和36之间隔了整整6.2秒。问题在于上位机在34请求后花了几秒钟计算内存分配,然后跳过35直接发36,中间没有保活请求,S3超时导致ECU退回了默认会话,36自然不被支持。
解决方式就是在传输过程中也要周期保活,或者在34请求之前临时把S3心跳间隔调小。这类“莫名其妙NRC 0x11”的问题,八成都是会话超时导致。
5.3 案例:编程会话每次进不了,复位一下就好
另一个客户报障说,ECU每次切编程会话都失败,NRC不是0x22就是0x33,但复位后第一次能成功,再次失败。查下来是两个原因叠加:
- 上一次刷写异常退出,ECU还停留在编程会话,部分服务被锁存
- 再次切编程会话时,安全访问解锁状态混乱,ECU判定为非法重复切换
解决办法是先发10 01回默认,等待一段明确时间,再重新执行“扩展->解锁->编程”的完整链路。有些ECU需要掉电重启才能彻底恢复正常,这时按规范流程走一遍电源循环即可。
这个案例给了一个通用经验:切会话失败时,先不要猜,把“当前会话确认”作为第一步。用10 01切回默认是成本最低的自愈手段。
5.4 0x29认证服务与0x10会话的关系
新一代安全架构下,0x29服务越来越常见。很多人在刷写或者在线配置时看到0x29,以为能替代0x10切会话,这是理解偏差。
0x29是Authentication服务,做的是身份认证,基于公钥基础设施和挑战码,认证成功后可以获取某种角色权限,比如“刷写允许”的权限。0x10是DiagnosticSessionControl,做的是会话状态切换。两者协同的典型流程是:
10 03 -> 29 02 ... -> 29 04 ... -> 10 02也就是说,先切到扩展会话,然后走0x29认证流程,拿到“allowed to program”的授权,再去切编程会话。如果ECU要求0x34而你没走0x29,切换会话时可能被回0x34。
要特别记住:0x29认证结果有时和会话的S3超时绑定。认证成功后如果迟迟没切编程会话,超时后认证授权也会一并失效。
5.5 工具与链路层面的会话状态干扰
最后说一个工具层面的问题。CANoe、PCAN、智己诊断仪这类工具,有的会内置自动保活机制,有的不会。如果你在CANoe里开了“TP层自动心跳”功能,它可能在会话状态里自动来回发送请求,掩盖了协议栈真实状态,导致问题只在实车上出现。
另一个常见干扰是ECU的看门狗复位。刷写过程中ECU如果因为地址校验失败等原因复位,会话状态会重置到默认。此时从工具侧看,总线连接还在,但会话已经变了。遇到刷写中途“掉线”又“自动恢复”,一定要检查ECU有没有复位过,最简单的方法就是读ECU启动时间或者上电计数器。
收个尾
会话切换看起来就是几条报文的组合,但真正考验人的是对ECU状态机的理解。我自己做测试的体会是:凡是刷写、标定、配置类问题,先花一分钟确认当前会话状态和S3超时,再谈后续。把0x10的报文格式、NRC含义、切换规则、保活策略这几件事刻在脑子里,能少踩很多坑。
最后分享一个小技巧:在写测试脚本时,把会话切换封装成一个原子函数,内部自动完成“读当前状态->切目标会话->确认响应->记录时间戳”,任何一次切换都会留下日志。这样不仅方便排查,也方便在回归测试里快速定位到底是ECU问题还是脚本问题。千万别图省事直接在测试用例里裸发10 03、10 02,脚本越随意,问题越难查。