
SBPF 어셈블리로 Solana 프로그램을 작성하는 방법
현재 Solana 생태계에서는 프로그램 최적화의 한계를 낮추려는 경쟁이 동시에 벌어지고 있습니다.
큰 틀에서 보면 Pinocchio 같은 라이브러리는 컴퓨팅 효율을 몇 자릿수나 개선하며 Rust 개발을 혁신하고 있습니다. 한편 가장 낮은 수준에서는 컴파일러를 불신하는 전담 개발자들이 한 단계 더 나아가고 있습니다. Rust나 C 같은 컴파일 언어로 Solana 프로그램을 작성하는 대신, 모든 명령어에서 최대 성능을 끌어내기 위해 바이트코드를 일일이 정교하게 작성합니다.
이러한 저수준 개선은 VM에 네이티브 언어로 직접 명령할 때만 가능합니다. 바로 모든 온체인 프로그램에서 사용되고 실행되는 바이트코드이자, Solana가 확장형 Berkeley Packet Filter(eBPF)를 변형한 sBPF 어셈블리입니다.
sBPF 어셈블리를 작성하면 Solana Virtual Machine의 최저 수준 인터페이스에 직접 접근할 수 있습니다. Rust 컴파일러와 LLVM도 최적화를 시도하지만, 언어 구문이 충분히 명시적이지 않거나 더 나은 컴파일 결정을 내릴 맥락이 부족해 최적이 아닌 바이트코드를 생성하는 경우가 많습니다. 명령어 수준을 완전히 제어할 수 있는 숙련된 개발자가 작성한 결과와 비교하면 특히 그렇습니다.
이처럼 제어 수준을 높이면 사용 편의성은 떨어지지만, 컴퓨팅 유닛 사용량과 바이너리 크기, 그에 따른 임대료를 크게 줄일 수 있습니다. 경쟁이 치열하고 성능이 핵심인 작업에서는 이러한 절감 효과가 특히 중요합니다.
동시에 모든 프로그램을 어셈블리로 작성해서는 안 된다는 주장도 타당합니다.
상황이 크게 개선되기는 했지만, 과거에는 도구가 제한적이었습니다. 더 중요한 점은 성능 향상의 대가로 정확성을 수동 검증해야 하고 감사 비용도 늘어난다는 것입니다. 자동화 도구가 부족하고 구문을 읽고 쓰고 이해하기가 더 어렵기 때문입니다.
반대로 컴파일 언어는 컴파일러의 결정을 감추는 블랙박스라고 볼 수도 있습니다. 따라서 어셈블리가 제공하는 투명성과 제어 능력 덕분에 컴파일 언어로 작업할 때는 쉽게 보이지 않던 부분을 발견할 수 있습니다. 실제로 최근 Rust SDK에서 이룬 성능 혁신의 대부분은 컴파일러보다 효율적인 바이트코드를 직접 작성할 수 있다는 사실을 깨닫고 연구한 결과였습니다.
이 글에서 다루는 내용:
- sBPF 어셈블리의 개념과 가상 머신을 직접 제어하는 방식
- Berkeley Packet Filter에서 eBPF로의 발전 과정과 Solana가 이를 채택한 이유
- sBPF의 가상 머신 아키텍처, 명령어 집합, 메모리 모델
- 개발 환경을 설정하고 sBPF 프로그램을 빌드하는 방법
- 실용적인 메모 예제로 배우는 단계별 어셈블리 프로그래밍
- 저수준 코드 작성 시 반드시 고려해야 할 보안 사항
어셈블리란?
어셈블리는 사람이 읽을 수 있는 기계어입니다. CPU 또는 VM의 명령어 집합에 직접 대응하는 가장 저수준의 프로그래밍 언어입니다.
어셈블리는 변수와 함수 대신 레지스터(CPU 내부의 빠른 임시 저장 공간), 메모리 주소(RAM 또는 디스크의 물리적 위치), 그리고 로드(메모리에서 읽기), 스토어(메모리에 저장), 산술 연산, 점프(제어 흐름) 같은 기본 연산을 사용합니다.
각 어셈블리 명령어는 동등한 기계어 명령어와 일대일로 대응합니다.
이러한 일대일 대응 덕분에 프로그래머는 어떤 레지스터에 데이터를 보관할지, 메모리에 어떻게 접근할지, 연산을 어떤 순서로 실행할지 등 프로세서의 동작을 정밀하게 제어할 수 있습니다.
하나의 함수가 수십 개의 명령어를 생성할 수 있는 고수준 언어와 달리, 어셈블리는 불투명한 추상화 없이 머신의 동작을 완전히 투명하게 보여주고 제어할 수 있게 합니다.
Berkeley Packet Filter(BPF)와 eBPF란?
Berkeley Packet Filter(BPF)는 Unix 커널에서 네트워크 패킷을 효율적으로 필터링하기 위한 가상 머신으로 1992년에 등장했습니다. 초기 BPF는 단순한 명령어 집합과 레지스터 기반 아키텍처를 사용해 커널 내부에서 샌드박스 코드를 안전하게 실행할 수 있었습니다.
확장형 Berkeley Packet Filter(eBPF)는 이 개념을 현대화해 패킷 필터를 범용 가상 머신으로 확장했습니다. eBPF는 64비트 아키텍처, 더 많은 레지스터, 풍부한 명령어 집합을 도입했습니다. 덕분에 네트워킹, 보안, 시스템 모니터링을 위한 복잡한 프로그램을 커널 공간에서 안전하게 실행할 수 있습니다.
Solana는 내장 샌드박싱을 갖춘 검증되고 안전한 실행 환경을 제공한다는 이유로 eBPF를 채택했습니다. 샌드박싱은 프로그램이 시스템 리소스에 접근하거나 노드를 중단시키거나 다른 프로그램을 방해하지 못하게 합니다. 결정론적 실행은 모든 검증인이 동일한 결과를 생성하도록 보장합니다.
또한 레지스터 기반 아키텍처와 성숙한 도구 체인은 고성능 온체인 실행에 적합했습니다. 기존 LLVM 백엔드를 통해 개발자가 Rust 같은 고수준 언어로 컴파일할 수도 있었습니다.
sBPF 가상 머신 아키텍처
Solana 프로그램이 실행되면 런타임은 sBPF 바이트코드를 메모리에 로드하고, 안전성을 보장하기 위한 정적 검증을 수행한 다음(무한 루프, 잘못된 메모리 접근, 올바른 명령어 사용 여부 검사), 가상 머신 안에서 코드를 실행합니다.
VM은 제어된 64비트 실행 환경을 제공합니다. 프로그램은 호스트 시스템 및 다른 프로그램과 완전히 격리되어 실행되며, 모든 리소스 접근은 런타임을 통해 중재됩니다.
sBPF 명령어 집합 아키텍처
sBPF는 11개의 64비트 레지스터(r0-r10)를 사용합니다. r10은 읽기 전용 프레임 포인터로, r0은 반환 레지스터로 사용됩니다.
명령어는 일관된 형식을 따릅니다. 연산 코드는 연산(산술, 논리, 메모리 접근, 점프)을 지정하고, 피연산자는 소스/대상 레지스터, 오프셋 및/또는 즉시 값을 나타냅니다.
주요 명령어 범주에는 ALU 연산(덧셈, 뺄셈, 비트 연산), 메모리 연산(로드/스토어), 제어 흐름(조건부/무조건 점프)이 있습니다.
sBPF 메모리 모델
sBPF 프로그램은 구조화된 메모리 레이아웃에서 작동합니다. 로컬 변수와 함수 호출을 위한 4KB 스택, 동적 할당을 위한 힙, 바이트코드와 상수를 포함한 읽기 전용 프로그램 데이터, 그리고 프로그램이 실행 중 접근할 수 있는 Solana 계정에 매핑된 계정 데이터 영역으로 구성됩니다.
모든 메모리 접근에는 경계 검사가 적용되며, 프로그램은 지정된 영역 밖의 메모리에 접근할 수 없습니다.
sBPF의 Solana 시스템 호출
sBPF 프로그램은 시스템 리소스에 직접 접근하거나 I/O 작업을 수행할 수 없습니다. 대신 제어권을 Solana 런타임으로 넘기는 특수 명령어인 시스템 호출을 통해 서비스를 요청합니다.
sBPF 어셈블리에서는 call 명령어와 호출 심볼을 사용해 시스템 호출을 실행합니다. 이 호출 심볼은 어셈블 과정에서 컴파일러가 호출 대상으로 수정합니다. 현재 시스템 호출은 텍스트 기반 동적 재배치를 통해 실행됩니다. JIT 컴파일 시 심볼을 32비트 Mumur3 해시에 매핑하는 복잡한 문자열 조회 테이블 시스템입니다. 하지만 이를 정적 시스템 호출로 대체해 호출 규약을 대폭 단순화하려는 제안이 진행 중입니다. 시스템 호출이 실행되면 인수는 레지스터 1~5를 통해 전달되고, 레지스터 5는 때때로 스택 스필로 사용됩니다. 반환 값은 r0에 다시 기록됩니다.
일반적인 시스템 호출에는 메모리 연산(sol_memcpy, sol_memcmp), 암호화 함수(해싱, 서명 검증), 로깅, 크로스 프로그램 호출이 있습니다.
일반 sBPF 명령어의 CU 비용은 모두 1이지만, 시스템 호출의 컴퓨팅 유닛 계산 방식은 크게 다릅니다. 각 시스템 호출은 프로토콜에 추가될 때 벤치마크를 거쳐 기본 호출 비용이 정해집니다(예를 들어 CPI 호출은 1000입니다). 경우에 따라 소비되는 데이터 양에 따라 추가 가변 비용도 적용됩니다.
SBPF 어셈블리 튜토리얼
환경 설정
기존에는 sBPF 어셈블리를 작성하려면 전체 Solana 도구 체인이 필요했습니다. 용량이 크고 복잡하며 플랫폼에 종속된 과정이었습니다.
이 때문에 Dean Little은 sBPF 프로그램의 초기 설정, 빌드, 컴파일, 테스트, 배포를 모두 지원하는 엔드투엔드 솔루션인 sBPF SDK를 만들었습니다.
Cargo를 사용하면 모든 운영체제에 SDK를 설치할 수 있습니다.
cargo install --git https://github.com/blueshift-gg/sbpf.git코드를 살펴보기 전에 구문 강조, 자동 완성, 오류 감지를 지원하는 VS Code sBPF Assembly 확장 프로그램도 설치하는 것이 좋습니다.
프로젝트 설정
다음 명령어로 새 프로젝트 스캐폴드를 생성하세요.
sbpf init <name_of_the_project>이 명령어는 Mollusk Rust 테스트가 포함된 프로젝트를 생성합니다.
TypeScript 테스트가 포함된 스캐폴드를 생성하려면 다음 명령어로 초기화할 수 있습니다.
sbpf init <name_of_the_project> --ts-tests 메모 sBPF 어셈블리 예제
메모보다 간단한 프로그램은 떠올리기 어렵습니다. 바로 이 점 때문에 sBPF 어셈블리를 처음 소개하기에 완벽합니다.
이 프로그램은 한 가지 작업만 수행합니다. 전송한 명령어 데이터를 그대로 받아 블록체인에 기록합니다. 계정도 복잡한 로직도 없습니다. Solana Virtual Machine을 명령어 수준에서 직접 제어할 뿐입니다.
먼저 전체 프로그램을 살펴보겠습니다.
.equ NUM_ACCOUNTS, 0x00
.equ DATA_LEN, 0x08
.equ DATA, 0x10
.globl entrypoint
entrypoint:
ldxdw r0, [r1+NUM_ACCOUNTS]
ldxdw r2, [r1+DATA_LEN]
add64 r1, DATA
call sol_log_
exit상수 정의
프로그램은 Solana 런타임의 직렬화된 입력 영역 구조를 매핑하는 상수 세 개를 정의하는 것으로 시작합니다.
.equ NUM_ACCOUNTS, 0x00 // Account count offset
.equ DATA_LEN, 0x08 // Data length offset
.equ DATA, 0x10 // Data start offset이 오프셋들은 Solana 런타임이 명령어 데이터를 메모리에 배치하는 위치에 해당합니다.
VM이 프로그램을 호출하면 레지스터 r1을 통해 구조화된 버퍼를 전달합니다. 이 상수를 사용하면 해당 구조를 탐색할 수 있습니다.
sbpf.xyz 같은 도구는 계정 및 명령어 데이터 레이아웃을 기준으로 이러한 오프셋을 자동 계산할 수 있습니다.
엔트리포인트 생성
이제 프로그램의 엔트리포인트와 검증 로직을 생성합니다.
.globl entrypoint
entrypoint:
ldxdw r0, [r1+NUM_ACCOUNTS].globl 엔트리포인트 지시문은 링커에 엔트리포인트 심볼을 전역에서 보이도록 지정합니다. Solana 런타임은 이 심볼을 찾아 프로그램 실행을 시작할 위치를 파악합니다. 흥미롭게도 보통 entrypoint라고 부르지만 실제로는 어떤 이름이든 사용할 수 있습니다.
첫 번째 명령어는 어셈블리 특유의 영리한 검증 기법을 사용합니다. r0은 반환 레지스터이고 0 이외의 반환 값은 오류 코드로 사용됩니다. 따라서 계정 수를 r0에 직접 로드하면 0개보다 많은 계정이 전달되었을 때 프로그램이 0이 아닌 오류 코드로 종료됩니다.
따라서 입력된 계정을 일일이 건너뛰며 VM에 있는 명령어 데이터의 오프셋을 검증할 필요가 없습니다.
sol_log 시스템 호출 실행
이제 마지막으로 sol_log_ 시스템 호출을 실행할 수 있습니다.
ldxdw r2, [r1+DATA_LEN] ; Load memo length
add64 r1, DATA ; Point r1 to memo data
call sol_log_ ; Log the memo
exit ; Exit with r0 valuesol_log_ 시스템 호출은 r2에 메시지 길이를, r1에 기록할 메시지의 포인터를 요구합니다. 다행히 직렬화된 입력 영역에서 런타임은 명령어 데이터 앞에 64비트 길이 카운터를 자동으로 추가합니다. 따라서 다음 작업만으로 sol_log_ 시스템 호출을 실행할 수 있습니다.
- DATA_LEN 오프셋의 값을 r2에 로드
- r1이 명령어 데이터 시작 위치의 오프셋을 가리키도록 설정
두 레지스터가 올바른 값을 가리키면 시스템 호출을 실행한 후 r0에 있는 값으로 종료하면 됩니다.
프로그램 빌드 및 배포
Claire Fan이 Rust로 작성한 SBPF 자체 내장 어셈블러를 사용해 프로그램을 빌드할 수 있습니다. Solana 플랫폼 도구와 함께 사용해야 했던 2GB가 넘는 LLVM 도구 체인을 대체하는 5MB Rust 바이너리입니다.
빌드하려면 다음 명령어를 실행하세요.
sbpf build프로그램을 빌드한 후 다음 명령어로 배포할 수 있습니다.
sbpf deploy프로그램과 상호작용
생성된 테스트로 프로그램을 테스트하려면 sbpf test을 실행하세요. 또는 sbpf e2e을 실행해 하나의 명령어로 빌드, 배포, 테스트하는 전체 파이프라인을 실행할 수 있습니다.
sBPF 어셈블리의 보안 고려 사항
어셈블리 코드를 작성한다는 것은 보안을 전적으로 책임진다는 뜻입니다. 실수를 잡아줄 컴파일러가 없습니다. 모든 명령어가 프로그램 안전성에 직접 영향을 주므로 다음과 같은 핵심 보안 원칙을 반드시 지켜야 합니다.
입력 검증
어셈블리 프로그램은 모든 입력을 수동으로 검증해야 합니다. 사용하기 전에 계정 수, 데이터 길이, 버퍼 크기를 항상 확인하세요.
예를 들어 메모 프로그램에서는 계정 수를 r0에 로드해 자동 검증을 구현했습니다. 계정이 잘못 전달되면 프로그램이 실패합니다. 데이터를 처리할 때는 메모리에 접근하기 전에 길이가 예상 범위 안에 있는지 확인하세요.
메모리 경계 검사
sBPF는 자동 경계 검사를 제공하지 않습니다. 배열이나 버퍼에 접근하기 전에 읽기/쓰기 작업이 할당된 경계 안에서 수행되는지 직접 확인하세요. 메모리 접근 전에 간단한 경계 검사를 수행하면 충돌과 데이터 손상을 방지할 수 있습니다.
jgt r2, MAX_LENGTH, error # Check if length exceeds limit
ldxb r3, [r1+r2] # Safe to load if check passes레지스터 관리
레지스터에는 프로그램의 중요한 상태가 저장됩니다. 함수나 시스템 호출을 실행할 때는 중요한 값을 스택이나 다른 레지스터에 저장해 보존하세요.
프레임 포인터(r10)와 r0의 반환 값은 특히 주의해야 합니다. 이 값이 손상되면 프로그램이 중단되거나 보안 취약점이 발생할 수 있습니다.
산술 연산 안전성
산술 연산에서는 오버플로를 직접 감지하는 것이 매우 중요합니다. 큰 값을 더하기 전에 결과가 64비트 한도를 초과할 수 있는지 확인하세요.
런타임 오류를 방지하려면 나눗셈 연산에서 0인지 명시적으로 확인해야 합니다.
시스템 호출 매개변수 검증
시스템 호출에는 유효한 매개변수가 필요하며 잘못된 입력이 있으면 실패합니다. 시스템 호출 전에 레지스터에 올바른 포인터, 길이, 값이 있는지 확인하세요. 잘못된 매개변수는 실패를 일으킬 뿐만 아니라 컴퓨팅 유닛도 불필요하게 소비합니다.
결론
sBPF 어셈블리는 모두를 위한 도구가 아닙니다. 바로 그것이 핵심입니다.
대부분의 개발자는 Rust를 사용하고 최적화는 컴파일러에 맡기는 편이 좋습니다. 하지만 마지막 컴퓨팅 유닛 하나까지 최적화하거나 성능이 핵심인 인프라를 구축한다면, 어셈블리는 어떤 고수준 언어도 제공할 수 없는 완전한 제어 능력을 제공합니다.
여기서는 sBPF가 Solana 아키텍처에 어떻게 통합되는지 이해하는 것부터 첫 메모 프로그램을 빌드하는 방법까지 기본 사항을 살펴봤습니다.
메모 예제가 단순해 보일 수 있지만, 더 복잡한 프로그램에서도 사용할 핵심 원칙을 보여줍니다.
- 직접적인 레지스터 조작
- 수동 메모리 관리
- 명시적인 시스템 호출 처리
이러한 이점에는 대가가 따릅니다. 안전장치를 속도와, 추상화를 제어 능력과 맞바꾸는 것입니다. Rust 코드를 이미 최적화했지만 더 높은 성능이 필요할 때, 매 마이크로초가 중요한 인프라를 구축할 때, 또는 컴파일러로는 제대로 최적화할 수 없는 작업이 필요할 때 어셈블리를 선택해야 합니다.
그 밖의 경우라면 불필요한 골칫거리를 피하고 Rust를 사용하세요.
도구는 계속 개선되고 있고 커뮤니티도 성장하고 있으며 성능상의 이점은 분명합니다. 하지만 “큰 힘에는 무언가를 망가뜨리지 않아야 할 큰 책임이 따른다”는 점을 기억하세요.
sBPF 어셈블리 사용법을 더 알아보려면 Blueshift의 어셈블리 입문 과정을 읽고, 제공되는 과제로 실력을 시험해 보세요!
관련 아티클
Helius 구독하기
최신 Solana 개발 소식을 확인하고 새 게시물 알림을 받아보세요


