panda 健康包 ABI:版本哈希、字段映射与无损镜像的边界

作者:

在

结论:panda 的健康包 ABI 里,「签名匹配」「ABI 兼容」「传输成功」「本轮采样完整」是四件不同的事,不能合并成一个布尔位。固件 0xd2 返回完整的 health_t(按 packed 字段相加为 48 字节),0xc2 对合法总线号返回 64 字节的 can_health_t;版本哈希只是头文件原文的 SHA-256 前 4 字节,不是抗恶意替换的签名。此外 pandaStates 并不是原始 MCU health 的无损镜像——temperature_pkt 在 C++ 映射中未进入 PandaState,total_tx_checksum_error_cnt 在 cereal 中没有对应字段。本文为固定源码快照 c8fb906 的静态阅读与离线探针,未连接实板、未做坏包注入。

组件与函数索引

入口 作用 边界
health_t / can_health_t packed 二进制结构,按字段宽度当前分别为 48、64 字节 CAN health 正好占满 64 字节控制包上限
get_health_pkt() / comms_control_handler() MCU 填充整机状态;三路 CAN 状态按请求总线号选择,响应结构体原始字节 0xc2 非法总线号不填充响应
MCU 0xdd 与构建哈希 返回 health.h 和 opendbc/safety/can.h 的 SHA-256 前 4 字节(小端整数) 注释变化也可能改哈希
Panda.connect() / get_packets_versions() 连接时读版本;短于 8 字节则记为 (0,0) 空包或短包返回零值,需上层甄别
ensure_version() / health() / can_health() 被装饰的读/收发入口按本地与设备哈希拒绝不匹配;CAN health 用固定 struct 格式解码 不是所有 Python 方法在连接瞬间统一失败
Panda::get_state() / get_can_state() C++ 直接按本地 sizeof 请求,返回值只检查非负,不要求读满 短读可能被解读为后部字段为零的有效快照

ABI 与版本门的实际差异

固件 0xd2 返回当前 health_t 的完整原始结构,0xc2 对合法总线号返回 can_health_t。编译期断言两者不超过 64 字节,但只约束固件本地缓冲容量,不说明不同主机版本的字段布局相同。版本哈希由头文件原文字节计算,仅忽略 CR,它检测构建所用头文件是否一致,注释变化也可能改哈希,且 32 位截断值不是抗恶意替换的签名。Python panda 在连接时读两个版本,再由 ensure_health_packet_version/ensure_can_packet_version 包装相关 API。

主机路径 版本门行为 不保证的内容
Python panda 连接读两个版本,包装相关 API 拒绝用不匹配结构解释健康包或发送 CAN 不是所有方法在连接瞬间统一失败
原生 C++ pandad 不调用 0xdd 校验哈希;通常由 Python 包装器刷写/核验签名,connect() 再检查签名是否匹配本地文件 属镜像身份门,不是每次读健康包前的显式 ABI 协商;调试环境变量可跳过
CAN_HEALTH_STRUCT 手写 64 字节格式 头文件变更不会自动改变 Python 布局,字段顺序仍需随固件同步维护

因此,若自制板只实现同名控制端点却使用不同结构布局,或部署时镜像/头文件不配套,仅凭端点返回非负值不足以证明安全解释。

Python 解析与失败边界

_parse_c_struct() 从头文件寻找精确的 struct __attribute__((packed)) health_t { 文本,只接受单行的 uint8_t、uint16_t、uint32_t、float 标量声明,并用小端无填充格式生成 Struct,当前 health_t 得到 48 字节;声明行尾注释、数组或新类型会在导入 Panda 类时抛 ValueError,缺失精确结构头还会在字符串切分处失败。

函数 行为 失败表现
get_packets_versions() 用 0xdd 请求 8 字节,恰好收到 8 字节才解为两个小端 uint32 空包或短包返回 (0, 0)
health() / can_health() 版本不符时在控制 I/O 之前抛 RuntimeError;随后 Struct.unpack() 要求长度恰好为 48/64 短读或超长响应抛 struct.error,没有「部分有效」字典
health() 缩放 interrupt_load_pkt 除以 255,temperature_pkt 减去 40,标志位拆为布尔值 插件不能把缩放值与原始包字节混用
can_health() LEC 四个 LEC 字段直接按 0 至 7 的字典查找 未知值抛 KeyError 而非返回未知状态
总线号 Python 入口没有先限定总线号,把 int(can_number) 原样传给 0xc2 的 param1 固件只在总线号小于 3 时填包,正常空响应会使解包失败

需要强调:Python 路径的失败表现与 C++ pandad 的短读零填充不同,不能用其中一条主机路径的成功条件推断另一条路径安全。C++ get_state() 与 get_can_state() 先清零结构体,随后只用 err >= 0 接受,因此短读可能被解读为「后部字段为零的有效快照」。这些是源码条件风险,并非正常固件必然短读。

字段映射与信息损失

fill_panda_state() 把点火、控制许可、安全模式、故障、供电、SPI 错误等字段转换到 PandaState;fill_panda_can_state() 处理 bus-off、LEC、错误计数、速率与 CAN FD 参数。当前 MCU health_t.temperature_pkt 在 C++ 映射中未进入 PandaState,而 Python health() 会转换为摄氏度;can_health_t.total_tx_checksum_error_cnt 在 cereal PandaCanState 中没有对应字段,也未发布。故 pandaStates 并不是原始 MCU health 的无损镜像,诊断插件不能据此假定所有板级指标可用。当 get_state() 或任一路 get_can_state() 返回负值时,send_panda_states() 不发布本轮消息;但短读由于上述接受策略仍可能发布。事件 valid 主要由句柄 comms_healthy 决定,不能替代每一字段的完整性/新鲜度证明。

自制硬件与插件契约

新网关应先交换独立的协议版本、结构布局/字段集 ID、设备 UID 与固件镜像身份;每个响应须核对实际长度恰好等于协商长度,再做解析。不要把「签名匹配」「ABI 兼容」「传输成功」「本轮采样完整」合并为一个布尔位。版本升级应有逐字段单位、缩放、缺测语义和向后兼容规则;若插件需要 MCU 温度或 TX checksum 错误计数,应明确新增消息字段或只读扩展通道,并保留原始值与时间戳。离线验收应覆盖 47/48/49 字节整机响应、63/64/65 字节 CAN 响应、同长度字段重排、版本不匹配、非法总线号、返回长度超过请求上限和字段缺测;上车前还需在无车台架用固件/主机版本矩阵验证拒绝策略、读回和安全默认态。

常见问题

版本哈希匹配能证明固件未被替换吗?

不能。版本哈希由头文件原文字节计算并截断为 32 位,注释变化也可能改哈希,它是构建一致性的检查而非抗恶意替换的签名。

C++ pandad 会像 Python 那样做健康包版本协商吗?

不会按同样方式。原生 pandad 不调用 0xdd 校验哈希,connect() 检查的是签名是否匹配本地文件,属镜像身份门,不是每次读健康包前的显式 ABI 协商,且调试环境变量可跳过。

短读的健康包会不会被当成有效快照?

可能会。C++ get_state() 与 get_can_state() 先清零结构体、只检查返回值非负,因此短读可能被解读为「后部字段为零的有效快照」;这是源码条件风险,并非正常固件必然短读。

pandaStates 是原始 MCU health 的完整镜像吗?

不是。temperature_pkt 在 C++ 映射中未进入 PandaState,total_tx_checksum_error_cnt 在 PandaCanState 中没有对应字段,因此部分板级指标在 pandaStates 里并不可用。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/PANDA_HEALTH_ABI_VERSION_HOST_MAPPING_DEEP_DIVE.md(取用了 ABI 与版本门差异、Python 解析与失败边界、字段映射信息损失、自制硬件契约与离线探针结论)。
  • 项目 02 —— openpilot-research/CAR_MESSAGES_FIELD_CONTRACT_DEEP_DIVE.md(仅用于确认健康字段进入上层消息有效位的位置)。

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注