- 开发工具
- 逆向工程
- 嵌入式
【免费下载链接】SavvyCAN
QT based cross platform canbus tool
Range State(范围状态)窗口是 SavvyCAN(QT 跨平台 CAN 总线分析工具,仓库根目录见 README.md)中一个独特的“信号猎手”工具:它不依赖 DBC 文件,而是通过统计启发式在原始 CAN 帧数据里自动筛选出“看起来像有意义物理量”的信号候选。本文以官方帮助文档 help/rangestate.md 为主体,结合源码 re/rangestatewindow.cpp 深入讲解其工作机理,读完你将掌握参数调优方法、候选信号筛选技巧,以及如何快速从海量 CAN 数据中定位车速、转速等平滑变化信号。
窗口定位:从“有规律的跳动”里找信号
窗口的目的是搜索“看起来可能很重要”的信号。正如官方帮助文档所描述的,这是一个比较模糊的定义——核心思想是找出**变化相对连贯(coherently)**的信号。观察图中的示例信号曲线:它有一些跳变,但整体看起来“像是有含义的东西”,这就是这个窗口的筛选逻辑。
需要特别说明的是:该工具必然会产生大量误报(false positives),但即便如此,它依然能提供不错的候选方向。最终判定某个信号是否重要、是否可用的工作,仍然需要你自己完成。它的典型使用场景是:当你没有任何 DBC 文件、需要逆向分析未知 CAN 报文时,用它快速圈定值得深入研究的位段。
在 SavvyCAN 中,该窗口由主窗口菜单的 “Range State” 动作触发(见 ui/mainwindow.ui 中的actionRange_State_2),经由MainWindow::showRangeWindow()创建并显示(mainwindow.cpp),窗口类RangeStateWindow定义于 re/rangestatewindow.h 与 re/rangestatewindow.cpp。
使用前必须设置的 7 项参数
使用该窗口前,需要按顺序配置以下参数(控件定义见界面文件 ui/rangestatewindow.ui):
ID Filter 列表——取消勾选任何你不想搜索的 CAN ID。列表由窗口打开时自动从当前已采集帧中提取(
refreshFilterList()遍历模型中的所有CANFrame,为每个出现过的 ID 创建一个带复选框的过滤项,见 re/rangestatewindow.cpp)。列表下方的 “All / None” 按钮可一键全选或全不选,实际生效状态记录在idFilters这个QMap<int, bool>中。Sensitivity(灵敏度)——这是一个主观性度量。它对结果的影响不算大,但在两个极端之间来回调整,会改变程序对“什么算是相关信号”的判断。底层实现中滑杆范围为 10~250(见 ui/rangestatewindow.ui 中
slideSensitivity的 minimum/maximum),并通过线性插值换算成一个 0.0~1.0 的采样点参与后续判定,具体见下文“候选判定算法”。Min Signal Size(最小信号尺寸)——你想搜索的最小信号位宽,最小可到 1 bit(如果你足够“大胆”的话)。界面允许范围 1~32。
Max Signal Size(最大信号尺寸)——最大位宽。Min 与 Max 之间的跨度直接决定整个搜索过程耗时。例如设置 Min=8、Max=16,程序会依次搜索 8、9、10、…、16 bit 的信号;由于同一组基础位会被反复覆盖,结果里会出现重复候选。
Granularity(粒度)——设置“位跳宽度”(bit jump width),即每次搜索新信号时向后移动的 bit 数。粒度为 1、最小位宽 8 时,程序会从 bit 0 开始搜索 8 bit 信号,再从 bit 1、bit 2……依次推进;粒度为 8 时则从 bit 0、bit 8、bit 16……开始。很多信号恰好落在字节边界上,因此粒度为 8 意味着所有搜索都从字节边界起步,对大多数情况足够;但有些设计者会把信号放在不对齐的边界上,此时需要更细的粒度才能找到它们。
Signal Mode(信号模式)——针对超过 8 bit 的信号的字节序选择。信号可以是大端(big endian)或小端(little endian),即信号的 bit 0 对应最高还是最低数值位。你可以只搜大端、只搜小端,或者“Try Both”两种都试。CAN 设备开发者通常会统一采用某一种字节序,但并非总是如此。下拉框三个选项在构造函数中依次加入(re/rangestatewindow.cpp)。
Signed Mode(有符号模式)——同理,任何超过 1 bit 的信号都可能是有符号(最高位为 1 表示负数、0 表示正数)或无符号。你可以只搜无符号、只搜有符号,或两种都试。信号什么时候会被设计成有符号其实没有固定规律,两种都可能,所以除非你非常确定,最稳妥的做法是允许程序两种都试,再由你从结果中挑选看起来最合理的。
补充说明:由于信号可能跨字节边界,即使位宽不超过 8 bit,也必须同时尝试两种字节序(源码 re/rangestatewindow.cpp 的注释明确说明了这一点)。
执行计算:点击 “Recalculate Candidate Signals”
参数配置完成后,点击 “Recalculate Candidate Signals” 按钮(btnRecalc)触发recalcButton()(re/rangestatewindow.cpp)。该过程会:
- 清空候选列表、已找到信号集合与图表;
- 弹出无取消按钮的模态进度对话框(
QProgressDialog); - 按 ID 逐个处理:对每个被勾选的 ID,先把模型帧中所有同 ID 帧拷贝到
frameCache(一次性预留足够空间),再调用signalsFactory()进行候选生成(re/rangestatewindow.cpp)。
计算量取决于你选择的参数组合:signalsFactory()中,信号尺寸从 Max 递减到 Min(步进为 Granularity),起始位从 bit 0 推进到frameCache首帧 payload 的 bit 总数,每个 (尺寸, 起始位) 组合还要根据 Signal Mode 和 Signed Mode 各尝试 1~4 种字节序/符号组合。因此文档建议“做好准备等待”——参数越宽、粒度越细,耗时越长。
候选判定算法:一阶/二阶差分如何识别“平滑信号”
这是整个窗口的核心原理。processSignal()(re/rangestatewindow.cpp)对每个候选信号执行如下判定链:
第一步:提取数值并计算范围。对frameCache中每一帧调用Utility::processIntegerSignal()(定义于 utility.h)取出该信号位的整数值,同时记录最低值与最高值。若最低值等于最高值,说明该信号从未变化,直接判定为无价值并返回(if (lowestValue == highestValue) return false;)。
第二步:范围门槛(range gate)。计算range = highestValue - lowestValue,并将其与信号类型允许的最大范围(有符号为1 << (bitLength - 1),无符号为1 << bitLength)比较。requiredRange通过Utility::Lerp()在“最大范围的 1%(低灵敏度)”与“最大范围的 20%(高灵敏度)”之间线性插值得到。范围不足的信号会被淘汰。
第三步:一阶差分(first order diff)检查。将数值减去最低值得到缩放序列,再对相邻值求差得到diff1。判定阈值comparisonValue = Lerp(range * 0.55, 0, lerpPoint),允许的超限次数maxOvers = Lerp(numFrames / 30.0, 2, lerpPoint)。若一阶差分绝对值超过阈值的次数太多,说明信号跳变剧烈,不是平滑的范围信号,直接淘汰。
第四步:二阶差分(second order diff)检查。对diff1再求差得到diff2,这相当于信号的“加速度”。平滑变化信号不应出现剧烈的加速度,因此阈值收紧为Lerp(range * 0.20, 1, lerpPoint),超限次数上限仅为Lerp(8, 2, lerpPoint)。这一关通过后,信号才被认定为候选。
其中灵敏度滑杆的换算公式为:lerpPoint = ((double)sensitivity - 10.0) / 240.0(re/rangestatewindow.cpp),而Utility::Lerp()是标准的线性插值value1 * (1 - samplePoint) + value2 * samplePoint(utility.h)。
解读候选列表:从编码看字段含义
通过判定的信号会出现在上方 “Candidate Signals” 列表中,每条记录包含:ID、起始位(startBit,注意位从 0 开始、最多到 63)、长度(len)、有符号/无符号、大端/小端。列表项的文本格式见 re/rangestatewindow.cpp。
候选信号在内部被编码为一个 64 位整数foundSignals条目(位段打包逻辑见 re/rangestatewindow.cpp),选中列表项时再按位解包还原(re/rangestatewindow.cpp):
| 位段 | 含义 |
|---|---|
| bit 0~31 | CAN 帧 ID |
| bit 32~39 | 起始位 startBit |
| bit 40~47 | 信号长度 bitLength |
| bit 48 | 是否为有符号(Signed) |
| bit 49 | 是否为大端(BigEndian) |
快速浏览候选:键盘上下键 + 实时图表
在候选列表中点击或选中某个信号,其图形化曲线会立即出现在下方绘图区(clickedSignalList()重新按 ID 提取帧数据、解包位段并调用createGraph()绘制,见 re/rangestatewindow.cpp)。
特别实用的技巧是:使用键盘上、下方向键在列表间移动,甚至可以按住方向键让列表快速滚动,一边滚动一边观察图表,看到“合眼缘”的信号再停下来。因为候选信号动辄上百条,逐个点开查看非常繁琐,这种“滚动 + 目视”的浏览方式效率极高。
绘图区本身基于 QCustomPlot(界面文件 ui/rangestatewindow.ui 中引用qcustomplot.h),createGraph()会按数值范围自动缩放 Y 轴并在上下留出约 20% 的边距(re/rangestatewindow.cpp)。
实战:用图形直觉锁定车速类信号
这个窗口的实战价值在于:当你大致知道目标信号“应该长什么样”时,能快速把它从几百个候选中找出来。文档以车辆速度为例:
- 车速不可能瞬间从 0 跳到 100,因此车速信号在时间轴上表现为大量连续上扬/下落的扫动曲线;
- 通过快速浏览各候选的图形,很容易排除杂乱无章的位段,锁定那些“看起来像是速度”的候选;
- 它可能是以 km/h 为单位的速度、可能是 mph、也可能是车轮 RPM——先找到形态符合的信号,再通过数值范围与车辆行为比对来确认具体含义。
与 SavvyCAN 数据流的联动机制
该窗口通过连接MainWindow::framesUpdated信号(re/rangestatewindow.cpp)感知帧数据变化:updatedFrames()处理三种情况——所有帧被删除(清空 ID 过滤列表)、加载全新帧集(重建过滤列表)、新增帧(自动为没出现过的 ID 补建过滤项并默认勾选,re/rangestatewindow.cpp)。注意:新增帧不会自动触发重新计算,必须手动点击 “Recalculate Candidate Signals” 才会生成新结果。
窗口还会在显示/关闭时通过readSettings()/writeSettings()读写位置与尺寸(受Main/SaveRestorePositions设置控制,re/rangestatewindow.cpp),并在按下 F1 时直接弹出本文所依据的帮助文档rangestate.md(re/rangestatewindow.cpp)。
小结
Range State 窗口把“信号是否平滑变化”这一逆向工程直觉,转化为可配置的统计判定流水线:位宽与粒度控制搜索覆盖范围,字节序与符号模式控制信号空间,灵敏度通过线性插值调节一阶/二阶差分的判定松紧。掌握这 7 项参数的取舍逻辑后,你就能在没有 DBC 文件的情况下,把成百上千条原始报文压缩成几十条值得细看的候选信号,是 CAN 逆向分析与未知协议探索中非常趁手的前期侦察工具。
- 开发工具
- 逆向工程
- 嵌入式
【免费下载链接】SavvyCAN
QT based cross platform canbus tool
相关推荐
algorithm-pattern实战应用:滑动窗口与二叉搜索树
algorithm pattern实战应用:滑动窗口与二叉搜索树 本文深入探讨了滑动窗口算法和二叉搜索树的核心概念、模板代码及实际应用。滑动窗口部分详细介绍了算
教程如何高效利用窗口信息:跨平台窗口监控实战指南
如何高效利用窗口信息:跨平台窗口监控实战指南 在数字化工作环境中,有效管理多个应用程序窗口是提升工作效率的关键。 active win 项目正是为此而生,它是一
semantic-router Context 信号实战指南:基于 Token 窗口的启发式长上下文路由
semantic router Context 信号实战指南:基于 Token 窗口的启发式长上下文路由 导读 context 是 semantic route
后端API网关模型推理服务AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考