大家好,又见面了,我是你们的朋友全栈君。
寄存器及调用约定
通用的RISC指令集,11个64位寄存器,一个程序计数器和512字节的栈空间构成。
10个通用寄存器 1个只读FP(帧指针寄存器),所有寄存器64bit宽。操作模式默认为64位,32位子寄存器只能通过特殊的算数逻辑单元ALU操作访问。
栈帧的边界由SP和FP限定
SP一直指向栈顶
每个进程的栈空间为一帧,FP指向当前进程栈空间的栈底。
- R0:函数返回值、程序退出值
- R1-R5:函数调用参数
- R6-R9:被调用者保存函数(调用保留的)寄存器
- R10:只读FP用于访问栈
R0-R5是临时寄存器,eBPF程序在调用之间将它们从寄存器转移到内存或从内存转移到寄存器。(spill/fill,解释见https://www.geeksforgeeks.org/what-is-spilling/)
指令编码
- 基础指令编码:一条指令64bit
- 宽指令编码:在基础指令编码后附加一个64bit的立即数,一共128bit
基础指令编码结构:
32 bits (MSB) | 16 bits | 4 bits | 4 bits | 8 bits (LSB) |
---|---|---|---|---|
immediate | offset | source register | destination register | opcode |
指令分类
Opcode低三位:
指令分类 | class | value | description |
---|---|---|---|
算数指令 | BPF_ALU | 0x04 | 32-bit 算数操作 |
BPF_ALU64 | 0x07 | 64-bit 算数操作 | |
跳转指令 | BPF_JMP | 0x05 | 64-bit 跳转操作 |
BPF_JMP32 | 0x06 | 32-bit 跳转操作 | |
载入指令 | BPF_LD | 0x00 | non-standard load operations |
BPF_LDX | 0x01 | 载入寄存器操作 | |
存储指令 | BPF_ST | 0x02 | 存储立即数操作 |
BPF_STX | 0x03 | 存储寄存器操作 |
算数和跳转指令
包括:BPF_ALU, BPF_ALU64, BPF_JMP and BPF_JMP32
opcode分为三部分
4 bits (MSB) | 1 bit | 3 bits (LSB) |
---|---|---|
operation code | source | instruction class |
第四位编码了source操作数:
source | value | description |
---|---|---|
BPF_K | 0x00 | 使用 32-bit 立即数作为源操作数 |
BPF_X | 0x08 | 使用 ‘src_reg’ 寄存器作为源操作数 |
算数指令
operation code编码的操作
code | value | description |
---|---|---|
BPF_ADD | 0x00 | dst = src |
BPF_SUB | 0x10 | dst -= src |
BPF_MUL | 0x20 | dst *= src |
BPF_DIV | 0x30 | dst /= src |
BPF_OR | 0x40 | dst |= src |
BPF_AND | 0x50 | dst &= src |
BPF_LSH | 0x60 | dst <<= src |
BPF_RSH | 0x70 | dst >>= src |
BPF_NEG | 0x80 | dst = ~src |
BPF_MOD | 0x90 | dst %= src |
BPF_XOR | 0xa0 | dst ^= src |
BPF_MOV | 0xb0 | dst = src |
BPF_ARSH | 0xc0 | sign extending shift right |
BPF_END | 0xd0 | byte swap operations (see separate section below) |
例子:
BPF_XOR | BPF_K | BPF_ALU
== src_reg = (u32) src_reg ^ (u32) imm32
字节交换指令
仅对目标寄存器进行操作,不使用源寄存器或立即数
source字段用于选择转换的字节序
source | value | description |
---|---|---|
BPF_TO_LE | 0x00 | 转换主机字节序到小端 |
BPF_TO_BE | 0x08 | 转换主机字节序到大端 |
立即数字段用于编码交换操作的宽度,可以是16/32/64
例子
BPF_ALU | BPF_TO_LE | BPF_END
with imm = 16
dst_reg = htole16(dst_reg)
跳转指令
code | value | description | notes |
---|---|---|---|
BPF_JA | 0x00 | PC = off | BPF_JMP only |
BPF_JEQ | 0x10 | PC = off if dst == src | |
BPF_JGT | 0x20 | PC = off if dst > src | unsigned |
BPF_JGE | 0x30 | PC = off if dst >= src | unsigned |
BPF_JSET | 0x40 | PC = off if dst & src | |
BPF_JNE | 0x50 | PC = off if dst != src | |
BPF_JSGT | 0x60 | PC = off if dst > src | signed |
BPF_JSGE | 0x70 | PC = off if dst >= src | signed |
BPF_CALL | 0x80 | function call | |
BPF_EXIT | 0x90 | function / program return | BPF_JMP only |
BPF_JLT | 0xa0 | PC = off if dst < src | unsigned |
BPF_JLE | 0xb0 | PC = off if dst <= src | unsigned |
BPF_JSLT | 0xc0 | PC = off if dst < src | signed |
BPF_JSLE | 0xd0 | PC = off if dst <= src | signed |
载入和存储指令
包括:BPF_LD, BPF_LDX, BPF_ST and BPF_STX
Opcode结构:
3 bits (MSB) | 2 bits | 3 bits (LSB) |
---|---|---|
mode | size | instruction class |
size修饰符
size modifier | value | description |
---|---|---|
BPF_W | 0x00 | word (4 bytes) |
BPF_H | 0x08 | half word (2 bytes) |
BPF_B | 0x10 | byte |
BPF_DW | 0x18 | double word (8 bytes) |
mode修饰符
mode modifier | value | description |
---|---|---|
BPF_IMM | 0x00 | 64-bit immediate instructions |
BPF_ABS | 0x20 | legacy BPF packet access (absolute) |
BPF_IND | 0x40 | legacy BPF packet access (indirect) |
BPF_MEM | 0x60 | regular load and store operations,寄存器和内存间传递数据的标准载入和存储指令 |
BPF_ATOMIC | 0xc0 | atomic operations,原子操作 |
举例
把立即数的值放到dst_reg off
的内存位置
BPF_MEM | <size> | BPF_ST
== *(size *) (dst_reg off) = imm32
原子操作
在内存上的操作,不会被中断或破坏,使用mode修饰符BPF_ATOMIC
,只支持32位和64位操作,不支持8/16位。
立即数字段用于编码实际的原子操作:
imm | value | description |
---|---|---|
BPF_ADD | 0x00 | atomic add |
BPF_OR | 0x40 | atomic or |
BPF_AND | 0x50 | atomic and |
BPF_XOR | 0xa0 | atomic xor |
例子:
BPF_ATOMIC | BPF_W | BPF_STX
with imm = BPF_ADD
*(u32 *)(dst_reg off16) = src_reg
除了简单原子操作,还有一个修饰符和两个复杂原子操作
imm | value | description |
---|---|---|
BPF_FETCH | 0x01 | modifier: return old value |
BPF_XCHG | 0xe0 | BPF_FETCH | atomic exchange |
BPF_CMPXCHG | 0xf0 | BPF_FETCH | atomic compare and exchange |
如果设置了BPF_FETCH,会使用修改前内存中的值覆盖src_reg
BPF_XCHG以原子操作交换src_reg
的值和dst_reg off
地址的值
BPF_CMPXCHG以原子操作将dst_reg off
地址的值和R0进行比较,如果相等,dst_reg off
地址的值将替换为src_reg
。操作前dst_reg off
地址的值会被扩展零,然后加载回R0。
clang可以生成原子指令通过默认的
-mcpu=v3
。如果较低版本的
-mcpu
被设置,clang只能生成不带BPF_FETCH
的BPF_ADD
如果需要启用原子特征,并保持较低版本的
-mcpu
,可以使用-Xclang -target-feature -Xclang alu32
64位立即数指令
带有BPF_IMM
mode修饰符的指令,对额外的64位立即数使用宽指令编码:
BPF_LD | BPF_DW | BPF_IMM
means dst_reg = imm64
传统的BPF Packet访问指令
用于访问数据包数据,并且只能在程序上下文是指向网络数据包的指针时使用。
两种指令形式
BPF_ABS | <size> | BPF_LD
,BPF_ABS
访问由立即数指定的绝对偏移的数据包数据BPF_IND | <size> | BPF_LD
,BPF_IND
访问除立即数外还包括寄存器值作为偏移的数据包数据。
七个隐式操作数:
- R6,隐式输入,指向 struct sk_buff 的指针
- R0,隐式输出,从数据包中获取的数据
- R1-5,临时寄存器,在调用
BPF_ABS | BPF_LD
或BPF_IND | BPF_LD
后被破坏
隐式的程序退出条件:当eBPF程序试图访问数据包边界外的数据时,执行将被终止。
例子:
BPF_IND | BPF_W | BPF_LD
R0 = ntohl(*(u32 *) (((struct sk_buff *) R6)->data src_reg imm32))
参考
eBPF Instruction Set ‒ The Linux Kernel documentation
BPF and XDP Reference Guide — Cilium 1.11.3 documentation
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/190954.html原文链接:https://javaforall.cn