sBPF 寄存器和内存模型
当您的 sBPF 程序执行时,它主要依赖两种存储机制:处理器内置的 11 个高速寄存器,以及用于存储其他所有内容的有组织的内存区域。
Registers
寄存器就像处理器中直接内置的 11 个编号存储槽(r0 到 r10)。每个寄存器存储一个 64 位的值,并能即时访问,无需延迟。当您执行 add64 r1, r2 时,处理器会立即从两个寄存器中检索值并执行计算。
权衡很简单:您总共只有 11 个存储槽,因此需要战略性地使用它们来存储您正在处理的值。
sBPF 为每个寄存器分配了特定的角色:
| 寄存器 | 角色 | 是否由被调用方保存? | 使用说明 |
r0 | 返回值 | 否 | 函数结果,系统调用返回值 |
r1 | 输入缓冲区指针 | 否 | 进入时指向 0x400000000 |
r2-r5 | 临时/参数寄存器 | 否 | 辅助函数参数,临时值 |
r6-r9 | 通用寄存器 | 是 | 必须在调用间保持值 |
r10 | 帧指针 | 是,只读 | 栈基址,不能直接修改 |
寄存器 r6 到 r9 是 由被调用方保存 的,这意味着它们的值在函数调用之间会保持不变。使用这些寄存器存储需要在函数调用中保留的重要数据。
其余的寄存器(r0-r5)在系统调用和函数调用期间会被覆盖。
寄存器 r10 是“特殊的”,因为它作为帧指针,指向栈空间的基址,并且保持只读。您可以使用负偏移量(例如 [r10 - 8] 和 [r10 - 16])访问栈变量。
Memory
寄存器存储活跃使用的值,而内存则存储其他所有内容。sBPF将内存组织为固定区域,并在所有程序中采用相同的布局:
| 区域 | 起始地址 | 用途 | 大小/备注 |
| Text | 0x100000000 | 代码和只读数据 | 程序二进制文件 |
| Stack | 0x200000000 | 局部变量 | 每个栈帧4 KiB,最大调用深度为64 |
| Heap | 0x300000000 | 动态分配 | 32 KiB |
| Input | 0x400000000 | 程序参数 | 序列化账户和指令数据 |
Text区域包含可执行代码和只读数据,例如字符串常量。使用
.quad指令定义的数据通常位于此处。Stack区域存储局部变量。通过
r10指向栈基址,可以使用负偏移量访问局部变量:[r10 - 16]、[r10 - 24]等。Input区域包含程序的参数。在程序入口,
r1指向该区域(0x400000000),允许读取传递给程序的序列化账户数据和指令参数。
这种固定布局提供了三个关键优势:通过程序间的内存隔离实现安全性,通过在所有验证器中使用相同地址实现确定性,以及通过编译器针对已知位置的优化提升性能。
Using Registers and Memory
以下是寄存器和内存在实际中的协作方式:
.globl entrypoint
entrypoint:
// On entry: r1 points to input data at 0x400000000
ldxdw r0, [r1 + 0] // Load first 8 bytes from input into r0
mov64 r2, 42 // Put 42 in register r2
add64 r0, r2 // Add them: r0 = r0 + r2
stxdw [r10 - 8], r0 // Store result on stack
mov64 r0, 0 // Return success
exit此程序展示了典型模式:
使用寄存器
r1(输入指针)从内存中读取数据使用寄存器
r0和r2进行计算使用寄存器
r10(帧指针)访问栈内存在寄存器
r0中返回结果
工作流程遵循一致的模式:将数据从内存加载到寄存器,在寄存器中执行计算,然后在需要时将结果存储回内存。
栈的使用
栈使用r10作为帧指针,指向当前栈帧的基址(最高地址)。局部变量使用负偏移量:
// Store a value on the stack
mov64 r0, 42
stxdw [r10 - 8], r0 // Store at first stack slot
// Load it back
ldxdw r1, [r10 - 8] // Load from first stack slot栈槽通常宽度为8字节,因此连续的变量使用类似[r10 - 8]、[r10 - 16]、[r10 - 24]等偏移量。
程序的入口和退出
程序从标记为.globl(通常是入口点)的符号开始执行。初始寄存器状态是最小且可预测的:
.globl entrypoint
entrypoint:
// On entry:
// r1 = 0x400000000 (input buffer pointer)
// r10 = 0x200001000 (0x200000000 stack start + 0x1000 4KiB frame size)
// r0, r2-r9 = 0 (all other registers zeroed)
// Your program logic here
mov64 r0, 0 // Success code (0 = SUCCESS)
exit // Return to runtime退出行为取决于调用深度:
在调用深度为0时,退出以
r0作为结果代码终止程序。在更深的调用层级,退出类似于return语句,恢复调用者保存的寄存器(
r6-r9)和调用者的帧指针(r10),然后在返回地址继续执行。
运行时通过隐式的序言和尾声代码自动处理设置和拆解,包括初始寄存器状态配置和最终的返回处理。