1 BPF 指令集架构 (ISA)

eBPF,也通常称为 BPF,是一项起源于 Linux 内核的技术,可以在特权上下文(例如操作系统内核)中运行不受信任的程序。本文档指定了 BPF 指令集架构 (ISA)。

作为历史记录,BPF 最初代表伯克利数据包过滤器 (Berkeley Packet Filter),但现在它可以做的事情远不止数据包过滤,因此该缩写不再有意义。BPF 现在被视为一个独立的术语,不代表任何东西。原来的 BPF 有时被称为 cBPF(经典 BPF),以区别于现在广泛部署的 eBPF(扩展 BPF)。

1.1 文档约定

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“NOT RECOMMENDED”、“MAY”和“OPTIONAL”应按照 BCP 14 https://www.rfc-editor.org/info/rfc2119 https://www.rfc-editor.org/info/rfc8174 中的描述进行解释,并且仅当它们全部大写时才进行解释,如此处所示。

为了简洁和一致性,本文档使用简写语法来指代类型系列,并在描述指令的语义时指代一些解释性的助记函数。这些类型的有效值范围和这些函数的语义在以下小节中定义。

1.1.1 类型

本文档使用符号 SN 来指代整数类型,以分别指定类型的有符号性 (S) 和位宽度 (N)。

有符号性符号的含义

S

含义

u

无符号

s

有符号

位宽符号的含义

N

位宽度

8

8 位

16

16 位

32

32 位

64

64 位

128

128 位

例如,u32 是一种有效值均为 32 位无符号数的类型,而 s16 是一种有效值均为 16 位有符号数的类型。

1.1.2 函数

以下字节交换函数是方向无关的。也就是说,对于下面讨论的任一方向的转换,都使用相同的函数。

  • be16:接受一个无符号的 16 位数字,并在主机字节序和大端 (IEN137) 字节序之间转换。

  • be32:接受一个无符号的 32 位数字,并在主机字节序和大端字节序之间转换。

  • be64:接受一个无符号的 64 位数字,并在主机字节序和大端字节序之间转换。

  • bswap16:接受一个大端或小端格式的无符号 16 位数字,并返回具有相同位宽但相反字节序的等效数字。

  • bswap32:接受一个大端或小端格式的无符号 32 位数字,并返回具有相同位宽但相反字节序的等效数字。

  • bswap64:接受一个大端或小端格式的无符号 64 位数字,并返回具有相同位宽但相反字节序的等效数字。

  • le16:接受一个无符号的 16 位数字,并在主机字节序和小端字节序之间转换。

  • le32:接受一个无符号的 32 位数字,并在主机字节序和小端字节序之间转换。

  • le64:接受一个无符号的 64 位数字,并在主机字节序和小端字节序之间转换。

1.1.3 定义

符号扩展

X 位数字 A 符号扩展为 Y 位数字 B,表示

  1. A 中的所有 X 位复制到 B 的低 X 位。

  2. B 的剩余 Y - X 位的值设置为 A 的最高有效位的值。

示例

在大端平台上将 8 位数字 A 符号扩展为 16 位数字 B

A:          10000110
B: 11111111 10000110

1.1.4 一致性组

实现不需要支持本文档中指定的所有指令(例如,已弃用的指令)。相反,指定了许多一致性组。实现必须支持 base32 一致性组,并且可以支持其他一致性组,其中支持一致性组意味着它必须支持该一致性组中的所有指令。

使用命名的符合性组可以在执行指令的运行时和生成运行时指令的编译器等工具之间实现互操作性。因此,可以由用户手动或由工具自动完成对符合性组的功能发现。

每个符合性组都有一个短的 ASCII 标签(例如,“base32”),它对应于一组强制性指令。也就是说,每个指令都具有一个或多个它所属的符合性组。

本文档定义了以下符合性组

  • base32:包括本规范中定义的所有指令,除非另有说明。

  • base64:包括 base32,以及明确注明在 base64 符合性组中的指令。

  • atomic32:包括 32 位原子操作指令(请参阅 原子操作)。

  • atomic64:包括 atomic32,以及 64 位原子操作指令。

  • divmul32:包括 32 位除法、乘法和模数指令。

  • divmul64:包括 divmul32,以及 64 位除法、乘法和模数指令。

  • packet:已弃用的数据包访问指令。

1.2 指令编码

BPF 有两种指令编码

  • 基本指令编码,它使用 64 位来编码一条指令

  • 宽指令编码,它在基本指令之后追加第二个 64 位,总共 128 位。

1.2.1 基本指令编码

基本指令编码如下

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    opcode     |     regs      |            offset             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                              imm                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
操作码

要执行的操作,编码如下

+-+-+-+-+-+-+-+-+
|specific |class|
+-+-+-+-+-+-+-+-+
特定

这些位的格式因指令类而异

指令类别(请参阅指令类别

寄存器

源寄存器和目标寄存器编号,在小端主机上按以下方式编码

+-+-+-+-+-+-+-+-+
|src_reg|dst_reg|
+-+-+-+-+-+-+-+-+

在大端主机上按以下方式编码

+-+-+-+-+-+-+-+-+
|dst_reg|src_reg|
+-+-+-+-+-+-+-+-+
src_reg

源寄存器编号(0-10),除非另有说明(64 位立即数指令 将此字段重用于其他目的)

dst_reg

目标寄存器编号(0-10),除非另有说明(未来的指令可能会将此字段重用于其他目的)

offset

与指针算术运算一起使用的有符号整数偏移量,除非另有说明(某些算术指令将此字段重用于其他目的)

imm

有符号整数立即数值

请注意,多字节字段(“offset”和“imm”)的内容在 Big-Endian 主机上使用 Big-Endian 字节顺序存储,在 Little-Endian 主机上使用 Little-Endian 字节顺序存储。

例如

opcode                  offset imm          assembly
       src_reg dst_reg
07     0       1        00 00  44 33 22 11  r1 += 0x11223344 // little
       dst_reg src_reg
07     1       0        00 00  11 22 33 44  r1 += 0x11223344 // big

请注意,大多数指令不使用所有字段。未使用的字段应清零。

1.2.2 宽指令编码

某些指令被定义为使用宽指令编码,该编码使用两个 32 位立即数值。基本指令格式后面的 64 位包含一条伪指令,其中“opcode”、“dst_reg”、“src_reg”和“offset”都设置为零。

如下图所示

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    opcode     |     regs      |            offset             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                              imm                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           reserved                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           next_imm                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
操作码

要执行的操作,如上所述进行编码

寄存器

源寄存器和目标寄存器编号(除非另有说明),如上所述进行编码

offset

与指针算术运算一起使用的有符号整数偏移量,除非另有说明

imm

有符号整数立即数值

保留

未使用,设置为零

next_imm

第二个有符号整数立即数值

1.2.3 指令类别

“opcode”字段的最低有效三位存储指令类别

指令类别

描述

参考

LD

0x0

非标准加载操作

加载和存储指令

LDX

0x1

加载到寄存器操作

加载和存储指令

ST

0x2

从立即数存储操作

加载和存储指令

STX

0x3

从寄存器存储操作

加载和存储指令

ALU

0x4

32 位算术运算

算术和跳转指令

JMP

0x5

64 位跳转操作

算术和跳转指令

JMP32

0x6

32 位跳转操作

算术和跳转指令

ALU64

0x7

64 位算术运算

算术和跳转指令

1.3 算术和跳转指令

对于算术和跳转指令(ALUALU64JMPJMP32),8 位“opcode”字段分为三个部分

+-+-+-+-+-+-+-+-+
|  code |s|class|
+-+-+-+-+-+-+-+-+
code

操作码,其含义因指令类别而异

s (source)

源操作数位置,除非另有说明,否则是以下之一

源操作数位置

source

描述

K

0

使用 32 位“imm”值作为源操作数

X

1

使用“src_reg”寄存器值作为源操作数

instruction class

指令类别(请参阅指令类别

1.3.1 算术指令

ALU 使用 32 位宽操作数,而 ALU64 使用 64 位宽操作数进行其他相同的操作。ALU64 指令属于 base64 一致性组,除非另有说明。“code”字段按如下方式编码操作,其中“src”指源操作数,“dst”指目标寄存器的值。

算术指令

名称

code

offset

描述

ADD

0x0

0

dst += src

SUB

0x1

0

dst -= src

MUL

0x2

0

dst *= src

DIV

0x3

0

dst = (src != 0) ? (dst / src) : 0

SDIV

0x3

1

dst = (src != 0) ? (dst s/ src) : 0

OR

0x4

0

dst |= src

AND

0x5

0

dst &= src

LSH

0x6

0

dst <<= (src & mask)

RSH

0x7

0

dst >>= (src & mask)

NEG

0x8

0

dst = -dst

MOD

0x9

0

dst = (src != 0) ? (dst % src) : dst

SMOD

0x9

1

dst = (src != 0) ? (dst s% src) : dst

XOR

0xa

0

dst ^= src

MOV

0xb

0

dst = src

MOVSX

0xb

8/16/32

dst = (s8,s16,s32)src

ARSH

0xc

0

符号扩展 dst >>= (src & mask)

END

0xd

0

字节交换操作(请参阅下面的字节交换指令

算术运算期间允许下溢和溢出,这意味着 64 位或 32 位值将回绕。如果 BPF 程序执行导致除以零,则目标寄存器将设置为零。如果执行导致模零运算,对于 ALU64,目标寄存器的值不变,而对于 ALU,目标寄存器的高 32 位将清零。

{ADD, X, ALU},其中 “code” = ADD,“source” = X 且 “class” = ALU,表示

dst = (u32) ((u32) dst + (u32) src)

其中“(u32)”表示高 32 位清零。

{ADD, X, ALU64} 表示

dst = dst + src

{XOR, K, ALU} 表示

dst = (u32) dst ^ (u32) imm

{XOR, K, ALU64} 表示

dst = dst ^ imm

请注意,大多数算术指令的“offset”设置为 0。只有三个指令(SDIVSMODMOVSX)的“offset”为非零值。

ALU 的除法、乘法和模运算是“divmul32”一致性组的一部分,而 ALU64 的除法、乘法和模运算是“divmul64”一致性组的一部分。除法和模运算支持无符号和有符号两种形式。

对于无符号运算(DIVMOD),对于 ALU,“imm”被解释为 32 位无符号值。对于 ALU64,“imm”首先从 32 位符号扩展为 64 位,然后被解释为 64 位无符号值。

对于有符号运算(SDIVSMOD),对于 ALU,“imm”被解释为 32 位有符号值。对于 ALU64,“imm”首先从 32 位符号扩展为 64 位,然后被解释为 64 位有符号值。

请注意,当被除数或除数为负数时,有符号模运算的定义各不相同,其中实现通常因语言而异,例如 Python、Ruby 等与 C、Go、Java 等不同。此规范要求有符号模运算必须使用截断除法(其中 -13 % 3 == -1),如 C、Go 等中实现的那样。

a % n = a - n * trunc(a / n)

MOVSX 指令执行带有符号扩展的移动操作。{MOVSX, X, ALU} 将 8 位和 16 位操作数符号扩展为 32 位操作数,并将剩余的高 32 位清零。{MOVSX, X, ALU64} 将 8 位、16 位和 32 位操作数符号扩展为 64 位操作数。与其他算术指令不同,MOVSX 仅为寄存器源操作数 ( X ) 定义。

{MOV, K, ALU64} 表示

dst = (s64)imm

{MOV, X, ALU} 表示

dst = (u32)src

{MOVSX, X, ALU} 其中“offset”为 8,表示

dst = (u32)(s32)(s8)src

仅当源位清除 ( K ) 时,才定义 NEG 指令。

移位操作对 64 位操作使用 0x3F (63) 的掩码,对 32 位操作使用 0x1F (31) 的掩码。

1.3.2 字节交换指令

字节交换指令使用 ALUALU64 指令类别,以及 4 位 “code” 字段 END

字节交换指令仅对目标寄存器进行操作,不使用单独的源寄存器或立即数值。

对于 ALU,操作码中的 1 位源操作数字段用于选择操作转换的字节顺序。对于 ALU64,操作码中的 1 位源操作数字段保留,必须设置为 0。

字节交换指令

source

描述

ALU

LE

0

在主机字节序和小端序之间转换

ALU

BE

1

在主机字节序和大端序之间转换

ALU64

保留

0

无条件执行字节交换

“imm” 字段编码交换操作的宽度。支持以下宽度:16、32 和 64。宽度为 64 的操作属于 base64 一致性组,其他交换操作属于 base32 一致性组。

示例

{END, LE, ALU},其中 “imm” = 16/32/64 表示

dst = le16(dst)
dst = le32(dst)
dst = le64(dst)

{END, BE, ALU},其中 “imm” = 16/32/64 表示

dst = be16(dst)
dst = be32(dst)
dst = be64(dst)

{END, TO, ALU64},其中 “imm” = 16/32/64 表示

dst = bswap16(dst)
dst = bswap32(dst)
dst = bswap64(dst)

1.3.3 跳转指令

JMP32 使用 32 位宽的操作数,表示 base32 一致性组,而 JMP 对相同的操作使用 64 位宽的操作数,除非另有说明,否则表示 base64 一致性组。“code” 字段按如下方式编码操作

跳转指令

code

src_reg

描述

注释

JA

0x0

0x0

PC += 偏移量

仅限 {JA, K, JMP}

JA

0x0

0x0

PC += imm

仅限 {JA, K, JMP32}

JEQ

0x1

任意

如果 dst == src,则 PC += 偏移量

JGT

0x2

任意

如果 dst > src,则 PC += 偏移量

无符号

JGE

0x3

任意

如果 dst >= src,则 PC += 偏移量

无符号

JSET

0x4

任意

如果 dst & src,则 PC += 偏移量

JNE

0x5

任意

如果 dst != src,则 PC += 偏移量

JSGT

0x6

任意

如果 dst > src,则 PC += 偏移量

有符号

JSGE

0x7

任意

如果 dst >= src,则 PC += 偏移量

有符号

CALL

0x8

0x0

通过静态 ID 调用辅助函数

仅限 {CALL, K, JMP},请参阅 辅助函数

CALL

0x8

0x1

调用 PC += imm

仅限 {CALL, K, JMP},请参阅 程序局部函数

CALL

0x8

0x2

通过 BTF ID 调用辅助函数

仅限 {CALL, K, JMP},请参阅 辅助函数

EXIT

0x9

0x0

返回

仅限 {CALL, K, JMP}

JLT

0xa

任意

如果 dst < src,则 PC += 偏移量

无符号

JLE

0xb

任意

如果 dst <= src,则 PC += 偏移量

无符号

JSLT

0xc

任意

如果 dst < src,则 PC += 偏移量

有符号

JSLE

0xd

任意

如果 dst <= src,则 PC += 偏移量

有符号

其中 “PC” 表示程序计数器,要递增的偏移量以 64 位指令为单位,相对于跳转指令之后的指令。因此,如果下一条指令是基本指令,则 “PC += 1” 会跳过下一条指令的执行,如果下一条指令是 128 位宽的指令,则会导致未定义的行为。

示例

{JSGE, X, JMP32} 表示

if (s32)dst s>= (s32)src goto +offset

其中 “s>=” 表示有符号 “>=” 比较。

{JLE, K, JMP} 表示

if dst <= (u64)(s64)imm goto +offset

{JA, K, JMP32} 表示

gotol +imm

其中 “imm” 表示分支偏移量来自 “imm” 字段。

请注意,JA 指令有两种形式。JMP 类允许由 “offset” 字段指定的 16 位跳转偏移量,而 JMP32 类允许由 “imm” 字段指定的 32 位跳转偏移量。大于 16 位的条件跳转可以转换为小于 16 位的条件跳转加上 32 位无条件跳转。

所有 CALLJA 指令都属于 base32 一致性组。

1.3.3.1 辅助函数

辅助函数是一个概念,BPF 程序可以通过该概念调用底层平台公开的一组函数调用。

历史上,每个辅助函数都由 “imm” 字段中编码的静态 ID 标识。有关辅助函数的更多文档超出了本文档的范围,标准化留待将来的工作,但该用法已广泛部署,更多信息可以在特定于平台的文档中找到(例如,Linux 内核文档)。

支持 BPF 类型格式 (BTF) 的平台支持通过 “imm” 字段中编码的 BTF ID 来标识辅助函数,其中 BTF ID 标识辅助函数的名称和类型。有关 BTF 的更多文档超出了本文档的范围,标准化留待将来的工作,但该用法已广泛部署,更多信息可以在特定于平台的文档中找到(例如,Linux 内核文档)。

1.3.3.2 程序局部函数

程序局部函数是由与调用者相同的 BPF 程序公开的函数,并按与调用指令之后的指令的偏移量引用,类似于 JA。偏移量在调用指令的 “imm” 字段中编码。程序局部函数中的 EXIT 将返回给调用者。

1.4 加载和存储指令

对于加载和存储指令(LDLDXSTSTX),8 位 “opcode” 字段按如下方式划分

+-+-+-+-+-+-+-+-+
|mode |sz |class|
+-+-+-+-+-+-+-+-+
模式

模式修饰符是以下之一

模式修饰符

模式修饰符

描述

参考

IMM

0

64 位立即数指令

64 位立即数指令

ABS

1

传统 BPF 数据包访问(绝对)

传统 BPF 数据包访问指令

IND

2

传统 BPF 数据包访问(间接)

传统 BPF 数据包访问指令

MEM

3

常规加载和存储操作

常规加载和存储操作

MEMSX

4

符号扩展加载操作

符号扩展加载操作

ATOMIC

6

原子操作

原子操作

sz(大小)

大小修饰符是以下之一

大小修饰符

大小

描述

W

0

字(4 字节)

H

1

半字(2 字节)

B

2

字节

DW

3

双字(8 字节)

使用 DW 的指令属于 base64 一致性组。

指令类别(请参阅指令类别

1.4.1 常规加载和存储操作

MEM 模式修饰符用于编码在寄存器和内存之间传输数据的常规加载和存储指令。

{MEM, <size>, STX} 表示

*(size *) (dst + offset) = src

{MEM, <size>, ST} 表示

*(size *) (dst + offset) = imm

{MEM, <size>, LDX} 表示

dst = *(unsigned size *) (src + offset)

其中 “<size>” 是以下之一:BHWDW,“无符号大小” 是以下之一:u8、u16、u32 或 u64。

1.4.2 符号扩展加载操作

MEMSX 模式修饰符用于编码在寄存器和内存之间传输数据的符号扩展加载指令。

{MEMSX, <size>, LDX} 表示

dst = *(signed size *) (src + offset)

其中 “<size>” 是以下之一:BHW,“有符号大小” 是以下之一:s8、s16 或 s32。

1.4.3 原子操作

原子操作是在内存上操作的操作,不会被其他 BPF 程序或本规范之外的其他方式对同一内存区域的访问中断或破坏。

BPF 支持的所有原子操作都编码为使用 ATOMIC 模式修饰符的存储操作,如下所示

  • {ATOMIC, W, STX} 用于 32 位操作,这些操作是 “atomic32” 一致性组的一部分。

  • {ATOMIC, DW, STX} 用于 64 位操作,这些操作是 “atomic64” 一致性组的一部分。

  • 不支持 8 位和 16 位宽的原子操作。

“imm” 字段用于编码实际的原子操作。简单原子操作使用在 “imm” 字段中定义的用于编码算术操作的值的子集来编码原子操作

简单原子操作

imm

描述

ADD

0x00

原子加

OR

0x40

原子或

AND

0x50

原子与

XOR

0xa0

原子异或

带有 ‘imm’ = ADD 的 {ATOMIC, W, STX} 表示

*(u32 *)(dst + offset) += src

带有 ‘imm’ = ADD 的 {ATOMIC, DW, STX} 表示

*(u64 *)(dst + offset) += src

除了简单的原子操作之外,还有一个修饰符和两个复杂的原子操作

复杂原子操作

imm

描述

FETCH

0x01

修饰符:返回旧值

XCHG

0xe0 | FETCH

原子交换

CMPXCHG

0xf0 | FETCH

原子比较并交换

对于简单的原子操作,FETCH 修饰符是可选的,对于复杂的原子操作,它始终被设置。 如果设置了 FETCH 标志,则该操作还会使用修改前内存中的值覆盖 src

XCHG 操作将 srcdst + offset 所寻址的值进行原子交换。

CMPXCHG 操作将 dst + offset 所寻址的值与 R0 进行原子比较。 如果它们匹配,则 dst + offset 所寻址的值将替换为 src。 在任何情况下,dst + offset 在操作之前的原始值都会进行零扩展并加载回 R0

1.4.4 64 位立即数指令

带有 IMM ‘mode’ 修饰符的指令使用 指令编码 中定义的宽指令编码,并使用基本指令的 ‘src_reg’ 字段来保存操作码子类型。

下表定义了一组 ‘src_reg’ 字段中带有操作码子类型的 {IMM, DW, LD} 指令,使用了下面进一步定义的诸如“map”之类的新术语。

64 位立即数指令

src_reg

伪代码

imm 类型

dst 类型

0x0

dst = (next_imm << 32) | imm

整数

整数

0x1

dst = map_by_fd(imm)

map fd

map

0x2

dst = map_val(map_by_fd(imm)) + next_imm

map fd

数据地址

0x3

dst = var_addr(imm)

变量 ID

数据地址

0x4

dst = code_addr(imm)

整数

代码地址

0x5

dst = map_by_idx(imm)

map 索引

map

0x6

dst = map_val(map_by_idx(imm)) + next_imm

map 索引

数据地址

其中

  • map_by_fd(imm) 表示将 32 位文件描述符转换为 map 的地址 (请参见 Maps)

  • map_by_idx(imm) 表示将 32 位索引转换为 map 的地址

  • map_val(map) 获取给定 map 中第一个值的地址

  • var_addr(imm) 获取具有给定 ID 的平台变量的地址 (请参见 平台变量)

  • code_addr(imm) 获取指定相对偏移量(以 64 位指令的数量为单位)的指令地址

  • 反汇编器可以使用 ‘imm type’ 进行显示

  • ‘dst type’ 可用于验证和 JIT 编译目的

1.4.4.1 Maps

Map 是某些平台上 BPF 程序可以访问的共享内存区域。 Map 可以具有在单独的文档中定义的各种语义,并且可能具有也可能不具有单个连续的内存区域,但 ‘map_val(map)’ 当前仅为确实具有单个连续内存区域的 map 定义。

如果平台支持,每个 map 都可以具有文件描述符 (fd),其中 ‘map_by_fd(imm)’ 表示获取具有指定文件描述符的 map。 还可以将每个 BPF 程序定义为使用加载时与该程序关联的一组 map,并且 ‘map_by_idx(imm)’ 表示获取与包含该指令的 BPF 程序关联的集合中具有给定索引的 map。

1.4.4.2 平台变量

平台变量是由运行时公开并通过整数 ID 标识的内存区域,某些平台上的 BPF 程序可以访问这些区域。 ‘var_addr(imm)’ 操作表示获取由给定 ID 标识的内存区域的地址。

1.4.5 旧版 BPF 数据包访问指令

BPF 先前引入了用于访问数据包数据的特殊指令,这些指令是从经典 BPF 继承的。 这些指令使用 LD 的指令类,WHB 的大小修饰符,以及 ABSIND 的模式修饰符。 ‘dst_reg’ 和 ‘offset’ 字段设置为零,并且对于 ABS,‘src_reg’ 设置为零。但是,这些指令已被弃用,不应再使用。所有旧版数据包访问指令都属于“数据包”一致性组。