☰
Wireshark抓包解析Modbus异常帧:0x83与0x86故障排查实战
2026/10/22 5:05:40 网站建设 项目流程

搞过Modbus调试的人,大概率都有过这种经历:主站PLC一直报通信超时,从站设备就是不理你。你先用万用表量了485的A/B线,电压正常;又核对了一遍波特率、校验位、从站地址,全没问题。可问题就是复现。这时候真正能定位问题的,往往不是万用表,而是Wireshark抓下来的那几帧报文。尤其当你看到响应帧里功能码变成了0x83、0x86这种“高位置1”的数值时,结论基本就出来了:从站活着,但你的请求有问题。

这篇文章我就把从抓包到解析异常帧的完整流程捋一遍。核心围绕Modbus TCP和Modbus RTU两种常见形态,重点讲Wireshark里怎么识别正常帧、怎么读懂0x83和0x86这种异常帧,以及拿到异常码之后怎么一步步定位到具体问题。适合刚接触Modbus调试的工程师,也适合被异常帧卡过几次的设备运维、自动化集成人员。内容全部来自我实战中验证过的操作方法,尽量做到看完就能上手。

1. 为什么Modbus排障离不开Wireshark和报文分析

1.1 Modbus协议的两副面孔:RTU和TCP

先说清楚一个基础认知。Modbus协议在工程现场主要两种载体:一种是跑在串口上的Modbus RTU,用RS232/RS485传输,帧格式是“从站地址 + 功能码 + 数据 + CRC校验”;另一种是跑在以太网上的Modbus TCP,帧格式是“MBAP报文头 + 功能码 + 数据”。两者底层数据结构不同,但应用层的功能码定义、寄存器地址规则、异常码规则完全一致。

类比一下:Modbus RTU像寄平邮,信封上手写收件人地址、贴邮票,丢件率和误投率靠CRC校验来兜底;Modbus TCP像寄快递,有快递单号(事务标识符)、面单(MBAP头),到了网点还要扫一遍码,天然适合跨网络传输。我们在Wireshark里常见的抓包对象绝大多数是Modbus TCP,因为以太网抓包太方便了;但如果你在现场用USB转485适配器抓串口报文,看到的则是Modbus RTU帧。

理解了两者的关系,你就知道为什么Wireshark能同时处理这两种形态了。Wireshark自带Modbus解析器,只要端口正确、报文完整,它能自动把十六进制字节翻译成字段含义,这也是我们做异常帧分析最重要的工具。

1.2 功能码的“置1报错”机制

Modbus有一个很巧妙的设计:正常响应帧的功能码,和请求帧的功能码完全一样。比如你发一个“03读保持寄存器”请求,从站正常处理完成后,回给你的帧里功能码依然是0x03。但一旦从站发现请求有问题,它不会静默丢弃,而是回一个带错误标识的响应帧。

这个错误标识的实现方式,就是把功能码的最高位置1,也就是在原功能码基础上加上0x80。比如:

  • 0x03 + 0x80 = 0x83,读保持寄存器请求异常
  • 0x06 + 0x80 = 0x86,写单个寄存器请求异常
  • 0x10 + 0x80 = 0x90,写多个寄存器请求异常
  • 0x01 + 0x80 = 0x81,读线圈请求异常

这就是标题里“83、86”这两个数的来历。它们不是凭空出来的随机数,而是“功能码+异常标志”的组合。Wireshark的解析器会在功能码字段直接显示“Illegal Function”或“Exception response”之类的提示,但很多人在Wireshark里只看到一排排十六进制字节,不知道怎么跟功能码对应起来。后面我会用实际报文一步步拆开讲。

这个机制的好处在于,只要看到响应帧的功能码最高位是1,就能立刻知道它是异常响应,再结合异常码字段,就能判断是地址越界、数据值超范围,还是功能码不支持。排障时等于从站直接给你指出了方向,比瞎猜高效太多。

2. Wireshark抓Modbus报文:环境、过滤与报文识别

2.1 抓包位置怎么选才有效

很多人拿着Wireshark却抓不到想要的报文,问题往往出在抓包位置选错了。Modbus TCP场景下,抓包点最好选择在主站侧或从站侧的直连网口上。如果你用笔记本接到交换机上做端口镜像,注意确认镜像方向是双向的,不然只能看到单向流量。

我自己的习惯是:能直连就直连。比如主站是PC上的组态软件,我直接在PC网卡上抓包;如果通信走的是串口服务器转以太网,我会在电脑到串口服务器之间串一个交换机再开镜像,或者直接把笔记本接到串口服务器的管理口抓取。Modbus RTU场景下,抓包介质通常是USB转485工具,抓的是串口上的原始字节流,再用软件解析成Hex格式,和Wireshark里的Modbus TCP解析方式不太一样,但分析思路相通。

这里有个现场细节要注意:通信链路中如果存在网关或路由器,抓包点选择不同,看到的报文可能不一致。比如主站发给网关的是Modbus TCP,网关转发给从站设备时却改成了Modbus RTU。如果你只在主站侧抓包,看到的全是“请求发出去了但没响应”的现象,因为真正被从站拒绝的异常帧发生在网关到从站的那段链路上。遇到这种架构,两边都要抓,才能定位到异常帧发生在哪一段。

2.2 过滤器配置与快速定位异常帧

打开Wireshark后,最基础的过滤表达式就两个:

  • 按端口过滤:tcp.port == 502
  • 按协议过滤:modbus

第一个用于Modbus TCP默认端口,第二个是显示过滤器,Wireshark会自动识别Modbus TCP协议。如果过滤后一片空白,先检查抓包网卡选对没有,再看通信双方的端口是不是502。很多设备支持自定义端口,比如有的PLC默认用502,但有些仪表厂家改成了1502或其他端口,这时候Wireshark不会自动识别为Modbus,需要手动处理,方法在后面的常见坑里详细说。

抓到报文后,如何快速找到异常帧?我的做法是先把显示过滤器切到modbus,然后按功能码列排序,或者直接看响应帧功能码字段的最高位。Wireshark在报文详情区会把这个字段解析得很清楚,比如0x83会显示为“Illegal Function (0x83)”或“Function code 3, Exception response”。比对着十六进制猜省事得多。

2.3 正常帧、异常帧在Wireshark里的长相差异

这里先用一个直观对比。同一个连接里,正常请求帧和异常响应帧在Wireshark的协议解析树中,区别非常明显。

正常响应帧,比如读两个保持寄存器成功,报文是这样的:

00 01 00 00 00 07 01 03 04 00 0A 00 0B

解析树里会看到:事务标识符0x0001、协议标识符0x0000、长度0x0007、单元标识0x01、功能码0x03、字节计数0x04、寄存器值0x000A和0x000B。

异常响应帧,比如同样读了不存在的地址,报文变成:

00 01 00 00 00 03 01 83 02

解析树里长度立刻变成0x0003,功能码变成0x83,后面紧跟异常码0x02。Wireshark会明确告诉你这是“Exception Response”,并且把异常码翻译成“Illegal Data Address”。看到这个,问题范围一下就缩小了。

我在现场教学员看抓包结果时,常让他们记一句话:正常帧功能码后面跟的是数据,异常帧功能码后面跟的是原因。这个“原因”就是异常码,也是我们后面要展开讲的核心。

3. 正常报文逐字节拆解:先搞清楚“对的”长什么样

3.1 Modbus TCP请求帧拆解,以03功能码为例

很多初学者拿到Wireshark的报文,最懵的就是那一串十六进制数。我建议你要么用Wireshark的解析树,要么自己动手数一遍字节。数一遍之后,你对Modbus TCP帧结构的理解会扎实很多。

以一条“读保持寄存器”请求为例:

00 01 00 00 00 06 01 03 00 00 00 0A

逐字节拆开:

  • 00 01:事务标识符,2字节,主站用来匹配请求和响应。能干读、能并发,但不要让它随机变,否则同一个TCP连接里请求和响应容易对不上号。
  • 00 00:协议标识符,2字节,固定为0x0000,表示Modbus协议。
  • 00 06:长度字段,2字节,表示从单元标识符开始到报文结束一共还有多少字节。这里6 = 1(单元标识) + 1(功能码) + 2(起始地址) + 2(寄存器数量)。
  • 01:单元标识符,1字节,也就是从站地址。如果网关后面挂了多个Modbus设备,用这个字段区分。
  • 03:功能码,1字节,读保持寄存器。
  • 00 00:起始地址,2字节,从0x0000开始读。
  • 00 0A:寄存器数量,2字节,读10个寄存器。

这个长度字段00 06是新手最容易数错的地方。有人会把MBAP头7字节也算进去,结果怎么看长度都不对。记住:长度字段只算“单元标识符 + PDU”的字节数,不包含事务标识符、协议标识符和长度字段自身。这个规则后面看响应帧也一样。

3.2 响应帧拆解,长度字段怎么算

接着上面的请求,正常响应帧是:

00 01 00 00 00 07 01 03 04 00 0A 00 0B
  • 00 01:事务标识符,响应必须和请求保持一致,这样主站才能确认这是它刚才那条请求的回复。
  • 00 00:协议标识符,仍然是0x0000。
  • 00 07:长度字段,7 = 1(单元标识) + 1(功能码) + 1(字节计数) + 4(两个寄存器的数据)。
  • 01:单元标识符。
  • 03:功能码,正常响应和请求一致。
  • 04:字节计数,表示后续寄存器数据的字节数。两个寄存器各2字节,所以是4。
  • 00 0A 00 0B:寄存器值,分别是0x000A和0x000B。

这里要注意一个细节:03功能码的响应,末尾字节数取决于实际返回的寄存器数量,而不是请求里要求的数量。如果从站只支持部分地址,有的从站会回异常帧,有的从站会尽量返回能读的部分然后按实际字节数填充。理解这一点,能帮你解释为什么有时候请求读10个寄存器,响应却只回了6个。

响应帧里我最常让人关注的是长度字段和事务标识符。长度字段告诉你这一帧到底有多少有效内容;事务标识符则帮你判断响应是不是“迟到”或“错位”了。很多偶发超时问题,最后追根溯源都是事务标识符对不上。

3.3 写操作的请求与响应:06和10功能码

读操作看懂了,写操作也就顺了。以06功能码“写单个寄存器”为例:

请求帧:

00 02 00 00 00 06 01 06 00 01 00 64
  • 00 02:事务标识符,这里我特意写成2,表示它是第二条请求。
  • 00 00:协议标识符。
  • 00 06:长度,6 = 1(单元标识) + 1(功能码) + 2(寄存器地址) + 2(寄存器值)。
  • 01:单元标识符。
  • 06:功能码,写单个寄存器。
  • 00 01:寄存器地址,0x0001。
  • 00 64:写入值,十进制100。

06功能码的正常响应和请求完全一样,原样返回。也就是说你写什么,它就回什么。很多人在抓包时看到响应帧和请求帧一模一样,怀疑自己抓重复了,其实这就是06功能码的正常行为。

10功能码“写多个寄存器”稍有不同。请求帧格式是:

00 03 00 00 00 0B 01 10 00 00 00 02 04 00 01 00 02
  • 00 0B:长度,11 = 1(单元标识) + 1(功能码) + 2(起始地址) + 2(寄存器数量) + 1(字节计数) + 4(数据)。
  • 10:功能码,十六进制0x10,十进制16。
  • 00 00:起始地址。
  • 00 02:写2个寄存器。
  • 04:后续数据4个字节。
  • 00 01 00 02:两个寄存器的值。

10功能码的响应帧比较简单,只回起始地址和寄存器数量:

00 03 00 00 00 06 01 10 00 00 00 02

长度字段是6 = 1(单元标识) + 1(功能码) + 2(起始地址) + 2(寄存器数量)。注意,它不会回你写入的具体数据。这个特性可以用来判断主站程序是否依赖响应内容,如果程序写了“写寄存器后从响应里读回数据”,那必然出问题。

4. 异常帧深度解析:83、86背后藏着什么

4.1 异常帧的结构和识别方法

异常帧的出现,说明从站收到了请求,并且明确知道这个请求有问题。它的帧结构比正常响应更短,因为不需要携带业务数据。以83异常帧为例:

00 01 00 00 00 03 01 83 02
  • 00 01:事务标识符,同样和请求保持一致。
  • 00 00:协议标识符。
  • 00 03:长度字段,3 = 1(单元标识) + 1(异常功能码) + 1(异常码)。
  • 01:单元标识符。
  • 83:异常功能码,最高位已置1,表示原功能码03的响应是异常。
  • 02:异常码,表示“非法数据地址”。

注意,异常帧里功能码后面只有这一个字节,后面没有业务数据。看到这种“短响应”,基本可以判断是从站拒绝了你。Wireshark里会直接标注“Exception Response, Illegal Data Address”,你都不用数十六进制。

86异常帧结构完全一样,只是功能码字段变成0x86,代表“写单个寄存器请求异常”。 后面那个异常码,决定了具体是什么问题。

4.2 异常码全表:每个码对应的现场含义

异常码才是排障的关键信息。我整理了Modbus标准异常码里最常遇到的几种,直接做成速查表:

异常码名称含义常见排查方向
01Illegal Function从站不支持该功能码查从站支持的Modbus功能码列表,确认主站指令是否用错
02Illegal Data Address数据地址越界对照寄存器映射表检查起始地址和数量,最常遇到
03Illegal Data Value数据值非法或超出量程检查写入值范围、数据类型、换算系数
04Slave Device Failure从站内部故障检查从站程序是否跑飞、硬件电源和通信接口
05Acknowledge从站已接收但处理时间长适当延长主站超时时间或降低轮询频率
06Slave Device Busy从站忙,无法处理等待从站空闲后重试,避免频繁并发写

实战中我最常碰到的是01、02、03这三个。02尤其多,因为很多设备其实只开放了一部分寄存器地址,你根据手册以为地址连续,实际上中间有保留区,一读到保留区就给你回02。

异常码01 “Illegal Function”反而经常被误解。有人一看到01就觉得是通信配置错了,其实不是。它只说明从站固件里不支持你发出去的这个功能码。比如你拿03去读“输入寄存器”,但设备手册明确写了输入寄存器只能用04功能码读,那从站就回01。有些从站功能做得很精简,比如只支持03和06,你发01或0F,同样回01。

异常码03 “Illegal Data Value”多发生在写操作中。比如某个寄存器用来控制设备档位,合法值是0到4,你写了个5进去,从站就回03。还有一种情况是数据类型不匹配,主站按16位无符号整数发送,从站内部其实按32位浮点解析,字节数对不上,也会触发03。

4.3 83和86的典型现场场景还原

场景一:83 02(读保持寄存器,非法数据地址)。某项目里主站每隔100ms轮询一次仪表,有一次改了程序后开始报通信故障,抓包发现主站发了一条读请求,起始地址是0x0200,寄存器数量0x000A,也就是从0x0200开始读10个寄存器。仪表手册上明确写到0x0200这个地址只有5个有效的保持寄存器,0x0205之后是保留区。你读的地址超出了实际分配范围,从站直接回83 02。

场景二:86 03(写单个寄存器,非法数据值)。也是这个项目,主站给仪表写设定温度,寄存器地址0x0001,写入值6000。仪表的温度量程是0到4000,也就是对应0到400摄氏度,你写入6000明显超量程,从站回86 03。这类问题在工艺参数调整、配方切换时特别容易冒出来,因为上位机界面对参数范围加了限制,但后台的通信程序没有同步校验,界面上输入不进去,报文里却照样发出去了。

场景三:批量读多个寄存器,一个地址非法导致全部失败。用03功能码读了0x0010到0x001F共16个寄存器,其中0x0018是非法地址,从站不会给你返回“部分成功”,而是整体回一个83 02。排障时需要把请求里的地址范围整体拉出来和手册比对,而不是只盯着其中一个地址。

还有一种情况需要特别警惕:同一个TCP连接里多个主站或同一主站内部多线程并发请求时,事务标识符一旦重复,从站响应会跟错误的请求配对。这时你看到的现象往往是“偶尔超时,偶尔回异常帧”,但异常码又不太一致。抓包时先把事务标识符列出来排个序,就能发现重复。

5. 完整排障案例:从“主站超时”到定位异常帧

5.1 现场现象和初步排查

某输送线项目,现场有一套PLC做主站,通过Modbus TCP读取一台温控仪表的数据,内容包含当前温度、目标温度、运行状态、报警状态,还负责下发目标温度设定值。投产一周后,操作员反映“偶尔报通信超时,一天能报个三五次”,过了几分钟自己又恢复了。

初步排查顺序是这样的:先看物理层,网线两端重新压了一遍,交换机端口指示灯正常;再看网络配置,PLC和仪表的IP地址、子网掩码、网关都没有问题;最后看软件参数,PLC通信块的轮询周期、超时时间也都按默认值设置。问题没解决,于是架起Wireshark抓包。

抓包前我把两个准备工作做了:第一,把仪表厂家的Modbus寄存器映射表打印出来;第二,在Wireshark里只保留tcp.port == 502的过滤条件,开启每秒刷新。抓了大约半小时,成功捕获到了一次完整的异常过程。

5.2 抓包发现83和86异常帧,逐条定位

翻看抓包结果时,最先注意到的是几条请求帧,然后紧接着出现了对应的异常响应帧。其中一条是:

请求:读保持寄存器,起始地址0x0050,数量0x0010

00 1A 00 00 00 06 01 03 00 50 00 10

响应:

00 1A 00 00 00 03 01 83 02

事务标识符0x001A完全对应,异常码02,非法数据地址。对照仪表寄存器映射表,发现0x0050到0x005F并不全是有效寄存器,0x0059到0x005F是保留区。PLC的通信配置是从0x0050连续读16个寄存器,而仪表实际在那里只分配了9个寄存器,越界了。

另外一条是写目标温度时出现的异常帧。请求内容为写单个寄存器0x0001,值0x2EE0,也就是十进制的12000。响应返回:

00 1B 00 00 00 03 01 86 03

异常码03,非法数据值。查手册确认,温度设定寄存器0x0001的合法输入范围是0到10000,对应0到1000摄氏度,12000超出了量程。

这里就解答了“为什么偶尔超时”:多数轮询周期内读的地址还在正常范围边缘,一旦PLC自动切换到了某个高级功能页面,读取的地址块扩大,就触发了83 02;操作员尝试写入偏高的温度设定值时,又触发了86 03。从站的异常响应在主站看来就是“通信异常”,于是报超时。

5.3 修复方案和验证结果

针对83 02,把PLC通信块里的读数量从16个改成9个,只读0x0050到0x0058,并确保后续程序处理数据时使用正确的地址偏移。针对86 03,在上位机界面和PLC通信逻辑里同时将温度写入值限制在0到10000,并且在写入前做一次范围判断,超限就拒绝下发。

改完后重新抓包观察了一个小时,所有请求都能收到正常响应帧,功能码03和06返回的数据都合理,没有再出现83、86。同时把Wireshark的过滤器加上modbus && modbus.func_code == 0x83,确认过滤结果为空。这个案例其实就是典型的地址越界和写入超范围叠加,遇到类似问题,先抓包看异常码,远比你反复折腾物理层和IP配置快得多。

6. 排障常见坑和实用技巧速查

6.1 Wireshark看不到Modbus报文的几个原因

**端口不是502。**这是最常见的问题。设备厂家把Modbus TCP端口改成了自定义端口,Wireshark默认不会把它识别为Modbus协议。解决方法:在抓包列表里右键任意一条报文,选择“Decode As”,把目标端口解析为Modbus TCP。如果你已经知道哪个端口,也可以直接在设置里加一个“Modbus TCP”的解码规则。

**过滤器写错。**有人输入modbus.tcp,结果一条报文都出不来。Wireshark里Modbus显示过滤器就是modbus。如果抓包文件里原本是Modbus RTU over TCP(比如串口服务器转发场景),你依然可以用modbus来过滤,但解析方式和纯Modbus TCP不一样,字段名可能不太好看。

**报文被TCP分段。**一个完整的Modbus帧可能被拆成两个TCP段传输,尤其当数据量较大时。Wireshark默认会对TCP流重组,但如果没看到解析树,可以先找到对应的TCP流,右键“Follow TCP Stream”,再回到原始报文中看重组后的内容。这个坑在抓大报文时特别常见,别急着下结论说“没有Modbus报文”。

6.2 区分“从站没回”和“从站回了异常帧”

这是两条完全不同的排查路径,一定要分开。

如果抓包里只有请求帧,没有对应响应帧,也没有异常帧,说明从站根本没理会你的请求。这时候要检查的是链路状态是否正常、从站是否在线、从站程序是否跑飞,以及请求是否被中间的网关拦截了。方向在通信链路和从站设备本身。

如果抓包里请求帧后面跟着83、86之类的异常帧,说明从站在线,通信链路也是通的,问题出在请求内容上。方向在地址映射、数据范围、功能码支持范围。这是我反复强调的一点,很多人一看到“通信超时”就疯狂排查物理链路,结果耽误大量时间。抓包一出来,异常帧就在那儿明摆着。

6.3 轮询周期、超时设置和抓包习惯的经验

关于超时和轮询,我给出几个实践参数供参考。Modbus TCP在同一局域网内的响应时间通常几毫秒到几十毫秒不等。主站超时时间设置建议不要低于150到200毫秒,太短的超时会把你自己的网络抖动误判为从站故障。轮询周期则要看具体设备的数据刷新频率,一般建议至少50毫秒以上,避免因为上次请求还没处理完,新请求又来了,触发从站忙。

抓包习惯方面,我自己每次到现场都会先做三件事:抓包前记录主站程序的寄存器地址表;抓包时用Wireshark的“time reference”功能给关键帧打标记;抓包后先把所有响应帧按事务标识符排序,看有没有不匹配的情况。这三件事养成习惯之后,能解释掉八成以上的“玄学”通信问题。

还有个小技巧,Wireshark里可以给异常帧做着色规则。在视图->着色规则里添加一条modbus && modbus.func_code == 0x83,底色标红,这样抓包时异常帧一眼就能扫出来。同样的规则可以给86、90等异常响应帧分别做。我一般在解决完问题后不会急着删这些规则,因为同一个项目里很可能还有类似故障,下次抓包时直接看到红色报文字段,定位会快很多。

排查Modbus异常帧这件事,说白了就是两件事:一是有没有异常帧,二是异常码是什么。有异常帧就按异常码排查请求内容,没有异常帧就回头查通信链路。80%的现场问题都跑不出这个框架。最后再分享一个我自己踩过的坑:早期我抓包只盯着功能码看,忽略了长度字段,结果有一次把03功能码响应里的“00 07”长度误读成了数据,分析老半天方向全错了。后来凡是拿到新报文,我都先数一遍长度字段,再去看功能码和数据。这个习惯帮我在后续排查里少走了很多弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询