Assembly
Introduction to Assembly

Introduction to Assembly

指令

现在您已经了解了 sBPF 的寄存器和内存区域,让我们来研究操作它们的指令。

指令是您的程序执行的基本操作——例如加法、从内存加载数据或跳转到不同位置。

什么是指令?

指令是您程序的基本构建块。可以将它们视为告诉处理器具体执行什么操作的命令:

  • add64 r1, r2: "将寄存器 r1r2 中的值相加,结果存储在 r1 中"
  • ldxdw r0, [r10 - 8]: "从堆栈内存中加载 8 字节到寄存器 r0"
  • jeq r1, 42, +3: "如果 r1 等于 42,则向前跳转 3 条指令"

每条指令仅执行一个操作,并精确编码为 8 字节数据,以便虚拟机即时解码。

sBPF 指令处理不同的数据大小:

  • 字节 = 8 位(1 字节)
  • 半字 = 16 位(2 字节)
  • 字 = 32 位(4 字节)
  • 双字 = 64 位(8 字节)

大多数 sBPF 操作使用 64 位值(双字),因为寄存器是 64 位的,但在需要提高效率时,您可以加载和存储较小的大小。

指令类别和格式

当您编译 Rust、C 或汇编代码时,工具链会生成一系列固定宽度的 8 字节指令,并将其打包到 ELF 的 .text 部分中。

每条指令遵循一致的结构,虚拟机可以一次性解码:

 
   1 byte    4 bits   4 bits     2 bytes         4 bytes
┌──────────┬────────┬────────┬──────────────┬──────────────────┐
│  opcode  │  dst   │  src   │   offset     │      imm         │
└──────────┴────────┴────────┴──────────────┴──────────────────┘
  • opcode: 定义操作类型。最高 3 位选择指令类别(算术、内存、跳转、调用、退出),而低 5 位指定具体的变体(加法、乘法、加载、条件跳转)。
  • dst: 目标寄存器编号(r0–r10),用于存储结果——算术结果、加载值或辅助函数返回值。
  • src: 提供输入的源寄存器。对于双操作数算术(add r1, r2),它提供第二个值。对于内存操作,它可以提供基地址。对于立即数变体(add r1, 10),这 4 位折叠到操作码中。
  • offset: 修改指令行为的小整数。对于加载/存储操作,它会加到源地址以到达 [src + offset]。对于跳转操作,它是以指令为单位的相对分支目标。
  • imm: 立即数字段。算术操作使用它来表示常量(add r1, 42),CALL 使用它来表示系统调用编号(sol_log = 16),内存操作可能将其视为绝对指针。

指令类别

不同的指令类型以特定方式使用这些字段:

  • 数据移动:在寄存器和内存之间移动值:
sbpf
mov64 r1, 42           // Put immediate value 42 into r1
                       // opcode=move_imm, dst=1, src=unused, imm=42
 
ldxdw r0, [r10 - 8]    // Load 8 bytes from stack into r0  
                       // opcode=load64, dst=0, src=10, offset=-8, imm=unused
 
stxdw [r1 + 16], r0    // Store r0 to memory at [r1 + 16]
                       // opcode=store64, dst=1, src=0, offset=16, imm=unused
  • 算术运算:执行数学运算:
sbpf
add64 r1, r2           // r1 = r1 + r2
                       // opcode=add_reg, dst=1, src=2, offset=unused, imm=unused
 
add64 r1, 100          // r1 = r1 + 100  
                       // opcode=add_imm, dst=1, src=unused, offset=unused, imm=100
  • 控制流:更改执行顺序:
sbpf
ja +5                  // Jump forward 5 instructions unconditionally
                       // opcode=jump, dst=unused, src=unused, offset=5, imm=unused
 
jeq r1, r2, +3         // If r1 == r2, jump forward 3 instructions
                       // opcode=jump_eq_reg, dst=1, src=2, offset=3, imm=unused
 
jeq r1, 42, +3         // If r1 == 42, jump forward 3 instructions  
                       // opcode=jump_eq_imm, dst=1, src=unused, offset=3, imm=42

操作码编码

操作码编码捕获了除操作类型之外的多种信息:

  • 指令类别:算术、内存、跳转、调用等
  • 操作大小:32位与64位操作
  • 源类型:寄存器与立即数
  • 特定操作:加法与减法、加载与存储等

这为指令变体创建了不同的操作码。例如,add64 r1, r2(寄存器源)使用的操作码与add64 r1, 42 (立即数源)不同。同样,add64add32在不同操作大小下也有不同的操作码。

算术运算进一步区分有符号和无符号变体。udiv64将值视为无符号(0到18亿亿),而sdiv64处理有符号值(-9亿亿到+9亿亿)。

指令执行

操作码决定了虚拟机如何解释其余字段。

当虚拟机遇到add64 r1, r2时,它读取操作码并识别这是一个使用两个寄存器的64位算术运算:

dst字段表示结果存入r1src字段指定r2为第二操作数,而offsetimmediate字段被忽略。

对于add64 r1, 42,操作码更改为表示立即数操作。此时,dst仍然指向r1,但src变得无意义,而immediate字段提供了第二操作数(42)。

内存操作以有意义的方式组合多个字段:

对于ldxdw r1, [r2+8],操作码表示一个64位内存加载,dst接收加载的值,src提供基地址,而offset(8)被添加以创建最终地址r2 + 8

控制流指令遵循相同的模式:

当您编写jeq r1, r2, +5时,操作码会编码一个比较两个寄存器的条件跳转。如果r1等于r2,虚拟机会将offset(5)加到程序计数器中,向前跳转5条指令。

操作码决定了哪些字段是有意义的。指令格式保持不变:操作码告诉您如何解释每个字段,从而消除了复杂的寻址模式或特殊情况。

函数调用和系统调用

sBPF的调用机制在不同版本中不断演进,以提高清晰度和安全性。在sBPF v3之前,call imm具有双重用途:立即数值决定了您是在调用内部函数还是在调用系统调用。

运行时根据立即数值范围区分这两者,系统调用号通常是像16这样的较小正整数,用于sol_log

从sBPF v3开始,指令被分离以实现明确的行为。call现在使用相对偏移量处理内部函数调用,而syscall imm明确调用运行时函数。这种分离使字节码意图更加清晰,并实现了更好的验证。

通过callx的间接调用也有所演进。早期版本将目标寄存器编码在立即字段中,但从v2开始,为了与通用指令格式保持一致,它被编码在源寄存器字段中。

操作码参考表

内存加载操作

操作码助记符描述
lddwlddw dst, imm加载64位立即数(第一个槽位)
lddwlddw dst, imm加载64位立即数(第二个槽位)
ldxwldxw dst, [src + off]从内存加载字
ldxhldxh dst, [src + off]从内存加载半字
ldxbldxb dst, [src + off]从内存加载字节
ldxdwldxdw dst, [src + off]从内存加载双字

内存存储操作

操作码助记符描述
stwstw [dst + off], imm存储字(立即数)
sthsth [dst + off], imm存储半字(立即数)
stbstb [dst + off], imm存储字节(立即数)
stdwstdw [dst + off], imm存储双字(立即数)
stxwstxw [dst + off], src从寄存器存储字
stxhstxh [dst + off], src从寄存器存储半字
stxbstxb [dst + off], src从寄存器存储字节
stxdwstxdw [dst + off], src从寄存器存储双字

算术操作(64位)

操作码助记符描述
add64add64 dst, imm加法(立即数)
add64add64 dst, src加法(寄存器)
sub64sub64 dst, imm减法(立即数)
sub64sub64 dst, src减法(寄存器)
mul64mul64 dst, imm乘法(立即数)
mul64mul64 dst, src乘法(寄存器)
div64div64 dst, imm除法(无符号立即数)
div64div64 dst, src除法(无符号寄存器)
sdiv64sdiv64 dst, imm除法(有符号立即数)
sdiv64sdiv64 dst, src除法(有符号寄存器)
mod64mod64 dst, imm取模(无符号立即数)
mod64mod64 dst, src取模(无符号寄存器)
smod64smod64 dst, imm取模(有符号立即数)
smod64smod64 dst, src取模(有符号寄存器)
neg64neg64 dst取反

算术操作(32位)

操作码助记符描述
add32add32 dst, imm加法(32位立即数)
add32add32 dst, src加法(32位寄存器)
sub32sub32 dst, imm减法(32位立即数)
sub32sub32 dst, src减法(32位寄存器)
mul32mul32 dst, imm乘法(32位立即数)
mul32mul32 dst, src乘法(32位寄存器)
div32div32 dst, imm除法(32位立即数)
div32div32 dst, src除法(32位寄存器)
sdiv32sdiv32 dst, imm除法(有符号32位立即数)
sdiv32sdiv32 dst, src除法(有符号32位寄存器)
mod32mod32 dst, imm取模(32位立即数)
mod32mod32 dst, src取模(32位寄存器)
smod32smod32 dst, imm取模(有符号32位立即数)
smod32smod32 dst, src取模(有符号32位寄存器)

逻辑操作(64位)

操作码助记符描述
or64or64 dst, imm按位或立即数
or64or64 dst, src按位或寄存器
and64and64 dst, imm按位与立即数
and64and64 dst, src按位与寄存器
lsh64lsh64 dst, imm左移立即数
lsh64lsh64 dst, src左移寄存器
rsh64rsh64 dst, imm右移立即数
rsh64rsh64 dst, src右移寄存器
xor64xor64 dst, imm按位异或立即数
xor64xor64 dst, src按位异或寄存器
mov64mov64 dst, imm移动立即数
mov64mov64 dst, src移动寄存器
arsh64arsh64 dst, imm算术右移立即数
arsh64arsh64 dst, src算术右移寄存器

逻辑操作(32位)

操作码助记符描述
or32or32 dst, imm按位或立即数(32位)
or32or32 dst, src按位或寄存器(32位)
and32and32 dst, imm按位与立即数(32位)
and32and32 dst, src按位与寄存器(32位)
lsh32lsh32 dst, imm左移立即数(32位)
lsh32lsh32 dst, src左移寄存器(32位)
rsh32rsh32 dst, imm右移立即数(32位)
rsh32rsh32 dst, src右移寄存器(32位)
xor32xor32 dst, imm按位异或立即数(32位)
xor32xor32 dst, src按位异或寄存器(32位)
mov32mov32 dst, imm移动立即数(32位)
mov32mov32 dst, src移动寄存器(32位)
arsh32arsh32 dst, imm算术右移立即数(32位)
arsh32arsh32 dst, src算术右移寄存器(32位)

控制流操作

操作码助记符描述
jaja off无条件跳转(跳转 0 = 跳到下一个)
jeqjeq dst, imm, off如果等于立即数则跳转
jeqjeq dst, src, off如果等于寄存器则跳转
jgtjgt dst, imm, off如果大于立即数则跳转(无符号)
jgtjgt dst, src, off如果大于寄存器则跳转(无符号)
jgejge dst, imm, off如果大于或等于立即数则跳转(无符号)
jgejge dst, src, off如果大于或等于寄存器则跳转(无符号)
jsetjset dst, imm, off如果位被设置(立即数掩码)则跳转
jsetjset dst, src, off如果位被设置(寄存器掩码)则跳转
jnejne dst, imm, off如果不等于立即数则跳转
jnejne dst, src, off如果不等于寄存器则跳转
jsgtjsgt dst, imm, off如果大于立即数则跳转(有符号)
jsgtjsgt dst, src, off如果大于寄存器则跳转(有符号)
jsgejsge dst, imm, off如果大于或等于立即数则跳转(有符号)
jsgejsge dst, src, off如果大于或等于寄存器则跳转(有符号)
jltjlt dst, imm, off如果小于立即数则跳转(无符号)
jltjlt dst, src, off如果小于寄存器则跳转(无符号)
jlejle dst, imm, off如果小于或等于立即数则跳转(无符号)
jlejle dst, src, off如果小于或等于寄存器则跳转(无符号)
jsltjslt dst, imm, off如果小于立即数则跳转(有符号)
jsltjslt dst, src, off如果小于寄存器则跳转(有符号)
jslejsle dst, imm, off如果小于或等于立即数则跳转(有符号)
jslejsle dst, src, off如果小于或等于寄存器则跳转(有符号)

函数调用操作

操作码助记符描述
callcall immsyscall imm调用函数或系统调用
callxcallx imm间接调用(寄存器在立即数字段中)
exitexitreturn从函数返回

字节交换操作

操作码助记符描述
bebe dst, imm字节交换(16、32 或 64 位)
lele dst, imm小端转换(已弃用)
Blueshift © 2025Commit: 0ce3b0d