Assembly
汇编语言入门

汇编语言入门

sBPF 寄存器和内存模型

当您的 sBPF 程序执行时,它主要依赖两种存储机制:处理器内置的 11 个高速寄存器,以及用于存储其他所有内容的有组织的内存区域。

Registers

寄存器就像处理器中直接内置的 11 个编号存储槽(r0r10)。每个寄存器存储一个 64 位的值,并能即时访问,无需延迟。当您执行 add64 r1, r2 时,处理器会立即从两个寄存器中检索值并执行计算。

权衡很简单:您总共只有 11 个存储槽,因此需要战略性地使用它们来存储您正在处理的值。

sBPF 为每个寄存器分配了特定的角色:

寄存器角色是否由被调用方保存?使用说明
r0返回值函数结果,系统调用返回值
r1输入缓冲区指针进入时指向 0x400000000
r2-r5临时/参数寄存器辅助函数参数,临时值
r6-r9通用寄存器必须在调用间保持值
r10帧指针是,只读栈基址,不能直接修改

寄存器 r6r9由被调用方保存 的,这意味着它们的值在函数调用之间会保持不变。使用这些寄存器存储需要在函数调用中保留的重要数据。

其余的寄存器(r0-r5)在系统调用和函数调用期间会被覆盖。

寄存器 r10 是“特殊的”,因为它作为帧指针,指向栈空间的基址,并且保持只读。您可以使用负偏移量(例如 [r10 - 8][r10 - 16])访问栈变量。

Memory

寄存器存储活跃使用的值,而内存则存储其他所有内容。sBPF将内存组织为固定区域,并在所有程序中采用相同的布局:

区域起始地址用途大小/备注
Text0x100000000代码和只读数据程序二进制文件
Stack0x200000000局部变量每个栈帧4 KiB,最大调用深度为64
Heap0x300000000动态分配32 KiB
Input0x400000000程序参数序列化账户和指令数据
  • Text区域包含可执行代码和只读数据,例如字符串常量。使用.quad指令定义的数据通常位于此处。

  • Stack区域存储局部变量。通过r10指向栈基址,可以使用负偏移量访问局部变量:[r10 - 16][r10 - 24]等。

  • Input区域包含程序的参数。在程序入口,r1指向该区域(0x400000000),允许读取传递给程序的序列化账户数据和指令参数。

这种固定布局提供了三个关键优势:通过程序间的内存隔离实现安全性,通过在所有验证器中使用相同地址实现确定性,以及通过编译器针对已知位置的优化提升性能。

尝试访问未映射的地址会触发AccessViolation,导致交易失败。

Using Registers and Memory

以下是寄存器和内存在实际中的协作方式:

sbpf
.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(输入指针)从内存中读取数据

  • 使用寄存器r0r2进行计算

  • 使用寄存器r10(帧指针)访问栈内存

  • 在寄存器r0中返回结果

工作流程遵循一致的模式:将数据从内存加载到寄存器,在寄存器中执行计算,然后在需要时将结果存储回内存。

栈的使用

栈使用r10作为帧指针,指向当前栈帧的基址(最高地址)。局部变量使用负偏移量:

sbpf
// 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(通常是入口点)的符号开始执行。初始寄存器状态是最小且可预测的:

sbpf
.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),然后在返回地址继续执行。

运行时通过隐式的序言和尾声代码自动处理设置和拆解,包括初始寄存器状态配置和最终的返回处理。

Blueshift © 2025Commit: e573eab