news 2026/9/14 16:41:48

Solana C 语言 BPF 程序开发指南:从 makefile 构建到单元测试的完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Solana C 语言 BPF 程序开发指南:从 makefile 构建到单元测试的完整实战

Solana C 语言 BPF 程序开发指南:从 makefile 构建到单元测试的完整实战

【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana

本指南以 Solana 官方 C SDK(sdk/bpf/c/README.md)为核心,系统讲解如何使用 C 语言编写 Solana 链上 BPF 程序:包括项目骨架搭建、bpf.mk构建系统用法、Criterion 单元测试框架集成,以及 C 语言开发的关键限制与规避方案。读完本文后,你将能独立完成一个 C 语言 Solana 程序的编写、编译、测试与部署准备工作。

一、背景:为什么用 C 写 Solana 程序

Solana 链上程序(Program)运行在 BPF(Berkeley Packet Filter)虚拟机之上,官方主推 Rust 生态,但同时也提供了完整的 C/C++ SDK。C 语言程序编译为bpfel目标 ELF 文件后,同样可通过 BPF Loader 部署上链。

在 Solana 源码仓库中,C 语言的完整开发工具链集中在 sdk/bpf/c 目录:包含头文件目录inc/、构建脚本bpf.mk、链接脚本bpf.ld,以及配套的 sdk/bpf/scripts(工具链下载脚本)与 sdk/bpf/env.sh(环境配置脚本)。仓库自带的 programs/sbf/c/src 下大量 C 示例程序(如noopallocmove_funds等)均采用本指南介绍的这套构建体系。

二、快速开始:搭建你的第一个 C 程序

根据 sdk/bpf/c/README.md 的 Quick start 章节,只需两个文件即可起步。

1. 编写 makefile

在项目根目录创建makefile,内容仅需一行:

include path/to/bpf.mk

path/to/bpf.mk指向仓库中的 sdk/bpf/c/bpf.mk。实际使用中通常写为相对路径,例如../../sdk/bpf/c/bpf.mk。该 makefile 接管了后续全部构建逻辑。

2. 编写程序源码

创建src/program.c,内容为:

#include <solana_sdk.h> extern uint64_t entrypoint(const uint8_t *input) { SolAccountInfo ka[1]; SolParameters params = (SolParameters) { .ka = ka }; if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { return ERROR_INVALID_ARGUMENT; } return SUCCESS; }

这个最小程序完成了三件核心工作:

  • entrypoint是程序入口:每个 Solana 程序必须导出uint64_t entrypoint(const uint8_t *input)函数,接收一个序列化后的输入缓冲区(见 entrypoint.h);
  • 准备SolParameters接收反序列化结果SolParameters结构体包含账户数组指针ka、账户数量ka_num、指令数据指针data/data_len以及当前程序 IDprogram_id
  • 调用sol_deserialize解析输入:成功返回true,失败返回ERROR_INVALID_ARGUMENT(该错误码定义于 types.h,值为内置错误的高 32 位编码)。

关于sol_deserialize的实现细节:deserialize.h 中的sol_deserialize采用零拷贝反序列化——它不复制数据,而是直接在原始缓冲区上填充SolAccountInfo的指针与长度字段,因此程序对lamports或账户数据的修改会直接作用到原缓冲区,结束时也无需再序列化写回。同时它支持账户去重(dup_info字段,重复账户直接复制首份账户的指针),这是 BPF Loader 输入格式的重要特征。

3. 构建与产物

执行make即可构建,默认输出目录为./out,产物为out/program.o。若你的源码目录、测试前缀等需要定制,可通过命令行覆盖bpf.mk中的可配置变量(详见下文「构建系统深度解析」)。

三、构建系统深度解析:bpf.mk 的工作原理

bpf.mk是整个 C 开发体验的基石。理解它的约定与目标规则,是高效开发的前提。

1. 目录与命名约定

make help输出的说明明确了默认约定(见 bpf.mk):

约定项默认值说明
源码目录SRC_DIR ?= ./src程序位于$(SRC_DIR)/<program name>
程序命名按目录名目录src/foo/即程序名foo
测试前缀TEST_PREFIX ?= test_测试文件须以test_开头,位于程序目录内
输出目录OUT_DIR ?= ./out所有产物输出到该目录
详细模式VV=1时回显构建命令,默认静默

2. 可用的 make 目标

目标作用
make help显示帮助信息与可用程序/测试列表
make all构建全部程序与测试并运行测试
make programs构建所有程序
make tests构建并运行所有测试
make <program name>按名称构建单个程序
make <test name>构建并运行单个测试
make dump_<program name>llvm-objdump反汇编输出程序(含源码标注)
make readelf_<program name>llvm-readelf显示 ELF 二进制信息
make clean删除$(OUT_DIR)

例如,仓库示例程序 programs/sbf/c/src/noop/noop.c 所在目录名为noop,那么make noop会编译出out/noop.somake dump_noop可查看其反汇编。

3. 编译与链接参数

bpf.mk中实际使用的工具链与参数(bpf.mk):

  • 工具链clang/clang++/ld.lld/llvm-objdump/llvm-readelf,全部来自bpf-tools(由 install.sh 自动下载);
  • C 编译标志-Werror -O2 -fno-builtin -std=c17,标准为C17
  • BPF 目标标志-target bpf -fPIC -march=bpfel+solanabpfel即 little-endian BPF);
  • 链接标志-z notext -shared --Bdynamic,使用 bpf.ld 链接脚本,入口点固定为entrypoint
  • SBFv2 支持:设置环境变量SOL_SBFV2=1时追加-DSOL_SBFV2=1编译宏并启用--pack-dyn-relocs=relr动态重定位打包。

链接脚本 bpf.ld 将 ELF 组织为textrodatadatadynamic四个段,并丢弃.eh_frame.gnu.hash.hash等无关段,以最小化链上程序体积。

依赖自动安装:install.sh 会在首次构建时自动下载 Criterion 测试框架与 Rust-BPF 平台工具(platform-tools),并缓存到~/.cache/solana;env.sh 则负责导出CCAROBJDUMPOBJCOPY等环境变量指向 bpf-tools 中的 LLVM 工具。

4. 构建产物与部署提示

每次成功链接出.so后,bpf.mk还会自动生成对应的<program>-keypair.json密钥对文件(若不存在),并提示部署命令:

solana program deploy /absolute/path/to/out/program.so

四、单元测试:基于 Criterion 的测试体系

内置的单元测试支持来自Criterion测试框架(详见 sdk/bpf/c/README.md 的 Unit tests 章节,以及 install.sh 中自动下载 Criterion 的逻辑)。

1. 编写测试

按约定,测试文件放在test/目录(命名须以test_开头)。创建test/example.c

#include <criterion/criterion.h> #include "../src/program.c" Test(test_suite_name, test_case_name) { cr_assert(true); }

关键点:

  • 直接#include程序源码(而非链接编译产物),使测试能访问程序内部的静态函数与符号;
  • 使用 Criterion 的Test(suite, case)宏声明用例,cr_assert进行断言;
  • 测试编译时会追加-DSOL_TEST宏与 Criterion 的头文件/库路径(见 bpf.mk),macOS 下还会通过install_name_tool修正libcriterion的动态库路径。

2. 运行测试

make test

make test会依次构建并执行所有测试;make <test name>只运行单个测试。测试可执行文件输出在$(OUT_DIR)/<program>/<test_name>,通过LD_LIBRARY_PATH指向 Criterion 库目录后运行(见 bpf.mk)。

3. 测试示例

仓库中 programs/sbf/c/src/sanity/sanity.c 等示例程序均包含test_sanity.c之类的 Criterion 测试,可作为编写更复杂测试用例(如构造SolAccountInfo数组、验证指令处理逻辑)的直接参考。

五、必须知道的限制与应对

sdk/bpf/c/README.md 明确列出了 C 开发的两条核心限制,理解它们能避免大量踩坑:

限制 1:程序必须完整包含在单个 .c 文件中

  • 构建脚本只扫描$(SRC_DIR)/<program>/下的*.c/*.cc文件并逐一编译、链接(见 bpf.mk),不支持跨目录多文件项目组织;
  • 应对方案:将业务逻辑拆分为多个头文件(.h),在唯一的.c文件中#include引入。头文件内容会被编译器内联进单一翻译单元,等效于单文件。

限制 2:没有 libc,但solana_sdk.h提供最小原语集

  • BPF 目标下标准 C 库不可用,printfmalloc等 libc 函数无法直接使用;
  • 替代方案solana_sdk.h(见 solana_sdk.h)聚合了全部可用头文件,提供包括:
头文件提供的原语
sol/types.h定长整数类型(uint8_t~uint64_t)、错误码、SOL_ARRAY_SIZE
sol/entrypoint.hSolAccountInfoSolParameters结构与entrypoint声明
sol/deserialize.hsol_deserialize输入反序列化
sol/log.hsol_log等链上日志输出原语
sol/cpi.h跨程序调用(CPI)支持
sol/sha.h、sol/keccak.h、sol/blake3.h、sol/secp256k1.h、sol/alt_bn128.h、sol/big_mod_exp.h密码学原语
sol/pubkey.h、sol/assert.h、sol/return_data.h公钥、断言、return data 等辅助能力
  • 内存分配方面:链上程序拥有固定虚拟地址的堆区(HEAP_START_ADDRESS0x300000000,长度 32KB,见 constants.h),SDK 通过sol_calloc/sol_free内置函数管理该区域,也允许程序自行在该区域实现自定义堆。

六、完整实战:把示例串起来

下面是一个贴合仓库真实结构的完整开发闭环示例:

my-program/ ├── makefile # 内容:include ../../sdk/bpf/c/bpf.mk ├── src/ │ └── counter/ │ ├── counter.c # 程序本体(含 entrypoint) │ └── test_counter.c # Criterion 测试(以 test_ 开头)
# makefile include ../../sdk/bpf/c/bpf.mk
// src/counter/counter.c #include <solana_sdk.h> extern uint64_t entrypoint(const uint8_t *input) { SolAccountInfo ka[1]; SolParameters params = (SolParameters) { .ka = ka }; if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { return ERROR_INVALID_ARGUMENT; } // ... 业务逻辑:读写 ka[0] 的 data 与 lamports ... sol_log("counter program executed"); return SUCCESS; }
// src/counter/test_counter.c #include <criterion/criterion.h> #include "counter.c" Test(counter_suite, returns_success) { // 构造输入、调用 entrypoint、断言返回值 cr_assert(true); }

对应的构建与验证命令:

make # 构建全部:生成 out/counter.so make counter # 只构建 counter 程序 make test_counter # 构建并运行单个测试 make dump_counter # 查看反汇编,检查 BPF 指令 make readelf_counter # 查看 ELF 段布局 make clean # 清理 out 目录

构建成功后,make会打印solana program deploy <abs path>/out/counter.so部署命令,配合生成的counter-keypair.json即可上链。

七、小结

Solana 的 C SDK 提供了一套开箱即用的 BPF 开发体验:通过 bpf.mk 一行 include 即可获得完整的编译、链接、反汇编、单元测试流水线;solana_sdk.h头文件族覆盖了反序列化、日志、CPI、密码学等链上开发必需原语。遵循「单 .c 文件 + 头文件拆分」「无 libc、用 SDK 原语」两条铁律,你就能用 C 语言写出可测试、可部署的 Solana 链上程序。如需更深入的原语用法,可直接查阅 sdk/bpf/c/inc/sol 下的各头文件源码,以及 programs/sbf/c/src 中的全部示例实现。

【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 16:41:09

NB-IoT从原理到实践:覆盖、功耗、选型与调试指南

第一次在项目会上听到 NB-IoT 这组词时&#xff0c;我脑子里闪过的是“NB”两个字&#xff0c;以为这是一项出门就能用、信号永远满格的黑科技。真正开始调模组、抓空口日志、跑弱覆盖场景之后&#xff0c;才意识到 NB-IoT 之所以叫窄带物联网&#xff0c;恰恰是因为它“窄”。…

作者头像 李华
网站建设 2026/9/14 16:41:05

无人机核心传感器模块解析与数据融合技术

1. 无人机传感器模块的核心作用解析现代无人机早已不再是简单的遥控玩具&#xff0c;其背后是一套精密复杂的传感器系统在支撑。这些传感器模块如同无人机的"感官器官"&#xff0c;让飞行器具备了感知环境、自主决策的能力。从最基本的飞行稳定控制&#xff0c;到高级…

作者头像 李华
网站建设 2026/9/14 16:39:36

多无人机协同路径规划的改进蜣螂优化算法实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 16:36:31

边缘AI视觉系统落地:延迟优化与断网韧性实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华